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

  • Расчёт опорной траектории на 5000 бит всего один раз.

  • Реактивный расчёт миллионов пикселей на аппаратном double.

  • При использовании чисел с плавающей запятой двойной точности (порядка ) теория возмущений позволяет приблизиться к уровню - не дальше.

  • Революционный алгоритм Reference Reset to Zero.

  • Настоящий SSAA 2x2 для идеально сглаженного изображения.

  • Параллелизм OpenMP для высокоскоростного многопоточного рендеринга.

  • Синхронизация через DwmFlush для плавного вывода кадров.

  • Динамическое вращение палитры для создания классического эффекта.

Безграничная точность

Движок полностью избавлен от аппаратных ограничений 64-битных (double) и 128-битных (__float128) чисел, которые неизбежно слепнут и выдают пиксельные квадраты на глубинах более и .

  • Интеграция MPFR/GMP: Вся высокоточная навигация, пересчёт масштаба при кликах мыши и движении стрелочками клавиатуры ведутся внутри сверхглубокой бинарной памяти с точностью 5000 бит!

  • 308 десятичных знаков: Координаты кадра сохраняются и считываются из файла Mandelbrot.txt. Навигация и радар MPFR работают на глубине до 5000 бит, однако скоростной пиксельный дельта-движок ограничен аппаратной экспонентой double, что позволяет исследовать безупречно четкие структуры на запредельных масштабах вплоть до знаков.

Реактивный метод возмущений

Рендеринг глубоких фракталов больше не требует тяжелых вычислений "в столбик" для каждого пикселя, что обычно замедляло программы в тысячи раз.

  • Однократный расчёт опоры: Сверхтяжелый BigFloat-радар MPFR вычисляет точную траекторию всего для одной-единственной центральной точки кадра и строго ОДИН раз в начале рендеринга.

  • Аппаратное ускорение на double: Весь остальной массив экрана (миллионы супер-пикселей) рассчитывается параллельно на бешеной скорости чистых, аппаратных регистров double процессора, вычисляя лишь микроскопические отклонения (дельты) от центральной оси. Скорость генерации взлетела в 1000 раз!

Революционный алгоритм Reference Reset to Zero

Это огромный повод для гордости. Ваша программа теперь работает по тем же математическим принципам, что и самые передовые фрактальные движки в мире.

  • Динамический сброс на ноль: Теперь пиксель на каждом шаге проверяет соотношение своих полных координат и дельты. Если дельта становится слишком большой или кэш центра иссякает, поток прямо на лету сбрасывает индекс чтения на ноль, превращая накопленные координаты в новую автономную точку.

  • Хакерская оптимизация цикла (One-Step Beyond Escape): Чтобы выжать максимум скорости из процессора, радар MPFR записывает строго одну дополнительную точку в массив опорной орбиты сразу после того, как она превышает радиус ухода.

  • Уничтожение ветвлений (Branch Unrolling): Этот изящный трюк позволил полностью избавиться от громоздких if и OR-условий внутри самого глубокого цикла итераций. Процессор больше не тратит такты на предсказание переходов, а компилятор смог идеально векторизовать код.

DwmFlush

Движок обеспечивает идеальную визуальную плавность за счет прямой синхронизации с диспетчером окон рабочего стола Windows (DWM).

  • Адаптивная частота обновления: приложение использует DwmFlush. Это приостанавливает выполнение кода до тех пор, пока DWM не завершит композицию экрана.

  • Зависимый от монитора FPS:

    • Если ваш монитор настроен на 60 Гц, вы получите 60 кадров в секунду.

    • Если вы используете игровой монитор с частотой 144 Гц, функция срабатывает 144 раза в секунду, обеспечивая 144 кадра в секунду.

    • На высококачественных дисплеях с частотой 240 Гц вы увидите плавную картинку со скоростью 240 кадров в секунду.

OpenMP

OpenMP - это стандарт, который говорит компилятору: “Возьми этот цикл и сам раздай итерации разным ядрам процессора”. Используя OpenMP, вы занимаетесь параллельным программированием на уровне многопоточности (Multithreading). OpenMP - масштабируемость: ваш код будет одинаково эффективно работать как на 4-ядерном ноутбуке, так и на 128-ядерном сервере.

