SFML/GRAPHICS - ЧАСИКИ!

 

Только что сегодня открыл для себя библиотеку SFML/Graphics. На эту тему есть прикольная статья, где разбирается пример изображения всяикх кубиков-хуюбиков и, главное, имеется пример реализации макета Солнечной системы. Очень любопытная штука. Для закрепления материала решил сделать свою собственную программу с графическим интерфейсом... Ну и чтобы не слишком уж мудрить, начал с элементарных часиков.

В качестве циферблата я заготовил файл Watch.png, размером 576 на 576 пикселей с прозрачным фоном.

Watch

Сам проект скомпилировал в моей любимой среде разработки MSYS2. Но для начала необходимо установить библиотеки. Отмучу 2 пути. Первый - это для UCRT окружения, а второй для MinGW.

1)

pacman -Syu

pacman -Su

pacman -S mingw-w64-ucrt-x86_64-sfml

pacman -S mingw-w64-ucrt-x86_64-freeglut

pacman -S mingw-w64-ucrt-x86_64-gdb

pacman -S mingw-w64-x86_64-sfml

pacman -S mingw-w64-x86_64-sfml-static

 

pacman -S mingw-w64-ucrt-x86_64-sfml-static

pacman -S mingw-w64-x86_64-sfml-static

2)

pacman -S --needed mingw-w64-x86_64-gcc mingw-w64-x86_64-sfml

А вот и код программы:

//main.cpp
//Подключаем необходимые заголовочные файлы
#include <SFML/Graphics.hpp> // Основная графическая библиотека #include <SFML/OpenGL.hpp> // Для работы с OpenGL-функциями #include <ctime> // Для работы со временем #include <cmath> // Для математических функций //На Windows добавляем заголовок для DPI-осознания #ifdef _WIN32 #include <windows.h> #endif //Главная функция программы int main() { // 1. Инициализация окна с настройками сглаживания sf::ContextSettings settings; settings.antialiasingLevel = 8; // Максимальный уровень сглаживания // Создаем окно размером 600x600 пикселей sf::RenderWindow window(sf::VideoMode(600, 600), "madWatch v1.0", sf::Style::Default, settings); // Для Windows делаем окно DPI-осознающим #ifdef _WIN32 SetProcessDPIAware(); #endif // 2. Загрузка и настройка текстуры циферблата sf::Texture dialTexture; if (!dialTexture.loadFromFile("Watch.png")) { return EXIT_FAILURE; // Выход если не удалось загрузить текстуру } //Создаем спрайт циферблата sf::Sprite dial(dialTexture); dial.setOrigin(288, 288); // Центруем текстуру (576/2) dial.setPosition(300, 300); // Позиционируем в центре окна //3. Функция для создания сглаженных стрелок auto createSmoothHand = [](float length, float width, const sf::Color& color) { sf::ConvexShape hand; hand.setPointCount(6); // Используем 6 точек для плавной формы //Форма стрелки (заостренный прямоугольник с основанием) hand.setPoint(0, sf::Vector2f(-width/2, -width)); hand.setPoint(1, sf::Vector2f(-width/4, -length)); hand.setPoint(2, sf::Vector2f(0, -length-width/2)); hand.setPoint(3, sf::Vector2f(width/4, -length)); hand.setPoint(4, sf::Vector2f(width/2, -width)); hand.setPoint(5, sf::Vector2f(0, 0)); hand.setFillColor(color); hand.setOrigin(0, 0); // Точка вращения в основании return hand; }; //Создаем стрелки с разными параметрами sf::ConvexShape hourHand = createSmoothHand(200.f, 12.f, sf::Color::Black); sf::ConvexShape minuteHand = createSmoothHand(240.f, 8.f, sf::Color::Black); sf::ConvexShape secondHand = createSmoothHand(240.f, 4.f, sf::Color::Red); //Позиционируем стрелки в центре окна const sf::Vector2f center(300.f, 300.f); hourHand.setPosition(center); minuteHand.setPosition(center); secondHand.setPosition(center); //4. Основной цикл программы while (window.isOpen()) { //Обработка событий sf::Event event; while (window.pollEvent(event)) { if (event.type == sf::Event::Closed) window.close(); //Закрытие окна при нажатии на крестик } //5. Получение и обработка текущего времени std::time_t currentTime = std::time(nullptr); std::tm* localTime = std::localtime(&currentTime); //Рассчитываем углы поворота для каждой стрелки: float hourAngle = (localTime->tm_hour % 12) * 30.f + localTime->tm_min * 0.5f; float minuteAngle = localTime->tm_min * 6.f + localTime->tm_sec * 0.1f; float secondAngle = localTime->tm_sec * 6.f; //Применяем вычисленные углы поворота hourHand.setRotation(hourAngle); minuteHand.setRotation(minuteAngle); secondHand.setRotation(secondAngle); //6. Отрисовка всех элементов window.clear(sf::Color(240, 240, 240)); //Светло-серый фон //Рисуем элементы в правильном порядке: window.draw(dial); // Циферблат window.draw(hourHand); // Часовая стрелка window.draw(minuteHand); // Минутная стрелка window.draw(secondHand); // Секундная стрелка //Отображаем все на экране window.display(); } return 0; }

