examples: A simple Mandelbrot fractal viewer, using SIMD extensions

Examples/Mandelbrot: Cosmetics

examples/Mandelbrot: fix.
This commit is contained in:
ismail-yilmaz 2026-06-09 22:59:03 +03:00
parent d7b55c68be
commit 5bc6810ea1
5 changed files with 237 additions and 0 deletions

View file

@ -0,0 +1,31 @@
#ifndef _Mandelbrot_Mandelbrot_h
#define _Mandelbrot_Mandelbrot_h
// Run this example in release mode (-O3)
#include <CtrlLib/CtrlLib.h>
using namespace Upp;
#define LAYOUTFILE <Mandelbrot/Mandelbrot.lay>
#include <CtrlCore/lay.h>
class Mandelbrot : public WithMainLayout<TopWindow> {
public:
Mandelbrot();
void Reset();
void LeftDown(Point pt, dword keyflags) override;
void LeftUp(Point pt, dword keyflags) override;
void MouseMove(Point pt, dword keyflags) override;
void MouseWheel(Point pt, int zdelta, dword keyflags) override;
private:
Image DrawScalar(int itermax);
Image DrawSIMD(int itermax);
void Update();
Rectf crect = { -2.0f, -1.5f, 1.0f, 1.5f };
bool rendering = false;
Point panpos = { -1, -1 };
};
#endif

View file

@ -0,0 +1,9 @@
LAYOUT(MainLayout, 584, 372)
ITEM(Upp::ImageCtrl, view, HSizePosZ(4, 4).VSizePosZ(4, 60))
ITEM(Upp::Button, render, SetLabel(t_("Reset")).RightPosZ(4, 148).BottomPosZ(5, 23))
ITEM(Upp::Option, simd, SetLabel(t_("Use SIMD")).LeftPosZ(264, 164).BottomPosZ(8, 16))
ITEM(Upp::StaticText, timing, SetAlign(Upp::ALIGN_CENTER).SetFrame(Upp::ThinInsetFrame()).HSizePosZ(4, 4).BottomPosZ(37, 19))
ITEM(Upp::DropList, maxiter, LeftPosZ(96, 160).BottomPosZ(6, 19))
ITEM(Upp::Label, dv___5, SetLabel(t_("Max. iterations")).LeftPosZ(4, 88).BottomPosZ(8, 16))
END_LAYOUT

View file

@ -0,0 +1,13 @@
description "A simple Mandelbrot fractal viewer, using SIMD extensions when available\377";
uses
CtrlLib;
file
Mandelbrot.h,
main.cpp,
Mandelbrot.lay;
mainconfig
"" = "GUI";

View file

@ -0,0 +1,9 @@
LAYOUT(SIMDDemoLayout, 584, 372)
ITEM(Upp::ImageCtrl, view, HSizePosZ(4, 4).VSizePosZ(4, 60))
ITEM(Upp::Button, render, SetLabel(t_("Reset")).RightPosZ(4, 148).BottomPosZ(5, 23))
ITEM(Upp::Option, simd, SetLabel(t_("Use SIMD")).LeftPosZ(264, 164).BottomPosZ(8, 16))
ITEM(Upp::StaticText, timing, SetAlign(Upp::ALIGN_CENTER).SetFrame(Upp::ThinInsetFrame()).HSizePosZ(4, 4).BottomPosZ(37, 19))
ITEM(Upp::DropList, maxiter, LeftPosZ(96, 160).BottomPosZ(6, 19))
ITEM(Upp::Label, dv___5, SetLabel(t_("Max. iterations")).LeftPosZ(4, 88).BottomPosZ(8, 16))
END_LAYOUT

View file