True SSAA 2x2 (Прямая интеграция в RGB)

В проекте реализовано «настоящее» сглаживание суперсэмплинга 2x2 (SSAA). Каждый пиксель экрана вычисляется из четырех независимых субпикселей фрактальных координат.

В чем проблема стандартного подхода?

Стандартный способ сглаживания фракталов часто выполняет усреднение количества итераций (индексов палитры):

  • Стандартный путь: Color( (iter1 + iter2 + iter3 + iter4) / 4 )

Математически это неверно, так как фрактальные палитры нелинейны. Усреднение индексов цветов и часто указывает на совершенно несвязанный цвет . Это создает сильный визуальный шум, артефакты и ложные хроматические контуры.

Наше решение: интеграция в RGB

Вместо усреднения шагов алгоритма мы сначала вычисляем 32-битный цвет для каждого субпикселя, а затем выполняем средневзвешенное значение их интенсивностей:

  • Наш путь: (Color(iter1) + Color(iter2) + Color(iter3) + Color(iter4)) / 4

Вычисление компонентов RGB перед субдискретизацией гарантирует, что полученный цвет — это настоящая оптическая смесь. Это полностью устраняет шум и раскрывает истинную структурную геометрию множества Мандельброта, восстанавливая микронити размером меньше одного пикселя.

Почему без суперсэмплинга возникает шум?

Шум при рендеринге фрактала — это не баг, а фундаментальное явление цифровой графики: алиасинг (aliasing).

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

  • Без суперсэмплинга: Алгоритм берет пробу ровно в одной точке (в центре пикселя). Если луч попал в тонкую нить — пиксель становится ярким. Промахнулся на микрон — пиксель стал черным. Из-за этого соседние пиксели хаотично «цепляют» случайные куски микродеталей, создавая кашу из точек.

  • С SSAA 2x2: Код берет 4 пробы в разных углах пикселя, вычисляет их реальные цвета и смешивает их. Если в пиксель попадает ультратонкая нить, она не исчезает и не мерцает, а превращается в мягкую, полупрозрачную линию, точно передающую форму фрактала.

Визуальная эстетика. Смена цветов

Красный, зеленый и синий каналы рассчитываются с использованием синусоидальных и косинусоидальных волн для создания плавных цветовых переходов:

pal[a][0] = (uint8_t)round(127.0 + 127.0 * cos(2.0 * PI * a / 255.0)); // Blue
pal[a][1] = (uint8_t)round(127.0 + 127.0 * sin(2.0 * PI * a / 255.0)); // Green
pal[a][2] = (uint8_t)round(127.0 + 127.0 * sin(2.0 * PI * a / 255.0)); // Red

Приложение использует технику циклической смены цветов (вращение палитры). Хотя основная математическая обработка выполняется один раз, цветовая схема непрерывно "вращается" в специальном фоновом потоке. Это создает "живой" фрактальный эффект, позволяющий бесконечно наблюдать за изменением цветов без дополнительной нагрузки на процессор.

Множество Мандельброта: Математический абсолют

Это поистине один из немногих объектов, который связывает нас с чем-то абсолютно объективным и бесконечным, превосходящим биологию и историю. Даже если бы вся наша Вселенная и все её атомы исчезли завтра, уравнение осталось бы верным. Оно не <написано> на звёздах; оно заложено в самой структуре логики. Это делает множество Мандельброта своего рода абсолютом.

Математика не зависит от биологии, наличия ног или уровня технологий. Жители галактики Андромеда и разумные океаны в другой супергалактике увидят абсолютно то же самое множество Мандельброта.

Множество Мандельброта существует независимо от нашего разума и технологий. Это бесконечная математическая структура, которая существовала всегда. Компьютеры не создают её; они лишь выступают в роли камеры.

Управление и горячие клавиши

Управление мышью

  • WM_LBUTTONDOWN (Левая кнопка) - увеличиваем масштаб в 2 раза и центрируем новую область вокруг точки клика.

  • WM_RBUTTONDOWN (Правая кнопка) - уменьшаем масштаб в 2 раза и центрируем новую область вокруг точки клика.