Все бы здорово, но оказалось, что такая реализация жрет довольно много системных ресурсов, то есть, 6,5% ЦП при том, что моя шарманка основана на i7-12700KF! Если кто не в курсе - это топовый Интеловский камень 12-го покаления, своего рода монстр в плане производительности.

watch 3png

Пришлось немного оптимизировать код.

Вот обновленный листинг:

//main.cpp
#include <SFML/Graphics.hpp>
#include <ctime> #include <thread> #include <chrono> // Используем литералы времени из C++14 using namespace std::chrono_literals; int main() { // 1. Настройка окна с VSync и сглаживанием sf::ContextSettings settings; settings.antialiasingLevel = 8; // 8x сглаживание // Создание окна с вертикальной синхронизацией sf::RenderWindow window(sf::VideoMode(600, 600), "madWatch v1.1", sf::Style::Default, settings); window.setVerticalSyncEnabled(true); // Включение VSync window.setFramerateLimit(60); // Дополнительное ограничение FPS // 2. Загрузка и настройка циферблата sf::Texture dialTexture; if (!dialTexture.loadFromFile("Watch.png")) { return EXIT_FAILURE; // Выход при ошибке загрузки } sf::Sprite dial(dialTexture); dial.setOrigin(288, 288); // Центр для изображения 576x576 dial.setPosition(300, 300); // Позиция в центре окна // 3. Создание плавных стрелок auto createHand = [](float length, float width, sf::Color color) { sf::ConvexShape hand; hand.setPointCount(7); // 7 точек для гладкой формы // Форма стрелки с заострённым концом hand.setPoint(0, sf::Vector2f(-width/2, width)); hand.setPoint(1, sf::Vector2f(-width/3, 0)); hand.setPoint(2, sf::Vector2f(-width/4, -length*0.9f)); hand.setPoint(3, sf::Vector2f(0, -length)); hand.setPoint(4, sf::Vector2f(width/4, -length*0.9f)); hand.setPoint(5, sf::Vector2f(width/3, 0)); hand.setPoint(6, sf::Vector2f(width/2, width)); hand.setFillColor(color); hand.setOrigin(0, 0); // Вращение вокруг основания return hand; }; // Инициализация стрелок sf::ConvexShape hourHand = createHand(200.f, 12.f, sf::Color::Black); sf::ConvexShape minuteHand = createHand(238.f, 8.f, sf::Color::Black); sf::ConvexShape secondHand = createHand(240.f, 4.f, sf::Color::Red); // Позиционирование в центре окна const sf::Vector2f center(300.f, 300.f); hourHand.setPosition(center); minuteHand.setPosition(center); secondHand.setPosition(center); // 4. Переменные для оптимизации sf::Clock frameClock; constexpr auto targetFrameTime = 16ms; // Целевое время кадра (~60 FPS) bool needsRedraw = true; // Флаг необходимости перерисовки std::tm lastTime = {}; // Для отслеживания изменения времени // 5. Главный цикл с оптимизацией CPU while (window.isOpen()) { // Фиксируем начало кадра auto frameStart = std::chrono::steady_clock::now(); // Обработка событий sf::Event event; while (window.pollEvent(event)) { if (event.type == sf::Event::Closed) { window.close(); } // При любом взаимодействии обновляем экран needsRedraw = true; } // 6. Обновление времени с минимальной нагрузкой std::time_t now = std::time(nullptr); std::tm currentTime = *std::localtime(&now); // Проверяем изменение секунды if (currentTime.tm_sec != lastTime.tm_sec) { lastTime = currentTime; needsRedraw = true; // Расчёт углов поворота float hourAngle = (currentTime.tm_hour % 12) * 30.f + currentTime.tm_min * 0.5f; float minuteAngle = currentTime.tm_min * 6.f + currentTime.tm_sec * 0.1f; float secondAngle = currentTime.tm_sec * 6.f; // Применяем повороты hourHand.setRotation(hourAngle); minuteHand.setRotation(minuteAngle); secondHand.setRotation(secondAngle); } // 7. Условная отрисовка if (needsRedraw) { window.clear(sf::Color(240, 240, 240)); // Светло-серый фон window.draw(dial); window.draw(hourHand); window.draw(minuteHand); window.draw(secondHand); window.display(); needsRedraw = false; } else { // Режим ожидания при отсутствии изменений std::this_thread::sleep_for(1ms); } // 8. Точное ограничение FPS auto frameDuration = std::chrono::steady_clock::now() - frameStart; if (frameDuration < targetFrameTime) { std::this_thread::sleep_for(targetFrameTime - frameDuration); } } return 0; }