@ -0,0 +1,175 @@
#include "Mandelbrot.h"
// Run this example in release mode (-O3)
force_inline RGBA GetFractalColor(int iter, int maxiter)
{
float hue = (float) iter / maxiter;
float val = (iter < maxiter) ? 1.0f : 0.0f;
return HsvColorf(hue, 1.0f, val);
}
Mandelbrot::Mandelbrot()
{
CtrlLayout(*this, "Mandelbrot Viewer");
#ifndef CPU_SIMD
simd.Hide();
#endif
for(int i : { 250, 500, 1000 })
maxiter.Add(i);
maxiter.SetIndex(1);
auto cb = [this] { Update(); };
render << [this] { Reset(); Update(); };
maxiter << cb;
simd << cb;
render.WhenAction();
view.IgnoreMouse();
}
void Mandelbrot::Reset()
{
rendering = true;
crect = { -2.0f, -1.5f, 1.0f, 1.5f };
maxiter.SetIndex(1);
simd = true;
rendering = false;
}
void Mandelbrot::Update()
{
if(rendering)
return;
rendering = true;
TimeStop ts;
Image img = ~simd ? DrawSIMD(~maxiter) : DrawScalar(~maxiter);
timing.SetText(Format("Generated in %02.0f milliseconds...", ts.Elapsed() / 1000.0));
view.SetImage(img);
rendering = false;
}
Image Mandelbrot::DrawScalar(int itermax)
{
const Size sz = view.GetSize();
const float dx = crect.GetWidth() / sz.cx;
const float dy = crect.GetHeight() / sz.cy;
ImageBuffer ib(sz);
RGBA* pix = ib;
for(int py = 0; py < sz.cy; py++) {
const float cy = crect.top + py * dy;
for(int px = 0; px < sz.cx; px++) {
float cx = crect.left + px * dx;
float zx = 0.0f, zy = 0.0f;
int iter = 0;
for(int k = 0; k < itermax; k++) {
float zx2 = zx * zx;
float zy2 = zy * zy;
if(zx2 + zy2 >= 4.0f)
break;
float zxy = zx * zy;
zx = zx2 - zy2 + cx;
zy = zxy + zxy + cy;
iter++;
}
pix[py * sz.cx + px] = GetFractalColor(iter, itermax);
}
ProcessEvents();
}
return ib;
}
Image Mandelbrot::DrawSIMD(int itermax)
{
#ifdef CPU_SIMD
const Size sz = view.GetSize();
const float dx = crect.GetWidth() / sz.cx;
const float dy = crect.GetHeight() / sz.cy;
ImageBuffer ib(sz);
RGBA* pix = ib;
const int block = 4;
const float offsets[block] = { 0.0f, dx, 2.0f * dx, 3.0f * dx };
f32x4 xoffsets; xoffsets.Load(offsets);
for(int py = 0; py < sz.cy; py++) {
f32x4 cy4 = f32all(crect.top + py * dy);
for(int px = 0; px < sz.cx; px += block) {
f32x4 cx4 = f32all(crect.left + px * dx) + xoffsets;
f32x4 zx = f32all(0.0f), zy = f32all(0.0f);
i32x4 iter = i32all(0);
for(int k = 0; k < itermax; k++) {
f32x4 zx2 = zx * zx;
f32x4 zy2 = zy * zy;
f32x4 mag2 = zx2 + zy2;
f32x4 mask = mag2 < f32all(4.0f);
if(!AnyTrue(mask))
break;
f32x4 zxy = zx * zy;
zx = zx2 - zy2 + cx4;
zy = zxy + zxy + cy4;
iter = IncrementIf(iter, mask);
}
alignas(16) int tmp[block];
iter.Store(tmp);
for(int i = 0; i < block; i++) {
if(px + i >= sz.cx) break;
pix[py * sz.cx + px + i] = GetFractalColor(tmp[i], itermax);
}
}
ProcessEvents();
}
return ib;
#else
return DrawScalar(itermax);
#endif
}
void Mandelbrot::LeftDown(Point pt, dword keyflags)
{
if(!rendering) {
panpos = pt;
SetCapture();
}
}
void Mandelbrot::LeftUp(Point pt, dword keyflags)
{
if(panpos.x != -1) {
panpos = Point(-1, -1);
ReleaseCapture();
}
}
void Mandelbrot::MouseMove(Point pt, dword keyflags)
{
if(rendering || panpos.x == -1)
return;
const Size sz = view.GetSize();
const Sizef csz = crect.GetSize();
const double dx = (double)(pt.x - panpos.x) / sz.cx * csz.cx;
const double dy = (double)(pt.y - panpos.y) / sz.cy * csz.cy;
crect.Offset((float) -dx, (float) -dy);
panpos = pt;
Update();
}
void Mandelbrot::MouseWheel(Point pt, int zdelta, dword keyflags)
{
if(rendering)
return;
const Size sz = view.GetSize();
const double scale = (zdelta > 0) ? 0.8 : 1.25;
const Sizef csz = crect.GetSize();
crect = Rectf(crect.TopLeft() + (Pointf(pt) / Sizef(sz)) * csz * (1.0 - scale), csz * scale);
Update();
}
GUI_APP_MAIN
{
Mandelbrot().Run();
}