Fixing offset cursor in OBS

Capture, scaling and output, oh my!

Background

OBS, the excellent media recording/streaming software has a slightly annoying behaviour for me. The cursor in the output is not shown at the same place as in the input- it’s offset., further down and further right than it should be. This gets worse the further away from the top-left corner the cursor is in-game.

A picture should make it more obvious, see examples.

It’s quite annoying because the dissonance of where someone is actually clicking versus what people watching the video (live or on playback) see is jarring, so I’ve been disabling the cursor. But that’s not optimal either, as the in-game cursor is the closes thing the player gets to an avatar / representation. You can see how someone thinks and plays by watching their cursor!

Examples

Still from EUIV. Note that this is a mocked-up example!

and compare that to when we move the cursor a little down and a lot right:

Even further away! (and also a mock-up)

Not good! In fact, the cursor can disappear completely from the output if it gets far enough down and/or right.

Explanation

I’m 99% sure this arises as a result of Windows Scaling / DPI manipulation. I set Windows to scale apps on my main monitor, so that my potato eyes can see what is going on better.

Got this from Microsoft’s support website. I thought about rebooting into Windows to screenshot my settings for verisimilitude… for about half a second.

There’s a side benefit to this- I run games in the same resolution as my OBS canvas, 1920×1080; this means there’s no scaling needed. That’s also my output resolution for videos, so scaling is skipped along the pipeline there. Thinking about it a little more I wonder if that’s actually the case for a scaled game/window; but that’s speculation for another time.

However, I suspect the way OBS reads the cursor position causes problems. In particular, I think the cursor coordinates are unscaled- for a base 1920×1080 resolution game, the x coordinate will be between 0 and 1920 and the y coordinate will be between 0 and 1080. But the game is scaled by 1.75 in my case, so there will actually be 3360 horizontal pixels and 1890 vertical pixels.

So the cursor ends up a multiple of 1.75 (or 7/4) away from where it should be. In the top left the coordinate values are low so the offset looks less bad, but moving down and right it looks worse as the gap appears larger, then by a little over half-way the cursor disappears entirely!

Demonstration

(it should be quite quick to mock up an example in python to show this, that’s a job for future-me)

Yes past-me, it was relatively quick and fun!

Cursor matched to EUIV for verisimilitude!

If you’re interested in the coordinates, I gotchu:

That pretty much matches up to the observed behaviour in game.

Fix

A problem has been identified, and it should be possible to fix relatively easily. Anyone who is in this situation has a couple of options:

  • fix it for themself and carry on
  • fix it for everyone

I’m not the only person to have this problem. I think it’s worth solving for others. Let’s look at cursor-capture.c:

void cursor_draw(struct cursor_data *data, long x_offset, long y_offset,
		 long width, long height)
{
	long x = data->cursor_pos.x + x_offset;
	long y = data->cursor_pos.y + y_offset;
	long x_draw = x - data->x_hotspot;
	long y_draw = y - data->y_hotspot;

	if (x < 0 || x > width || y < 0 || y > height)
		return;

	if (data->visible && !!data->texture) {
		gs_blend_state_push();
		gs_blend_function(GS_BLEND_SRCALPHA, GS_BLEND_INVSRCALPHA);
		gs_enable_color(true, true, true, false);

		gs_matrix_push();
		obs_source_draw(data->texture, x_draw, y_draw, 0, 0, false);
		gs_matrix_pop();

		gs_enable_color(true, true, true, true);
		gs_blend_state_pop();
	}
}

cursor-capture.c, L218

As an aside, it’s really heartening to see a codebase where:

  • the file you’re looking for is easy to find
  • the function is predictably named
  • the function is relatively straightforward

Nice.

I don’t speak C++ very well, but it seems that the final (x,y) position where the cursor is drawn is modified by two things: an *_offset, and a *_hotspot. It’s possible that the OBS devs, smart folks that they are, have already considered scaling. That’s something that can be explored at leisure, git grep is oftentimes helpful in this case.

In the meantime a proof-of-concept fix can be applied, quick-and-dirty style:

long x_draw = (x - data->x_hotspot) * 4 / 7;
long y_draw = (y - data->y_hotspot) * 4 / 7;

Told you it was quick and dirty! However, by multiplying by 4/7 we should negate the 175% scaling throwing off the position.

If the quick-and-dirty fix works, we have a starting place to work from to propose a patch. Off the top of my head:

  • there should be an option to toggle in the GUI: either in the source properties, or in global options, or possibly both
  • there should be something to configure the amount of scaling to counteract, probably in the form of a combobox- drop-down with common values with free text option for specific value
  • as a ‘stretch goal’, there may be a way to query the Windows API to find out the current scaling factor, though that can vary on a per-monitor basis so maybe best to do default monitor (?)

But before that I need to figure out how to compile OBS for Windows. That might be a separate project in and of itself… stay tuned!

One thought on “Fixing offset cursor in OBS”

Tell us what's on your mind