xref: /haiku/src/apps/mandelbrot/FractalEngine.cpp (revision d1f885b435e9892ac028f4be2b80536b9dd37413)
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 	fSubsampling(2),
36 	fMessenger(parent, looper),
37 	fIterations(1024),
38 	fColorset(Colorset_Royal)
39 {
40 	fDoSet = &FractalEngine::DoSet_Mandelbrot;
41 
42 	fRenderSem = create_sem(0, "RenderSem");
43 	fRenderStoppedSem = create_sem(0, "RenderStopped");
44 
45 	system_info info;
46 	get_system_info(&info);
47 	fThreadCount = info.cpu_count;
48 
49 	if (fThreadCount > MAX_RENDER_THREADS)
50 		fThreadCount = MAX_RENDER_THREADS;
51 
52 	for (uint8 i = 0; i < fThreadCount; i++) {
53 		fRenderThreads[i] = spawn_thread(&FractalEngine::RenderThread,
54 			BString().SetToFormat("RenderThread%d", i).String(),
55 			B_NORMAL_PRIORITY, this);
56 		resume_thread(fRenderThreads[i]);
57 	}
58 }
59 
60 
61 FractalEngine::~FractalEngine()
62 {
63 	delete_sem(fRenderSem);
64 	delete_sem(fRenderStoppedSem);
65 }
66 
67 
68 void FractalEngine::MessageReceived(BMessage* msg)
69 {
70 	switch (msg->what) {
71 	case MSG_CHANGE_SET:
72 		switch (msg->GetUInt8("set", 0)) {
73 		case 0: fDoSet = &FractalEngine::DoSet_Mandelbrot; break;
74 		case 1: fDoSet = &FractalEngine::DoSet_BurningShip; break;
75 		case 2: fDoSet = &FractalEngine::DoSet_Tricorn; break;
76 		case 3: fDoSet = &FractalEngine::DoSet_Julia; break;
77 		case 4: fDoSet = &FractalEngine::DoSet_OrbitTrap; break;
78 		case 5: fDoSet = &FractalEngine::DoSet_Multibrot; break;
79 		}
80 		break;
81 	case MSG_SET_PALETTE:
82 		switch (msg->GetUInt8("palette", 0)) {
83 		case 0: fColorset = Colorset_Royal; break;
84 		case 1: fColorset = Colorset_DeepFrost; break;
85 		case 2: fColorset = Colorset_Frost; break;
86 		case 3: fColorset = Colorset_Fire; break;
87 		case 4: fColorset = Colorset_Midnight; break;
88 		case 5: fColorset = Colorset_Grassland; break;
89 		case 6: fColorset = Colorset_Lightning; break;
90 		case 7: fColorset = Colorset_Spring; break;
91 		case 8: fColorset = Colorset_HighContrast; break;
92 		}
93 		break;
94 	case MSG_SET_ITERATIONS:
95 		fIterations = msg->GetUInt16("iterations", 0);
96 		break;
97 	case MSG_SET_SUBSAMPLING:
98 		fSubsampling = msg->GetUInt8("subsampling", 1);
99 		break;
100 
101 	case MSG_RESIZE: {
102 		TRACE("Got MSG_RESIZE threads rendering\n");
103 		if (fStopRender) {
104 			// Will be true throughout this whole handler. Set false at the end
105 			TRACE("Breaking out of MSG_RESIZE handler\n");
106 			break;
107 		}
108 		StopRender();
109 
110 		delete fRenderBuffer;
111 		fRenderBuffer = NULL;
112 
113 		fWidth = msg->GetUInt16("width", 320);
114 		fHeight = msg->GetUInt16("height", 240);
115 
116 		TRACE("Creating new buffer. width %u height %u\n", fWidth, fHeight);
117 		fRenderBufferLen = fWidth * fHeight * 3;
118 		fRenderBuffer = new uint8[fRenderBufferLen];
119 		memset(fRenderBuffer, 0, fRenderBufferLen);
120 
121 		BMessage message(MSG_BUFFER_CREATED);
122 		fMessenger.SendMessage(&message);
123 
124 		fStopRender = false;
125 		break;
126 	}
127 	case MSG_RENDER: {
128 		TRACE("Got MSG_RENDER.\n");
129 		StopRender();
130 		fStopRender = false;
131 		Render(msg->GetDouble("locationX", 0), msg->GetDouble("locationY", 0),
132 			msg->GetDouble("size", 0.005));
133 		break;
134 	}
135 	case MSG_THREAD_RENDER_COMPLETE: {
136 		TRACE("Got MSG_THREAD_RENDER_COMPLETE for thread %d\n",
137 			msg->GetUInt8("thread", 0));
138 
139 		int32 threadsStopped;
140 		get_sem_count(fRenderStoppedSem, &threadsStopped);
141 		if (threadsStopped == fThreadCount) {
142 			TRACE("Done rendering!\n");
143 			BMessage message(MSG_RENDER_COMPLETE);
144 			fMessenger.SendMessage(&message);
145 		}
146 		break;
147 	}
148 
149 	default:
150 		BLooper::MessageReceived(msg);
151 		break;
152 	}
153 }
154 
155 
156 void FractalEngine::WriteToBitmap(BBitmap* bitmap)
157 {
158 	bitmap->ImportBits(fRenderBuffer,
159 		fRenderBufferLen, fWidth * 3, 0,
160 		B_RGB24);
161 }
162 
163 
164 void FractalEngine::StopRender()
165 {
166 	if (fRenderStopped)
167 		return;
168 	fRenderStopped = true;
169 		// true if another call to StopRender() won't work properly because
170 		// the fRenderStoppedSem semaphores have already been acquired.
171 	TRACE("Stopping render...\n");
172 	fStopRender = true;
173 	for (uint i = 0; i < fThreadCount; i++)
174 		acquire_sem(fRenderStoppedSem);
175 
176 	TRACE("Render stopped.\n");
177 		// note that fStopRender is NOT set to false at the end here.
178 		// This is to allow the message handlers to use this variable to
179 		// block duplication of stuff.
180 }
181 
182 
183 void FractalEngine::Render(double locationX, double locationY, double size)
184 {
185 	fRenderStopped = false;
186 		// This means that future Render calls will need to call stop render
187 	if (fStopRender)
188 		debugger("Error: Render shouldn't be called while fStopRender = true\n");
189 
190 	fLocationX = locationX;
191 	fLocationY = locationY;
192 	fSize = size;
193 
194 	TRACE("Location: %g;%g;%g\n", fLocationX, fLocationY, fSize);
195 
196 	for (uint8 i = 0; i < fThreadCount; i++) {
197 		release_sem(fRenderSem);
198 	}
199 }
200 
201 status_t FractalEngine::RenderThread(void* data)
202 {
203 	FractalEngine* engine = static_cast<FractalEngine*>(data);
204 	thread_id self = find_thread(NULL);
205 	uint8 threadNum = 0;
206 	for (uint8 i = 0; i < engine->fThreadCount; i++) {
207 		if (engine->fRenderThreads[i] == self) {
208 			threadNum = i;
209 			break;
210 		}
211 	}
212 
213 	while (true) {
214 		release_sem(engine->fRenderStoppedSem);
215 
216 		TRACE("Thread %d awaiting semaphore...\n", threadNum);
217 		acquire_sem(engine->fRenderSem);
218 		TRACE("Thread %d got semaphore!\n", threadNum);
219 
220 		uint16 width = engine->fWidth;
221 		uint16 height = engine->fHeight;
222 		uint16 halfHeight = height / 2;
223 		uint16 threadWidth = width / engine->fThreadCount;
224 
225 		uint16 startX = threadWidth * threadNum;
226 		uint16 endX = threadWidth * (threadNum + 1);
227 
228 		for (uint16 y = 0; y<halfHeight; y++) {
229 			for (uint16 x = startX; x<endX; x++) {
230 				engine->RenderPixel(x, halfHeight + y);
231 					// max y to RenderPixel will be
232 					// floor(height/2)*2 = height-height%2.
233 					// Thus there will be a line of blank pixels if height
234 					// is not even. Is this a problem?
235 				engine->RenderPixel(x, halfHeight - y - 1);
236 					// min y to RenderPixel will be
237 					// halfHeight-(halfHeight-1)-1 = 0
238 			}
239 
240 			if (engine->fStopRender) {
241 				TRACE("Thread %d stopping\n", threadNum);
242 				break; // Restart the loop to update width, height, etc.
243 			}
244 		}
245 
246 		if (!engine->fStopRender) {
247 			BMessage message(FractalEngine::MSG_THREAD_RENDER_COMPLETE);
248 			message.AddUInt8("thread", threadNum);
249 			engine->PostMessage(&message);
250 		}
251 	}
252 	return B_OK;
253 }
254 
255 
256 void FractalEngine::RenderPixel(uint32 x, uint32 y)
257 {
258 	double real = (x * fSize + fLocationX) - (fWidth / 2 * fSize);
259 	double imaginary = (y * -(fSize) + fLocationY) - (fHeight / 2 * -(fSize));
260 
261 	double subsampleDelta = fSize / fSubsampling;
262 	uint16 nSamples = fSubsampling * fSubsampling;
263 
264 	int16 totalR = 0;
265 	int16 totalG = 0;
266 	int16 totalB = 0;
267 	for (uint8 subX = 0; subX < fSubsampling; subX++) {
268 		for (uint8 subY = 0; subY < fSubsampling; subY++) {
269 			double sampleReal = real + subX * subsampleDelta;
270 			double sampleImaginary = imaginary + subY * subsampleDelta;
271 
272 			int32 sampleIterToEscape = (this->*fDoSet)(sampleReal, sampleImaginary);
273 
274 			uint16 sampleLoc = 0;
275 			if (sampleIterToEscape == -1)
276 				// Didn't escape.
277 				sampleLoc = 999;
278 			else
279 				sampleLoc = 998 - (sampleIterToEscape % 999);
280 			sampleLoc *= 3;
281 
282 			totalR += fColorset[sampleLoc + 0];
283 			totalG += fColorset[sampleLoc + 1];
284 			totalB += fColorset[sampleLoc + 2];
285 		}
286 	}
287 
288 
289 	uint32 offsetBase = fWidth * y * 3 + x * 3;
290 	fRenderBuffer[offsetBase + 2] = totalR / nSamples; // fRenderBuffer is BGR
291 	fRenderBuffer[offsetBase + 1] = totalG / nSamples;
292 	fRenderBuffer[offsetBase + 0] = totalB / nSamples;
293 }
294 
295 
296 // Magic numbers & other general constants
297 const double gJuliaA = 0;
298 const double gJuliaB = 1;
299 
300 const uint8 gEscapeHorizon = 4;
301 
302 
303 int32 FractalEngine::DoSet_Mandelbrot(double real, double imaginary)
304 {
305 	double zReal = 0;
306 	double zImaginary = 0;
307 
308 	for (int32 i = 0; i < fIterations; i++) {
309 		double zRealSq = zReal * zReal;
310 		double zImaginarySq = zImaginary * zImaginary;
311 		double nzReal = (zRealSq + (-1 * zImaginarySq));
312 
313 		zImaginary = 2 * (zReal * zImaginary);
314 		zReal = nzReal;
315 
316 		zReal += real;
317 		zImaginary += imaginary;
318 
319 		// If it is outside the 2 unit circle...
320 		if ((zRealSq + zImaginarySq) > gEscapeHorizon)
321 			return i; // stop it from running longer
322 	}
323 	return -1;
324 }
325 
326 
327 int32 FractalEngine::DoSet_BurningShip(double real, double imaginary)
328 {
329 	double zReal = 0;
330 	double zImaginary = 0;
331 
332 	// It looks "upside down" otherwise.
333 	imaginary = -imaginary;
334 
335 	for (int32 i = 0; i < fIterations; i++) {
336 		zReal = fabs(zReal);
337 		zImaginary = fabs(zImaginary);
338 
339 		double zRealSq = zReal * zReal;
340 		double zImaginarySq = zImaginary * zImaginary;
341 		double nzReal = (zRealSq + (-1 * zImaginarySq));
342 
343 		zImaginary = 2 * (zReal * zImaginary);
344 		zReal = nzReal;
345 
346 		zReal += real;
347 		zImaginary += imaginary;
348 
349 		// If it is outside the 2 unit circle...
350 		if ((zRealSq + zImaginarySq) > gEscapeHorizon)
351 			return i; // stop it from running longer
352 	}
353 	return -1;
354 }
355 
356 
357 int32 FractalEngine::DoSet_Tricorn(double real, double imaginary)
358 {
359 	double zReal = 0;
360 	double zImaginary = 0;
361 
362 	real = -real;
363 
364 	for (int32 i = 0; i < fIterations; i++) {
365 		double znRe = zImaginary * -1;
366 		zImaginary = zReal * -1;
367 		zReal = znRe; // Swap the real and complex parts each time.
368 
369 		double zRealSq = zReal * zReal;
370 		double zImaginarySq = zImaginary * zImaginary;
371 		double nzReal = (zRealSq + (-1 * zImaginarySq));
372 
373 		zImaginary = 2 * (zReal * zImaginary);
374 		zReal = nzReal;
375 
376 		zReal += real;
377 		zImaginary += imaginary;
378 
379 		// If it is outside the 2 unit circle...
380 		if ((zRealSq + zImaginarySq) > gEscapeHorizon)
381 			return i; // stop it from running longer
382 	}
383 	return -1;
384 }
385 
386 
387 int32 FractalEngine::DoSet_Julia(double real, double imaginary)
388 {
389 	double zReal = real;
390 	double zImaginary = imaginary;
391 
392 	double muRe = gJuliaA;
393 	double muIm = gJuliaB;
394 
395 	for (int32 i = 0; i < fIterations; i++) {
396 		double zRealSq = zReal * zReal;
397 		double zImaginarySq = zImaginary * zImaginary;
398 		double nzReal = (zRealSq + (-1 * (zImaginarySq)));
399 
400 		zImaginary = 2 * (zReal * zImaginary);
401 		zReal = nzReal;
402 
403 		zReal += muRe;
404 		zImaginary += muIm;
405 
406 		// If it is outside the 2 unit circle...
407 		if ((zRealSq + zImaginarySq) > gEscapeHorizon)
408 			return i; // stop it from running longer
409 	}
410 	return -1;
411 }
412 
413 
414 int32 FractalEngine::DoSet_OrbitTrap(double real, double imaginary)
415 {
416 	double zReal = 0;
417 	double zImaginary = 0;
418 
419 	double closest = 10000000;
420 	double distance = 0;
421 	double lineDist = 0;
422 
423 	for (int32 i = 0; i < fIterations; i++) {
424 		double zRealSq = zReal * zReal;
425 		double zImaginarySq = zImaginary * zImaginary;
426 		double nzReal = (zRealSq + (-1 * zImaginarySq));
427 
428 		zImaginary = 2 * (zReal * zImaginary);
429 		zReal = nzReal;
430 
431 		zReal += real;
432 		zImaginary += imaginary;
433 
434 		distance = sqrt(zRealSq + zImaginarySq);
435 		lineDist = fabs(zReal + zImaginary);
436 
437 		// If it is closer than ever before...
438 		if (lineDist < closest)
439 			closest = lineDist;
440 
441 		if (distance > gEscapeHorizon)
442 			return static_cast<int32>(floor(4 * log(4 / closest)));
443 	}
444 	return static_cast<int32>(floor(4 * log(4 / closest)));
445 }
446 
447 
448 int32 FractalEngine::DoSet_Multibrot(double real, double imaginary)
449 {
450 	double zReal = 0;
451 	double zImaginary = 0;
452 
453 	for (int32 i = 0; i < fIterations; i++) {
454 		double zRealSq = zReal * zReal;
455 		double zImaginarySq = zImaginary * zImaginary;
456 		double nzReal = (zRealSq * zReal - 3 * zReal * zImaginarySq);
457 
458 		zImaginary = 3 * (zRealSq * zImaginary) - (zImaginarySq * zImaginary);
459 
460 		zReal = nzReal;
461 		zReal += real;
462 		zImaginary += imaginary;
463 
464 		// If it is outside the 2 unit circle...
465 		if ((zRealSq + zImaginarySq) > gEscapeHorizon)
466 			return i; // stop it from running longer
467 	}
468 	return -1;
469 }
470