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