Множество Мандельброта. 80-бит FPU x87. OpenMP - параллельным программированием на уровне многопоточности. Синий, зеленый и красный - синусоидальными и косинусоидальными волнами: 127 + 127 cos(2 PI a / 255) и 127 + 127 sin(2 PI a / 255). DwmFlush - синхронизация с монитором 60 fps. Суперсэмплинг 2x2 (4 прохода). Делал я. Посмотрите - движется! Я сделал на g++. Свободно распространяемого компилятора языка C++. Скачайте и посмотрите! Это экзешник, в ГитХаб.
github: Download Latest Version Windows And Source code
Самое полезное - это увеличиваем / уменьшаем и центрируем. Вы на экран любое из множество Мандельброта. Какое вам нравится? Какое интересное? Вы можете все! И потом запишется в файл Mandelbrot.txt - три строки из файла. Вещественная часть центра и мнимая часть центра и ширина видимой области. Потом другая программа читает Mandelbrot.txt и создает Mandelbrot.bmp и уже не суперсэмплинг 2x2 (4 прохода) а 8x8 (64 прохода)!
Можно медиаэлемент github.com/user-attachments/assets видео - вы сейчас видите две из трех видео.
Управление мышью
WM_LBUTTONDOWN (Левая кнопка) - увеличиваем масштаб в 2 раза и центрируем новую область вокруг точки клика.
WM_RBUTTONDOWN (Правая кнопка) - уменьшаем масштаб в 2 раза и центрируем новую область вокруг точки клика.
Навигация с помощью клавиатуры
В 1 - 8 - восемь мест Множество Мандельброта на экран.
VK_LEFT (Стрелка ВЛЕВО) и VK_RIGHT (Стрелка ВПРАВО) - увеличиваем и уменьшаем в 1.1 раза но без точки клика.
Управление данными
Очень важно VK_RETURN (Enter, Ввод) - у вас сейчас на экран какое-то Множество Мандельброта. И сейчас оно запишется в файл! Mandelbrot.txt
А VK_BACK (это та самая клавиша НАД Enter, Backspace) - читает Mandelbrot.txt (читаем три строки из файла) и запускает на экран.
Вот код
#ifndef UNICODE #define UNICODE #endif #include <windows.h> #include <dwmapi.h> #include <vector> #include <cmath> #include <thread> #include <mutex> #include <atomic> #include <omp.h> #include <fstream> #include <string> #include <iomanip> const int WIDTH = 1000; const int HEIGHT = 1000; const int SS_W = 2000; const int SS_H = 2000; const int PALETTE_SIZE = 1024; struct FractalParams { long double step; long double labsc; long double bordi; uint32_t iter_max; long double size; }; std::mutex g_params_mutex; FractalParams g_params; std::atomic<bool> g_abort{false}; HANDLE g_render_event; uint32_t g_ss_buffer[SS_W * SS_H]; const long double PRESETS[8][3] = { {-0.550345905862346513L, 0.625931416301985337L, 0.0000000000000029L}, {-0.88380294401099034L, -0.23531813998049201L, 0.0000000000000019L}, {-0.691488093510181825L, -0.465680729473216972L, 0.0000000000000016L}, {-1.26392609056234794L, -0.17578764215262827L, 0.000000000000023L}, {-0.77781161182303603L, 0.13164688997878032L, 0.000000000000032L}, {0.36053464666960874L, 0.64131558138012431L, 0.0000000000000035L}, {-1.18963036804118707L, 0.30427573376836223L, 0.0000000000000014L}, {-0.5503493176297569L, 0.6259309572825709L, 0.00000000000031L} }; void generate_full_palette(RGBQUAD* pal) { const double pi = 3.141592653589793; for (int i = 0; i < PALETTE_SIZE; i++) { double angle = (2.0 * pi * i) / (double)PALETTE_SIZE; pal[i].rgbRed = (uint8_t)(127.0 + 127.0 * std::sin(angle * 4)); pal[i].rgbBlue = (uint8_t)(127.0 + 127.0 * std::cos(angle * 4)); pal[i].rgbGreen = (uint8_t)(127.0 + 127.0 * std::sin(angle * 4)); pal[i].rgbReserved = 0; } } void thread_mandelbrot_calc() { while (true) { WaitForSingleObject(g_render_event, INFINITE); ResetEvent(g_render_event); g_abort = false; FractalParams p; { std::lock_guard<std::mutex> lock(g_params_mutex); p = g_params; } long double ss_step = p.step / 2.0L; #pragma omp parallel for schedule(dynamic) for (int ss_y = 0; ss_y < SS_H; ++ss_y) { if (g_abort) continue; for (int ss_x = 0; ss_x < SS_W; ++ss_x) { long double rec = p.labsc + (ss_x * ss_step); long double imc = p.bordi - (ss_y * ss_step); long double re = 0, im = 0, re2 = 0, im2 = 0; uint32_t i = 0; while (i < p.iter_max && (re2 + im2) < 1000000.0L) { im = 2.0L * re * im + imc; re = re2 - im2 + rec; re2 = re * re; im2 = im * im; i++; } g_ss_buffer[ss_y * SS_W + ss_x] = i; } } } } void thread_palette_rotator(HDC hdc_win, HDC hdc_m, RGBQUAD* pixels) { RGBQUAD pal[PALETTE_SIZE]; generate_full_palette(pal); double offset = 0; while (true) { #pragma omp parallel for schedule(static, 256) for (int y = 0; y < HEIGHT; ++y) { for (int x = 0; x < WIDTH; ++x) { uint32_t sumR = 0, sumG = 0, sumB = 0; for (int sy = 0; sy < 2; ++sy) { for (int sx = 0; sx < 2; ++sx) { int ss_idx = (y * 2 + sy) * SS_W + (x * 2 + sx); uint32_t it = g_ss_buffer[ss_idx]; if (it >= 50000) { sumR += 255; sumG += 255; sumB += 255; } else { int inverted_it = 50000 - it; int idx = (int)(inverted_it + offset) % PALETTE_SIZE; if (idx < 0) idx += PALETTE_SIZE; sumR += pal[idx].rgbRed; sumG += pal[idx].rgbGreen; sumB += pal[idx].rgbBlue; } } } int pix_idx = y * WIDTH + x; pixels[pix_idx].rgbRed = (uint8_t)(sumR >> 2); pixels[pix_idx].rgbGreen = (uint8_t)(sumG >> 2); pixels[pix_idx].rgbBlue = (uint8_t)(sumB >> 2); pixels[pix_idx].rgbReserved = 0; } } offset = fmod(offset - 1.0, (double)PALETTE_SIZE); BitBlt(hdc_win, 0, 0, WIDTH, HEIGHT, hdc_m, 0, 0, SRCCOPY); DwmFlush(); } } LRESULT CALLBACK wnd_proc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { switch (msg) { case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: { g_abort = true; std::lock_guard<std::mutex> lock(g_params_mutex); long double mouse_x = (long double)((short)LOWORD(lp)); long double mouse_y = (long double)((short)HIWORD(lp)); long double clicked_re = g_params.labsc + (mouse_x * g_params.step); long double clicked_im = g_params.bordi - (mouse_y * g_params.step); if (msg == WM_LBUTTONDOWN) { g_params.size /= 2.0L; } else { g_params.size *= 2.0L; } g_params.step = g_params.size / (long double)WIDTH; g_params.labsc = clicked_re - (g_params.size / 2.0L); g_params.bordi = clicked_im + (g_params.size / 2.0L); SetEvent(g_render_event); return 0; } case WM_KEYDOWN: { if (wp == VK_LEFT || wp == VK_RIGHT) { g_abort = true; std::lock_guard<std::mutex> lock(g_params_mutex); long double center_re = g_params.labsc + (g_params.size / 2.0L); long double center_im = g_params.bordi - (g_params.size / 2.0L); if (wp == VK_LEFT) g_params.size /= 1.1L; else g_params.size *= 1.1L; g_params.step = g_params.size / (long double)WIDTH; g_params.labsc = center_re - (g_params.size / 2.0L); g_params.bordi = center_im + (g_params.size / 2.0L); SetEvent(g_render_event); return 0; } if (wp >= '1' && wp <= '8') { int idx = (int)(wp - '1'); g_abort = true; std::lock_guard<std::mutex> lock(g_params_mutex); g_params.size = PRESETS[idx][2]; g_params.step = g_params.size / (long double)WIDTH; g_params.labsc = PRESETS[idx][0] - (g_params.size / 2.0L); g_params.bordi = PRESETS[idx][1] + (g_params.size / 2.0L); SetEvent(g_render_event); return 0; } if (wp == VK_BACK) { std::ifstream file("Mandelbrot.txt"); if (file.is_open()) { std::vector<long double> coords; long double val; while (file >> val) { coords.push_back(val); if (coords.size() == 3) break; } file.close(); if (coords.size() == 3) { g_abort = true; std::lock_guard<std::mutex> lock(g_params_mutex); long double c_re = coords[0]; long double c_im = coords[1]; long double new_size = coords[2]; g_params.size = new_size; g_params.step = new_size / (long double)WIDTH; g_params.labsc = c_re - (new_size / 2.0L); g_params.bordi = c_im + (new_size / 2.0L); SetEvent(g_render_event); } } return 0; } if (wp == VK_RETURN) { std::lock_guard<std::mutex> lock(g_params_mutex); long double center_re = g_params.labsc + (g_params.size / 2.0L); long double center_im = g_params.bordi - (g_params.size / 2.0L); std::ofstream file("Mandelbrot.txt"); if (file.is_open()) { file << std::fixed << std::setprecision(20); file << center_re << "\n"; file << center_im << "\n"; file << g_params.size << "\n"; file.close(); } return 0; } break; } case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, msg, wp, lp); } int main() { HINSTANCE inst = GetModuleHandle(NULL); WNDCLASS wc = {0}; wc.lpfnWndProc = wnd_proc; wc.hInstance = inst; wc.hIcon = LoadIcon(inst, MAKEINTRESOURCE(1)); wc.lpszClassName = L"MandelClass"; wc.hCursor = LoadCursor(NULL, IDC_ARROW); RegisterClass(&wc); HWND hwnd = CreateWindowEx(0, L"MandelClass", L"Mandelbrot set. 32-bit TrueColor. 60 FPS. 80-bit long double. OpenMP. Supersampling 2x2 (4 passes). Color rotation", WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, WIDTH + 16, HEIGHT + 38, NULL, NULL, inst, NULL); HDC hdc_win = GetDC(hwnd); HDC hdc_mem = CreateCompatibleDC(hdc_win); BITMAPINFO bmi = {0}; bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmi.bmiHeader.biWidth = WIDTH; bmi.bmiHeader.biHeight = -HEIGHT; bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 32; bmi.bmiHeader.biCompression = BI_RGB; RGBQUAD* screen_pixels = nullptr; HBITMAP h_bmp = CreateDIBSection(hdc_mem, &bmi, DIB_RGB_COLORS, (void**)&screen_pixels, NULL, 0); SelectObject(hdc_mem, h_bmp); g_params.size = 0.00000000000003L; g_params.iter_max = 50000; g_params.step = g_params.size / (long double)WIDTH; g_params.labsc = -0.9177640112013507L - (g_params.size / 2.0L); g_params.bordi = -0.2787829020420787L + (g_params.size / 2.0L); g_render_event = CreateEvent(NULL, TRUE, TRUE, NULL); std::thread(thread_mandelbrot_calc).detach(); std::thread(thread_palette_rotator, hdc_win, hdc_mem, screen_pixels).detach(); MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return 0; }
Комментарии (13)