Ключевые особенности новой версии:

  1. Полная статическая сборка:

    g++ main.cpp -o madWatch.exe -std=c++14 -DSFML_STATIC \
    -I/ucrt64/include -L/ucrt64/lib \
    -lsfml-graphics-s -lsfml-window-s -lsfml-system-s \
    -lopengl32 -lgdi32 -lfreetype -lwinmm \
    -mwindows -static
    
    g++ main.cpp -o madWatch.exe -std=c++14 -DSFML_STATIC \
    -I/ucrt64/include -L/ucrt64/lib \
    -lsfml-graphics-s -lsfml-window-s -lsfml-system-s \
    -lopengl32 -lgdi32 -lfreetype -lwinmm \
    -mwindows -static
    
  2. Оптимизации производительности:

    • VSync + ограничение FPS

    • "Ленивая" отрисовка (только при изменении времени)

    • Точно контролируемые периоды ожидания

  3. Качественная визуализация:

    • Гладкие стрелки сложной формы

    • Правильные углы поворота

    • Светлый фон для лучшего контраста

  4. Современный C++:

    • Литералы времени (16ms)

    • constexpr для констант времени

    • Хронометраж через steady_clock

 

Компилируем так:

g++ main.cpp -o Watch.exe -DSFML_STATIC -I/ucrt64/include -L/ucrt64/lib -lsfml-graphics-s -lsfml-window-s -lsfml-system-s -lopengl32 -lgdi32 -lfreetype -lwinmm -Wl,-Bstatic -lstdc++ -lpthread -Wl,-Bdynamic -mwindows

Это статическая сборка с упаковкой всех необходимых библиотек в исполняемый файл. Единственный нюанс... Файл циферблата остается отдельно от самой программы, в той же папке, однако эта особенность позволяет менять "шкурки" под любой вкус и настроение. Главное подогнать размер: как уже упомяналось ранее, это 576 на 576 пикселей.

watch 4png

Ну и наконец еще одна версия... Я думаю, финальная на этот раз. madWatch 1.2 содержит в себе массив данных, представляющих собой циферблат по умолчанию, который подключается "инклюдом" в коде основной программы. Таким образом мы отвязываемся от необходимости иметь в папке программы файл "шкурки". Однако, если она там есть, мы будем использовать именно ее!

Чтобы сгенерировать массив, используем команду:

hexdump -v -e '16/1 "0x%02x, " "\n"' Watch.png > watch_image.h

Так мы получим заголовочный файл watch_image.h со списком байтов, содержащих картинку циферблата. К сожалению, без оформления в массив. Нам надо скопировать эти байты

Ctrl+A
Ctrl+C

Сохранить временно в каком-нибудь блокноте... Пустой хвост ", 0x , 0x , 0x , 0x , 0x , 0x , 0x ," в конце списка байтов следует удалить.

Затем вставим "купированный" список байтов в следующий шаблон:

// watch_image.h
#ifndef WATCH_IMAGE_H
#define WATCH_IMAGE_H
 
#include <cstddef>
 
namespace EmbeddedResources {
    const unsigned char WATCH_PNG[] = {
        // Вставьте сюда корректные байты PNG-файла
        // сгенерированные через `hexdump` или `xxd -i`
        0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A,
        // ... остальные байты изображения ...
    };
    const size_t WATCH_PNG_SIZE = sizeof(WATCH_PNG);
}
#endif // WATCH_IMAGE_H

