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