I'm not sure if this is a help request, a feature request or actually providing help to others so it might be better in a different category...
Before we start, you should know that this is not intended to be just complaints. I'd really like to help improve this. First, though, we need to understand what the problems actually are and why they are problems.
One of the main things that has irritated me with using SFML is trying to get it to place nicely with already created windows. Note that my points here will about about Microsoft Windows only.
Creating a window with SFML is simple; it's great!
However, it lacks some features that require the ability to control the window yourself (adding a 'proper' window menu, for example). This can be ignored a lot of the time for most games but falls flat for other (more serious/professional) application types, forcing windows to be created without SFML.
So, instead we can create a window manually (this isn't really much of an issue but it's nicer when SFML does the work for you!). Then, we can pass the window's handle to an SFML window and it can take over control for that window. This is also a nice feature.
However, this starts to create some issues when faced with DPI-scaling. Newer operating systems use scaling and often start with a scale of
not 100%. For example, I'm using Windows 11 and the default scale was 150%.
The issue is that things should be scaled by this value so that the user experiences scaled things automatically. If they don't, they are ignoring the user's request; this is an accessibility issue. When you create a window manually, it "cares" about the user's decision and it is scaled. The app itself doesn't see the scale and the accessibility is applied automatically.
If you pass control of a window already created (and scaled properly) to SFML, SFML removes scaling so that its size matches its expected number of pixels. This is understandable for when you are working with exact pixels. This means that window "shrinks" (assuming a larger scale in my examples) to fit SFML's preferences.
Firstly, I'd say that this in itself is a bit of an issue since the reason we create a window without SFML is access to window features that SFML doesn't provide and this removes the ability to do this.
If you've programmed directly in Windows already, you'll understand that you can create (child) windows within other windows (almost everything is a window!). So, creating a window as a child of the actual window as a destination for SFML graphics to be displayed is actually a good idea. This allows you to place SFML graphics in specified rectangles around the window.
Unfortunately, when you pass this child window to SFML, SFML will remove scaling - for everything! That includes its parent window! This means that you cannot create and control one window and give a child window to SFML to play with as it affects the parent window as well.
In addition to this, SFML's removal of the scaling actually affects
all windows by the instance so
all windows will be "unscaled" by SFML even if not at all related to the window used SFML. This is quite a disturbing situation.
It's worth noting too, that when the window is "unscaled" by SFML, the title bar is also "unscaled" and I've seen the window title disappearing as it's too small!
I should mention that I've looked around for previous info about this and a few things popped up that might interest someone experiencing issues with scaling:
https://en.sfml-dev.org/forums/index.php?topic=17092.0https://en.sfml-dev.org/forums/index.php?topic=19617.msg141246#msg141246https://en.sfml-dev.org/forums/index.php?topic=28770.msg178734#msg178734https://github.com/SFML/SFML/pull/2268All are many years old.
There doesn't seem to be anything in SFML 3's list of task related to this, unfortunately.
I will also provide a "simple" sample code you can use to see it in action:
#include <SFML/Graphics.hpp>
#include <Windows.h>
LRESULT CALLBACK MainWindowProcedure(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK ChildWindowProcedure(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK Main2WindowProcedure(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
int main()
{
HINSTANCE hInstance{ GetModuleHandle(NULL) };
// main window
const wchar_t mainClassName[] = L"Main Window Class";
WNDCLASS wc = { };
wc.lpfnWndProc = MainWindowProcedure;
wc.hInstance = hInstance;
wc.lpszClassName = mainClassName;
RegisterClass(&wc);
HWND hWnd = CreateWindowEx(
0, mainClassName, L"SFML in child window test", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, // position
1000, 1000, // size
NULL, NULL, hInstance, NULL
);
if (!hWnd)
return EXIT_FAILURE;
ShowWindow(hWnd, SW_SHOWNORMAL);
// reference window (window 2: identical to - but independent from - main window)
const wchar_t main2ClassName[] = L"Main 2 Window Class";
WNDCLASS wc2 = { };
wc2.lpfnWndProc = Main2WindowProcedure;
wc2.hInstance = hInstance;
wc2.lpszClassName = main2ClassName;
RegisterClass(&wc2);
HWND hWnd2 = CreateWindowEx(
0, main2ClassName, L"SFML in child window test 2 (for reference size)", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, // position
1000, 1000, // size
NULL, NULL, hInstance, NULL
);
if (!hWnd2)
return EXIT_FAILURE;
ShowWindow(hWnd2, SW_SHOWNORMAL);
// child window (child of main window, not window 2)
const wchar_t childClassName[] = L"Child Window Class";
WNDCLASS wcChild = { };
wcChild.lpfnWndProc = ChildWindowProcedure;
wcChild.hInstance = hInstance;
wcChild.lpszClassName = childClassName;
RegisterClass(&wcChild);
HWND hWndChild = CreateWindowEx(
0, childClassName, L"Child Window", WS_CHILD | WS_BORDER,
250, 250, // position
500, 500, // size
hWnd, // parent
NULL, hInstance, NULL
);
if (!hWndChild)
return EXIT_FAILURE;
ShowWindow(hWndChild, SW_SHOWNORMAL);
sf::RenderWindow sfmlWindow(hWndChild); // sfml window is created from the child window (of main window)
sfmlWindow.clear(sf::Color(255u, 160u, 64u)); // fill the SFML window (child) with a solid colour
sfmlWindow.display();
MSG msg{};
while (GetMessage(&msg, NULL, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
LRESULT CALLBACK MainWindowProcedure(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hWnd, msg, wParam, lParam);
}
}
LRESULT CALLBACK ChildWindowProcedure(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hWnd, msg, wParam, lParam);
}
}
LRESULT CALLBACK Main2WindowProcedure(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hWnd, msg, wParam, lParam);
}
}
This is complete and should compile (you will need to link SFML Graphics as normal). Note that if you are using "Multi-byte Character Set" instead of "Unicode Character Set", you'll need to either change it to the Unicode one or replace the "wide" characters with standard ones.
Note:
this code also outputs an error (that I must admit that I don't understand):
An internal OpenGL call failed in RenderWindow.cpp(125).
Expression:
glGetIntegerv(GLEXT_GL_FRAMEBUFFER_BINDING, reinterpret_cast<GLint*>(&m_defaultFrameBuffer))
Error description:
GL_INVALID_OPERATION
The specified operation is not allowed in the current state.
And finally...
As mentioned at the beginning of this post, I will also be giving some help/advice.
As a work-around, you can fix this issue yourself but it's on a "per exe file" basis.
To do this, you can modify the properties of the executable manually (right-click file and click on Properties).
Then, go to Compatibility tab, click on Change high DPI settings, activate Override high DPI scaling behaviour and choose Application or System from the drop-down menu.
Application lets SFML scale the window but the title bar still works.
System disallows SFML from changing the scaling of the windows.
In conclusion, having to "override" settings per file is a bit of a dirty "hack" and it would be cleaner if we could at minimum let SFML know we don't want it to be in charge of scaling.