From 5bc6810ea11ad7a9eb9be5e82990b4bfb2ea9ec8 Mon Sep 17 00:00:00 2001 From: ismail-yilmaz Date: Tue, 9 Jun 2026 22:59:03 +0300 Subject: [PATCH] examples: A simple Mandelbrot fractal viewer, using SIMD extensions Examples/Mandelbrot: Cosmetics examples/Mandelbrot: fix. --- examples/Mandelbrot/Mandelbrot.h | 31 +++++ examples/Mandelbrot/Mandelbrot.lay | 9 ++ examples/Mandelbrot/Mandelbrot.upp | 13 +++ examples/Mandelbrot/SIMDDemo.lay | 9 ++ examples/Mandelbrot/main.cpp | 175 +++++++++++++++++++++++++++++ 5 files changed, 237 insertions(+) create mode 100644 examples/Mandelbrot/Mandelbrot.h create mode 100644 examples/Mandelbrot/Mandelbrot.lay create mode 100644 examples/Mandelbrot/Mandelbrot.upp create mode 100644 examples/Mandelbrot/SIMDDemo.lay create mode 100644 examples/Mandelbrot/main.cpp diff --git a/examples/Mandelbrot/Mandelbrot.h b/examples/Mandelbrot/Mandelbrot.h new file mode 100644 index 000000000..c14fd54ad --- /dev/null +++ b/examples/Mandelbrot/Mandelbrot.h @@ -0,0 +1,31 @@ +#ifndef _Mandelbrot_Mandelbrot_h +#define _Mandelbrot_Mandelbrot_h + +// Run this example in release mode (-O3) + +#include + +using namespace Upp; + +#define LAYOUTFILE +#include + +class Mandelbrot : public WithMainLayout { +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 diff --git a/examples/Mandelbrot/Mandelbrot.lay b/examples/Mandelbrot/Mandelbrot.lay new file mode 100644 index 000000000..71188a8e8 --- /dev/null +++ b/examples/Mandelbrot/Mandelbrot.lay @@ -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 + diff --git a/examples/Mandelbrot/Mandelbrot.upp b/examples/Mandelbrot/Mandelbrot.upp new file mode 100644 index 000000000..738c0cf14 --- /dev/null +++ b/examples/Mandelbrot/Mandelbrot.upp @@ -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"; + diff --git a/examples/Mandelbrot/SIMDDemo.lay b/examples/Mandelbrot/SIMDDemo.lay new file mode 100644 index 000000000..4b84e39ba --- /dev/null +++ b/examples/Mandelbrot/SIMDDemo.lay @@ -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 + diff --git a/examples/Mandelbrot/main.cpp b/examples/Mandelbrot/main.cpp new file mode 100644 index 000000000..c2c15d529 --- /dev/null +++ b/examples/Mandelbrot/main.cpp @@ -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(); +} \ No newline at end of file