SDL2绘制曼德博集合时窗口无响应,已处理事件仍未解决
问题描述
尝试用SDL2可视化曼德博集合,编写了如下C++程序:
#include <numeric> #include <iostream> #include <complex> #include <process.h> double scale_factor = 1000; SDL_Window *window = nullptr; SDL_Renderer *renderer = nullptr; int is_in_mandelbrot_set(std::complex<double> c) { std::complex<double> z(0, 0); for (int i = 0; i < 200; i++) { z = std::pow(z, 2) + c; if (std::norm(z) > 10) { return i; }; } return 0; } float lerp(float a, float b, float f) { return (a * (1.0 - f)) + (b * f); } int main(int argv, char **args) { SDL_CreateWindowAndRenderer(1000, 1000, 0, &window, &renderer); SDL_RenderSetScale(renderer, 2, 2); for (double x = 0.0; x < 1.0; x += 1 / scale_factor) { for (double y = 0.0; y < 1.0; y += 1 / scale_factor) { double loc_x = lerp(-2.0, 2.0, x); double loc_y = lerp(-2.0, 2.0, y); int iters = is_in_mandelbrot_set(std::complex<double>(loc_x, loc_y)); if (iters == 0) { SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); SDL_RenderDrawPointF(renderer, loc_x * 100 + 200, loc_y * 100 + 200); } else { SDL_SetRenderDrawColor( renderer, 4 * iters % 255, 4 * iters % 255, 4 * iters % 255, 255); SDL_RenderDrawPointF(renderer, loc_x * 100 + 200, loc_y * 100 + 200); } } } bool quit = false; while (!quit) { SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: quit = true; exit; } } SDL_RenderPresent(renderer); } }
目前仅通过将scale_factor降至200左右可避免窗口无响应,但会大幅损失画质。推测绘制过程中事件循环未执行,尝试新增线程也未解决问题,恳请帮助解决。
解决方案
核心问题分析
你的代码把所有像素的计算和绘制放在主事件循环之前,这会导致主线程被长时间阻塞:当scale_factor=1000时,要处理1000*1000=100万像素的曼德博计算,这个过程会持续数秒甚至更久,期间SDL无法处理任何窗口事件(比如关闭窗口、系统的窗口重绘请求),所以系统会判定窗口无响应。
另外,SDL渲染器不是线程安全的,如果在子线程中调用SDL_RenderDrawPointF等渲染函数,会导致不可预期的错误,这也是新增线程无效的原因。
具体修复步骤
- 将绘制任务拆分到主事件循环中:每次事件循环只绘制一行或几行像素,这样既能逐步完成渲染,又能保证事件被及时处理。
- 优化曼德博计算效率:用
z * z替代std::pow(z, 2),后者是通用幂函数,对于平方运算来说直接乘法效率高得多。 - 修正坐标映射逻辑:直接将窗口坐标线性映射到曼德博集合的坐标范围(-2~2),避免浮点偏移错误,同时使用整数坐标的
SDL_RenderDrawPoint提升绘制速度。 - 修复事件处理的语法错误:
exit是无效语句,应该使用SDL_Quit()并退出程序。
修改后的代码
#include <iostream> #include <complex> #include <SDL.h> const int WINDOW_WIDTH = 1000; const int WINDOW_HEIGHT = 1000; const int MAX_ITER = 200; SDL_Window *window = nullptr; SDL_Renderer *renderer = nullptr; // 优化曼德博集合计算:用z*z替代std::pow(z,2) int is_in_mandelbrot_set(std::complex<double> c) { std::complex<double> z(0, 0); for (int i = 1; i <= MAX_ITER; ++i) { z = z * z + c; if (std::norm(z) > 4.0) // 通常用4作为逃逸阈值,比10更合理 { return i; } } return 0; } int main(int argc, char **args) { SDL_Init(SDL_INIT_VIDEO); SDL_CreateWindowAndRenderer(WINDOW_WIDTH, WINDOW_HEIGHT, 0, &window, &renderer); SDL_SetWindowTitle(window, "Mandelbrot Set"); // 记录当前绘制的行,逐步渲染 int current_y = 0; bool quit = false; while (!quit) { // 处理事件,保证窗口响应 SDL_Event event; while (SDL_PollEvent(&event)) { if (event.type == SDL_QUIT) { quit = true; } } // 每次循环绘制10行,平衡渲染速度和响应性 const int batch_size = 10; for (int y = current_y; y < current_y + batch_size && y < WINDOW_HEIGHT; ++y) { for (int x = 0; x < WINDOW_WIDTH; ++x) { // 将窗口坐标映射到曼德博集合的坐标范围(-2.0~2.0) double real = (static_cast<double>(x) / WINDOW_WIDTH) * 4.0 - 2.0; double imag = (static_cast<double>(y) / WINDOW_HEIGHT) * 4.0 - 2.0; std::complex<double> c(real, imag); int iters = is_in_mandelbrot_set(c); if (iters == 0) { SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); // 集合内的点用黑色 } else { // 生成渐变灰度,避免单一模运算的生硬颜色 Uint8 brightness = static_cast<Uint8>(255 * (iters / static_cast<double>(MAX_ITER))); SDL_SetRenderDrawColor(renderer, brightness, brightness, brightness, 255); } SDL_RenderDrawPoint(renderer, x, y); } } current_y += batch_size; // 更新画面 SDL_RenderPresent(renderer); // 所有行绘制完成后,保持事件循环 if (current_y >= WINDOW_HEIGHT) { current_y = WINDOW_HEIGHT; // 停止继续绘制 } } // 清理资源 SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); return 0; }
额外优化建议
- 如果想要更快的渲染速度,可以使用SDL纹理(
SDL_Texture)来批量更新像素,而不是逐点绘制。 - 可以添加交互功能,比如鼠标拖拽平移、滚轮缩放,提升可视化体验。
- 对于更高精度的需求,可以使用
long double替代double,但会增加计算时间。
内容的提问来源于stack exchange,提问作者SBECK1
相关产品推荐
相关产品推荐

