1 /* 2 3 StarWindow.cpp 4 5 by Pierre Raynaud-Richard. 6 7 */ 8 9 /* 10 Copyright 1999, Be Incorporated. All Rights Reserved. 11 This file may be used under the terms of the Be Sample Code License. 12 */ 13 14 #include <Application.h> 15 16 #include "StarWindow.h" 17 #include "Stars.h" 18 19 #include <string.h> 20 #include <stdlib.h> 21 22 #include <AppFileInfo.h> 23 #include <FindDirectory.h> 24 #include <Alert.h> 25 #include <File.h> 26 #include <Path.h> 27 28 #include <Debug.h> 29 30 // return the version_info of a file, described by its 31 // name and its generic folder (in find_directory syntax). 32 status_t get_file_version_info( directory_which dir, 33 char *filename, 34 version_info *info) { 35 BPath path; 36 BFile file; 37 status_t res; 38 BAppFileInfo appinfo; 39 40 // find the directory 41 if ((res = find_directory(dir, &path)) != B_NO_ERROR) 42 return res; 43 44 // find the file 45 path.Append(filename); 46 file.SetTo(path.Path(), O_RDONLY); 47 if ((res = file.InitCheck()) != B_NO_ERROR) 48 return res; 49 50 // get the version_info 51 if ((res = appinfo.SetTo(&file)) != B_NO_ERROR) 52 return res; 53 return appinfo.GetVersionInfo(info, B_APP_VERSION_KIND); 54 } 55 56 enum { 57 // pseudo-random generator parameters (not very good ones, 58 // but good enough for what we do here). 59 CRC_START = 0x56dec231, 60 CRC_KEY = 0x1789feb3 61 }; 62 63 64 StarWindow::StarWindow(BRect frame, const char *name) 65 : BDirectWindow(frame, name, B_TITLED_WINDOW, 0) 66 { 67 uint32 i; 68 int32 x, y, dx, dy, cnt, square; 69 70 // init the crc pseudo-random generator 71 crc_alea = CRC_START; 72 73 // allocate the star struct array 74 star_count_max = 8192; 75 star_count = 0; 76 star_list = (star*)malloc(sizeof(star)*star_count_max); 77 78 // initialise the default state of the star array 79 for (i = 0; i < star_count_max; i++) { 80 // peek a random vector. This is certainly not the nicest way 81 // to do it (the probability and the angle are linked), but that's 82 // simple and doesn't require any trigonometry. 83 do { 84 dx = (crc_alea&0xffff) - 0x8000; 85 CrcStep(); 86 CrcStep(); 87 88 dy = (crc_alea&0xffff) - 0x8000; 89 CrcStep(); 90 CrcStep(); 91 } while ((dx == 0) && (dy == 0)); 92 93 // enforce a minimal length by doubling the vector as many times 94 // as needed. 95 square = dx*dx+dy*dy; 96 while (square < 0x08000000) { 97 dx <<= 1; 98 dy <<= 1; 99 square <<= 2; 100 } 101 102 // save the starting speed vector. 103 star_list[i].dx0 = dx; 104 star_list[i].dy0 = dy; 105 106 // simulate the animation to see how many moves are needed to 107 // get out by at least 1024 in one direction. That will give us 108 // an minimal value for how long we should wait before restarting 109 // the animation. It wouldn't work if the window was getting 110 // much larger than 2048 pixels in one dimension. 111 cnt = 0; 112 x = 0; 113 y = 0; 114 while ((x<0x4000000) && (x>-0x4000000) && (y<0x4000000) && (y>-0x4000000)) { 115 x += dx; 116 y += dy; 117 dx += (dx>>4); 118 dy += (dy>>4); 119 cnt++; 120 } 121 122 // add a random compenent [0 to 15] to the minimal count before 123 // restart. 124 star_list[i].count0 = cnt + ((crc_alea&0xf0000)>>16); 125 // make the star initialy invisible and fixed, then spread the 126 // value of their restart countdown so that they won't start all 127 // at the same time, but progressively 128 star_list[i].last_draw = INVALID; 129 star_list[i].x = 0x40000000; 130 star_list[i].y = 0x40000000; 131 star_list[i].dx = 0; 132 star_list[i].dy = 0; 133 star_list[i].count = (i&255); 134 } 135 136 // allocate the semaphore used to synchronise the star animation drawing access. 137 drawing_lock = create_sem(0, "star locker"); 138 139 // spawn the star animation thread (we have better set the force quit flag to 140 // false first). 141 kill_my_thread = false; 142 my_thread = spawn_thread(StarWindow::StarAnimation, "StarAnimation", 143 B_DISPLAY_PRIORITY, (void*)this); 144 resume_thread(my_thread); 145 146 // add a view in the background to insure that the content area will 147 // be properly erased in black. This erase mechanism is not synchronised 148 // with the star animaton, which means that from time to time, some 149 // stars will be erreneously erased by the view redraw. But as every 150 // single star is erased and redraw every frame, that graphic glitch 151 // will last less than a frame, and that just in the area being redraw 152 // because of a window resize, move... Which means the glitch won't 153 // be noticeable. The other solution for erasing the background would 154 // have been to do it on our own (which means some serious region 155 // calculation and handling all color_space). Better to use the kit 156 // to do it, as it gives us access to hardware acceleration... 157 frame.OffsetTo(0.0, 0.0); 158 //view = new BView(frame, "", B_FOLLOW_ALL, B_WILL_DRAW); 159 160 // The only think we want from the view mechanism is to 161 // erase the background in black. Because of the way the 162 // star animation is done, this erasing operation doesn't 163 // need to be synchronous with the animation. That the 164 // reason why we use both the direct access and the view 165 // mechanism to draw in the same area of the StarWindow. 166 // Such thing is usualy not recommended as synchronisation 167 // is generally an issue (drawing in random order usualy 168 // gives remanent incorrect result). 169 // set the view color to be black (nicer update). 170 //view->SetViewColor(0, 0, 0); 171 //AddChild(view); 172 173 // Add a shortcut to switch in and out of fullscreen mode. 174 AddShortcut('f', B_COMMAND_KEY, new BMessage('full')); 175 176 // As we said before, the window shouldn't get wider than 2048 in any 177 // direction, so those limits will do. 178 SetSizeLimits(40.0, 2000.0, 40.0, 2000.0); 179 180 // If the graphic card/graphic driver we use doesn't support directwindow 181 // in window mode, then we need to switch to fullscreen immediately, or 182 // the user won't see anything, as long as it doesn't used the undocumented 183 // shortcut. That would be bad behavior... 184 if (!BDirectWindow::SupportsWindowMode()) { 185 bool sSwapped; 186 char *buf; 187 BAlert *quit_alert; 188 189 key_map *map; 190 get_key_map(&map, &buf); 191 192 if (map != NULL) { 193 sSwapped = (map->left_control_key == 0x5d) 194 && (map->left_command_key == 0x5c); 195 } else 196 sSwapped = false; 197 198 free(map); 199 free(buf); 200 quit_alert = new BAlert("QuitAlert", sSwapped ? 201 "This demo runs only in full screen mode.\n" 202 "While running, press 'Ctrl-Q' to quit.": 203 "This demo runs only in full screen mode.\n" 204 "While running, press 'Alt-Q' to quit.", 205 "Quit", "Start demo", NULL, 206 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 207 if (quit_alert->Go() == 0) 208 ((StarsApp*)be_app)->abort_required = true; 209 else 210 SetFullScreen(true); 211 } 212 } 213 214 215 StarWindow::~StarWindow() 216 { 217 // force the drawing_thread to quit. This is the easiest way to deal 218 // with potential closing problem. When it's not practical, we 219 // recommand to use Hide() and Sync() to force the disconnection of 220 // the direct access, and use some other flag to guarantee that your 221 // drawing thread won't draw anymore. After that, you can pursue the 222 // window destructor and kill your drawing thread... 223 kill_my_thread = true; 224 delete_sem(drawing_lock); 225 226 status_t result; 227 wait_for_thread(my_thread, &result); 228 229 // Free window resources. As they're used by the drawing thread, we 230 // need to terminate that thread before freeing them, or we could crash. 231 free(star_list); 232 } 233 234 235 bool 236 StarWindow::QuitRequested() 237 { 238 be_app->PostMessage(B_QUIT_REQUESTED); 239 return true; 240 } 241 242 243 void 244 StarWindow::MessageReceived(BMessage *message) 245 { 246 int8 key_code; 247 248 switch (message->what) { 249 // Switch between full-screen mode and windowed mode. 250 case 'full': 251 SetFullScreen(!IsFullScreen()); 252 break; 253 case B_KEY_DOWN: 254 if (!IsFullScreen()) 255 break; 256 if (message->FindInt8("byte", &key_code) != B_OK) 257 break; 258 if (key_code == B_ESCAPE) 259 PostMessage(B_QUIT_REQUESTED); 260 break; 261 default: 262 BDirectWindow::MessageReceived(message); 263 break; 264 } 265 } 266 267 268 void 269 StarWindow::CrcStep() 270 { 271 // basic crc pseudo-random generator 272 crc_alea <<= 1; 273 if (crc_alea < 0) 274 crc_alea ^= CRC_KEY; 275 } 276 277 278 void 279 StarWindow::DirectConnected(direct_buffer_info *info) 280 { 281 // you need to use that mask to read the buffer state. 282 switch (info->buffer_state & B_DIRECT_MODE_MASK) { 283 // start a direct screen connection. 284 case B_DIRECT_START: 285 SwitchContext(info); // update the direct screen infos. 286 release_sem(drawing_lock); // unblock the animation thread. 287 break; 288 // stop a direct screen connection. 289 case B_DIRECT_STOP: 290 acquire_sem(drawing_lock); // block the animation thread. 291 break; 292 // modify the state of a direct screen connection. 293 case B_DIRECT_MODIFY: 294 acquire_sem(drawing_lock); // block the animation thread. 295 SwitchContext(info); // update the direct screen infos. 296 release_sem(drawing_lock); // unblock the animation thread. 297 break; 298 299 default: 300 break; 301 } 302 } 303 304 305 // This function update the internal graphic context of the StarWindow 306 // object to reflect the infos send through the DirectConnected API. 307 // It also update the state of stars (and erase some of them) to 308 // insure a clean transition during resize. As this function is called 309 // in DirectConnected, it's a bad idea to do any heavy drawing (long) 310 // operation. But as we only update the stars (the background will be 311 // updated a little later by the view system), it's not a big deal. 312 void 313 StarWindow::SwitchContext(direct_buffer_info *info) 314 { 315 star *s; 316 int32 x, y, deltax, deltay; 317 uint32 i, j, window_area, cx, cy; 318 uint32 star_count_new; 319 clipping_rect *r; 320 321 // calculate the new star count, depending the size of the window frame. 322 // we do that because we want to keep the star count proportionnal to 323 // the size of the window, to keep an similar overall star density feeling 324 window_area = (info->window_bounds.right-info->window_bounds.left+1)* 325 (info->window_bounds.bottom-info->window_bounds.top+1); 326 // max out beyond 1M pixels. 327 if (window_area > (1<<20)) 328 window_area = (1<<20); 329 star_count_new = (star_count_max*(window_area>>10))>>10; 330 if (star_count_new > star_count_max) 331 star_count_new = star_count_max; 332 333 // set the position of the new center of the window (in case of move or resize) 334 cx = (info->window_bounds.right+info->window_bounds.left+1)/2; 335 cy = (info->window_bounds.bottom+info->window_bounds.top+1)/2; 336 337 // update to the new clipping region. The local copy is kept relative 338 // to the center of the animation (origin of the star coordinate). 339 clipping_bound.left = info->clip_bounds.left - cx; 340 clipping_bound.right = info->clip_bounds.right - cx; 341 clipping_bound.top = info->clip_bounds.top - cy; 342 clipping_bound.bottom = info->clip_bounds.bottom - cy; 343 // the clipping count is bounded (see comment in header file). 344 clipping_list_count = info->clip_list_count; 345 if (clipping_list_count > MAX_CLIPPING_RECT_COUNT) 346 clipping_list_count = MAX_CLIPPING_RECT_COUNT; 347 for (i=0; i<clipping_list_count; i++) { 348 clipping_list[i].left = info->clip_list[i].left - cx; 349 clipping_list[i].right = info->clip_list[i].right - cx; 350 clipping_list[i].top = info->clip_list[i].top - cy; 351 clipping_list[i].bottom = info->clip_list[i].bottom - cy; 352 } 353 354 // update the new rowbyte 355 // NOTE: "row_bytes" is completely misnamed, and was misused too 356 row_bytes = info->bytes_per_row / (info->bits_per_pixel / 8); 357 358 // update the screen bases (only one of the 3 will be really used). 359 draw_ptr8 = (uint8*)info->bits + info->bytes_per_row 360 * info->window_bounds.top + info->window_bounds.left 361 * (info->bits_per_pixel / 8); 362 // Note: parenthesis around "info->bits_per_pixel / 8" 363 // are needed to avoid an overflow when info->window_bounds.left 364 // becomes negative. 365 366 draw_ptr16 = (uint16*)draw_ptr8; 367 draw_ptr32 = (uint32*)draw_ptr8; 368 369 // cancel the erasing of all stars if the buffer has been reset. 370 // Because of a bug in the R3 direct window protocol, B_BUFFER_RESET is not set 371 // whew showing a previously hidden window. The second test is a reasonnable 372 // way to work around that bug... 373 if ((info->buffer_state & B_BUFFER_RESET) || 374 (need_r3_buffer_reset_work_around && 375 ((info->buffer_state & (B_DIRECT_MODE_MASK|B_BUFFER_MOVED)) == B_DIRECT_START))) { 376 s = star_list; 377 for (i=0; i<star_count_max; i++) { 378 s->last_draw = INVALID; 379 s++; 380 } 381 } 382 // in the other case, update the stars that will stay visible. 383 else { 384 // calculate the delta vector due to window resize or move. 385 deltax = cx_old - (cx - info->window_bounds.left); 386 deltay = cy_old - (cy - info->window_bounds.top); 387 // check all the stars previously used. 388 s = star_list; 389 for (i=0; i<star_count; i++) { 390 // if the star wasn't visible before, then no more question. 391 if (s->last_draw == INVALID) 392 goto not_defined; 393 // convert the old position into the new referential. 394 x = (s->x>>16) + deltax; 395 y = (s->y>>16) + deltay; 396 // check if the old position is still visible in the new clipping 397 if ((x < clipping_bound.left) || (x > clipping_bound.right) || 398 (y < clipping_bound.top) || (y > clipping_bound.bottom)) 399 goto invisible; 400 if (clipping_list_count == 1) 401 goto visible; 402 r = clipping_list; 403 for (j=0; j<clipping_list_count; j++) { 404 if ((x >= r->left) && (x <= r->right) && 405 (y >= r->top) && (y <= r->bottom)) 406 goto visible; 407 r++; 408 } 409 goto invisible; 410 411 // if it's still visible... 412 visible: 413 if (i >= star_count_new) { 414 // ...and the star won't be used anylonger, then we erase it. 415 if (pixel_depth == 32) 416 draw_ptr32[s->last_draw] = 0; 417 else if (pixel_depth == 16) 418 draw_ptr16[s->last_draw] = 0; 419 else 420 draw_ptr8[s->last_draw] = 0; 421 } 422 goto not_defined; 423 424 // if the star just became invisible and it was because the 425 // context was modified and not fully stop, then we need to erase 426 // those stars who just became invisible (or they could leave 427 // artefacts in the drawing area in some cases). This problem is 428 // a side effect of the interaction between a complex resizing 429 // case (using 2 B_DIRECT_MODIFY per step), and the dynamic 430 // star count management we are doing. In most case, you never 431 // have to erase things going out of the clipping region... 432 invisible: 433 if ((info->buffer_state & B_DIRECT_MODE_MASK) == B_DIRECT_MODIFY) { 434 if (pixel_depth == 32) 435 draw_ptr32[s->last_draw] = 0; 436 else if (pixel_depth == 16) 437 draw_ptr16[s->last_draw] = 0; 438 else 439 draw_ptr8[s->last_draw] = 0; 440 } 441 // and set its last position as invalid. 442 s->last_draw = INVALID; 443 not_defined: 444 s++; 445 } 446 447 // initialise all the new star (the ones which weren't used but 448 // will be use after that context update) to set their last position 449 // as invalid. 450 s = star_list+star_count; 451 for (i=star_count; i<star_count_new; i++) { 452 s->last_draw = INVALID; 453 s++; 454 } 455 } 456 457 // update the window origin offset. 458 window_offset = row_bytes*(cy-info->window_bounds.top) + (cx-info->window_bounds.left); 459 460 // set the pixel_depth and the pixel data, from the color_space. 461 switch (info->pixel_format) { 462 case B_RGBA32 : 463 case B_RGB32 : 464 pixel_depth = 32; 465 ((uint8*)&pixel32)[0] = 0x20; 466 ((uint8*)&pixel32)[1] = 0xff; 467 ((uint8*)&pixel32)[2] = 0x20; 468 ((uint8*)&pixel32)[3] = 0xff; 469 break; 470 case B_RGB16 : 471 pixel_depth = 16; 472 ((uint8*)&pixel16)[0] = 0xe0; 473 ((uint8*)&pixel16)[1] = 0x07; 474 break; 475 case B_RGB15 : 476 case B_RGBA15 : 477 pixel_depth = 16; 478 ((uint8*)&pixel16)[0] = 0xe0; 479 ((uint8*)&pixel16)[1] = 0x03; 480 break; 481 case B_CMAP8 : 482 pixel_depth = 8; 483 pixel8 = 52; 484 break; 485 case B_RGBA32_BIG : 486 case B_RGB32_BIG : 487 pixel_depth = 32; 488 ((uint8*)&pixel32)[3] = 0x20; 489 ((uint8*)&pixel32)[2] = 0xff; 490 ((uint8*)&pixel32)[1] = 0x20; 491 ((uint8*)&pixel32)[0] = 0xff; 492 break; 493 case B_RGB16_BIG : 494 pixel_depth = 16; 495 ((uint8*)&pixel16)[1] = 0xe0; 496 ((uint8*)&pixel16)[0] = 0x07; 497 break; 498 case B_RGB15_BIG : 499 case B_RGBA15_BIG : 500 pixel_depth = 16; 501 ((uint8*)&pixel16)[1] = 0xe0; 502 ((uint8*)&pixel16)[0] = 0x03; 503 break; 504 default: // unsupported color space? 505 fprintf(stderr, "ERROR - unsupported color space!\n"); 506 exit(1); 507 break; 508 } 509 510 // set the new star count. 511 star_count = star_count_new; 512 513 // save a copy of the variables used to calculate the move of the window center 514 cx_old = cx - info->window_bounds.left; 515 cy_old = cy - info->window_bounds.top; 516 } 517 518 519 // This is the thread doing the star animation itself. It would be easy to 520 // adapt to do any other sort of pixel animation. 521 status_t 522 StarWindow::StarAnimation(void *data) 523 { 524 star *s; 525 int32 x, y; 526 uint32 i, j; 527 bigtime_t time; 528 StarWindow *w; 529 clipping_rect *r; 530 531 // receive a pointer to the StarWindow object. 532 w = (StarWindow*)data; 533 534 // loop, frame after frame, until asked to quit. 535 while (!w->kill_my_thread) { 536 // we want a frame to take at least 16 ms. 537 time = system_time()+16000; 538 539 // get the right to do direct screen access. 540 while (acquire_sem(w->drawing_lock) == B_INTERRUPTED) 541 ; 542 if (w->kill_my_thread) 543 break; 544 545 // go through the array of star, for all currently used star. 546 s = w->star_list; 547 for (i=0; i<w->star_count; i++) { 548 if (s->count == 0) { 549 // restart the star animation, from a random point close to 550 // the center [-16, +15], both axis. 551 x = s->x = ((w->crc_alea&0x1f00)>>8) - 16; 552 y = s->y = ((w->crc_alea&0x1f0000)>>16) - 16; 553 s->dx = s->dx0; 554 s->dy = s->dy0; 555 // add a small random component to the duration of the star 556 s->count = s->count0 + (w->crc_alea&0x7); 557 w->CrcStep(); 558 } 559 else { 560 // just move the star 561 s->count--; 562 x = s->x += s->dx; 563 y = s->y += s->dy; 564 s->dx += (s->dx>>4); 565 s->dy += (s->dy>>4); 566 } 567 // erase the previous position, if necessary 568 if (s->last_draw != INVALID) { 569 if (w->pixel_depth == 32) 570 w->draw_ptr32[s->last_draw] = 0; 571 else if (w->pixel_depth == 16) 572 w->draw_ptr16[s->last_draw] = 0; 573 else 574 w->draw_ptr8[s->last_draw] = 0; 575 } 576 // check if the new position is visible in the current clipping 577 x >>= 16; 578 y >>= 16; 579 if ((x < w->clipping_bound.left) || (x > w->clipping_bound.right) || 580 (y < w->clipping_bound.top) || (y > w->clipping_bound.bottom)) 581 goto invisible; 582 if (w->clipping_list_count == 1) { 583 visible: 584 // if it's visible, then draw it. 585 s->last_draw = w->window_offset + w->row_bytes*y + x; 586 if (w->pixel_depth == 32) 587 w->draw_ptr32[s->last_draw] = w->pixel32; 588 else if (w->pixel_depth == 16) 589 w->draw_ptr16[s->last_draw] = w->pixel16; 590 else 591 w->draw_ptr8[s->last_draw] = w->pixel8; 592 goto loop; 593 } 594 // handle complex clipping cases 595 r = w->clipping_list; 596 for (j=0; j<w->clipping_list_count; j++) { 597 if ((x >= r->left) && (x <= r->right) && 598 (y >= r->top) && (y <= r->bottom)) 599 goto visible; 600 r++; 601 } 602 invisible: 603 // if not visible, register the fact that the star wasn't draw. 604 s->last_draw = INVALID; 605 loop: 606 s++; 607 } 608 609 // release the direct screen access 610 release_sem(w->drawing_lock); 611 612 // snooze for whatever time is left from the initial allocation done 613 // at the beginning of the loop. 614 time -= system_time(); 615 if (time > 0) 616 snooze(time); 617 } 618 return 0; 619 } 620 621