SDL_GL_GetDrawableSize + on a retina mac rendering issue

Currently on macOS with the latest SDL release. I am creating a window with and without SDL_VIDEO_HIGHDPI_DISABLED along with a GL context. SDL_GL_GetDrawableSize is returning what I would expect: 2x win size with high dpi enabled and win size when high dpi is disabled.

When I go to render everything is working as expected with SDL_VIDEO_HIGHDPI_DISABLED on a retina MacBook. If high dpi is enabled, I am seeing the second image below. In both cases the framebuffer is being created with the output of SDL_GL_GetDrawableSize. With high dpi disabled, it seems I still have to create my framebuffer at retina scale: SDL_GL_GetDrawableSize * 2.

What I am actually trying to do here is have everything look identical whether in high dpi mode or not, but have the backing store just be scaled up if high dpi is disabled while still on a retina screen (I believe for macOS this is handled by the contentsScale of the CALayer). Is it possible to get this behavior with SDL methods directly?

1 Like

This was fixed here:

It’s not in any official release yet.

Oh that’s fantastic! I was beginning to think I was going insane. I’ll pull the latest and build and see if it takes care of the issue.

Update: pulled the latest SDL from hg and unfortunately the fix doesn’t fix things for me.

If you’re doing OpenGL yourself, make sure to set your viewport to match the actual pixel dimensions of the window. So use what you get from SDL_GL_GetDrawableSize().

If you’re using SDL’s accelerated 2D renderer, use something like

const int logicalWidth = 800;
const int logicalHeight = 600;
int pixelWidth = 0, pixelHeight = 0;
SDL_GetRendererOutputSize(MyRenderer, &pixelWidth, &pixelHeight);
const float scaleX = (float)pixelWidth / (float)logicalWidth;
const float scaleY = (float)pixelHeight / (float)logicalHeight;
SDL_RenderSetScale(MyRenderer, scaleX, scaleY);

which will tell SDL to scale whatever values you give it in SDL_RenderCopy() so it looks the same on Retina and non-Retina screens. It will also work for non-Retina windows on Retina screens.

Also, not to be That Guy, but be aware that OpenGL on Macs is deprecated. If all you’re doing is 2D rendering, with no shader effects or anything, perhaps look into SDL’s accelerated 2D renderer, which presents an easy to use 2D API and then behind the scenes uses the GPU (Metal on post-2012 Macs, OpenGL on older ones and Linux, and DirectX on Windows).

Yeah, I know the details. My framework works with metal, DX and GL so when the great depreciation occurs it’ll be ready.

I already set the viewport before all drawing but the issue is that the backbuffer or backing NSView only takes up 1/4 of the window so that’s is all I can render to. I was hoping to avoid writing Obj-C to prove the view hierarchy and see if I can manually wrestle them into filling the window but I guess I’ll give that a go next.

I tested with the latest SDL from version control on an old OpenGL + SDL app of mine and high DPI works fine for me on macOS 10.15 (Info.plist has NSHighResolutionCapable == YES, and window is created with SDL_WINDOW_ALLOW_HIGHDPI flag).

At first, even after setting the viewport to the correct SDL_GL_GetDrawableSize() dimensions I was getting results like yours, until I noticed that I also needed to use those in a call to glBlitBuffer(). Then it worked fine.

Thanks for the tip. I ran it through the view hierarchy debugger and it looks good on the view front. The SDLView has the proper constraints and takes up the full window space. I am guessing the CALayer has to be the issue, seeing as how there is nothing else left.

See my edit above. Are you using glBlitBuffer() or something like that, where you’d also need to pass the drawable size instead of the window size?

Also, make sure you really are compiling and linking to the latest SDL from version control. Had it happen a time or two where I was still linking to an old SDL.

Double checked that I am linking against the correct SDL with some logging added and I definitively am. I use SDL_GetDrawableSize every frame and set the glViewport and scissor with it. I’m not doing any glBlitBuffer calls, just glBindFramebuffer.

While noodling around in the view hieararchy debugger I think I may have found the bug. If I set the contentsScale of the layer to 1 (instead of the value of 2 it has by default) then adjust the gravity to bottom-left everything lines up correctly.

e (void)[[[[[NSApp windows] objectAtIndex:0] contentView] layer] setContentsScale:1.0]**
e (void)[[[[[NSApp windows] objectAtIndex:0] contentView] layer] setContentsGravity:kCAGravityBottomLeft]

Now the issue with this seems to be something else I can’t quite decipher yet. In the screenshot below I set the contentsScale to 3 and gravity to bottom-left. You can see there is still something that is 4x too big (the black box). Setting contentsScale to 1 and aligning the layer just hides the fact that 3/4 of that layer is not being rendered to.

