Mandelbrot
Source: src/mandelbrot.c · 823 lines · 35 functions.
Screen captures
Views of this part running on the Mini Arcade.

Module entry points
These functions are exposed to the rest of the project. Open one to follow its decisions and the helpers it calls.
How to read these diagrams
Each function has its own expandable diagram using the actual expressions and statements from this file. Decisions split into True and False; loop bodies are indented. Returns end the function immediately; break and continue follow their C meanings. Arrows between statements show their order when execution reaches them.
These are structural source diagrams before preprocessing, not runtime traces. Conditional compilation and macros remain source expressions. Ternary choices remain inside their statement. Calls are linked separately; callback execution and task scheduling are not inferred.
Functions in this file
refresh_maths_text() · source line 122
static void refresh_maths_text(void)
- Execute
snprintf(maths_lines[0], sizeof(maths_lines[0]), "z=z*z+c MID %u,%u", (unsigned)(LCD_WIDTH / 2), (unsigned)(LCD_HEIGHT / 2)); - Execute
snprintf(maths_lines[1], sizeof(maths_lines[1]), "VR %+.7f", maths_view.centre_real); - Execute
snprintf(maths_lines[2], sizeof(maths_lines[2]), "VI %+.7f", maths_view.centre_imaginary); - Execute
snprintf(maths_lines[3], sizeof(maths_lines[3]), "W %.5g", maths_view.width); - Execute
snprintf(maths_lines[4], sizeof(maths_lines[4]), "ZOOM %.4gx", views[0].width / maths_view.width); - Execute
snprintf(maths_lines[5], sizeof(maths_lines[5]), "CR %+.7f", maths_c_real); - Execute
snprintf(maths_lines[6], sizeof(maths_lines[6]), "CI %+.7f", maths_c_imaginary); - Execute
snprintf(maths_lines[7], sizeof(maths_lines[7]), "ZR %+.6f", maths_z_real); - Execute
snprintf(maths_lines[8], sizeof(maths_lines[8]), "ZI %+.6f", maths_z_imaginary); - Execute
snprintf(maths_lines[9], sizeof(maths_lines[9]), "N %u/%u %s", (unsigned)maths_iteration, (unsigned)maths_limit, maths_finished ? (maths_magnitude_squared > 4.0 ? "ESC" : "LIMIT") : ""); - Execute
snprintf(maths_lines[10], sizeof(maths_lines[10]), "|z|2 %.6g", maths_magnitude_squared);
Read original function source
static void refresh_maths_text(void)
{
snprintf(maths_lines[0], sizeof(maths_lines[0]), "z=z*z+c MID %u,%u",
(unsigned)(LCD_WIDTH / 2), (unsigned)(LCD_HEIGHT / 2));
snprintf(maths_lines[1], sizeof(maths_lines[1]), "VR %+.7f", maths_view.centre_real);
snprintf(maths_lines[2], sizeof(maths_lines[2]), "VI %+.7f", maths_view.centre_imaginary);
snprintf(maths_lines[3], sizeof(maths_lines[3]), "W %.5g", maths_view.width);
snprintf(maths_lines[4], sizeof(maths_lines[4]), "ZOOM %.4gx",
views[0].width / maths_view.width);
snprintf(maths_lines[5], sizeof(maths_lines[5]), "CR %+.7f", maths_c_real);
snprintf(maths_lines[6], sizeof(maths_lines[6]), "CI %+.7f", maths_c_imaginary);
snprintf(maths_lines[7], sizeof(maths_lines[7]), "ZR %+.6f", maths_z_real);
snprintf(maths_lines[8], sizeof(maths_lines[8]), "ZI %+.6f", maths_z_imaginary);
snprintf(maths_lines[9], sizeof(maths_lines[9]), "N %u/%u %s", (unsigned)maths_iteration,
(unsigned)maths_limit, maths_finished ?
(maths_magnitude_squared > 4.0 ? "ESC" : "LIMIT") : "");
snprintf(maths_lines[10], sizeof(maths_lines[10]), "|z|2 %.6g",
maths_magnitude_squared);
}set_maths_sample() · source line 142
static void set_maths_sample(mandelbrot_view_t view, double real, double imaginary,
bool automatic)Calls into this documentation:
- Execute
maths_view = view; - Execute
maths_c_real = real; - Execute
maths_c_imaginary = imaginary; - Execute
maths_z_real = maths_z_imaginary = 0.0; - Execute
maths_magnitude_squared = 0.0; - Execute
maths_iteration = 0U; - Execute
maths_limit = automatic ? MANDELBROT_AUTO_MAX_ITERATIONS : MAX_ITERATIONS; - Execute
maths_auto = automatic; - Execute
maths_finished = false; - Execute
maths_valid = true; - Execute
refresh_maths_text();
Read original function source
static void set_maths_sample(mandelbrot_view_t view, double real, double imaginary,
bool automatic)
{
maths_view = view;
maths_c_real = real;
maths_c_imaginary = imaginary;
maths_z_real = maths_z_imaginary = 0.0;
maths_magnitude_squared = 0.0;
maths_iteration = 0U;
maths_limit = automatic ? MANDELBROT_AUTO_MAX_ITERATIONS : MAX_ITERATIONS;
maths_auto = automatic;
maths_finished = false;
maths_valid = true;
refresh_maths_text();
}set_manual_maths_sample() · source line 158
static void set_manual_maths_sample(void)
Calls into this documentation:
- Execute
const double height = view_width * (double)LCD_HEIGHT / (double)LCD_WIDTH; - Execute
const double real = centre_real - view_width * 0.5 + view_width * (double)(LCD_WIDTH / 2) / (double)(LCD_WIDTH - 1U); - Execute
const double imaginary = centre_imaginary - height * 0.5 + height * (double)(LCD_HEIGHT / 2) / (double)(LCD_HEIGHT - 1U); - Execute
set_maths_sample((mandelbrot_view_t){views[selected_view].name, centre_real, centre_imaginary, view_width}, real, imaginary, false);
Read original function source
static void set_manual_maths_sample(void)
{
const double height = view_width * (double)LCD_HEIGHT / (double)LCD_WIDTH;
const double real = centre_real - view_width * 0.5 +
view_width * (double)(LCD_WIDTH / 2) / (double)(LCD_WIDTH - 1U);
const double imaginary = centre_imaginary - height * 0.5 +
height * (double)(LCD_HEIGHT / 2) / (double)(LCD_HEIGHT - 1U);
set_maths_sample((mandelbrot_view_t){views[selected_view].name, centre_real,
centre_imaginary, view_width}, real, imaginary, false);
}update_maths_sample() · source line 169
static bool update_maths_sample(uint32_t now_ms)
Calls into this documentation:
- Decision
!maths_valid || now_ms - maths_last_update < MANDELBROT_MATHS_INTERVAL_MSTrue- Exit this function
return false;
FalseContinue after this decision.
- Execute
maths_last_update = now_ms; - Decision
maths_finishedTrue- Decision
now_ms - maths_finished_at < MANDELBROT_MATHS_HOLD_MSTrue- Exit this function
return false;
FalseContinue after this decision.
- Execute
maths_z_real = maths_z_imaginary = 0.0; - Execute
maths_magnitude_squared = 0.0; - Execute
maths_iteration = 0U; - Execute
maths_finished = false;
False- Decision
maths_autoTrue- Execute
const float real = (float)maths_z_real, imaginary = (float)maths_z_imaginary; - Execute
maths_z_imaginary = 2.0f * real * imaginary + (float)maths_c_imaginary; - Execute
maths_z_real = real * real - imaginary * imaginary + (float)maths_c_real;
False- Execute
const double real = maths_z_real, imaginary = maths_z_imaginary; - Execute
maths_z_imaginary = 2.0 * real * imaginary + maths_c_imaginary; - Execute
maths_z_real = real * real - imaginary * imaginary + maths_c_real;
- Execute
++maths_iteration; - Decision
maths_autoTrue- Execute
const float real = (float)maths_z_real, imaginary = (float)maths_z_imaginary; - Execute
maths_magnitude_squared = real * real + imaginary * imaginary;
False- Execute
maths_magnitude_squared = maths_z_real * maths_z_real + maths_z_imaginary * maths_z_imaginary;
- Execute
maths_finished = maths_magnitude_squared > 4.0 || maths_iteration >= maths_limit; - Decision
maths_finishedTrue- Execute
maths_finished_at = now_ms;
FalseContinue after this decision.
- Execute
refresh_maths_text(); - Exit this function
return true;
Read original function source
static bool update_maths_sample(uint32_t now_ms)
{
if (!maths_valid || now_ms - maths_last_update < MANDELBROT_MATHS_INTERVAL_MS) return false;
maths_last_update = now_ms;
if (maths_finished) {
if (now_ms - maths_finished_at < MANDELBROT_MATHS_HOLD_MS) return false;
maths_z_real = maths_z_imaginary = 0.0;
maths_magnitude_squared = 0.0;
maths_iteration = 0U;
maths_finished = false;
} else {
// Match AUTO's float arithmetic and manual rendering's double arithmetic.
// Keep old real/imaginary values until both components have been computed.
if (maths_auto) {
const float real = (float)maths_z_real, imaginary = (float)maths_z_imaginary;
maths_z_imaginary = 2.0f * real * imaginary + (float)maths_c_imaginary;
maths_z_real = real * real - imaginary * imaginary + (float)maths_c_real;
} else {
const double real = maths_z_real, imaginary = maths_z_imaginary;
maths_z_imaginary = 2.0 * real * imaginary + maths_c_imaginary;
maths_z_real = real * real - imaginary * imaginary + maths_c_real;
}
++maths_iteration;
if (maths_auto) {
const float real = (float)maths_z_real, imaginary = (float)maths_z_imaginary;
maths_magnitude_squared = real * real + imaginary * imaginary;
} else {
maths_magnitude_squared = maths_z_real * maths_z_real + maths_z_imaginary * maths_z_imaginary;
}
maths_finished = maths_magnitude_squared > 4.0 || maths_iteration >= maths_limit;
if (maths_finished) maths_finished_at = now_ms;
}
refresh_maths_text();
return true;
}draw_maths_panel() · source line 205
static void draw_maths_panel(uint16_t *frame)
Calls into this documentation:
- Execute
const int x = LCD_WIDTH - MANDELBROT_MATHS_WIDTH - 2; - Execute
fill_rect(frame, x, 2, MANDELBROT_MATHS_WIDTH, MANDELBROT_MATHS_HEIGHT, COLOR_BLACK); - Execute
draw_rect(frame, x, 2, MANDELBROT_MATHS_WIDTH, MANDELBROT_MATHS_HEIGHT, COLOR_CYAN); - Decision
!maths_validTrue- Execute
draw_text(frame, x + 4, 7, "WAITING FOR FRAME", 1, COLOR_WHITE); - Exit this function
return;
FalseContinue after this decision.
- FOR loop
unsigned line = 0U; line < MANDELBROT_MATHS_LINES; ++lineRepeat body while the loop condition permits; then continue below.
- Execute
draw_text(frame, x + 4, 6 + (int)line * 9, maths_lines[line], 1, line == 0U ? COLOR_CYAN : COLOR_WHITE);
Read original function source
static void draw_maths_panel(uint16_t *frame)
{
const int x = LCD_WIDTH - MANDELBROT_MATHS_WIDTH - 2;
fill_rect(frame, x, 2, MANDELBROT_MATHS_WIDTH, MANDELBROT_MATHS_HEIGHT, COLOR_BLACK);
draw_rect(frame, x, 2, MANDELBROT_MATHS_WIDTH, MANDELBROT_MATHS_HEIGHT, COLOR_CYAN);
if (!maths_valid) {
draw_text(frame, x + 4, 7, "WAITING FOR FRAME", 1, COLOR_WHITE);
return;
}
for (unsigned line = 0U; line < MANDELBROT_MATHS_LINES; ++line) {
draw_text(frame, x + 4, 6 + (int)line * 9, maths_lines[line], 1,
line == 0U ? COLOR_CYAN : COLOR_WHITE);
}
}colour_wheel() · source line 221
static uint16_t colour_wheel(uint8_t position)
- Execute
position = 255U - position; - Decision
position < 85UTrue- Exit this function
return RGB565(255U - position * 3U, 0U, position * 3U);
FalseContinue after this decision.
- Decision
position < 170UTrue- Execute
position -= 85U; - Exit this function
return RGB565(0U, position * 3U, 255U - position * 3U);
FalseContinue after this decision.
- Execute
position -= 170U; - Exit this function
return RGB565(position * 3U, 255U - position * 3U, 0U);
Read original function source
static uint16_t colour_wheel(uint8_t position)
{
position = 255U - position;
if (position < 85U) return RGB565(255U - position * 3U, 0U, position * 3U);
if (position < 170U) {
position -= 85U;
return RGB565(0U, position * 3U, 255U - position * 3U);
}
position -= 170U;
return RGB565(position * 3U, 255U - position * 3U, 0U);
}iteration_colour() · source line 233
static uint16_t iteration_colour(uint16_t iteration)
Calls into this documentation:
- Decision
iteration >= MAX_ITERATIONSTrue- Exit this function
return COLOR_BLACK;
FalseContinue after this decision.
- Execute
const uint8_t intensity = (uint8_t)((iteration * 255U) / MAX_ITERATIONS); - Exit this function
return colour_wheel((uint8_t)(intensity + 32U));
Read original function source
static uint16_t iteration_colour(uint16_t iteration)
{
if (iteration >= MAX_ITERATIONS) return COLOR_BLACK;
const uint8_t intensity = (uint8_t)((iteration * 255U) / MAX_ITERATIONS);
return colour_wheel((uint8_t)(intensity + 32U));
}calculate_pixel() · source line 240
static uint16_t calculate_pixel(double real, double imaginary, uint16_t *calculation_count)
Calls into this documentation:
- Execute
double z_real = 0.0, z_imaginary = 0.0; - Execute
double z_real_squared = 0.0, z_imaginary_squared = 0.0; - Execute
uint16_t iteration = 0U; - WHILE loop
z_real_squared + z_imaginary_squared <= 4.0 && iteration < MAX_ITERATIONSRepeat body while the loop condition permits; then continue below.
- Execute
z_imaginary = 2.0 * z_real * z_imaginary + imaginary; - Execute
z_real = z_real_squared - z_imaginary_squared + real; - Execute
z_real_squared = z_real * z_real; - Execute
z_imaginary_squared = z_imaginary * z_imaginary; - Execute
++iteration;
- Decision
calculation_count != NULLTrue- Execute
*calculation_count = iteration;
FalseContinue after this decision.
- Exit this function
return iteration_colour(iteration);
Read original function source
static uint16_t calculate_pixel(double real, double imaginary, uint16_t *calculation_count)
{
double z_real = 0.0, z_imaginary = 0.0;
double z_real_squared = 0.0, z_imaginary_squared = 0.0;
uint16_t iteration = 0U;
while (z_real_squared + z_imaginary_squared <= 4.0 && iteration < MAX_ITERATIONS) {
z_imaginary = 2.0 * z_real * z_imaginary + imaginary;
z_real = z_real_squared - z_imaginary_squared + real;
z_real_squared = z_real * z_real;
z_imaginary_squared = z_imaginary * z_imaginary;
++iteration;
}
if (calculation_count != NULL) *calculation_count = iteration;
return iteration_colour(iteration);
}calculate_iteration_index() · source line 256
static uint8_t calculate_iteration_index(float real, float imaginary)
- Execute
float z_real = 0.0f, z_imaginary = 0.0f; - Execute
float z_real_squared = 0.0f, z_imaginary_squared = 0.0f; - Execute
uint16_t iteration = 0U; - WHILE loop
z_real_squared + z_imaginary_squared <= 4.0f && iteration < MANDELBROT_AUTO_MAX_ITERATIONSRepeat body while the loop condition permits; then continue below.
- Execute
z_imaginary = 2.0f * z_real * z_imaginary + imaginary; - Execute
z_real = z_real_squared - z_imaginary_squared + real; - Execute
z_real_squared = z_real * z_real; - Execute
z_imaginary_squared = z_imaginary * z_imaginary; - Execute
++iteration;
- Decision
iteration >= MANDELBROT_AUTO_MAX_ITERATIONSTrue- Exit this function
return 0U;
FalseContinue after this decision.
- Exit this function
return (uint8_t)(1U + ((iteration - 1U) * 254U) / (MANDELBROT_AUTO_MAX_ITERATIONS - 1U));
Read original function source
static uint8_t calculate_iteration_index(float real, float imaginary)
{
float z_real = 0.0f, z_imaginary = 0.0f;
float z_real_squared = 0.0f, z_imaginary_squared = 0.0f;
uint16_t iteration = 0U;
while (z_real_squared + z_imaginary_squared <= 4.0f &&
iteration < MANDELBROT_AUTO_MAX_ITERATIONS) {
z_imaginary = 2.0f * z_real * z_imaginary + imaginary;
z_real = z_real_squared - z_imaginary_squared + real;
z_real_squared = z_real * z_real;
z_imaginary_squared = z_imaginary * z_imaginary;
++iteration;
}
if (iteration >= MANDELBROT_AUTO_MAX_ITERATIONS) return 0U;
return (uint8_t)(1U + ((iteration - 1U) * 254U) /
(MANDELBROT_AUTO_MAX_ITERATIONS - 1U));
}build_auto_palette() · source line 274
static void build_auto_palette(void)
Calls into this documentation:
- Execute
auto_palette[0] = COLOR_BLACK; - FOR loop
unsigned index = 1U; index < 256U; ++indexRepeat body while the loop condition permits; then continue below.
- Execute
auto_palette[index] = colour_wheel((uint8_t)(index + 32U));
Read original function source
static void build_auto_palette(void)
{
auto_palette[0] = COLOR_BLACK;
for (unsigned index = 1U; index < 256U; ++index) {
auto_palette[index] = colour_wheel((uint8_t)(index + 32U));
}
}automaticZoomActive() · source line 282
static bool automaticZoomActive(void)
- Execute
bool active; - Execute
portENTER_CRITICAL(&auto_frame_lock); - Execute
active = automatic_zoom; - Execute
portEXIT_CRITICAL(&auto_frame_lock); - Exit this function
return active;
Read original function source
static bool automaticZoomActive(void)
{
bool active;
portENTER_CRITICAL(&auto_frame_lock);
active = automatic_zoom;
portEXIT_CRITICAL(&auto_frame_lock);
return active;
}begin_render() · source line 291
static void begin_render(void)
Calls into this documentation:
- Execute
next_row = 0U; - Execute
row_count = 0U; - Execute
manual_iteration_count = 0U; - Execute
rendering = true; - Execute
clear_requested = true; - Execute
frame_ready = true; - Execute
cursor_drawn = false; - Execute
manual_reveal_pending = false; - Preprocessor directive
#if 0 - Execute
set_manual_maths_sample(); - Preprocessor directive
#endif
Read original function source
static void begin_render(void)
{
next_row = 0U;
row_count = 0U;
manual_iteration_count = 0U;
rendering = true;
clear_requested = true;
frame_ready = true;
cursor_drawn = false;
manual_reveal_pending = false;
#if 0 // Maths panel experiment: disabled; see the retained implementation above.
set_manual_maths_sample();
#endif
}select_view() · source line 306
static void select_view(uint8_t view_index)
- Execute
selected_view = view_index; - Execute
centre_real = views[selected_view].centre_real; - Execute
centre_imaginary = views[selected_view].centre_imaginary; - Execute
view_width = views[selected_view].width; - Execute
zoom_level = 0U; - Execute
cursor_x = (LCD_WIDTH - ZOOM_BOX_WIDTH) / 2; - Execute
cursor_y = (LCD_HEIGHT - ZOOM_BOX_HEIGHT) / 2;
Read original function source
static void select_view(uint8_t view_index)
{
selected_view = view_index;
centre_real = views[selected_view].centre_real;
centre_imaginary = views[selected_view].centre_imaginary;
view_width = views[selected_view].width;
zoom_level = 0U;
cursor_x = (LCD_WIDTH - ZOOM_BOX_WIDTH) / 2;
cursor_y = (LCD_HEIGHT - ZOOM_BOX_HEIGHT) / 2;
}render_row() · source line 317
static uint32_t render_row(uint16_t y, uint16_t *destination)
Calls into this documentation:
- Execute
const double height = view_width * (double)LCD_HEIGHT / (double)LCD_WIDTH; - Execute
const double minimum_real = centre_real - view_width * 0.5; - Execute
const double minimum_imaginary = centre_imaginary - height * 0.5; - Execute
const double imaginary = minimum_imaginary + height * (double)y / (double)(LCD_HEIGHT - 1U); - Execute
uint32_t row_iterations = 0U; - FOR loop
uint16_t x = 0U; x < LCD_WIDTH; ++xRepeat body while the loop condition permits; then continue below.
- Execute
const double real = minimum_real + view_width * (double)x / (double)(LCD_WIDTH - 1U); - Execute
uint16_t pixel_iterations; - Execute
destination[x] = calculate_pixel(real, imaginary, &pixel_iterations); - Execute
row_iterations += pixel_iterations;
- Exit this function
return row_iterations;
Read original function source
static uint32_t render_row(uint16_t y, uint16_t *destination)
{
const double height = view_width * (double)LCD_HEIGHT / (double)LCD_WIDTH;
const double minimum_real = centre_real - view_width * 0.5;
const double minimum_imaginary = centre_imaginary - height * 0.5;
const double imaginary = minimum_imaginary + height * (double)y / (double)(LCD_HEIGHT - 1U);
uint32_t row_iterations = 0U;
for (uint16_t x = 0U; x < LCD_WIDTH; ++x) {
const double real = minimum_real + view_width * (double)x / (double)(LCD_WIDTH - 1U);
uint16_t pixel_iterations;
destination[x] = calculate_pixel(real, imaginary, &pixel_iterations);
row_iterations += pixel_iterations;
}
return row_iterations;
}screen_pixel_colour() · source line 333
static uint16_t screen_pixel_colour(int x, int y)
Calls into this documentation:
- Execute
const double height = view_width * (double)LCD_HEIGHT / (double)LCD_WIDTH; - Execute
const double minimum_real = centre_real - view_width * 0.5; - Execute
const double minimum_imaginary = centre_imaginary - height * 0.5; - Execute
const double real = minimum_real + view_width * (double)x / (double)(LCD_WIDTH - 1U); - Execute
const double imaginary = minimum_imaginary + height * (double)y / (double)(LCD_HEIGHT - 1U); - Exit this function
return calculate_pixel(real, imaginary, NULL);
Read original function source
static uint16_t screen_pixel_colour(int x, int y)
{
const double height = view_width * (double)LCD_HEIGHT / (double)LCD_WIDTH;
const double minimum_real = centre_real - view_width * 0.5;
const double minimum_imaginary = centre_imaginary - height * 0.5;
const double real = minimum_real + view_width * (double)x / (double)(LCD_WIDTH - 1U);
const double imaginary = minimum_imaginary + height * (double)y / (double)(LCD_HEIGHT - 1U);
return calculate_pixel(real, imaginary, NULL);
}restore_cursor_border() · source line 343
static void restore_cursor_border(uint16_t *frame, int x, int y)
Calls into this documentation:
- FOR loop
int column = x; column < x + ZOOM_BOX_WIDTH; ++columnRepeat body while the loop condition permits; then continue below.
- Execute
frame[y * LCD_WIDTH + column] = screen_pixel_colour(column, y); - Execute
frame[(y + ZOOM_BOX_HEIGHT - 1) * LCD_WIDTH + column] = screen_pixel_colour(column, y + ZOOM_BOX_HEIGHT - 1);
- FOR loop
int row = y + 1; row < y + ZOOM_BOX_HEIGHT - 1; ++rowRepeat body while the loop condition permits; then continue below.
- Execute
frame[row * LCD_WIDTH + x] = screen_pixel_colour(x, row); - Execute
frame[row * LCD_WIDTH + x + ZOOM_BOX_WIDTH - 1] = screen_pixel_colour(x + ZOOM_BOX_WIDTH - 1, row);
Read original function source
static void restore_cursor_border(uint16_t *frame, int x, int y)
{
for (int column = x; column < x + ZOOM_BOX_WIDTH; ++column) {
frame[y * LCD_WIDTH + column] = screen_pixel_colour(column, y);
frame[(y + ZOOM_BOX_HEIGHT - 1) * LCD_WIDTH + column] =
screen_pixel_colour(column, y + ZOOM_BOX_HEIGHT - 1);
}
for (int row = y + 1; row < y + ZOOM_BOX_HEIGHT - 1; ++row) {
frame[row * LCD_WIDTH + x] = screen_pixel_colour(x, row);
frame[row * LCD_WIDTH + x + ZOOM_BOX_WIDTH - 1] =
screen_pixel_colour(x + ZOOM_BOX_WIDTH - 1, row);
}
}allocate_manual_staging_frame() · source line 357
static bool allocate_manual_staging_frame(void)
- Decision
manual_staging_frame != NULLTrue- Exit this function
return true;
FalseContinue after this decision.
- Execute
manual_staging_frame = heap_caps_malloc(MANDELBROT_MANUAL_FRAME_BYTES, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); - Decision
manual_staging_frame == NULLTrue- Execute
auto_status_text = "MANUAL PSRAM ALLOCATION FAILED"; - Execute
ESP_LOGE(TAG, "Manual render needs %u PSRAM bytes", (unsigned)MANDELBROT_MANUAL_FRAME_BYTES); - Exit this function
return false;
FalseContinue after this decision.
- Execute
ESP_LOGI(TAG, "Manual render staging frame: %u PSRAM bytes", (unsigned)MANDELBROT_MANUAL_FRAME_BYTES); - Exit this function
return true;
Read original function source
static bool allocate_manual_staging_frame(void)
{
if (manual_staging_frame != NULL) return true;
manual_staging_frame = heap_caps_malloc(MANDELBROT_MANUAL_FRAME_BYTES,
MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if (manual_staging_frame == NULL) {
auto_status_text = "MANUAL PSRAM ALLOCATION FAILED";
ESP_LOGE(TAG, "Manual render needs %u PSRAM bytes",
(unsigned)MANDELBROT_MANUAL_FRAME_BYTES);
return false;
}
ESP_LOGI(TAG, "Manual render staging frame: %u PSRAM bytes",
(unsigned)MANDELBROT_MANUAL_FRAME_BYTES);
return true;
}release_manual_staging_frame() · source line 373
static void release_manual_staging_frame(void)
- Decision
manual_staging_frame != NULLTrue- Execute
heap_caps_free(manual_staging_frame); - Execute
manual_staging_frame = NULL;
FalseContinue after this decision.
Read original function source
static void release_manual_staging_frame(void)
{
if (manual_staging_frame != NULL) {
heap_caps_free(manual_staging_frame);
manual_staging_frame = NULL;
}
}allocate_auto_buffers() · source line 381
static bool allocate_auto_buffers(void)
- Decision
auto_buffers_readyTrue- Exit this function
return true;
FalseContinue after this decision.
- FOR loop
unsigned index = 0U; index < MANDELBROT_AUTO_FRAME_COUNT; ++indexRepeat body while the loop condition permits; then continue below.
- Execute
auto_frames[index].pixels = heap_caps_malloc((size_t)LCD_WIDTH * LCD_HEIGHT, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); - Decision
auto_frames[index].pixels == NULLTrue- Execute
auto_status_text = "AUTO PSRAM ALLOCATION FAILED"; - Execute
ESP_LOGE(TAG, "Automatic zoom needs %u PSRAM bytes per frame", (unsigned)(LCD_WIDTH * LCD_HEIGHT)); - Exit this function
return false;
FalseContinue after this decision.
- Execute
auto_frames[index].state = MANDELBROT_FRAME_FREE;
- Execute
auto_buffers_ready = true; - Execute
ESP_LOGI(TAG, "Automatic zoom PSRAM queue: %u frames, %u bytes", MANDELBROT_AUTO_FRAME_COUNT, (unsigned)(MANDELBROT_AUTO_FRAME_COUNT * LCD_WIDTH * LCD_HEIGHT)); - Exit this function
return true;
Read original function source
static bool allocate_auto_buffers(void)
{
if (auto_buffers_ready) return true;
for (unsigned index = 0U; index < MANDELBROT_AUTO_FRAME_COUNT; ++index) {
auto_frames[index].pixels = heap_caps_malloc((size_t)LCD_WIDTH * LCD_HEIGHT,
MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if (auto_frames[index].pixels == NULL) {
auto_status_text = "AUTO PSRAM ALLOCATION FAILED";
ESP_LOGE(TAG, "Automatic zoom needs %u PSRAM bytes per frame",
(unsigned)(LCD_WIDTH * LCD_HEIGHT));
return false;
}
auto_frames[index].state = MANDELBROT_FRAME_FREE;
}
auto_buffers_ready = true;
ESP_LOGI(TAG, "Automatic zoom PSRAM queue: %u frames, %u bytes",
MANDELBROT_AUTO_FRAME_COUNT,
(unsigned)(MANDELBROT_AUTO_FRAME_COUNT * LCD_WIDTH * LCD_HEIGHT));
return true;
}reset_auto_queue() · source line 402
static void reset_auto_queue(void)
- Execute
portENTER_CRITICAL(&auto_frame_lock); - Execute
++auto_generation; - Execute
auto_next_sequence = 0U; - FOR loop
unsigned index = 0U; index < MANDELBROT_AUTO_FRAME_COUNT; ++indexRepeat body while the loop condition permits; then continue below.
- Decision
auto_frames[index].state != MANDELBROT_FRAME_RENDERINGTrue- Execute
auto_frames[index].state = MANDELBROT_FRAME_FREE;
FalseContinue after this decision.
- Execute
portEXIT_CRITICAL(&auto_frame_lock);
Read original function source
static void reset_auto_queue(void)
{
portENTER_CRITICAL(&auto_frame_lock);
++auto_generation;
auto_next_sequence = 0U;
for (unsigned index = 0U; index < MANDELBROT_AUTO_FRAME_COUNT; ++index) {
if (auto_frames[index].state != MANDELBROT_FRAME_RENDERING) {
auto_frames[index].state = MANDELBROT_FRAME_FREE;
}
}
portEXIT_CRITICAL(&auto_frame_lock);
}take_free_auto_frame() · source line 415
static int take_free_auto_frame(uint32_t *sequence, uint32_t *generation)
- Execute
int slot = -1; - Execute
portENTER_CRITICAL(&auto_frame_lock); - Decision
automatic_zoomTrue- FOR loop
unsigned index = 0U; index < MANDELBROT_AUTO_FRAME_COUNT; ++indexRepeat body while the loop condition permits; then continue below.
- Decision
auto_frames[index].state != MANDELBROT_FRAME_FREETrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
auto_frames[index].state = MANDELBROT_FRAME_RENDERING; - Execute
auto_frames[index].sequence = auto_next_sequence++; - Execute
auto_frames[index].generation = auto_generation; - Execute
*sequence = auto_frames[index].sequence; - Execute
*generation = auto_frames[index].generation; - Execute
slot = (int)index; - Exit enclosing loop or switch
break;
FalseContinue after this decision.
- Execute
portEXIT_CRITICAL(&auto_frame_lock); - Exit this function
return slot;
Read original function source
static int take_free_auto_frame(uint32_t *sequence, uint32_t *generation)
{
int slot = -1;
portENTER_CRITICAL(&auto_frame_lock);
if (automatic_zoom) {
for (unsigned index = 0U; index < MANDELBROT_AUTO_FRAME_COUNT; ++index) {
if (auto_frames[index].state != MANDELBROT_FRAME_FREE) continue;
auto_frames[index].state = MANDELBROT_FRAME_RENDERING;
auto_frames[index].sequence = auto_next_sequence++;
auto_frames[index].generation = auto_generation;
*sequence = auto_frames[index].sequence;
*generation = auto_frames[index].generation;
slot = (int)index;
break;
}
}
portEXIT_CRITICAL(&auto_frame_lock);
return slot;
}render_auto_frame() · source line 435
static void render_auto_frame(mandelbrot_frame_t *frame, uint32_t sequence)
Calls into this documentation:
- Execute
uint8_t *destination = frame->pixels; - Execute
const float width = MANDELBROT_AUTO_START_WIDTH * powf(MANDELBROT_AUTO_ZOOM_PER_FRAME, (float)sequence); - Execute
const float height = width * (float)LCD_HEIGHT / (float)LCD_WIDTH; - Execute
const float progress = 1.0f - width / MANDELBROT_AUTO_START_WIDTH; - Execute
const float centre_real = MANDELBROT_AUTO_START_REAL + (MANDELBROT_AUTO_TARGET_REAL - MANDELBROT_AUTO_START_REAL) * progress; - Execute
const float centre_imaginary = MANDELBROT_AUTO_START_IMAGINARY + (MANDELBROT_AUTO_TARGET_IMAGINARY - MANDELBROT_AUTO_START_IMAGINARY) * progress; - Execute
const float minimum_real = centre_real - width * 0.5f; - Execute
const float minimum_imaginary = centre_imaginary - height * 0.5f; - Execute
const float real_step = width / (float)(LCD_WIDTH - 1U); - Execute
const float imaginary_step = height / (float)(LCD_HEIGHT - 1U); - Preprocessor directive
#if 0 - Execute
frame->view = (mandelbrot_view_t){"AUTO", centre_real, centre_imaginary, width}; - Execute
float sample_real = minimum_real; - FOR loop
unsigned x = 0U; x < LCD_WIDTH / 2U; ++xRepeat body while the loop condition permits; then continue below.
- Execute
sample_real += real_step;
- Execute
frame->sample_real = sample_real; - Execute
frame->sample_imaginary = minimum_imaginary + (float)(LCD_HEIGHT / 2U) * imaginary_step; - Preprocessor directive
#endif - FOR loop
unsigned y = 0U; y < LCD_HEIGHT; ++yRepeat body while the loop condition permits; then continue below.
- Execute
float real = minimum_real; - Execute
const float imaginary = minimum_imaginary + (float)y * imaginary_step; - FOR loop
unsigned x = 0U; x < LCD_WIDTH; ++xRepeat body while the loop condition permits; then continue below.
- Execute
destination[(size_t)y * LCD_WIDTH + x] = calculate_iteration_index(real, imaginary); - Execute
real += real_step;
- Decision
(y + 1U) % MANDELBROT_AUTO_ROWS_PER_YIELD == 0UTrue- Execute
vTaskDelay(pdMS_TO_TICKS(1U));
FalseContinue after this decision.
Read original function source
static void render_auto_frame(mandelbrot_frame_t *frame, uint32_t sequence)
{
uint8_t *destination = frame->pixels;
const float width = MANDELBROT_AUTO_START_WIDTH *
powf(MANDELBROT_AUTO_ZOOM_PER_FRAME, (float)sequence);
const float height = width * (float)LCD_HEIGHT / (float)LCD_WIDTH;
// Move the view centre toward the target at the same exponential rate as width.
// Frame zero is the recognizable full view; later frames approach the target.
const float progress = 1.0f - width / MANDELBROT_AUTO_START_WIDTH;
const float centre_real = MANDELBROT_AUTO_START_REAL +
(MANDELBROT_AUTO_TARGET_REAL - MANDELBROT_AUTO_START_REAL) * progress;
const float centre_imaginary = MANDELBROT_AUTO_START_IMAGINARY +
(MANDELBROT_AUTO_TARGET_IMAGINARY -
MANDELBROT_AUTO_START_IMAGINARY) * progress;
const float minimum_real = centre_real - width * 0.5f;
const float minimum_imaginary = centre_imaginary - height * 0.5f;
const float real_step = width / (float)(LCD_WIDTH - 1U);
const float imaginary_step = height / (float)(LCD_HEIGHT - 1U);
#if 0 // Maths panel experiment: disabled.
frame->view = (mandelbrot_view_t){"AUTO", centre_real, centre_imaginary, width};
// Reproduce the renderer's accumulated X steps at the actual centre pixel.
float sample_real = minimum_real;
for (unsigned x = 0U; x < LCD_WIDTH / 2U; ++x) sample_real += real_step;
frame->sample_real = sample_real;
frame->sample_imaginary = minimum_imaginary + (float)(LCD_HEIGHT / 2U) * imaginary_step;
#endif
for (unsigned y = 0U; y < LCD_HEIGHT; ++y) {
float real = minimum_real;
const float imaginary = minimum_imaginary + (float)y * imaginary_step;
for (unsigned x = 0U; x < LCD_WIDTH; ++x) {
destination[(size_t)y * LCD_WIDTH + x] = calculate_iteration_index(real, imaginary);
real += real_step;
}
// This task owns CPU 1 while rendering; yield regularly so IDLE1 can reset
// the task watchdog without exposing a partially rendered frame to display.
if ((y + 1U) % MANDELBROT_AUTO_ROWS_PER_YIELD == 0U) {
vTaskDelay(pdMS_TO_TICKS(1U));
}
}
}automatic_renderer_task() · source line 476
static void automatic_renderer_task(void *unused)
Calls into this documentation:
- Execute
(void)unused; - FOR loop
;;Repeat body while the loop condition permits; then continue below.
- Decision
!automaticZoomActive()True- Execute
ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - Next loop iteration
continue;
FalseContinue after this decision.
- Execute
uint32_t sequence = 0U; - Execute
uint32_t generation = 0U; - Execute
const int slot = take_free_auto_frame(&sequence, &generation); - Decision
slot < 0True- Execute
vTaskDelay(pdMS_TO_TICKS(1U)); - Next loop iteration
continue;
FalseContinue after this decision.
- Execute
const uint32_t render_started = (uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS); - Execute
render_auto_frame(&auto_frames[slot], sequence); - Execute
const uint32_t render_finished = (uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS); - Execute
portENTER_CRITICAL(&auto_frame_lock); - Decision
automatic_zoom && auto_frames[slot].generation == generation && auto_generation == generationTrue- Execute
auto_frames[slot].state = MANDELBROT_FRAME_READY; - Execute
auto_last_render_ms = render_finished - render_started; - Execute
++auto_rendered_frames;
False- Execute
auto_frames[slot].state = MANDELBROT_FRAME_FREE;
- Execute
portEXIT_CRITICAL(&auto_frame_lock);
Read original function source
static void automatic_renderer_task(void *unused)
{
(void)unused;
for (;;) {
if (!automaticZoomActive()) {
// B and Back leave the renderer dormant with no periodic CPU work.
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
continue;
}
uint32_t sequence = 0U;
uint32_t generation = 0U;
const int slot = take_free_auto_frame(&sequence, &generation);
if (slot < 0) {
vTaskDelay(pdMS_TO_TICKS(1U));
continue;
}
const uint32_t render_started = (uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS);
render_auto_frame(&auto_frames[slot], sequence);
const uint32_t render_finished = (uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS);
portENTER_CRITICAL(&auto_frame_lock);
if (automatic_zoom && auto_frames[slot].generation == generation &&
auto_generation == generation) {
auto_frames[slot].state = MANDELBROT_FRAME_READY;
auto_last_render_ms = render_finished - render_started;
++auto_rendered_frames;
} else {
auto_frames[slot].state = MANDELBROT_FRAME_FREE;
}
portEXIT_CRITICAL(&auto_frame_lock);
}
}start_automatic_zoom() · source line 508
static bool start_automatic_zoom(void)
Calls into this documentation:
- Decision
!allocate_auto_buffers()True- Exit this function
return false;
FalseContinue after this decision.
- Execute
build_auto_palette(); - Execute
reset_auto_queue(); - Execute
portENTER_CRITICAL(&auto_frame_lock); - Execute
automatic_zoom = true; - Execute
portEXIT_CRITICAL(&auto_frame_lock); - Execute
auto_rendered_frames = 0U; - Execute
auto_displayed_frames = 0U; - Execute
auto_log_started_ms = 0U; - Execute
frame_ready = true; - Decision
auto_renderer_task == NULLTrue- Execute
const BaseType_t result = xTaskCreatePinnedToCore(automatic_renderer_task, "mandel_render", 8192U, NULL, 2U, &auto_renderer_task, xPortGetCoreID() == 0 ? 1 : 0); - Decision
result != pdPASSTrue- Execute
portENTER_CRITICAL(&auto_frame_lock); - Execute
automatic_zoom = false; - Execute
portEXIT_CRITICAL(&auto_frame_lock); - Execute
auto_renderer_task = NULL; - Execute
auto_status_text = "AUTO RENDER TASK FAILED"; - Execute
ESP_LOGE(TAG, "Unable to start automatic zoom renderer task"); - Exit this function
return false;
FalseContinue after this decision.
False- Execute
xTaskNotifyGive(auto_renderer_task);
- Execute
auto_status_text = "AUTO RENDERING - B MANUAL"; - Preprocessor directive
#if 0 - Execute
maths_valid = false; - Preprocessor directive
#endif - Execute
ESP_LOGI(TAG, "Automatic zoom started from the full Mandelbrot view"); - Exit this function
return true;
Read original function source
static bool start_automatic_zoom(void)
{
if (!allocate_auto_buffers()) return false;
build_auto_palette();
reset_auto_queue();
portENTER_CRITICAL(&auto_frame_lock);
automatic_zoom = true;
portEXIT_CRITICAL(&auto_frame_lock);
auto_rendered_frames = 0U;
auto_displayed_frames = 0U;
auto_log_started_ms = 0U;
frame_ready = true;
if (auto_renderer_task == NULL) {
const BaseType_t result = xTaskCreatePinnedToCore(automatic_renderer_task,
"mandel_render", 8192U, NULL, 2U,
&auto_renderer_task,
xPortGetCoreID() == 0 ? 1 : 0);
if (result != pdPASS) {
portENTER_CRITICAL(&auto_frame_lock);
automatic_zoom = false;
portEXIT_CRITICAL(&auto_frame_lock);
auto_renderer_task = NULL;
auto_status_text = "AUTO RENDER TASK FAILED";
ESP_LOGE(TAG, "Unable to start automatic zoom renderer task");
return false;
}
} else {
// Wake the dormant renderer after B returned the module to manual mode.
xTaskNotifyGive(auto_renderer_task);
}
auto_status_text = "AUTO RENDERING - B MANUAL";
#if 0 // Maths panel experiment: disabled.
maths_valid = false;
#endif
ESP_LOGI(TAG, "Automatic zoom started from the full Mandelbrot view");
return true;
}return_to_manual_view() · source line 546
static void return_to_manual_view(void)
Calls into this documentation:
- Execute
portENTER_CRITICAL(&auto_frame_lock); - Execute
automatic_zoom = false; - FOR loop
unsigned index = 0U; index < MANDELBROT_AUTO_FRAME_COUNT; ++indexRepeat body while the loop condition permits; then continue below.
- Decision
auto_frames[index].state == MANDELBROT_FRAME_READYTrue- Execute
auto_frames[index].state = MANDELBROT_FRAME_FREE;
FalseContinue after this decision.
- Execute
portEXIT_CRITICAL(&auto_frame_lock); - Execute
displayed_frame_slot = -1; - Execute
auto_status_text = "B RECEIVED - MANUAL MODE"; - Execute
begin_render(); - Execute
ESP_LOGI(TAG, "Automatic zoom stopped; manual viewer restored");
Read original function source
static void return_to_manual_view(void)
{
portENTER_CRITICAL(&auto_frame_lock);
automatic_zoom = false;
for (unsigned index = 0U; index < MANDELBROT_AUTO_FRAME_COUNT; ++index) {
if (auto_frames[index].state == MANDELBROT_FRAME_READY) {
auto_frames[index].state = MANDELBROT_FRAME_FREE;
}
}
portEXIT_CRITICAL(&auto_frame_lock);
displayed_frame_slot = -1;
auto_status_text = "B RECEIVED - MANUAL MODE";
begin_render();
ESP_LOGI(TAG, "Automatic zoom stopped; manual viewer restored");
}take_ready_auto_frame() · source line 562
static int take_ready_auto_frame(void)
- Execute
int slot = -1; - Execute
uint32_t oldest_sequence = UINT32_MAX; - Execute
portENTER_CRITICAL(&auto_frame_lock); - Decision
automatic_zoomTrue- FOR loop
unsigned index = 0U; index < MANDELBROT_AUTO_FRAME_COUNT; ++indexRepeat body while the loop condition permits; then continue below.
- Decision
auto_frames[index].state == MANDELBROT_FRAME_READY && auto_frames[index].sequence < oldest_sequenceTrue- Execute
oldest_sequence = auto_frames[index].sequence; - Execute
slot = (int)index;
FalseContinue after this decision.
- Decision
slot >= 0True- Execute
auto_frames[slot].state = MANDELBROT_FRAME_DISPLAYING;
FalseContinue after this decision.
FalseContinue after this decision.
- Execute
portEXIT_CRITICAL(&auto_frame_lock); - Exit this function
return slot;
Read original function source
static int take_ready_auto_frame(void)
{
int slot = -1;
uint32_t oldest_sequence = UINT32_MAX;
portENTER_CRITICAL(&auto_frame_lock);
if (automatic_zoom) {
for (unsigned index = 0U; index < MANDELBROT_AUTO_FRAME_COUNT; ++index) {
if (auto_frames[index].state == MANDELBROT_FRAME_READY &&
auto_frames[index].sequence < oldest_sequence) {
oldest_sequence = auto_frames[index].sequence;
slot = (int)index;
}
}
if (slot >= 0) auto_frames[slot].state = MANDELBROT_FRAME_DISPLAYING;
}
portEXIT_CRITICAL(&auto_frame_lock);
return slot;
}ready_auto_frame_count() · source line 581
static unsigned ready_auto_frame_count(void)
- Execute
unsigned count = 0U; - Execute
portENTER_CRITICAL(&auto_frame_lock); - FOR loop
unsigned index = 0U; index < MANDELBROT_AUTO_FRAME_COUNT; ++indexRepeat body while the loop condition permits; then continue below.
- Decision
auto_frames[index].state == MANDELBROT_FRAME_READYTrue- Execute
++count;
FalseContinue after this decision.
- Execute
portEXIT_CRITICAL(&auto_frame_lock); - Exit this function
return count;
Read original function source
static unsigned ready_auto_frame_count(void)
{
unsigned count = 0U;
portENTER_CRITICAL(&auto_frame_lock);
for (unsigned index = 0U; index < MANDELBROT_AUTO_FRAME_COUNT; ++index) {
if (auto_frames[index].state == MANDELBROT_FRAME_READY) ++count;
}
portEXIT_CRITICAL(&auto_frame_lock);
return count;
}log_auto_status() · source line 592
static void log_auto_status(uint32_t now_ms)
Calls into this documentation:
- Decision
auto_log_started_ms == 0UTrue- Execute
auto_log_started_ms = now_ms;
FalseContinue after this decision.
- Decision
now_ms - auto_log_started_ms < MANDELBROT_AUTO_LOG_INTERVAL_MSTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
ESP_LOGI(TAG, "AUTO fps=%u render_fps=%u render=%ums ready=%u psram=%u bytes", (unsigned)auto_displayed_frames, (unsigned)auto_rendered_frames, (unsigned)auto_last_render_ms, ready_auto_frame_count(), (unsigned)(MANDELBROT_AUTO_FRAME_COUNT * LCD_WIDTH * LCD_HEIGHT)); - Execute
auto_displayed_frames = 0U; - Execute
auto_rendered_frames = 0U; - Execute
auto_log_started_ms = now_ms;
Read original function source
static void log_auto_status(uint32_t now_ms)
{
if (auto_log_started_ms == 0U) auto_log_started_ms = now_ms;
if (now_ms - auto_log_started_ms < MANDELBROT_AUTO_LOG_INTERVAL_MS) return;
ESP_LOGI(TAG, "AUTO fps=%u render_fps=%u render=%ums ready=%u psram=%u bytes",
(unsigned)auto_displayed_frames, (unsigned)auto_rendered_frames,
(unsigned)auto_last_render_ms, ready_auto_frame_count(),
(unsigned)(MANDELBROT_AUTO_FRAME_COUNT * LCD_WIDTH * LCD_HEIGHT));
auto_displayed_frames = 0U;
auto_rendered_frames = 0U;
auto_log_started_ms = now_ms;
}startMandelbrot() · source line 605
void startMandelbrot(uint32_t now_ms)
Calls into this documentation:
- Execute
running = true; - Execute
select_view(0U); - Execute
last_cursor_move = now_ms; - Preprocessor directive
#if 0 - Execute
maths_last_update = now_ms; - Preprocessor directive
#endif - Execute
automatic_zoom = false; - Execute
displayed_frame_slot = -1; - Execute
(void)allocate_manual_staging_frame(); - Execute
begin_render();
Read original function source
void startMandelbrot(uint32_t now_ms)
{
running = true;
select_view(0U);
last_cursor_move = now_ms;
#if 0 // Maths panel experiment: disabled.
maths_last_update = now_ms;
#endif
automatic_zoom = false;
displayed_frame_slot = -1;
(void)allocate_manual_staging_frame();
begin_render();
}stopMandelbrot() · source line 619
void stopMandelbrot(void)
Calls into this documentation:
- Execute
running = false; - Execute
rendering = false; - Execute
frame_ready = false; - Execute
portENTER_CRITICAL(&auto_frame_lock); - Execute
automatic_zoom = false; - Execute
portEXIT_CRITICAL(&auto_frame_lock); - Execute
release_manual_staging_frame();
Read original function source
void stopMandelbrot(void)
{
running = false;
rendering = false;
frame_ready = false;
portENTER_CRITICAL(&auto_frame_lock);
automatic_zoom = false;
portEXIT_CRITICAL(&auto_frame_lock);
release_manual_staging_frame();
}mandelbrotAction() · source line 630
void mandelbrotAction(bool zoom, bool start_automatic, bool return_to_manual,
bool next_view, uint32_t now_ms)Calls into this documentation:
- Decision
!runningTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
return_to_manual && automaticZoomActive()True- Execute
return_to_manual_view(); - Exit this function
return;
FalseContinue after this decision.
- Decision
start_automaticTrue- Execute
auto_status_text = "C RECEIVED - STARTING AUTO"; - Execute
frame_ready = true; - Execute
start_automatic_zoom(); - Exit this function
return;
FalseContinue after this decision.
- Decision
automaticZoomActive() || (!zoom && !next_view)True- Exit this function
return;
FalseContinue after this decision.
- Decision
next_viewTrue- Execute
select_view((uint8_t)((selected_view + 1U) % (sizeof(views) / sizeof(views[0]))));
False- Execute
const double height = view_width * (double)LCD_HEIGHT / (double)LCD_WIDTH; - Execute
const double minimum_real = centre_real - view_width * 0.5; - Execute
const double minimum_imaginary = centre_imaginary - height * 0.5; - Execute
centre_real = minimum_real + view_width * (double)(cursor_x + ZOOM_BOX_WIDTH / 2) / (double)(LCD_WIDTH - 1U); - Execute
centre_imaginary = minimum_imaginary + height * (double)(cursor_y + ZOOM_BOX_HEIGHT / 2) / (double)(LCD_HEIGHT - 1U); - Execute
view_width *= (double)ZOOM_BOX_WIDTH / (double)LCD_WIDTH; - Execute
++zoom_level;
- Execute
last_cursor_move = now_ms; - Execute
begin_render();
Read original function source
void mandelbrotAction(bool zoom, bool start_automatic, bool return_to_manual,
bool next_view, uint32_t now_ms)
{
if (!running) return;
if (return_to_manual && automaticZoomActive()) {
return_to_manual_view();
return;
}
if (start_automatic) {
auto_status_text = "C RECEIVED - STARTING AUTO";
frame_ready = true;
start_automatic_zoom();
return;
}
if (automaticZoomActive() || (!zoom && !next_view)) return;
if (next_view) {
select_view((uint8_t)((selected_view + 1U) % (sizeof(views) / sizeof(views[0]))));
} else {
const double height = view_width * (double)LCD_HEIGHT / (double)LCD_WIDTH;
const double minimum_real = centre_real - view_width * 0.5;
const double minimum_imaginary = centre_imaginary - height * 0.5;
centre_real = minimum_real + view_width * (double)(cursor_x + ZOOM_BOX_WIDTH / 2) /
(double)(LCD_WIDTH - 1U);
centre_imaginary = minimum_imaginary + height * (double)(cursor_y + ZOOM_BOX_HEIGHT / 2) /
(double)(LCD_HEIGHT - 1U);
view_width *= (double)ZOOM_BOX_WIDTH / (double)LCD_WIDTH;
++zoom_level;
}
last_cursor_move = now_ms;
begin_render();
}updateMandelbrot() · source line 662
void updateMandelbrot(int x_offset, int y_offset, bool joystick_valid, uint32_t now_ms)
Calls into this documentation:
- Decision
!runningTrue- Exit this function
return;
FalseContinue after this decision.
- Preprocessor directive
#if 0 - Execute
row_count = 0U; - Execute
const bool maths_changed = update_maths_sample(now_ms); - Decision
maths_changedTrue- Execute
frame_ready = true;
FalseContinue after this decision.
- Preprocessor directive
#endif - Decision
automaticZoomActive()True- Execute
log_auto_status(now_ms); - Exit this function
return;
FalseContinue after this decision.
- Decision
joystick_valid && now_ms - last_cursor_move >= CURSOR_REPEAT_MSTrue- Decision
x_offset >= JOYSTICK_ENTERTrue- Execute
cursor_x += CURSOR_STEP;
False- Decision
x_offset <= -JOYSTICK_ENTERTrue- Execute
cursor_x -= CURSOR_STEP;
FalseContinue after this decision.
- Decision
y_offset >= JOYSTICK_ENTERTrue- Execute
cursor_y -= CURSOR_STEP;
False- Decision
y_offset <= -JOYSTICK_ENTERTrue- Execute
cursor_y += CURSOR_STEP;
FalseContinue after this decision.
- Decision
cursor_x < 0True- Execute
cursor_x = 0;
FalseContinue after this decision.
- Decision
cursor_x > LCD_WIDTH - ZOOM_BOX_WIDTHTrue- Execute
cursor_x = LCD_WIDTH - ZOOM_BOX_WIDTH;
FalseContinue after this decision.
- Decision
cursor_y < 0True- Execute
cursor_y = 0;
FalseContinue after this decision.
- Decision
cursor_y > LCD_HEIGHT - ZOOM_BOX_HEIGHTTrue- Execute
cursor_y = LCD_HEIGHT - ZOOM_BOX_HEIGHT;
FalseContinue after this decision.
- Execute
last_cursor_move = now_ms; - Execute
frame_ready = true;
FalseContinue after this decision.
- Decision
!renderingTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
rendered_start_row = next_row; - Execute
row_count = 0U; - WHILE loop
row_count < ROWS_PER_UPDATE && next_row < LCD_HEIGHTRepeat body while the loop condition permits; then continue below.
- Execute
uint16_t *destination = rendered_rows[row_count]; - Decision
manual_staging_frame != NULLTrue- Execute
destination = manual_staging_frame + (size_t)next_row * LCD_WIDTH;
FalseContinue after this decision.
- Execute
manual_iteration_count += render_row(next_row, destination); - Execute
++row_count; - Execute
++next_row;
- Execute
frame_ready = frame_ready || row_count != 0U || clear_requested; - Decision
next_row >= LCD_HEIGHTTrue- Execute
rendering = false; - Execute
manual_reveal_pending = manual_staging_frame != NULL;
FalseContinue after this decision.
Read original function source
void updateMandelbrot(int x_offset, int y_offset, bool joystick_valid, uint32_t now_ms)
{
if (!running) return;
#if 0 // Maths panel experiment: disabled.
// Overlay-only redraws must not copy an old pair of manual render rows.
row_count = 0U;
const bool maths_changed = update_maths_sample(now_ms);
if (maths_changed) frame_ready = true;
#endif
if (automaticZoomActive()) {
log_auto_status(now_ms);
return;
}
if (joystick_valid && now_ms - last_cursor_move >= CURSOR_REPEAT_MS) {
if (x_offset >= JOYSTICK_ENTER) cursor_x += CURSOR_STEP;
else if (x_offset <= -JOYSTICK_ENTER) cursor_x -= CURSOR_STEP;
if (y_offset >= JOYSTICK_ENTER) cursor_y -= CURSOR_STEP;
else if (y_offset <= -JOYSTICK_ENTER) cursor_y += CURSOR_STEP;
if (cursor_x < 0) cursor_x = 0;
if (cursor_x > LCD_WIDTH - ZOOM_BOX_WIDTH) cursor_x = LCD_WIDTH - ZOOM_BOX_WIDTH;
if (cursor_y < 0) cursor_y = 0;
if (cursor_y > LCD_HEIGHT - ZOOM_BOX_HEIGHT) cursor_y = LCD_HEIGHT - ZOOM_BOX_HEIGHT;
last_cursor_move = now_ms;
frame_ready = true;
}
if (!rendering) return;
rendered_start_row = next_row;
row_count = 0U;
while (row_count < ROWS_PER_UPDATE && next_row < LCD_HEIGHT) {
uint16_t *destination = rendered_rows[row_count];
if (manual_staging_frame != NULL) {
destination = manual_staging_frame + (size_t)next_row * LCD_WIDTH;
}
manual_iteration_count += render_row(next_row, destination);
++row_count;
++next_row;
}
frame_ready = frame_ready || row_count != 0U || clear_requested;
if (next_row >= LCD_HEIGHT) {
rendering = false;
// Keep the 100% screen visible for one frame before showing the fractal.
manual_reveal_pending = manual_staging_frame != NULL;
}
}mandelbrotTakeFrame() · source line 707
bool mandelbrotTakeFrame(void)
Calls into this documentation:
- Decision
automaticZoomActive()True- Execute
displayed_frame_slot = take_ready_auto_frame(); - Decision
displayed_frame_slot >= 0True- Exit this function
return true;
FalseContinue after this decision.
- Execute
const bool ready = frame_ready; - Execute
frame_ready = false; - Exit this function
return ready;
FalseContinue after this decision.
- Execute
const bool ready = frame_ready; - Execute
frame_ready = false; - Exit this function
return ready;
Read original function source
bool mandelbrotTakeFrame(void)
{
if (automaticZoomActive()) {
displayed_frame_slot = take_ready_auto_frame();
if (displayed_frame_slot >= 0) {
return true;
}
const bool ready = frame_ready;
frame_ready = false;
return ready;
}
const bool ready = frame_ready;
frame_ready = false;
return ready;
}draw_manual_progress() · source line 723
static void draw_manual_progress(uint16_t *frame)
Calls into this documentation:
- Execute
const unsigned percent = ((unsigned)next_row * 100U) / LCD_HEIGHT; - Execute
const int bar_x = (LCD_WIDTH - MANDELBROT_PROGRESS_BAR_WIDTH) / 2; - Execute
const int bar_y = 102; - Execute
const int inner_width = MANDELBROT_PROGRESS_BAR_WIDTH - 2; - Execute
const int filled_width = (inner_width * (int)percent) / 100; - Execute
char text[32]; - Execute
clear(frame, COLOR_BLACK); - Execute
drawPet2001TextCentredTransparent(frame, LCD_WIDTH / 2, 20, "MANDELBROT", COLOR_LIME, 3); - Execute
drawPet2001TextCentredTransparent(frame, LCD_WIDTH / 2, 51, "Z=Z*Z+C", COLOR_LIGHTGREEN, 2); - Execute
drawPet2001TextCentredTransparent(frame, LCD_WIDTH / 2, 76, "CALCULATING", COLOR_GREENYELLOW, 2); - Execute
draw_rect(frame, bar_x, bar_y, MANDELBROT_PROGRESS_BAR_WIDTH, MANDELBROT_PROGRESS_BAR_HEIGHT, COLOR_CRIMSON); - Decision
filled_width > 0True- Execute
fill_rect(frame, bar_x + 1, bar_y + 1, filled_width, MANDELBROT_PROGRESS_BAR_HEIGHT - 2, COLOR_RED);
FalseContinue after this decision.
- Execute
snprintf(text, sizeof(text), "%u%%", percent); - Execute
drawPet2001TextCentredTransparent(frame, LCD_WIDTH / 2, 119, text, COLOR_LIME, 2); - Execute
snprintf(text, sizeof(text), "ITERATIONS %lu", (unsigned long)manual_iteration_count); - Execute
drawPet2001TextCentredTransparent(frame, LCD_WIDTH / 2, 146, text, COLOR_LIGHTGREEN, 1);
Read original function source
static void draw_manual_progress(uint16_t *frame)
{
const unsigned percent = ((unsigned)next_row * 100U) / LCD_HEIGHT;
const int bar_x = (LCD_WIDTH - MANDELBROT_PROGRESS_BAR_WIDTH) / 2;
const int bar_y = 102;
const int inner_width = MANDELBROT_PROGRESS_BAR_WIDTH - 2;
const int filled_width = (inner_width * (int)percent) / 100;
char text[32];
clear(frame, COLOR_BLACK);
drawPet2001TextCentredTransparent(frame, LCD_WIDTH / 2, 20, "MANDELBROT", COLOR_LIME, 3);
drawPet2001TextCentredTransparent(frame, LCD_WIDTH / 2, 51, "Z=Z*Z+C", COLOR_LIGHTGREEN, 2);
drawPet2001TextCentredTransparent(frame, LCD_WIDTH / 2, 76, "CALCULATING", COLOR_GREENYELLOW, 2);
draw_rect(frame, bar_x, bar_y, MANDELBROT_PROGRESS_BAR_WIDTH,
MANDELBROT_PROGRESS_BAR_HEIGHT, COLOR_CRIMSON);
if (filled_width > 0) {
fill_rect(frame, bar_x + 1, bar_y + 1, filled_width,
MANDELBROT_PROGRESS_BAR_HEIGHT - 2, COLOR_RED);
}
snprintf(text, sizeof(text), "%u%%", percent);
drawPet2001TextCentredTransparent(frame, LCD_WIDTH / 2, 119, text, COLOR_LIME, 2);
snprintf(text, sizeof(text), "ITERATIONS %lu", (unsigned long)manual_iteration_count);
drawPet2001TextCentredTransparent(frame, LCD_WIDTH / 2, 146, text, COLOR_LIGHTGREEN, 1);
}drawMandelbrot() · source line 748
void drawMandelbrot(uint16_t *frame)
Calls into this documentation:
- Decision
!running || frame == NULLTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
automaticZoomActive()True- Decision
displayed_frame_slot < 0True- Execute
fill_rect(frame, 0, 0, LCD_WIDTH, 12, COLOR_BLACK); - Execute
drawPet2001TextTransparent(frame, 2, 2, auto_status_text, COLOR_WHITE, 1); - Exit this function
return;
FalseContinue after this decision.
- Execute
const mandelbrot_frame_t *displayed = &auto_frames[displayed_frame_slot]; - Execute
const uint8_t *source = displayed->pixels; - FOR loop
size_t pixel = 0U; pixel < (size_t)LCD_WIDTH * LCD_HEIGHT; ++pixelRepeat body while the loop condition permits; then continue below.
- Execute
frame[pixel] = auto_palette[source[pixel]];
- Preprocessor directive
#if 0 - Execute
set_maths_sample(displayed->view, displayed->sample_real, displayed->sample_imaginary, true); - Preprocessor directive
#endif - Execute
portENTER_CRITICAL(&auto_frame_lock); - Execute
auto_frames[displayed_frame_slot].state = MANDELBROT_FRAME_FREE; - Execute
portEXIT_CRITICAL(&auto_frame_lock); - Execute
displayed_frame_slot = -1; - Execute
++auto_displayed_frames; - Exit this function
return;
FalseContinue after this decision.
- Preprocessor directive
#if 0 - Decision
!clear_requested && row_count == 0U && cursor_drawn && cursor_x == drawn_cursor_x && cursor_y == drawn_cursor_yTrue- Execute
draw_maths_panel(frame); - Exit this function
return;
FalseContinue after this decision.
- Preprocessor directive
#endif - Decision
manual_staging_frame != NULLTrue- Decision
rendering || manual_reveal_pendingTrue- Execute
draw_manual_progress(frame); - Decision
manual_reveal_pendingTrue- Execute
manual_reveal_pending = false; - Execute
frame_ready = true;
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Execute
memcpy(frame, manual_staging_frame, MANDELBROT_MANUAL_FRAME_BYTES); - Execute
clear_requested = false;
False- Decision
clear_requestedTrue- Execute
clear(frame, COLOR_BLACK); - Execute
clear_requested = false;
FalseContinue after this decision.
- FOR loop
uint8_t row = 0U; row < row_count; ++rowRepeat body while the loop condition permits; then continue below.
- Execute
memcpy(frame + (size_t)(rendered_start_row + row) * LCD_WIDTH, rendered_rows[row], sizeof(rendered_rows[row]));
- Decision
cursor_drawnTrue- Execute
restore_cursor_border(frame, drawn_cursor_x, drawn_cursor_y);
FalseContinue after this decision.
- Execute
char status[40]; - Execute
snprintf(status, sizeof(status), "%s Z%u", views[selected_view].name, (unsigned)zoom_level); - Execute
fill_rect(frame, 0, 0, 128, 10, COLOR_BLACK); - Execute
drawPet2001TextTransparent(frame, 2, 1, status, COLOR_WHITE, 1); - Execute
draw_rect(frame, cursor_x, cursor_y, ZOOM_BOX_WIDTH, ZOOM_BOX_HEIGHT, COLOR_WHITE); - Execute
drawn_cursor_x = cursor_x; - Execute
drawn_cursor_y = cursor_y; - Execute
cursor_drawn = true; - Execute
drawPet2001TextTransparent(frame, 2, 148, "A/K ZOOM C AUTO E VIEW", COLOR_WHITE, 1); - Execute
drawPet2001TextTransparent(frame, 2, 158, "B MANUAL BOOT BACK", COLOR_WHITE, 1); - Decision
auto_status_text != NULLTrue- Execute
fill_rect(frame, 0, 12, LCD_WIDTH, 10, COLOR_BLACK); - Execute
drawPet2001TextTransparent(frame, 2, 12, auto_status_text, COLOR_WHITE, 1);
FalseContinue after this decision.
- Preprocessor directive
#if 0 - Execute
draw_maths_panel(frame); - Preprocessor directive
#endif
Read original function source
void drawMandelbrot(uint16_t *frame)
{
if (!running || frame == NULL) return;
if (automaticZoomActive()) {
if (displayed_frame_slot < 0) {
fill_rect(frame, 0, 0, LCD_WIDTH, 12, COLOR_BLACK);
drawPet2001TextTransparent(frame, 2, 2, auto_status_text, COLOR_WHITE, 1);
return;
}
const mandelbrot_frame_t *displayed = &auto_frames[displayed_frame_slot];
const uint8_t *source = displayed->pixels;
for (size_t pixel = 0U; pixel < (size_t)LCD_WIDTH * LCD_HEIGHT; ++pixel) {
frame[pixel] = auto_palette[source[pixel]];
}
#if 0 // Maths panel experiment: disabled.
// Copy metadata before releasing the slot for the background renderer.
set_maths_sample(displayed->view, displayed->sample_real,
displayed->sample_imaginary, true);
#endif
portENTER_CRITICAL(&auto_frame_lock);
auto_frames[displayed_frame_slot].state = MANDELBROT_FRAME_FREE;
portEXIT_CRITICAL(&auto_frame_lock);
displayed_frame_slot = -1;
++auto_displayed_frames;
return;
}
#if 0 // Maths panel experiment: disabled.
if (!clear_requested && row_count == 0U && cursor_drawn &&
cursor_x == drawn_cursor_x && cursor_y == drawn_cursor_y) {
// A maths-only tick does not need to recalculate the cursor border pixels.
draw_maths_panel(frame);
return;
}
#endif
if (manual_staging_frame != NULL) {
if (rendering || manual_reveal_pending) {
draw_manual_progress(frame);
if (manual_reveal_pending) {
manual_reveal_pending = false;
frame_ready = true;
}
return;
}
memcpy(frame, manual_staging_frame, MANDELBROT_MANUAL_FRAME_BYTES);
clear_requested = false;
} else {
if (clear_requested) {
clear(frame, COLOR_BLACK);
clear_requested = false;
}
for (uint8_t row = 0U; row < row_count; ++row) {
memcpy(frame + (size_t)(rendered_start_row + row) * LCD_WIDTH,
rendered_rows[row], sizeof(rendered_rows[row]));
}
}
if (cursor_drawn) restore_cursor_border(frame, drawn_cursor_x, drawn_cursor_y);
char status[40];
snprintf(status, sizeof(status), "%s Z%u", views[selected_view].name,
(unsigned)zoom_level);
fill_rect(frame, 0, 0, 128, 10, COLOR_BLACK);
drawPet2001TextTransparent(frame, 2, 1, status, COLOR_WHITE, 1);
draw_rect(frame, cursor_x, cursor_y, ZOOM_BOX_WIDTH, ZOOM_BOX_HEIGHT, COLOR_WHITE);
drawn_cursor_x = cursor_x;
drawn_cursor_y = cursor_y;
cursor_drawn = true;
drawPet2001TextTransparent(frame, 2, 148, "A/K ZOOM C AUTO E VIEW", COLOR_WHITE, 1);
drawPet2001TextTransparent(frame, 2, 158, "B MANUAL BOOT BACK", COLOR_WHITE, 1);
if (auto_status_text != NULL) {
fill_rect(frame, 0, 12, LCD_WIDTH, 10, COLOR_BLACK);
drawPet2001TextTransparent(frame, 2, 12, auto_status_text, COLOR_WHITE, 1);
}
#if 0 // Maths panel experiment: disabled.
// Compose last so manual rows, status text and cursor restoration stay below it.
draw_maths_panel(frame);
#endif
}Complete original file, including constants and data
/* Coded By JD(2026) */
#error "Reference listing only — not provided as build-ready source."
// Mandelbrot viewer with an optional PSRAM-backed automatic zoom renderer.
#include "mandelbrot.h"
#include "game_graphics.h"
#include "PET2001_font.h"
#include "esp_heap_caps.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <math.h>
#include <stdio.h>
#include <string.h>
#define MAX_ITERATIONS 100U
#define ROWS_PER_UPDATE 2U
#define ZOOM_BOX_WIDTH 80
#define ZOOM_BOX_HEIGHT 42
#define JOYSTICK_ENTER 600
#define CURSOR_STEP 8
#define CURSOR_REPEAT_MS 70U
// Maths panel: one sample iteration per display tick, never a render-time wait.
#define MANDELBROT_MATHS_INTERVAL_MS 16U
#define MANDELBROT_MATHS_HOLD_MS 1000U // Hold escape/limit result before replaying.
#define MANDELBROT_MATHS_WIDTH 154
#define MANDELBROT_MATHS_HEIGHT 106
#define MANDELBROT_MATHS_LINES 11U
// Automatic zoom tuning. These can be changed without altering the queue.
#define MANDELBROT_AUTO_FRAME_COUNT 4U
#define MANDELBROT_AUTO_START_REAL -0.50f
#define MANDELBROT_AUTO_START_IMAGINARY 0.00f
#define MANDELBROT_AUTO_START_WIDTH 3.76f
// This fixed target produces the familiar Seahorse Valley detail.
#define MANDELBROT_AUTO_TARGET_REAL -0.7436439f
#define MANDELBROT_AUTO_TARGET_IMAGINARY 0.1318259f
// Every rendered frame is this fraction as wide as the preceding frame.
// Values nearer 1.0 zoom more slowly; it must remain greater than zero.
#define MANDELBROT_AUTO_ZOOM_PER_FRAME 0.95f
#define MANDELBROT_AUTO_LOG_INTERVAL_MS 1000U
// Automatic frames trade a little edge detail for regular animation updates.
// Keep this below the manual viewer's MAX_ITERATIONS value.
#define MANDELBROT_AUTO_MAX_ITERATIONS 64U
// Yielding after this many rows lets CPU 1's idle task run and prevents a watchdog reset.
#define MANDELBROT_AUTO_ROWS_PER_YIELD 4U
// Manual rendering is staged off-screen so no partially calculated rows reach the LCD.
#define MANDELBROT_MANUAL_FRAME_BYTES (LCD_WIDTH * LCD_HEIGHT * sizeof(uint16_t))
// Visual tuning controls for the centred manual-render progress screen.
#define MANDELBROT_PROGRESS_BAR_WIDTH 200
// Taller than the PET text strokes so progress is clear at a glance.
#define MANDELBROT_PROGRESS_BAR_HEIGHT 16
typedef struct {
const char *name;
double centre_real;
double centre_imaginary;
double width;
} mandelbrot_view_t;
typedef enum {
MANDELBROT_FRAME_FREE,
MANDELBROT_FRAME_RENDERING,
MANDELBROT_FRAME_READY,
MANDELBROT_FRAME_DISPLAYING,
} mandelbrot_frame_state_t;
typedef struct {
uint8_t *pixels;
mandelbrot_frame_state_t state;
uint32_t sequence;
uint32_t generation;
// Published with the pixels so the panel describes the displayed frame.
mandelbrot_view_t view;
double sample_real, sample_imaginary;
} mandelbrot_frame_t;
static const char *const TAG = "mandelbrot";
static const mandelbrot_view_t views[] = {
{"FULL", -0.50, 0.00, 3.76},
{"HORSE", -0.74364, 0.13183, 0.030},
{"ELEPH", 0.285, 0.010, 0.180},
{"SPIRAL",-0.761574,-0.084760, 0.018},
};
static uint16_t rendered_rows[ROWS_PER_UPDATE][LCD_WIDTH];
static uint16_t auto_palette[256];
static uint16_t *manual_staging_frame;
static mandelbrot_frame_t auto_frames[MANDELBROT_AUTO_FRAME_COUNT];
static portMUX_TYPE auto_frame_lock = portMUX_INITIALIZER_UNLOCKED;
static TaskHandle_t auto_renderer_task;
static int displayed_frame_slot = -1;
static uint8_t selected_view, row_count, zoom_level;
static uint16_t next_row, rendered_start_row;
static int cursor_x, cursor_y, drawn_cursor_x, drawn_cursor_y;
static double centre_real, centre_imaginary, view_width;
static uint32_t last_cursor_move, auto_generation, auto_next_sequence;
static uint32_t manual_iteration_count;
static uint32_t auto_rendered_frames, auto_displayed_frames, auto_last_render_ms;
static uint32_t auto_log_started_ms;
static const char *auto_status_text;
static bool running, rendering, clear_requested, frame_ready, cursor_drawn;
static bool manual_reveal_pending;
static bool automatic_zoom, auto_buffers_ready;
// The maths-panel experiment is retained below, but is disabled because the
// panel occupied too much of the fractal display. Keep it here for a future,
// smaller presentation rather than removing the working calculations.
#if 0
// Owned only by the main/display task. The worker publishes frame metadata above.
static mandelbrot_view_t maths_view;
static double maths_c_real, maths_c_imaginary, maths_z_real, maths_z_imaginary;
static double maths_magnitude_squared;
static uint16_t maths_iteration, maths_limit;
static uint32_t maths_last_update, maths_finished_at;
static bool maths_valid, maths_auto, maths_finished;
static char maths_lines[MANDELBROT_MATHS_LINES][24];
static void refresh_maths_text(void)
{
snprintf(maths_lines[0], sizeof(maths_lines[0]), "z=z*z+c MID %u,%u",
(unsigned)(LCD_WIDTH / 2), (unsigned)(LCD_HEIGHT / 2));
snprintf(maths_lines[1], sizeof(maths_lines[1]), "VR %+.7f", maths_view.centre_real);
snprintf(maths_lines[2], sizeof(maths_lines[2]), "VI %+.7f", maths_view.centre_imaginary);
snprintf(maths_lines[3], sizeof(maths_lines[3]), "W %.5g", maths_view.width);
snprintf(maths_lines[4], sizeof(maths_lines[4]), "ZOOM %.4gx",
views[0].width / maths_view.width);
snprintf(maths_lines[5], sizeof(maths_lines[5]), "CR %+.7f", maths_c_real);
snprintf(maths_lines[6], sizeof(maths_lines[6]), "CI %+.7f", maths_c_imaginary);
snprintf(maths_lines[7], sizeof(maths_lines[7]), "ZR %+.6f", maths_z_real);
snprintf(maths_lines[8], sizeof(maths_lines[8]), "ZI %+.6f", maths_z_imaginary);
snprintf(maths_lines[9], sizeof(maths_lines[9]), "N %u/%u %s", (unsigned)maths_iteration,
(unsigned)maths_limit, maths_finished ?
(maths_magnitude_squared > 4.0 ? "ESC" : "LIMIT") : "");
snprintf(maths_lines[10], sizeof(maths_lines[10]), "|z|2 %.6g",
maths_magnitude_squared);
}
static void set_maths_sample(mandelbrot_view_t view, double real, double imaginary,
bool automatic)
{
maths_view = view;
maths_c_real = real;
maths_c_imaginary = imaginary;
maths_z_real = maths_z_imaginary = 0.0;
maths_magnitude_squared = 0.0;
maths_iteration = 0U;
maths_limit = automatic ? MANDELBROT_AUTO_MAX_ITERATIONS : MAX_ITERATIONS;
maths_auto = automatic;
maths_finished = false;
maths_valid = true;
refresh_maths_text();
}
static void set_manual_maths_sample(void)
{
const double height = view_width * (double)LCD_HEIGHT / (double)LCD_WIDTH;
const double real = centre_real - view_width * 0.5 +
view_width * (double)(LCD_WIDTH / 2) / (double)(LCD_WIDTH - 1U);
const double imaginary = centre_imaginary - height * 0.5 +
height * (double)(LCD_HEIGHT / 2) / (double)(LCD_HEIGHT - 1U);
set_maths_sample((mandelbrot_view_t){views[selected_view].name, centre_real,
centre_imaginary, view_width}, real, imaginary, false);
}
static bool update_maths_sample(uint32_t now_ms)
{
if (!maths_valid || now_ms - maths_last_update < MANDELBROT_MATHS_INTERVAL_MS) return false;
maths_last_update = now_ms;
if (maths_finished) {
if (now_ms - maths_finished_at < MANDELBROT_MATHS_HOLD_MS) return false;
maths_z_real = maths_z_imaginary = 0.0;
maths_magnitude_squared = 0.0;
maths_iteration = 0U;
maths_finished = false;
} else {
// Match AUTO's float arithmetic and manual rendering's double arithmetic.
// Keep old real/imaginary values until both components have been computed.
if (maths_auto) {
const float real = (float)maths_z_real, imaginary = (float)maths_z_imaginary;
maths_z_imaginary = 2.0f * real * imaginary + (float)maths_c_imaginary;
maths_z_real = real * real - imaginary * imaginary + (float)maths_c_real;
} else {
const double real = maths_z_real, imaginary = maths_z_imaginary;
maths_z_imaginary = 2.0 * real * imaginary + maths_c_imaginary;
maths_z_real = real * real - imaginary * imaginary + maths_c_real;
}
++maths_iteration;
if (maths_auto) {
const float real = (float)maths_z_real, imaginary = (float)maths_z_imaginary;
maths_magnitude_squared = real * real + imaginary * imaginary;
} else {
maths_magnitude_squared = maths_z_real * maths_z_real + maths_z_imaginary * maths_z_imaginary;
}
maths_finished = maths_magnitude_squared > 4.0 || maths_iteration >= maths_limit;
if (maths_finished) maths_finished_at = now_ms;
}
refresh_maths_text();
return true;
}
static void draw_maths_panel(uint16_t *frame)
{
const int x = LCD_WIDTH - MANDELBROT_MATHS_WIDTH - 2;
fill_rect(frame, x, 2, MANDELBROT_MATHS_WIDTH, MANDELBROT_MATHS_HEIGHT, COLOR_BLACK);
draw_rect(frame, x, 2, MANDELBROT_MATHS_WIDTH, MANDELBROT_MATHS_HEIGHT, COLOR_CYAN);
if (!maths_valid) {
draw_text(frame, x + 4, 7, "WAITING FOR FRAME", 1, COLOR_WHITE);
return;
}
for (unsigned line = 0U; line < MANDELBROT_MATHS_LINES; ++line) {
draw_text(frame, x + 4, 6 + (int)line * 9, maths_lines[line], 1,
line == 0U ? COLOR_CYAN : COLOR_WHITE);
}
}
#endif
static uint16_t colour_wheel(uint8_t position)
{
position = 255U - position;
if (position < 85U) return RGB565(255U - position * 3U, 0U, position * 3U);
if (position < 170U) {
position -= 85U;
return RGB565(0U, position * 3U, 255U - position * 3U);
}
position -= 170U;
return RGB565(position * 3U, 255U - position * 3U, 0U);
}
static uint16_t iteration_colour(uint16_t iteration)
{
if (iteration >= MAX_ITERATIONS) return COLOR_BLACK;
const uint8_t intensity = (uint8_t)((iteration * 255U) / MAX_ITERATIONS);
return colour_wheel((uint8_t)(intensity + 32U));
}
static uint16_t calculate_pixel(double real, double imaginary, uint16_t *calculation_count)
{
double z_real = 0.0, z_imaginary = 0.0;
double z_real_squared = 0.0, z_imaginary_squared = 0.0;
uint16_t iteration = 0U;
while (z_real_squared + z_imaginary_squared <= 4.0 && iteration < MAX_ITERATIONS) {
z_imaginary = 2.0 * z_real * z_imaginary + imaginary;
z_real = z_real_squared - z_imaginary_squared + real;
z_real_squared = z_real * z_real;
z_imaginary_squared = z_imaginary * z_imaginary;
++iteration;
}
if (calculation_count != NULL) *calculation_count = iteration;
return iteration_colour(iteration);
}
static uint8_t calculate_iteration_index(float real, float imaginary)
{
float z_real = 0.0f, z_imaginary = 0.0f;
float z_real_squared = 0.0f, z_imaginary_squared = 0.0f;
uint16_t iteration = 0U;
while (z_real_squared + z_imaginary_squared <= 4.0f &&
iteration < MANDELBROT_AUTO_MAX_ITERATIONS) {
z_imaginary = 2.0f * z_real * z_imaginary + imaginary;
z_real = z_real_squared - z_imaginary_squared + real;
z_real_squared = z_real * z_real;
z_imaginary_squared = z_imaginary * z_imaginary;
++iteration;
}
if (iteration >= MANDELBROT_AUTO_MAX_ITERATIONS) return 0U;
return (uint8_t)(1U + ((iteration - 1U) * 254U) /
(MANDELBROT_AUTO_MAX_ITERATIONS - 1U));
}
static void build_auto_palette(void)
{
auto_palette[0] = COLOR_BLACK;
for (unsigned index = 1U; index < 256U; ++index) {
auto_palette[index] = colour_wheel((uint8_t)(index + 32U));
}
}
static bool automaticZoomActive(void)
{
bool active;
portENTER_CRITICAL(&auto_frame_lock);
active = automatic_zoom;
portEXIT_CRITICAL(&auto_frame_lock);
return active;
}
static void begin_render(void)
{
next_row = 0U;
row_count = 0U;
manual_iteration_count = 0U;
rendering = true;
clear_requested = true;
frame_ready = true;
cursor_drawn = false;
manual_reveal_pending = false;
#if 0 // Maths panel experiment: disabled; see the retained implementation above.
set_manual_maths_sample();
#endif
}
static void select_view(uint8_t view_index)
{
selected_view = view_index;
centre_real = views[selected_view].centre_real;
centre_imaginary = views[selected_view].centre_imaginary;
view_width = views[selected_view].width;
zoom_level = 0U;
cursor_x = (LCD_WIDTH - ZOOM_BOX_WIDTH) / 2;
cursor_y = (LCD_HEIGHT - ZOOM_BOX_HEIGHT) / 2;
}
static uint32_t render_row(uint16_t y, uint16_t *destination)
{
const double height = view_width * (double)LCD_HEIGHT / (double)LCD_WIDTH;
const double minimum_real = centre_real - view_width * 0.5;
const double minimum_imaginary = centre_imaginary - height * 0.5;
const double imaginary = minimum_imaginary + height * (double)y / (double)(LCD_HEIGHT - 1U);
uint32_t row_iterations = 0U;
for (uint16_t x = 0U; x < LCD_WIDTH; ++x) {
const double real = minimum_real + view_width * (double)x / (double)(LCD_WIDTH - 1U);
uint16_t pixel_iterations;
destination[x] = calculate_pixel(real, imaginary, &pixel_iterations);
row_iterations += pixel_iterations;
}
return row_iterations;
}
static uint16_t screen_pixel_colour(int x, int y)
{
const double height = view_width * (double)LCD_HEIGHT / (double)LCD_WIDTH;
const double minimum_real = centre_real - view_width * 0.5;
const double minimum_imaginary = centre_imaginary - height * 0.5;
const double real = minimum_real + view_width * (double)x / (double)(LCD_WIDTH - 1U);
const double imaginary = minimum_imaginary + height * (double)y / (double)(LCD_HEIGHT - 1U);
return calculate_pixel(real, imaginary, NULL);
}
static void restore_cursor_border(uint16_t *frame, int x, int y)
{
for (int column = x; column < x + ZOOM_BOX_WIDTH; ++column) {
frame[y * LCD_WIDTH + column] = screen_pixel_colour(column, y);
frame[(y + ZOOM_BOX_HEIGHT - 1) * LCD_WIDTH + column] =
screen_pixel_colour(column, y + ZOOM_BOX_HEIGHT - 1);
}
for (int row = y + 1; row < y + ZOOM_BOX_HEIGHT - 1; ++row) {
frame[row * LCD_WIDTH + x] = screen_pixel_colour(x, row);
frame[row * LCD_WIDTH + x + ZOOM_BOX_WIDTH - 1] =
screen_pixel_colour(x + ZOOM_BOX_WIDTH - 1, row);
}
}
static bool allocate_manual_staging_frame(void)
{
if (manual_staging_frame != NULL) return true;
manual_staging_frame = heap_caps_malloc(MANDELBROT_MANUAL_FRAME_BYTES,
MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if (manual_staging_frame == NULL) {
auto_status_text = "MANUAL PSRAM ALLOCATION FAILED";
ESP_LOGE(TAG, "Manual render needs %u PSRAM bytes",
(unsigned)MANDELBROT_MANUAL_FRAME_BYTES);
return false;
}
ESP_LOGI(TAG, "Manual render staging frame: %u PSRAM bytes",
(unsigned)MANDELBROT_MANUAL_FRAME_BYTES);
return true;
}
static void release_manual_staging_frame(void)
{
if (manual_staging_frame != NULL) {
heap_caps_free(manual_staging_frame);
manual_staging_frame = NULL;
}
}
static bool allocate_auto_buffers(void)
{
if (auto_buffers_ready) return true;
for (unsigned index = 0U; index < MANDELBROT_AUTO_FRAME_COUNT; ++index) {
auto_frames[index].pixels = heap_caps_malloc((size_t)LCD_WIDTH * LCD_HEIGHT,
MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if (auto_frames[index].pixels == NULL) {
auto_status_text = "AUTO PSRAM ALLOCATION FAILED";
ESP_LOGE(TAG, "Automatic zoom needs %u PSRAM bytes per frame",
(unsigned)(LCD_WIDTH * LCD_HEIGHT));
return false;
}
auto_frames[index].state = MANDELBROT_FRAME_FREE;
}
auto_buffers_ready = true;
ESP_LOGI(TAG, "Automatic zoom PSRAM queue: %u frames, %u bytes",
MANDELBROT_AUTO_FRAME_COUNT,
(unsigned)(MANDELBROT_AUTO_FRAME_COUNT * LCD_WIDTH * LCD_HEIGHT));
return true;
}
static void reset_auto_queue(void)
{
portENTER_CRITICAL(&auto_frame_lock);
++auto_generation;
auto_next_sequence = 0U;
for (unsigned index = 0U; index < MANDELBROT_AUTO_FRAME_COUNT; ++index) {
if (auto_frames[index].state != MANDELBROT_FRAME_RENDERING) {
auto_frames[index].state = MANDELBROT_FRAME_FREE;
}
}
portEXIT_CRITICAL(&auto_frame_lock);
}
static int take_free_auto_frame(uint32_t *sequence, uint32_t *generation)
{
int slot = -1;
portENTER_CRITICAL(&auto_frame_lock);
if (automatic_zoom) {
for (unsigned index = 0U; index < MANDELBROT_AUTO_FRAME_COUNT; ++index) {
if (auto_frames[index].state != MANDELBROT_FRAME_FREE) continue;
auto_frames[index].state = MANDELBROT_FRAME_RENDERING;
auto_frames[index].sequence = auto_next_sequence++;
auto_frames[index].generation = auto_generation;
*sequence = auto_frames[index].sequence;
*generation = auto_frames[index].generation;
slot = (int)index;
break;
}
}
portEXIT_CRITICAL(&auto_frame_lock);
return slot;
}
static void render_auto_frame(mandelbrot_frame_t *frame, uint32_t sequence)
{
uint8_t *destination = frame->pixels;
const float width = MANDELBROT_AUTO_START_WIDTH *
powf(MANDELBROT_AUTO_ZOOM_PER_FRAME, (float)sequence);
const float height = width * (float)LCD_HEIGHT / (float)LCD_WIDTH;
// Move the view centre toward the target at the same exponential rate as width.
// Frame zero is the recognizable full view; later frames approach the target.
const float progress = 1.0f - width / MANDELBROT_AUTO_START_WIDTH;
const float centre_real = MANDELBROT_AUTO_START_REAL +
(MANDELBROT_AUTO_TARGET_REAL - MANDELBROT_AUTO_START_REAL) * progress;
const float centre_imaginary = MANDELBROT_AUTO_START_IMAGINARY +
(MANDELBROT_AUTO_TARGET_IMAGINARY -
MANDELBROT_AUTO_START_IMAGINARY) * progress;
const float minimum_real = centre_real - width * 0.5f;
const float minimum_imaginary = centre_imaginary - height * 0.5f;
const float real_step = width / (float)(LCD_WIDTH - 1U);
const float imaginary_step = height / (float)(LCD_HEIGHT - 1U);
#if 0 // Maths panel experiment: disabled.
frame->view = (mandelbrot_view_t){"AUTO", centre_real, centre_imaginary, width};
// Reproduce the renderer's accumulated X steps at the actual centre pixel.
float sample_real = minimum_real;
for (unsigned x = 0U; x < LCD_WIDTH / 2U; ++x) sample_real += real_step;
frame->sample_real = sample_real;
frame->sample_imaginary = minimum_imaginary + (float)(LCD_HEIGHT / 2U) * imaginary_step;
#endif
for (unsigned y = 0U; y < LCD_HEIGHT; ++y) {
float real = minimum_real;
const float imaginary = minimum_imaginary + (float)y * imaginary_step;
for (unsigned x = 0U; x < LCD_WIDTH; ++x) {
destination[(size_t)y * LCD_WIDTH + x] = calculate_iteration_index(real, imaginary);
real += real_step;
}
// This task owns CPU 1 while rendering; yield regularly so IDLE1 can reset
// the task watchdog without exposing a partially rendered frame to display.
if ((y + 1U) % MANDELBROT_AUTO_ROWS_PER_YIELD == 0U) {
vTaskDelay(pdMS_TO_TICKS(1U));
}
}
}
static void automatic_renderer_task(void *unused)
{
(void)unused;
for (;;) {
if (!automaticZoomActive()) {
// B and Back leave the renderer dormant with no periodic CPU work.
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
continue;
}
uint32_t sequence = 0U;
uint32_t generation = 0U;
const int slot = take_free_auto_frame(&sequence, &generation);
if (slot < 0) {
vTaskDelay(pdMS_TO_TICKS(1U));
continue;
}
const uint32_t render_started = (uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS);
render_auto_frame(&auto_frames[slot], sequence);
const uint32_t render_finished = (uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS);
portENTER_CRITICAL(&auto_frame_lock);
if (automatic_zoom && auto_frames[slot].generation == generation &&
auto_generation == generation) {
auto_frames[slot].state = MANDELBROT_FRAME_READY;
auto_last_render_ms = render_finished - render_started;
++auto_rendered_frames;
} else {
auto_frames[slot].state = MANDELBROT_FRAME_FREE;
}
portEXIT_CRITICAL(&auto_frame_lock);
}
}
static bool start_automatic_zoom(void)
{
if (!allocate_auto_buffers()) return false;
build_auto_palette();
reset_auto_queue();
portENTER_CRITICAL(&auto_frame_lock);
automatic_zoom = true;
portEXIT_CRITICAL(&auto_frame_lock);
auto_rendered_frames = 0U;
auto_displayed_frames = 0U;
auto_log_started_ms = 0U;
frame_ready = true;
if (auto_renderer_task == NULL) {
const BaseType_t result = xTaskCreatePinnedToCore(automatic_renderer_task,
"mandel_render", 8192U, NULL, 2U,
&auto_renderer_task,
xPortGetCoreID() == 0 ? 1 : 0);
if (result != pdPASS) {
portENTER_CRITICAL(&auto_frame_lock);
automatic_zoom = false;
portEXIT_CRITICAL(&auto_frame_lock);
auto_renderer_task = NULL;
auto_status_text = "AUTO RENDER TASK FAILED";
ESP_LOGE(TAG, "Unable to start automatic zoom renderer task");
return false;
}
} else {
// Wake the dormant renderer after B returned the module to manual mode.
xTaskNotifyGive(auto_renderer_task);
}
auto_status_text = "AUTO RENDERING - B MANUAL";
#if 0 // Maths panel experiment: disabled.
maths_valid = false;
#endif
ESP_LOGI(TAG, "Automatic zoom started from the full Mandelbrot view");
return true;
}
static void return_to_manual_view(void)
{
portENTER_CRITICAL(&auto_frame_lock);
automatic_zoom = false;
for (unsigned index = 0U; index < MANDELBROT_AUTO_FRAME_COUNT; ++index) {
if (auto_frames[index].state == MANDELBROT_FRAME_READY) {
auto_frames[index].state = MANDELBROT_FRAME_FREE;
}
}
portEXIT_CRITICAL(&auto_frame_lock);
displayed_frame_slot = -1;
auto_status_text = "B RECEIVED - MANUAL MODE";
begin_render();
ESP_LOGI(TAG, "Automatic zoom stopped; manual viewer restored");
}
static int take_ready_auto_frame(void)
{
int slot = -1;
uint32_t oldest_sequence = UINT32_MAX;
portENTER_CRITICAL(&auto_frame_lock);
if (automatic_zoom) {
for (unsigned index = 0U; index < MANDELBROT_AUTO_FRAME_COUNT; ++index) {
if (auto_frames[index].state == MANDELBROT_FRAME_READY &&
auto_frames[index].sequence < oldest_sequence) {
oldest_sequence = auto_frames[index].sequence;
slot = (int)index;
}
}
if (slot >= 0) auto_frames[slot].state = MANDELBROT_FRAME_DISPLAYING;
}
portEXIT_CRITICAL(&auto_frame_lock);
return slot;
}
static unsigned ready_auto_frame_count(void)
{
unsigned count = 0U;
portENTER_CRITICAL(&auto_frame_lock);
for (unsigned index = 0U; index < MANDELBROT_AUTO_FRAME_COUNT; ++index) {
if (auto_frames[index].state == MANDELBROT_FRAME_READY) ++count;
}
portEXIT_CRITICAL(&auto_frame_lock);
return count;
}
static void log_auto_status(uint32_t now_ms)
{
if (auto_log_started_ms == 0U) auto_log_started_ms = now_ms;
if (now_ms - auto_log_started_ms < MANDELBROT_AUTO_LOG_INTERVAL_MS) return;
ESP_LOGI(TAG, "AUTO fps=%u render_fps=%u render=%ums ready=%u psram=%u bytes",
(unsigned)auto_displayed_frames, (unsigned)auto_rendered_frames,
(unsigned)auto_last_render_ms, ready_auto_frame_count(),
(unsigned)(MANDELBROT_AUTO_FRAME_COUNT * LCD_WIDTH * LCD_HEIGHT));
auto_displayed_frames = 0U;
auto_rendered_frames = 0U;
auto_log_started_ms = now_ms;
}
void startMandelbrot(uint32_t now_ms)
{
running = true;
select_view(0U);
last_cursor_move = now_ms;
#if 0 // Maths panel experiment: disabled.
maths_last_update = now_ms;
#endif
automatic_zoom = false;
displayed_frame_slot = -1;
(void)allocate_manual_staging_frame();
begin_render();
}
void stopMandelbrot(void)
{
running = false;
rendering = false;
frame_ready = false;
portENTER_CRITICAL(&auto_frame_lock);
automatic_zoom = false;
portEXIT_CRITICAL(&auto_frame_lock);
release_manual_staging_frame();
}
void mandelbrotAction(bool zoom, bool start_automatic, bool return_to_manual,
bool next_view, uint32_t now_ms)
{
if (!running) return;
if (return_to_manual && automaticZoomActive()) {
return_to_manual_view();
return;
}
if (start_automatic) {
auto_status_text = "C RECEIVED - STARTING AUTO";
frame_ready = true;
start_automatic_zoom();
return;
}
if (automaticZoomActive() || (!zoom && !next_view)) return;
if (next_view) {
select_view((uint8_t)((selected_view + 1U) % (sizeof(views) / sizeof(views[0]))));
} else {
const double height = view_width * (double)LCD_HEIGHT / (double)LCD_WIDTH;
const double minimum_real = centre_real - view_width * 0.5;
const double minimum_imaginary = centre_imaginary - height * 0.5;
centre_real = minimum_real + view_width * (double)(cursor_x + ZOOM_BOX_WIDTH / 2) /
(double)(LCD_WIDTH - 1U);
centre_imaginary = minimum_imaginary + height * (double)(cursor_y + ZOOM_BOX_HEIGHT / 2) /
(double)(LCD_HEIGHT - 1U);
view_width *= (double)ZOOM_BOX_WIDTH / (double)LCD_WIDTH;
++zoom_level;
}
last_cursor_move = now_ms;
begin_render();
}
void updateMandelbrot(int x_offset, int y_offset, bool joystick_valid, uint32_t now_ms)
{
if (!running) return;
#if 0 // Maths panel experiment: disabled.
// Overlay-only redraws must not copy an old pair of manual render rows.
row_count = 0U;
const bool maths_changed = update_maths_sample(now_ms);
if (maths_changed) frame_ready = true;
#endif
if (automaticZoomActive()) {
log_auto_status(now_ms);
return;
}
if (joystick_valid && now_ms - last_cursor_move >= CURSOR_REPEAT_MS) {
if (x_offset >= JOYSTICK_ENTER) cursor_x += CURSOR_STEP;
else if (x_offset <= -JOYSTICK_ENTER) cursor_x -= CURSOR_STEP;
if (y_offset >= JOYSTICK_ENTER) cursor_y -= CURSOR_STEP;
else if (y_offset <= -JOYSTICK_ENTER) cursor_y += CURSOR_STEP;
if (cursor_x < 0) cursor_x = 0;
if (cursor_x > LCD_WIDTH - ZOOM_BOX_WIDTH) cursor_x = LCD_WIDTH - ZOOM_BOX_WIDTH;
if (cursor_y < 0) cursor_y = 0;
if (cursor_y > LCD_HEIGHT - ZOOM_BOX_HEIGHT) cursor_y = LCD_HEIGHT - ZOOM_BOX_HEIGHT;
last_cursor_move = now_ms;
frame_ready = true;
}
if (!rendering) return;
rendered_start_row = next_row;
row_count = 0U;
while (row_count < ROWS_PER_UPDATE && next_row < LCD_HEIGHT) {
uint16_t *destination = rendered_rows[row_count];
if (manual_staging_frame != NULL) {
destination = manual_staging_frame + (size_t)next_row * LCD_WIDTH;
}
manual_iteration_count += render_row(next_row, destination);
++row_count;
++next_row;
}
frame_ready = frame_ready || row_count != 0U || clear_requested;
if (next_row >= LCD_HEIGHT) {
rendering = false;
// Keep the 100% screen visible for one frame before showing the fractal.
manual_reveal_pending = manual_staging_frame != NULL;
}
}
bool mandelbrotTakeFrame(void)
{
if (automaticZoomActive()) {
displayed_frame_slot = take_ready_auto_frame();
if (displayed_frame_slot >= 0) {
return true;
}
const bool ready = frame_ready;
frame_ready = false;
return ready;
}
const bool ready = frame_ready;
frame_ready = false;
return ready;
}
static void draw_manual_progress(uint16_t *frame)
{
const unsigned percent = ((unsigned)next_row * 100U) / LCD_HEIGHT;
const int bar_x = (LCD_WIDTH - MANDELBROT_PROGRESS_BAR_WIDTH) / 2;
const int bar_y = 102;
const int inner_width = MANDELBROT_PROGRESS_BAR_WIDTH - 2;
const int filled_width = (inner_width * (int)percent) / 100;
char text[32];
clear(frame, COLOR_BLACK);
drawPet2001TextCentredTransparent(frame, LCD_WIDTH / 2, 20, "MANDELBROT", COLOR_LIME, 3);
drawPet2001TextCentredTransparent(frame, LCD_WIDTH / 2, 51, "Z=Z*Z+C", COLOR_LIGHTGREEN, 2);
drawPet2001TextCentredTransparent(frame, LCD_WIDTH / 2, 76, "CALCULATING", COLOR_GREENYELLOW, 2);
draw_rect(frame, bar_x, bar_y, MANDELBROT_PROGRESS_BAR_WIDTH,
MANDELBROT_PROGRESS_BAR_HEIGHT, COLOR_CRIMSON);
if (filled_width > 0) {
fill_rect(frame, bar_x + 1, bar_y + 1, filled_width,
MANDELBROT_PROGRESS_BAR_HEIGHT - 2, COLOR_RED);
}
snprintf(text, sizeof(text), "%u%%", percent);
drawPet2001TextCentredTransparent(frame, LCD_WIDTH / 2, 119, text, COLOR_LIME, 2);
snprintf(text, sizeof(text), "ITERATIONS %lu", (unsigned long)manual_iteration_count);
drawPet2001TextCentredTransparent(frame, LCD_WIDTH / 2, 146, text, COLOR_LIGHTGREEN, 1);
}
void drawMandelbrot(uint16_t *frame)
{
if (!running || frame == NULL) return;
if (automaticZoomActive()) {
if (displayed_frame_slot < 0) {
fill_rect(frame, 0, 0, LCD_WIDTH, 12, COLOR_BLACK);
drawPet2001TextTransparent(frame, 2, 2, auto_status_text, COLOR_WHITE, 1);
return;
}
const mandelbrot_frame_t *displayed = &auto_frames[displayed_frame_slot];
const uint8_t *source = displayed->pixels;
for (size_t pixel = 0U; pixel < (size_t)LCD_WIDTH * LCD_HEIGHT; ++pixel) {
frame[pixel] = auto_palette[source[pixel]];
}
#if 0 // Maths panel experiment: disabled.
// Copy metadata before releasing the slot for the background renderer.
set_maths_sample(displayed->view, displayed->sample_real,
displayed->sample_imaginary, true);
#endif
portENTER_CRITICAL(&auto_frame_lock);
auto_frames[displayed_frame_slot].state = MANDELBROT_FRAME_FREE;
portEXIT_CRITICAL(&auto_frame_lock);
displayed_frame_slot = -1;
++auto_displayed_frames;
return;
}
#if 0 // Maths panel experiment: disabled.
if (!clear_requested && row_count == 0U && cursor_drawn &&
cursor_x == drawn_cursor_x && cursor_y == drawn_cursor_y) {
// A maths-only tick does not need to recalculate the cursor border pixels.
draw_maths_panel(frame);
return;
}
#endif
if (manual_staging_frame != NULL) {
if (rendering || manual_reveal_pending) {
draw_manual_progress(frame);
if (manual_reveal_pending) {
manual_reveal_pending = false;
frame_ready = true;
}
return;
}
memcpy(frame, manual_staging_frame, MANDELBROT_MANUAL_FRAME_BYTES);
clear_requested = false;
} else {
if (clear_requested) {
clear(frame, COLOR_BLACK);
clear_requested = false;
}
for (uint8_t row = 0U; row < row_count; ++row) {
memcpy(frame + (size_t)(rendered_start_row + row) * LCD_WIDTH,
rendered_rows[row], sizeof(rendered_rows[row]));
}
}
if (cursor_drawn) restore_cursor_border(frame, drawn_cursor_x, drawn_cursor_y);
char status[40];
snprintf(status, sizeof(status), "%s Z%u", views[selected_view].name,
(unsigned)zoom_level);
fill_rect(frame, 0, 0, 128, 10, COLOR_BLACK);
drawPet2001TextTransparent(frame, 2, 1, status, COLOR_WHITE, 1);
draw_rect(frame, cursor_x, cursor_y, ZOOM_BOX_WIDTH, ZOOM_BOX_HEIGHT, COLOR_WHITE);
drawn_cursor_x = cursor_x;
drawn_cursor_y = cursor_y;
cursor_drawn = true;
drawPet2001TextTransparent(frame, 2, 148, "A/K ZOOM C AUTO E VIEW", COLOR_WHITE, 1);
drawPet2001TextTransparent(frame, 2, 158, "B MANUAL BOOT BACK", COLOR_WHITE, 1);
if (auto_status_text != NULL) {
fill_rect(frame, 0, 12, LCD_WIDTH, 10, COLOR_BLACK);
drawPet2001TextTransparent(frame, 2, 12, auto_status_text, COLOR_WHITE, 1);
}
#if 0 // Maths panel experiment: disabled.
// Compose last so manual rows, status text and cursor restoration stay below it.
draw_maths_panel(frame);
#endif
}