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