wataru
18.03.2026 12:02Это ваша третья статья на хабр по этой маленькой демке. Я правильно понимаю, что единственное отличие от предыдущей статьи, что тут у вас 2x2 прохода вместо 8x8, а отличие от первой, что вы тут VSYNC запилили?
Вы будете после каждого тривиального изменения статью на хабр постить?

aokoroko Автор
18.03.2026 12:02DwmFlush - синхронизация с монитором 60 fps. Делал я. Посмотрите - движется! А в 8x8 не движется.

wataru
18.03.2026 12:02Повторю свой вопрос: Вы будете после каждого тривиального изменения статью на хабр постить?

aokoroko Автор
18.03.2026 12:02Нет. Изменения не будет так как хорошо весьма. И не тривиального мне кажется.

wataru
18.03.2026 12:02По-вашему воткнуть вызов DwmFlush() в функцию прорисовки - это нетривиальное изменение, заслуживающее отдельной статьи?

aokoroko Автор
18.03.2026 12:02А вы скачайте и посмотрите. Если не интересно то я не знаю...

peg59
18.03.2026 12:02Ну так что насчет вращения палитры? Очень бьет по глазам. Как замедлить или выключить?

wataru
18.03.2026 12:02Ну смотрите, вот ваша статья от 25 февраля: https://habr.com/ru/articles/1001498/
И она и вот эта обе ссылаются на один и тот же репозиторий. В этом репозитории с 25 февраля было только 4 комита 15 марта. Из них только один менял код: https://github.com/Divetoxx/Mandelbrot-2/commit/118353923097e3756729c379dccd693631b0f66e#diff-e44b21fdead157a47cc6889827a81a973b681314398117f459c3a1955ca1e2e1
Там чуть-чуть поменяны какие-то константы, в одном месте переставлены 2 строчки, в другом исправлено форматирование и одна константа поменяна.
DwmFlush был добавлен раньше первой статьи - 17 февраля.
Т.е. то, что вы вот в этой статье аж в заголовок засунли, было уже на момент предыдущей статьи.
Спрашивается, нафига вы вот эту статью написали?

wataru
18.03.2026 12:02Божечки, промотал ваш профиль на хабре, практически идентичных статей от вас аж 6(шесть) штук! Какое безобразие.
peg59
Ваша анимация цвета ужасно мельтешит на небольших увеличениях. Сделайте возможность регулировать или вообще ее убирать.