Setting the contentsScale to 1 will probably effectively disable highdpi.

Do you think you could post some bare-minimum basic code (which only uses SDL itself and OpenGL, no dependencies) that reproduces the issue for you?

Yeah, I’ll whip something up when I get back to a computer.

Setting the contentsScale to 1 will probably effectively disable highdpi.

This is exactly what I want to do. I am not passing SDL_WINDOW_ALLOW_HIGHDPI and I am setting the hint SDL_HINT_VIDEO_HIGHDPI_DISABLED.

Just so it’s clear, high-dpi works fine, it is when disabling high-dpi on a retina mac that the issue occurs.

My old OpenGL app works fine with Retina disabled (just leaving out SDL_WINDOW_ALLOW_HIGHDPI should be enough) using the latest SDL from version control (reports as 2.0.11).

Like @slime said, can you post a bare minimum example with code?

Alright! Progress! I figured out the culprit:

int width = 0;
int height = 0;
SDL_GL_GetDrawableSize(win, &width, &height);

glEnable(GL_SCISSOR_TEST);
glScissor(0, 0, width, height);

What I don’t know is if this is a bug on my end in my understanding of what is going on here? Scissoring to the drawable size I would think would not have the effect of rendering only to the bottom 1/4 of the backbuffer. Not only that, but it doesn’t even just scissor but it just renders the everything at 1/4 size…

Minimal repro is below compileable with cc sdl_dpi.c sdl2-config --cflags --libs -framework OpenGL -o sdl_dpi:

#include <stdbool.h>
#include <SDL.h>
#include "SDL_opengl.h"


void draw(SDL_Window* win) {
	int width = 0;
	int height = 0;
	SDL_GL_GetDrawableSize(win, &width, &height);
	printf("%d, %d\n", width, height);
	
	glViewport(0, 0, width, height);
	glEnable(GL_SCISSOR_TEST);
	glScissor(0, 0, width, height);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	SDL_GL_SwapWindow(win);
}


int main() {	
	SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);

	SDL_Window* win = SDL_CreateWindow("dpi demo", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1024, 768,
		SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
	SDL_GL_CreateContext(win);
	
	glClearColor(0.4f, 1.0f, 0.6f, 1.0f);
	
	bool done = false;
	while (!done) {
		SDL_Event ev;
		while (0 < SDL_PollEvent(&ev)) {
			if (ev.type == SDL_QUIT)
				done = true;
		}
		
		draw(win);
	}
		
	return 1;
}

It really does look like you aren’t using the latest version of SDL from version control. The current “published” version of SDL (2.0.10) has the bug that makes it do what you’re seeing. Clone the latest SDL from Mercurial, which has the fix, and build it yourself.

Using the SDL from homebrew (2.0.10) gives this (never mind the garbage from the old OpenGL game I was trying to repro this with, though nice potential security problem Apple):

However, using the SDL I cloned from SDL’s version control and built a few days ago (which reports itself as SDL 2.0.11) gives the correct result:

Add

SDL_version compiled, linked;
SDL_VERSION(&compiled);
SDL_GetVersion(&linked);
printf("Compiled with SDL %d.%d.%d, linked with %d.%d.%d\n",
    compiled.major, compiled.minor, compiled.patch,
    linked.major, linked.minor, linked.patch);

somewhere in main() to really see what version of SDL you’re using. It should say compiled and linked against SDL 2.0.11.

Also, why are you using the scissor test but setting the scissor rect to be the whole drawable? The point of it is for when you want to restrict drawing to a certain portion of the screen. The driver and GPU will clip anything that falls outside the view bounds without needing the scissor test enabled.

1 Like

To clone the latest SDL from version control, do:

brew install mercurial
mkdir sdl-latest
cd sdl-latest
hg clone http://hg.libsdl.org/SDL .

I’m an idiot, thank you for pointing that out for me :wink:
Compiled with SDL 2.0.11, linked with SDL 2.0.10

Despite compiling with a specific dylib file (cc sdl_dpi.c -I/Users/desaro/Desktop/SDL/include libSDL2.dylib -framework OpenGL -o sdl_dpi) and checking the @rpath to make sure it was pointing to the executable directory, macOS was still linking against 2.0.10. Forcing it to link against the dylib next to the executable with export LD_LIBRARY_PATH=. resulted in the correct output Compiled with SDL 2.0.11, linked with SDL 2.0.11 and the correct behavior.

Thanks you for your patience and pointing me in the right direction.

Also, why are you using the scissor test but setting the scissor rect to be the whole drawable?

I am using Sokol for my graphics abstraction and it auto-scissors to the viewport size.

Glad to help :+1:

Linker issues like this can be a real PITA.