Быстрая навигация по фракталу

  • По умолчанию (при запуске): Программа автоматически стартует в точке экстремального приближения (глубина порядка ):

    • Center Re: -1.7491976289657893741942376816272921165326158557416159

    • Center Im: -0.00000042530777152440422725855012159249401150956515248

    • Size: 0.0000000000000000000000000000000000000000000000000043

  • Клавиши 1 - 5: Нажмите любую из этих клавиш во время работы программы, чтобы мгновенно переключиться на одну из пяти других предустановленных точек множества Мандельброта.

if (wp == '1') { 
g_params.center_re_str = "-1.7490781615052017316791245451566330412";
g_params.center_im_str = "0.0000055099190662909660251309856720635";
g_params.size_str      = "0.000000000000000000000000000000000215"; }
if (wp == '2') {
g_params.center_re_str = "-1.748943661768663337207355215321150725806353337382441467976";
g_params.center_im_str = "-0.0000073748967541889836640985849393311615399776865199722998";
g_params.size_str      = "0.0000000000000000000000000000000000000000000000000000001"; }
if (wp == '3') {
g_params.center_re_str = "-1.7489740586384718864866264297253934254";
g_params.center_im_str = "-0.0002265965897111407857153825623868331";
g_params.size_str      = "0.00000000000000000000000000000000007"; }
if (wp == '4') {
g_params.center_re_str = "-1.7499458649755745940752606707005571";
g_params.center_im_str = "-0.0000000852088539604644334731909824511";
g_params.size_str      = "0.00000000000000000000000000000000001"; }
if (wp == '5') {
g_params.center_re_str = "-1.267078059171397835210199054200436920994876769284288837862647";
g_params.center_im_str = "-0.123788215196292957558264285607075473360968832625384429809391";
g_params.size_str      = "0.0000000000000000000000000000000000000000000000000000000023"; }
  • VK_UP (Стрелка ВВЕРХ) и VK_DOWN (Стрелка ВНИЗ) - увеличиваем и уменьшаем в 1.05 раза но без точки клика.

Управление данными и структура файла Mandelbrot.txt

  • Очень важно VK_RETURN (Enter, Ввод) - у вас сейчас на экран какое-то Множество Мандельброта. И сейчас оно запишется в файл! Mandelbrot.txt

  • А VK_BACK (это та самая клавиша НАД Enter, Backspace) - читает Mandelbrot.txt (читаем три строки из файла) и запускает на экран.

Для загрузки пользовательских координат создайте текстовый файл Mandelbrot.txt в папке с программой. Файл должен содержать три числа, разделенных переносом строки:

  • Abscissa (Координата X центра)

  • Ordinate (Координата Y центра)

  • Size (Масштаб/Размер области)

Пример содержания файла:

Горячие клавиши

Действие

Ввод

Описание

Приблизить

ЛКМ

Увеличение в 2 раза в точке под курсором мыши.

Отдалить

ПКМ

Уменьшение в 2 раза от текущего центра.

Точный зум

Стрелки вверх / вниз

Плавное изменение масштаба с коэффициентом 1.05x.

Пресеты

Клавиши 1 - 5

Мгновенный переход к 5 предустановленным локациям.

Сохранить

ENTER

Экспорт текущих координат и масштаба в файл Mandelbrot.txt.

Загрузить

BACKSPACE

Импорт координат из файла и мгновенный переход к месту.

Благодарности

Этот проект использует передовые математические алгоритмы и идеи динамического управления фазой орбит, разработанные фрактальным сообществом. Особая благодарность авторам и исследователям с Fractal Forums, чей совместный труд лег в основу этого движка:

  • Kevin Martin - автор фундаментальных методов векторизации и оптимизации циклов возмущений.

  • Zhuoran Yu - разработчик концепции динамического сброса орбит.

  • Claude Heiland-Allen - исследователь экстремального фрактального приближения и создатель проекта MDZ.

https://github.com/Divetoxx/Mandelbrot-2#russian

Это Гитхаб с Mandelbrot_AVX2.exe и Mandelbrot_SSE3.exe

