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