Множество Мандельброта. 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; }