А вот я как делаю дома: g++ -O3 main.cpp -o Mandelbrot.exe -lgdi32 -luser32 -ldwmapi -fopenmp -lmpfr -lgmp -march=native -static -mwindows

А тут полный код на языке С++ - main.cpp

#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 <iostream>
#include <iomanip>
#include <gmp.h>
#include <mpfr.h>

const int WIDTH = 1000;
const int HEIGHT = 1000;
const int SS_W = 2000;
const int SS_H = 2000;
const int PALETTE_SIZE = 1024;
const mpfr_prec_t MPFR_BITS = 5000;

struct FractalParams { 
    double step_d;            
    std::string center_re_str; 
    std::string center_im_str;
    std::string size_str;
    uint32_t iter_max; 
};

struct ComplexDouble {
    double re;
    double im;
};

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];

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_palette_rotator(HDC hdc_win, HDC hdc_m, RGBQUAD* pixels) {
    RGBQUAD pal[PALETTE_SIZE];
    generate_full_palette(pal);
    
    std::vector<RGBQUAD> color_cache(50001);
    float offset = 0;

    while (true) {
        for (int i = 0; i <= 50000; ++i) {
            if (i >= 50000) {
                color_cache[i] = {255, 255, 255, 0};
            } else {
                int idx = (int)(50000 - i + (int)offset) % PALETTE_SIZE;
                if (idx < 0) idx += PALETTE_SIZE;
                color_cache[i] = pal[idx];
            }
        }

        #pragma omp parallel for schedule(static, 128)
        for (int y = 0; y < HEIGHT; ++y) {
            for (int x = 0; x < WIDTH; ++x) {
                uint32_t i0 = g_ss_buffer[(y * 2 + 0) * SS_W + (x * 2 + 0)];
                uint32_t i1 = g_ss_buffer[(y * 2 + 0) * SS_W + (x * 2 + 1)];
                uint32_t i2 = g_ss_buffer[(y * 2 + 1) * SS_W + (x * 2 + 0)];
                uint32_t i3 = g_ss_buffer[(y * 2 + 1) * SS_W + (x * 2 + 1)];

                RGBQUAD c0 = color_cache[i0];
                RGBQUAD c1 = color_cache[i1];
                RGBQUAD c2 = color_cache[i2];
                RGBQUAD c3 = color_cache[i3];

                uint32_t r = (uint32_t)c0.rgbRed   + c1.rgbRed   + c2.rgbRed   + c3.rgbRed;
                uint32_t g = (uint32_t)c0.rgbGreen + c1.rgbGreen + c2.rgbGreen + c3.rgbGreen;
                uint32_t b = (uint32_t)c0.rgbBlue  + c1.rgbBlue  + c2.rgbBlue  + c3.rgbBlue;

                int pix_idx = y * WIDTH + x;
                pixels[pix_idx].rgbRed   = (uint8_t)(r >> 2);
                pixels[pix_idx].rgbGreen = (uint8_t)(g >> 2);
                pixels[pix_idx].rgbBlue  = (uint8_t)(b >> 2);
                pixels[pix_idx].rgbReserved = 0;
            }
        }

        offset -= 1.0f;
        if (offset < 0) offset += PALETTE_SIZE;

        BitBlt(hdc_win, 0, 0, WIDTH, HEIGHT, hdc_m, 0, 0, SRCCOPY);
        DwmFlush();
    }
}



