xref: /haiku/src/apps/mandelbrot/FractalEngine.cpp (revision 99158cceddacb52ebe30708c4e6a27b4fb711c8f)
1 /*
2  * Copyright 2016, Haiku, Inc. All rights reserved.
3  * Distributed under the terms of the MIT license.
4  *
5  * Authors:
6  *		Augustin Cavalier <waddlesplash>
7  *		kerwizzy
8  */
9 #include "FractalEngine.h"
10 
11 #include <algorithm>
12 #include <math.h>
13 
14 #include <Bitmap.h>
15 #include <String.h>
16 
17 #include "Colorsets.h"
18 
19 
20 // #define TRACE_MANDELBROT_ENGINE
21 #ifdef TRACE_MANDELBROT_ENGINE
22 #	include <stdio.h>
23 #	define TRACE(x...) printf(x)
24 #else
25 #	define TRACE(x...)
26 #endif
27 
28 
29 FractalEngine::FractalEngine(BHandler* parent, BLooper* looper)
30 	:
31 	BLooper("FractalEngine"),
32 	fWidth(0), fHeight(0),
33 	fRenderBuffer(NULL),
34 	fRenderBufferLen(0),
35 	fMessenger(parent, looper),
36 	fThreadsRendering(0),
37 	fIterations(1024),
38 	fColorset(Colorset_Royal)
39 {
40 	fDoSet = &FractalEngine::DoSet_Mandelbrot;
41 
42 	fRenderSem = create_sem(0, "RenderSem");
43 
44 	system_info info;
45 	get_system_info(&info);
46 	fThreadCount = info.cpu_count;
47 
48 	if (fThreadCount > MAX_RENDER_THREADS)
49 		fThreadCount = MAX_RENDER_THREADS;
50 
51 	for (uint8 i = 0; i < fThreadCount; i++) {
52 		fRestartRenderThread[i] = false;
53 		fRenderThreadRunning[i] = false;
54 		fRenderThreads[i] = spawn_thread(&FractalEngine::RenderThread,
55 			BString().SetToFormat("RenderThread%d", i).String(),
56 			B_NORMAL_PRIORITY, this);
57 		resume_thread(fRenderThreads[i]);
58 	}
59 }
60 
61 
62 FractalEngine::~FractalEngine()
63 {
64 }
65 
66 
67 void FractalEngine::MessageReceived(BMessage* msg)
68 {
69 	switch (msg->what) {
70 	case MSG_CHANGE_SET:
71 		switch (msg->GetUInt8("set", 0)) {
72 		case 0: fDoSet = &FractalEngine::DoSet_Mandelbrot; break;
73 		case 1: fDoSet = &FractalEngine::DoSet_BurningShip; break;
74 		case 2: fDoSet = &FractalEngine::DoSet_Tricorn; break;
75 		case 3: fDoSet = &FractalEngine::DoSet_Julia; break;
76 		case 4: fDoSet = &FractalEngine::DoSet_OrbitTrap; break;
77 		case 5: fDoSet = &FractalEngine::DoSet_Multibrot; break;
78 		}
79 		break;
80 	case MSG_SET_PALETTE:
81 		switch (msg->GetUInt8("palette", 0)) {
82 		case 0: fColorset = Colorset_Royal; break;
83 		case 1: fColorset = Colorset_DeepFrost; break;
84 		case 2: fColorset = Colorset_Frost; break;
85 		case 3: fColorset = Colorset_Fire; break;
86 		case 4: fColorset = Colorset_Midnight; break;
87 		case 5: fColorset = Colorset_Grassland; break;
88 		case 6: fColorset = Colorset_Lightning; break;
89 		case 7: fColorset = Colorset_Spring; break;
90 		case 8: fColorset = Colorset_HighContrast; break;
91 		}
92 		break;
93 	case MSG_SET_ITERATIONS:
94 		fIterations = msg->GetUInt16("iterations", 0);
95 		break;
96 
97 	case MSG_RESIZE: {
98 		delete fRenderBuffer;
99 		fRenderBuffer = NULL;
100 
101 		fWidth = msg->GetUInt16("width", 320);
102 		fHeight = msg->GetUInt16("height", 240);
103 
104 		TRACE("Creating new buffer. width %u height %u\n", fWidth, fHeight);
105 
106 		fRenderBufferLen = fWidth * fHeight * 3;
107 		fRenderBuffer = new uint8[fRenderBufferLen];
108 		memset(fRenderBuffer, 0, fRenderBufferLen);
109 
110 		BMessage message(MSG_BUFFER_CREATED);
111 		fMessenger.SendMessage(&message);
112 		break;
113 	}
114 	case MSG_RENDER: {
115 		TRACE("Got MSG_RENDER. fThreadsRendering = %u\n",fThreadsRendering);
116 		Render(msg->GetDouble("locationX", 0), msg->GetDouble("locationY", 0),
117 			msg->GetDouble("size", 0.005));
118 		break;
119 	}
120 	case MSG_THREAD_RENDER_COMPLETE: {
121 		TRACE("Got MSG_THREAD_RENDER_COMPLETE for thread %d\n",
122 			msg->GetUInt8("thread", 0));
123 
124 		fThreadsRendering--;
125 		TRACE("Set fRendering = %d\n", fThreadsRendering);
126 		if (!fThreadsRendering) {
127 			TRACE("Done rendering!\n");
128 			BMessage message(MSG_RENDER_COMPLETE);
129 			fMessenger.SendMessage(&message);
130 		}
131 		break;
132 	}
133 
134 	default:
135 		BLooper::MessageReceived(msg);
136 		break;
137 	}
138 }
139 
140 
141 void FractalEngine::WriteToBitmap(BBitmap* bitmap)
142 {
143 	bitmap->ImportBits(fRenderBuffer,
144 		fRenderBufferLen, fWidth * 3, 0,
145 		B_RGB24);
146 }
147 
148 
149 void FractalEngine::Render(double locationX, double locationY, double size)
150 {
151 	fLocationX = locationX;
152 	fLocationY = locationY;
153 	fSize = size;
154 
155 	TRACE("Location: %g;%g;%g\n", fLocationX, fLocationY, fSize);
156 
157 	for (uint8 i = 0; i < fThreadCount; i++) {
158 		if (fRenderThreadRunning[i])
159 			fRestartRenderThread[i] = true;
160 		else
161 			release_sem(fRenderSem);
162 		fRenderThreadRunning[i] = true;
163 	}
164 
165 	fThreadsRendering = fThreadCount;
166 	TRACE("Set fRendering = %d\n", fThreadsRendering);
167 }
168 
169 
170 status_t FractalEngine::RenderThread(void* data)
171 {
172 	FractalEngine* engine = static_cast<FractalEngine*>(data);
173 	thread_id self = find_thread(NULL);
174 	uint8 threadNum = 0;
175 	for (uint8 i = 0; i < engine->fThreadCount; i++) {
176 		if (engine->fRenderThreads[i] == self) {
177 			threadNum = i;
178 			break;
179 		}
180 	}
181 
182 	bool restarting = false;
183 	while (true) {
184 		TRACE("Thread %d starting another render pass\n", threadNum);
185 
186 		if (!restarting) {
187 			TRACE("Thread %d awaiting semaphore...\n", threadNum);
188 			acquire_sem(engine->fRenderSem);
189 			TRACE("Thread %d got semaphore!\n", threadNum);
190 		} else {
191 			TRACE("Thread %d setting restart = false\n", threadNum);
192 			restarting = false;
193 		}
194 
195 
196 		uint16 width = engine->fWidth;
197 		uint16 height = engine->fHeight;
198 		uint16 halfHeight = height / 2;
199 		uint16 threadWidth = width / engine->fThreadCount;
200 
201 		uint16 startX = threadWidth * threadNum;
202 		uint16 endX = threadWidth * (threadNum + 1);
203 
204 		for (uint16 y = 0; y<halfHeight; y++) {
205 			for (uint16 x = startX; x<endX; x++) {
206 				engine->RenderPixel(x, halfHeight + y);
207 					// max y to RenderPixel will be
208 					// floor(height/2)*2 = height-height%2.
209 					// Thus there will be a line of blank pixels if height
210 					// is not even. Is this a problem?
211 				engine->RenderPixel(x, halfHeight - y - 1);
212 					// min y to RenderPixel will be
213 					// halfHeight-(halfHeight-1)-1 = 0
214 			}
215 
216 			if (engine->fRestartRenderThread[threadNum]) {
217 				TRACE("Thread %d Got restart message\n", threadNum);
218 
219 				engine->fRestartRenderThread[threadNum] = false;
220 				restarting = true;
221 				break; // Restart the loop to update width, height, etc.
222 			}
223 		}
224 
225 		if (!restarting) {
226 			engine->fRenderThreadRunning[threadNum] = false;
227 			BMessage message(FractalEngine::MSG_THREAD_RENDER_COMPLETE);
228 			message.AddUInt8("thread", threadNum);
229 			engine->PostMessage(&message);
230 		}
231 	}
232 	return B_OK;
233 }
234 
235 
236 void FractalEngine::RenderPixel(uint32 x, uint32 y)
237 {
238 	double real = (x * fSize + fLocationX) - (fWidth / 2 * fSize);
239 	double imaginary = (y * -(fSize) + fLocationY) - (fHeight / 2 * -(fSize));
240 
241 	int32 iterToEscape = (this->*fDoSet)(real, imaginary);
242 	uint16 loc = 0;
243 	if (iterToEscape == -1)
244 		// Didn't escape.
245 		loc = 999;
246 	else
247 		loc = 998 - (iterToEscape % 999);
248 
249 	uint32 offsetBase = fWidth * y * 3 + x * 3;
250 	loc *= 3;
251 	fRenderBuffer[offsetBase + 2] = fColorset[loc + 0]; // fRenderBuffer is BGR
252 	fRenderBuffer[offsetBase + 1] = fColorset[loc + 1];
253 	fRenderBuffer[offsetBase + 0] = fColorset[loc + 2];
254 }
255 
256 
257 // Magic numbers & other general constants
258 const double gJuliaA = 0;
259 const double gJuliaB = 1;
260 
261 const uint8 gEscapeHorizon = 4;
262 
263 
264 int32 FractalEngine::DoSet_Mandelbrot(double real, double imaginary)
265 {
266 	double zReal = 0;
267 	double zImaginary = 0;
268 
269 	for (int32 i = 0; i < fIterations; i++) {
270 		double zRealSq = zReal * zReal;
271 		double zImaginarySq = zImaginary * zImaginary;
272 		double nzReal = (zRealSq + (-1 * zImaginarySq));
273 
274 		zImaginary = 2 * (zReal * zImaginary);
275 		zReal = nzReal;
276 
277 		zReal += real;
278 		zImaginary += imaginary;
279 
280 		// If it is outside the 2 unit circle...
281 		if ((zRealSq + zImaginarySq) > gEscapeHorizon)
282 			return i; // stop it from running longer
283 	}
284 	return -1;
285 }
286 
287 
288 int32 FractalEngine::DoSet_BurningShip(double real, double imaginary)
289 {
290 	double zReal = 0;
291 	double zImaginary = 0;
292 
293 	// It looks "upside down" otherwise.
294 	imaginary = -imaginary;
295 
296 	for (int32 i = 0; i < fIterations; i++) {
297 		zReal = fabs(zReal);
298 		zImaginary = fabs(zImaginary);
299 
300 		double zRealSq = zReal * zReal;
301 		double zImaginarySq = zImaginary * zImaginary;
302 		double nzReal = (zRealSq + (-1 * zImaginarySq));
303 
304 		zImaginary = 2 * (zReal * zImaginary);
305 		zReal = nzReal;
306 
307 		zReal += real;
308 		zImaginary += imaginary;
309 
310 		// If it is outside the 2 unit circle...
311 		if ((zRealSq + zImaginarySq) > gEscapeHorizon)
312 			return i; // stop it from running longer
313 	}
314 	return -1;
315 }
316 
317 
318 int32 FractalEngine::DoSet_Tricorn(double real, double imaginary)
319 {
320 	double zReal = 0;
321 	double zImaginary = 0;
322 
323 	real = -real;
324 
325 	for (int32 i = 0; i < fIterations; i++) {
326 		double znRe = zImaginary * -1;
327 		zImaginary = zReal * -1;
328 		zReal = znRe; // Swap the real and complex parts each time.
329 
330 		double zRealSq = zReal * zReal;
331 		double zImaginarySq = zImaginary * zImaginary;
332 		double nzReal = (zRealSq + (-1 * zImaginarySq));
333 
334 		zImaginary = 2 * (zReal * zImaginary);
335 		zReal = nzReal;
336 
337 		zReal += real;
338 		zImaginary += imaginary;
339 
340 		// If it is outside the 2 unit circle...
341 		if ((zRealSq + zImaginarySq) > gEscapeHorizon)
342 			return i; // stop it from running longer
343 	}
344 	return -1;
345 }
346 
347 
348 int32 FractalEngine::DoSet_Julia(double real, double imaginary)
349 {
350 	double zReal = real;
351 	double zImaginary = imaginary;
352 
353 	double muRe = gJuliaA;
354 	double muIm = gJuliaB;
355 
356 	for (int32 i = 0; i < fIterations; i++) {
357 		double zRealSq = zReal * zReal;
358 		double zImaginarySq = zImaginary * zImaginary;
359 		double nzReal = (zRealSq + (-1 * (zImaginarySq)));
360 
361 		zImaginary = 2 * (zReal * zImaginary);
362 		zReal = nzReal;
363 
364 		zReal += muRe;
365 		zImaginary += muIm;
366 
367 		// If it is outside the 2 unit circle...
368 		if ((zRealSq + zImaginarySq) > gEscapeHorizon)
369 			return i; // stop it from running longer
370 	}
371 	return -1;
372 }
373 
374 
375 int32 FractalEngine::DoSet_OrbitTrap(double real, double imaginary)
376 {
377 	double zReal = 0;
378 	double zImaginary = 0;
379 
380 	double closest = 10000000;
381 	double distance = 0;
382 	double lineDist = 0;
383 
384 	for (int32 i = 0; i < fIterations; i++) {
385 		double zRealSq = zReal * zReal;
386 		double zImaginarySq = zImaginary * zImaginary;
387 		double nzReal = (zRealSq + (-1 * zImaginarySq));
388 
389 		zImaginary = 2 * (zReal * zImaginary);
390 		zReal = nzReal;
391 
392 		zReal += real;
393 		zImaginary += imaginary;
394 
395 		distance = sqrt(zRealSq + zImaginarySq);
396 		lineDist = fabs(zReal + zImaginary);
397 
398 		// If it is closer than ever before...
399 		if (lineDist < closest)
400 			closest = lineDist;
401 
402 		if (distance > gEscapeHorizon)
403 			return static_cast<int32>(floor(4 * log(4 / closest)));
404 	}
405 	return static_cast<int32>(floor(4 * log(4 / closest)));
406 }
407 
408 
409 int32 FractalEngine::DoSet_Multibrot(double real, double imaginary)
410 {
411 	double zReal = 0;
412 	double zImaginary = 0;
413 
414 	for (int32 i = 0; i < fIterations; i++) {
415 		double zRealSq = zReal * zReal;
416 		double zImaginarySq = zImaginary * zImaginary;
417 		double nzReal = (zRealSq * zReal - 3 * zReal * zImaginarySq);
418 
419 		zImaginary = 3 * (zRealSq * zImaginary) - (zImaginarySq * zImaginary);
420 
421 		zReal = nzReal;
422 		zReal += real;
423 		zImaginary += imaginary;
424 
425 		// If it is outside the 2 unit circle...
426 		if ((zRealSq + zImaginarySq) > gEscapeHorizon)
427 			return i; // stop it from running longer
428 	}
429 	return -1;
430 }
431