Получится динная такая "простыня" массива данных. Так мы подготовим корректный заголовочный файл watch_image.h, который затем подключим в main.cpp через инклюд.

//main.cpp
#include <SFML/Graphics.hpp>
#include <ctime> #include <thread> #include <chrono> #include <sys/stat.h> // Для проверки существования файла #include "watch_image.h" bool fileExists(const char* filename) { struct stat buffer; return (stat(filename, &buffer) == 0); } bool loadTexture(sf::Texture& texture) { // Сначала пробуем загрузить внешний файл if (fileExists("Watch.png")) { return texture.loadFromFile("Watch.png"); } // Если файла нет, используем встроенный ресурс return texture.loadFromMemory(EmbeddedResources::WATCH_PNG, EmbeddedResources::WATCH_PNG_SIZE); } int main() { // 1. Настройка окна с VSync и сглаживанием sf::ContextSettings settings; settings.antialiasingLevel = 8; sf::RenderWindow window(sf::VideoMode(600, 600), "madWatch v1.2", sf::Style::Default, settings); window.setVerticalSyncEnabled(true); window.setFramerateLimit(60); // 2. Загрузка и настройка циферблата sf::Texture dialTexture; if (!loadTexture(dialTexture)) { return EXIT_FAILURE; } sf::Sprite dial(dialTexture); dial.setOrigin(288, 288); dial.setPosition(300, 300); // 3. Создание стрелок auto createHand = [](float length, float width, sf::Color color) { sf::ConvexShape hand; hand.setPointCount(7); hand.setPoint(0, sf::Vector2f(-width/2, width)); hand.setPoint(1, sf::Vector2f(-width/3, 0)); hand.setPoint(2, sf::Vector2f(-width/4, -length*0.9f)); hand.setPoint(3, sf::Vector2f(0, -length)); hand.setPoint(4, sf::Vector2f(width/4, -length*0.9f)); hand.setPoint(5, sf::Vector2f(width/3, 0)); hand.setPoint(6, sf::Vector2f(width/2, width)); hand.setFillColor(color); hand.setOrigin(0, 0); return hand; }; sf::ConvexShape hourHand = createHand(200.f, 12.f, sf::Color::Black); sf::ConvexShape minuteHand = createHand(238.f, 8.f, sf::Color::Black); sf::ConvexShape secondHand = createHand(240.f, 4.f, sf::Color::Red); const sf::Vector2f center(300.f, 300.f); hourHand.setPosition(center); minuteHand.setPosition(center); secondHand.setPosition(center); // 4. Главный цикл sf::Clock frameClock; const std::chrono::milliseconds targetFrameTime(16); bool needsRedraw = true; std::tm lastTime = {}; while (window.isOpen()) { auto frameStart = std::chrono::steady_clock::now(); sf::Event event; while (window.pollEvent(event)) { if (event.type == sf::Event::Closed) window.close(); needsRedraw = true; } std::time_t now = std::time(nullptr); std::tm currentTime = *std::localtime(&now); if (currentTime.tm_sec != lastTime.tm_sec) { lastTime = currentTime; needsRedraw = true; float hourAngle = (currentTime.tm_hour % 12) * 30.f + currentTime.tm_min * 0.5f; float minuteAngle = currentTime.tm_min * 6.f + currentTime.tm_sec * 0.1f; float secondAngle = currentTime.tm_sec * 6.f; hourHand.setRotation(hourAngle); minuteHand.setRotation(minuteAngle); secondHand.setRotation(secondAngle); } if (needsRedraw) { window.clear(sf::Color(240, 240, 240)); window.draw(dial); window.draw(hourHand); window.draw(minuteHand); window.draw(secondHand); window.display(); needsRedraw = false; } else { std::this_thread::sleep_for(std::chrono::milliseconds(1)); } auto frameDuration = std::chrono::steady_clock::now() - frameStart; if (frameDuration < targetFrameTime) { std::this_thread::sleep_for(targetFrameTime - frameDuration); } } return 0; }

А вот ниже вариант с определением разрешения экрана и масштабированием окна программы вместе с циферблатом и стрелками. Не знаю, нахера это надо, ну пусть будет наглядное пособие, как это сделать:

//main.cpp
//madmentat.ru
#ifdef _WIN32
#include <windows.h> #endif #include <SFML/Graphics.hpp> #include <ctime> #include <thread> #include <chrono> #include <sys/stat.h> #include <iostream> #include "watch_image.h"   // Проверка существования файла bool fileExists(const char* filename) { struct stat buffer; return (stat(filename, &buffer) == 0); } // Загрузка текстуры bool loadTexture(sf::Texture& texture) { if (fileExists("Watch.png")) { return texture.loadFromFile("Watch.png"); } return texture.loadFromMemory(EmbeddedResources::WATCH_PNG, EmbeddedResources::WATCH_PNG_SIZE); } // Получение разрешения текущего монитора (Windows) #ifdef _WIN32 sf::Vector2u getCurrentMonitorResolution(const sf::WindowHandle& handle) { HMONITOR monitor = MonitorFromWindow(handle, MONITOR_DEFAULTTONEAREST); if (!monitor) { std::cerr << "Ошибка: не удалось определить монитор!" << std::endl; return sf::Vector2u(0, 0); } MONITORINFO info{}; info.cbSize = sizeof(MONITORINFO); if (!GetMonitorInfo(monitor, &info)) { std::cerr << "Ошибка: не удалось получить информацию о мониторе!" << std::endl; return sf::Vector2u(0, 0); } unsigned width = info.rcMonitor.right - info.rcMonitor.left; unsigned height = info.rcMonitor.bottom - info.rcMonitor.top; std::cout << "Текущее разрешение монитора: " << width << "x" << height << std::endl; return sf::Vector2u(width, height); } #endif int main() { #ifdef _WIN32 SetProcessDPIAware(); #endif sf::ContextSettings settings; settings.antialiasingLevel = 8; unsigned windowSize = 576; // Начальный размер sf::RenderWindow window(sf::VideoMode(windowSize, windowSize), "madWatch v1.3", sf::Style::Titlebar | sf::Style::Close, settings); window.setVerticalSyncEnabled(true); window.setFramerateLimit(60); sf::Texture dialTexture; if (!loadTexture(dialTexture)) { return EXIT_FAILURE; } sf::Sprite dial(dialTexture); dial.setOrigin(288, 288); auto createHand = [](float length, float width, sf::Color color) { sf::ConvexShape hand; hand.setPointCount(7); hand.setPoint(0, sf::Vector2f(-width/2, width)); hand.setPoint(1, sf::Vector2f(-width/3, 0)); hand.setPoint(2, sf::Vector2f(-width/4, -length*0.9f)); hand.setPoint(3, sf::Vector2f(0, -length)); hand.setPoint(4, sf::Vector2f(width/4, -length*0.9f)); hand.setPoint(5, sf::Vector2f(width/3, 0)); hand.setPoint(6, sf::Vector2f(width/2, width)); hand.setFillColor(color); hand.setOrigin(0, 0); return hand; }; sf::Vector2u lastMonitorRes(0, 0); sf::Vector2i lastWindowPos(0, 0); std::tm lastTime = {}; bool needsRedraw = true; while (window.isOpen()) { auto frameStart = std::chrono::steady_clock::now(); // Получаем текущее разрешение монитора и позицию окна #ifdef _WIN32 sf::Vector2u currentMonitorRes = getCurrentMonitorResolution(window.getSystemHandle()); #else sf::Vector2u currentMonitorRes = sf::VideoMode::getDesktopMode(); #endif sf::Vector2i currentWindowPos = window.getPosition(); // Обновляем заголовок окна std::string title = "madWatch v1.3 [" + std::to_string(currentMonitorRes.x) + "x" + std::to_string(currentMonitorRes.y) + "]"; window.setTitle(title); // Если монитор сменился, пересоздаём окно if (currentMonitorRes != lastMonitorRes && currentMonitorRes.y > 0) { lastMonitorRes = currentMonitorRes; unsigned screenHeight = currentMonitorRes.y; if (screenHeight <= 1080) { windowSize = 450; } else if (screenHeight <= 1440) { windowSize = 576; } else { windowSize = 700; } lastWindowPos = window.getPosition(); std::cout << "Пересоздание окна с размером: " << windowSize << "x" << windowSize << " на позиции: " << lastWindowPos.x << "," << lastWindowPos.y << std::endl; // Пересоздаём окно window.create(sf::VideoMode(windowSize, windowSize), title, sf::Style::Titlebar | sf::Style::Close, settings); window.setVerticalSyncEnabled(true); window.setFramerateLimit(60); window.setPosition(lastWindowPos); // Немедленная отрисовка после пересоздания float scaleFactor = windowSize / 600.f; dial.setPosition(windowSize / 2.f, windowSize / 2.f); dial.setScale(scaleFactor, scaleFactor); sf::ConvexShape hourHand = createHand(200.f * scaleFactor, 12.f * scaleFactor, sf::Color::Black); sf::ConvexShape minuteHand = createHand(238.f * scaleFactor, 8.f * scaleFactor, sf::Color::Black); sf::ConvexShape secondHand = createHand(240.f * scaleFactor, 4.f * scaleFactor, sf::Color::Red); const sf::Vector2f center(windowSize / 2.f, windowSize / 2.f); hourHand.setPosition(center); minuteHand.setPosition(center); secondHand.setPosition(center); std::time_t now = std::time(nullptr); std::tm currentTime = *std::localtime(&now); float hourAngle = (currentTime.tm_hour % 12) * 30.f + currentTime.tm_min * 0.5f; float minuteAngle = currentTime.tm_min * 6.f + currentTime.tm_sec * 0.1f; float secondAngle = currentTime.tm_sec * 6.f; hourHand.setRotation(hourAngle); minuteHand.setRotation(minuteAngle); secondHand.setRotation(secondAngle); window.clear(sf::Color(240, 240, 240)); window.draw(dial); window.draw(hourHand); window.draw(minuteHand); window.draw(secondHand); window.display(); } // Обновляем масштаб float scaleFactor = windowSize / 600.f; dial.setPosition(windowSize / 2.f, windowSize / 2.f); dial.setScale(scaleFactor, scaleFactor); sf::ConvexShape hourHand = createHand(200.f * scaleFactor, 12.f * scaleFactor, sf::Color::Black); sf::ConvexShape minuteHand = createHand(238.f * scaleFactor, 8.f * scaleFactor, sf::Color::Black); sf::ConvexShape secondHand = createHand(240.f * scaleFactor, 4.f * scaleFactor, sf::Color::Red); const sf::Vector2f center(windowSize / 2.f, windowSize / 2.f); hourHand.setPosition(center); minuteHand.setPosition(center); secondHand.setPosition(center); // Обработка событий sf::Event event; while (window.pollEvent(event)) { if (event.type == sf::Event::Closed) window.close(); needsRedraw = true; } // Обновляем время std::time_t now = std::time(nullptr); std::tm currentTime = *std::localtime(&now); if (currentTime.tm_sec != lastTime.tm_sec) { lastTime = currentTime; needsRedraw = true; float hourAngle = (currentTime.tm_hour % 12) * 30.f + currentTime.tm_min * 0.5f; float minuteAngle = currentTime.tm_min * 6.f + currentTime.tm_sec * 0.1f; float secondAngle = currentTime.tm_sec * 6.f; hourHand.setRotation(hourAngle); minuteHand.setRotation(minuteAngle); secondHand.setRotation(secondAngle); } // Отрисовка if (needsRedraw) { float hourAngle = (currentTime.tm_hour % 12) * 30.f + currentTime.tm_min * 0.5f; float minuteAngle = currentTime.tm_min * 6.f + currentTime.tm_sec * 0.1f; float secondAngle = currentTime.tm_sec * 6.f; hourHand.setRotation(hourAngle); minuteHand.setRotation(minuteAngle); secondHand.setRotation(secondAngle); window.clear(sf::Color(240, 240, 240)); window.draw(dial); window.draw(hourHand); window.draw(minuteHand); window.draw(secondHand); window.display(); needsRedraw = false; } else { std::this_thread::sleep_for(std::chrono::milliseconds(1)); } // Стабильный FPS auto frameDuration = std::chrono::steady_clock::now() - frameStart; if (frameDuration < std::chrono::milliseconds(16)) { std::this_thread::sleep_for(std::chrono::milliseconds(16) - frameDuration); } } return 0; }

Компилируем со статической линковкой, как обычно, плюс еще добавляем параметр "-mwindows" для того чтобы программа получилась именно оконной, а не консольной. Иначе вместе с ней будет открываться cmd.

g++ main.cpp -o madWatch.exe -std=c++14 -DSFML_STATIC -I. -I/ucrt64/include -L/ucrt64/lib \ -lsfml-graphics-s -lsfml-window-s -lsfml-system-s -lopengl32 -lgdi32 -lfreetype -lwinmm \ -static -static-libgcc -static-libstdc++ -mwindows