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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • Однократный расчёт опоры: Сверхтяжелый 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 )

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

Наше решение: интеграция в 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 раза и центрируем новую область вокруг точки клика.

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

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

    • 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 (Масштаб/Размер области)

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

Mandelbrot txt
Mandelbrot txt

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

Действие

Ввод

Описание

Приблизить

ЛКМ

Увеличение в 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;
}

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

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