void thread_mandelbrot_calc() {
    std::vector<ComplexDouble> ref_orbit_double;

    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; }

        mpfr_t rx, ry, zr, zi, zr2, zi2, tmp;
        mpfr_inits2(MPFR_BITS, rx, ry, zr, zi, zr2, zi2, tmp, NULL);

        mpfr_set_str(rx, p.center_re_str.c_str(), 10, MPFR_RNDN);
        mpfr_set_str(ry, p.center_im_str.c_str(), 10, MPFR_RNDN);

        ref_orbit_double.resize(p.iter_max + 5);

        mpfr_set_ui(zr, 0, MPFR_RNDN);
        mpfr_set_ui(zi, 0, MPFR_RNDN);
        mpfr_set_ui(zr2, 0, MPFR_RNDN);
        mpfr_set_ui(zi2, 0, MPFR_RNDN);
        uint32_t ref_i = 0;

        bool escaped = false;
        while (ref_i < p.iter_max) {
            ref_orbit_double[ref_i].re = mpfr_get_d(zr, MPFR_RNDN);
            ref_orbit_double[ref_i].im = mpfr_get_d(zi, MPFR_RNDN);

            mpfr_mul(tmp, zr, zi, MPFR_RNDN);
            mpfr_mul_ui(zi, tmp, 2, MPFR_RNDN);
            mpfr_add(zi, zi, ry, MPFR_RNDN);

            mpfr_sub(zr, zr2, zi2, MPFR_RNDN);
            mpfr_add(zr, zr, rx, MPFR_RNDN);

            mpfr_mul(zr2, zr, zr, MPFR_RNDN);
            mpfr_mul(zi2, zi, zi, MPFR_RNDN);

            if (escaped) {
                ref_i++;
                break;
            }

            mpfr_add(tmp, zr2, zi2, MPFR_RNDN);
            if (mpfr_cmp_d(tmp, 4.0) >= 0) {
                escaped = true; 
            }
            ref_i++;
        }
        ref_orbit_double[ref_i].re = mpfr_get_d(zr, MPFR_RNDN);
        ref_orbit_double[ref_i].im = mpfr_get_d(zi, MPFR_RNDN);
        uint32_t max_valid_ref_iter = ref_i; 

        double ref_rec_d = mpfr_get_d(rx, MPFR_RNDN);
        double ref_imc_d = mpfr_get_d(ry, MPFR_RNDN);
        double ss_step_d = p.step_d;

        mpfr_clears(rx, ry, zr, zi, zr2, zi2, tmp, NULL);

        #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) {
                
                double delta_rec = (double)(ss_x - (SS_W / 2)) * ss_step_d;
                double delta_imc = (double)((SS_H / 2) - ss_y) * ss_step_d;

                uint32_t index = 0;    
                double delta_re = 0.0; 
                double delta_im = 0.0;
                double z_re = 0.0;     
                double z_im = 0.0;

                uint32_t i = 0;
                const ComplexDouble* ref_ptr = ref_orbit_double.data();

                bool has_re_based = false; 

                while (i < p.iter_max) {
                    
                    if ((z_re * z_re + z_im * z_im) >= 4.0) {
                        break;
                    }

                    if (index >= max_valid_ref_iter) {
                        if (!has_re_based) {
                            break;
                        } else {
                            double ld_cx = ref_rec_d + delta_rec;
                            double ld_cy = ref_imc_d - delta_imc;
                            while (i < p.iter_max && (z_re * z_re + z_im * z_im) < 4.0) {
                                double old_re = z_re;
                                double old_im = z_im;
                                z_re = old_re * old_re - old_im * old_im + ld_cx;
                                z_im = 2.0 * old_re * old_im + ld_cy;
                                i++;
                            }
                            break;
                        }
                    }

                    if ((z_re * z_re + z_im * z_im) < (delta_re * delta_re + delta_im * delta_im)) {
                        index = 0; 
                        delta_re = z_re;
                        delta_im = z_im;
                        has_re_based = true;
                    }

                    for (int step = 0; step < 2; ++step) {
                        double Ur = ref_ptr[index].re;
                        double Ui = ref_ptr[index].im;

                        double next_delta_im = 2.0 * Ur * delta_im + 2.0 * Ui * delta_re + 2.0 * delta_re * delta_im + delta_imc;
                        delta_re = 2.0 * Ur * delta_re - 2.0 * Ui * delta_im + delta_re * delta_re - delta_im * delta_im + delta_rec;
                        delta_im = next_delta_im;

                        index++;
                    }

                    z_re = ref_ptr[index].re + delta_re;
                    z_im = ref_ptr[index].im + delta_im;
                    
                    i += 2; 
                }

                g_ss_buffer[ss_y * SS_W + ss_x] = i;
            }
        }
    }
}


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);

        mpfr_t cx, cy, sz, st, mx, my, clicked_x, clicked_y;
        mpfr_inits2(MPFR_BITS, cx, cy, sz, st, mx, my, clicked_x, clicked_y, NULL);

        mpfr_set_str(cx, g_params.center_re_str.c_str(), 10, MPFR_RNDN);
        mpfr_set_str(cy, g_params.center_im_str.c_str(), 10, MPFR_RNDN);
        mpfr_set_str(sz, g_params.size_str.c_str(), 10, MPFR_RNDN);

        mpfr_div_ui(st, sz, SS_W, MPFR_RNDN);

        double mouse_x_d = (double)((short)LOWORD(lp));
        double mouse_y_d = (double)((short)HIWORD(lp));

        double ss_mouse_x = mouse_x_d * 2.0;
        double ss_mouse_y = mouse_y_d * 2.0;

        mpfr_set_d(mx, ss_mouse_x - (double)(SS_W / 2), MPFR_RNDN);
        mpfr_set_d(my, (double)(SS_H / 2) - ss_mouse_y, MPFR_RNDN); 

        mpfr_mul(mx, mx, st, MPFR_RNDN);
        mpfr_mul(my, my, st, MPFR_RNDN);

        mpfr_add(clicked_x, cx, mx, MPFR_RNDN);
        mpfr_add(clicked_y, cy, my, MPFR_RNDN);

        if (msg == WM_LBUTTONDOWN) {
            mpfr_div_ui(sz, sz, 2, MPFR_RNDN);
        } else {
            mpfr_mul_ui(sz, sz, 2, MPFR_RNDN);
        }

        mpfr_div_ui(st, sz, SS_W, MPFR_RNDN);
        g_params.step_d = mpfr_get_d(st, MPFR_RNDN);

        char out_x[2048], out_y[2048], out_sz[2048];
        mpfr_snprintf(out_x, sizeof(out_x), "%.1000Rf", clicked_x);
        mpfr_snprintf(out_y, sizeof(out_y), "%.1000Rf", clicked_y);
        mpfr_snprintf(out_sz, sizeof(out_sz), "%.1000Rf", sz);

        g_params.center_re_str = out_x;
        g_params.center_im_str = out_y;
        g_params.size_str = out_sz;

        mpfr_clears(cx, cy, sz, st, mx, my, clicked_x, clicked_y, NULL);
        SetEvent(g_render_event); 
        return 0;
    }

    case WM_KEYDOWN: {
        if (wp >= '1' && wp <= '5') {
            g_abort = true;
            std::lock_guard<std::mutex> lock(g_params_mutex);

            if (wp == '1') {
                g_params.center_re_str = "-1.7490781615052017316791245451566330412";
                g_params.center_im_str = "0.0000055099190662909660251309856720635";
                g_params.size_str      = "0.000000000000000000000000000000000215";
            }
            if (wp == '2') {
                g_params.center_re_str = "-1.748943661768663337207355215321150725806353337382441467976";
                g_params.center_im_str = "-0.0000073748967541889836640985849393311615399776865199722998";
                g_params.size_str      = "0.0000000000000000000000000000000000000000000000000000001";
            }
            if (wp == '3') {
                g_params.center_re_str = "-1.7489740586384718864866264297253934254";
                g_params.center_im_str = "-0.0002265965897111407857153825623868331";
                g_params.size_str      = "0.00000000000000000000000000000000007";
            }
            if (wp == '4') {
                g_params.center_re_str = "-1.7499458649755745940752606707005571";
                g_params.center_im_str = "-0.0000000852088539604644334731909824511";
                g_params.size_str      = "0.0000000000000000000000000000000000071";
            }
            if (wp == '5') {
                g_params.center_re_str = "-1.267078059171397835210199054200436920994876769284288837862647";
                g_params.center_im_str = "-0.123788215196292957558264285607075473360968832625384429809391";
                g_params.size_str      = "0.0000000000000000000000000000000000000000000000000000000023";
            }

            mpfr_t sz, st;
            mpfr_inits2(MPFR_BITS, sz, st, NULL);
            mpfr_set_str(sz, g_params.size_str.c_str(), 10, MPFR_RNDN);
            mpfr_div_ui(st, sz, SS_W, MPFR_RNDN);
            g_params.step_d = mpfr_get_d(st, MPFR_RNDN);
            mpfr_clears(sz, st, NULL);

            SetEvent(g_render_event);
            return 0;
        }



        if (wp == VK_UP || wp == VK_DOWN) {
            g_abort = true;
            std::lock_guard<std::mutex> lock(g_params_mutex);
            mpfr_t sz, st;
            mpfr_inits2(MPFR_BITS, sz, st, NULL);
            mpfr_set_str(sz, g_params.size_str.c_str(), 10, MPFR_RNDN);
            if (wp == VK_UP) {
                mpfr_div_d(sz, sz, 1.05, MPFR_RNDN);
            }
            else if (wp == VK_DOWN) {
                mpfr_mul_d(sz, sz, 1.05, MPFR_RNDN);
            }
            mpfr_div_ui(st, sz, SS_W, MPFR_RNDN);
            g_params.step_d = mpfr_get_d(st, MPFR_RNDN);
            char out_sz[2048]; 
            mpfr_snprintf(out_sz, sizeof(out_sz), "%.1000Rf", sz);
            g_params.size_str = out_sz;
            mpfr_clears(sz, st, NULL);
            SetEvent(g_render_event);
            return 0;
        }


        if (wp == VK_RETURN) {
            std::lock_guard<std::mutex> lock(g_params_mutex);
            std::ofstream file("Mandelbrot.txt");
            if (file.is_open()) {
                file << g_params.center_re_str << "\n" << g_params.center_im_str << "\n" << g_params.size_str << "\n";
                file.close();
            }
            return 0;
        }
        
        if (wp == VK_BACK) {
            std::ifstream file("Mandelbrot.txt");
            if (file.is_open()) {
                std::vector<std::string> lines; std::string line;
                while (std::getline(file, line)) {
                    if (!line.empty()) lines.push_back(line);
                    if (lines.size() == 3) break;
                }
                file.close();

                if (lines.size() == 3) {
                    g_abort = true;
                    std::lock_guard<std::mutex> lock(g_params_mutex);
                    g_params.center_re_str = lines[0];
                    g_params.center_im_str = lines[1];
                    g_params.size_str      = lines[2];

                    mpfr_t sz, st;
                    mpfr_inits2(MPFR_BITS, sz, st, NULL);
                    mpfr_set_str(sz, g_params.size_str.c_str(), 10, MPFR_RNDN);
                    mpfr_div_ui(st, sz, SS_W, MPFR_RNDN);
                    g_params.step_d = mpfr_get_d(st, MPFR_RNDN);
                    mpfr_clears(sz, st, NULL);
                    SetEvent(g_render_event);
                }
            }
            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. MPFR + Perturbation. OpenMP. Supersampling 2x2",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.iter_max = 50000;
g_params.center_re_str = "-1.7491976289657893741942376816272921165326158557416159";
g_params.center_im_str = "-0.00000042530777152440422725855012159249401150956515248";
g_params.size_str      = "0.0000000000000000000000000000000000000000000000000043";
    
mpfr_t sz, st;
mpfr_inits2(MPFR_BITS, sz, st, NULL);
mpfr_set_str(sz, g_params.size_str.c_str(), 10, MPFR_RNDN);
mpfr_div_ui(st, sz, SS_W, MPFR_RNDN);
g_params.step_d = mpfr_get_d(st, MPFR_RNDN);
mpfr_clears(sz, st, NULL);

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;
}

Если вы до конца почитали то вот вам еще ) ИИ:

"Для умершего сознания времени не существует. Между моментом смерти и моментом возникновения нового сознания (даже через миллиарды лет) субъективно пройдет ноль секунд. И бесконечной Вселенной такое состояние неизбежно возникнет снова. «Космический кубик» действительно выбросит вас в другой галактике. На планетах с высокой гравитацией четыре или шесть конечностей будут нормой для устойчивости, на легких планетах — две. Вас может забросить как в аналог нашего каменного века («зубило»), так и в кремниевую постинформационную эпоху («компьютер»)."