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 * info->window_bounds.top + (info->window_bounds.left * info->bits_per_pixel / 8); 360 draw_ptr16 = (uint16*)draw_ptr8; 361 draw_ptr32 = (uint32*)draw_ptr8; 362 363 // cancel the erasing of all stars if the buffer has been reset. 364 // Because of a bug in the R3 direct window protocol, B_BUFFER_RESET is not set 365 // whew showing a previously hidden window. The second test is a reasonnable 366 // way to work around that bug... 367 if ((info->buffer_state & B_BUFFER_RESET) || 368 (need_r3_buffer_reset_work_around && 369 ((info->buffer_state & (B_DIRECT_MODE_MASK|B_BUFFER_MOVED)) == B_DIRECT_START))) { 370 s = star_list; 371 for (i=0; i<star_count_max; i++) { 372 s->last_draw = INVALID; 373 s++; 374 } 375 } 376 // in the other case, update the stars that will stay visible. 377 else { 378 // calculate the delta vector due to window resize or move. 379 deltax = cx_old - (cx - info->window_bounds.left); 380 deltay = cy_old - (cy - info->window_bounds.top); 381 // check all the stars previously used. 382 s = star_list; 383 for (i=0; i<star_count; i++) { 384 // if the star wasn't visible before, then no more question. 385 if (s->last_draw == INVALID) 386 goto not_defined; 387 // convert the old position into the new referential. 388 x = (s->x>>16) + deltax; 389 y = (s->y>>16) + deltay; 390 // check if the old position is still visible in the new clipping 391 if ((x < clipping_bound.left) || (x > clipping_bound.right) || 392 (y < clipping_bound.top) || (y > clipping_bound.bottom)) 393 goto invisible; 394 if (clipping_list_count == 1) 395 goto visible; 396 r = clipping_list; 397 for (j=0; j<clipping_list_count; j++) { 398 if ((x >= r->left) && (x <= r->right) && 399 (y >= r->top) && (y <= r->bottom)) 400 goto visible; 401 r++; 402 } 403 goto invisible; 404 405 // if it's still visible... 406 visible: 407 if (i >= star_count_new) { 408 // ...and the star won't be used anylonger, then we erase it. 409 if (pixel_depth == 32) 410 draw_ptr32[s->last_draw] = 0; 411 else if (pixel_depth == 16) 412 draw_ptr16[s->last_draw] = 0; 413 else 414 draw_ptr8[s->last_draw] = 0; 415 } 416 goto not_defined; 417 418 // if the star just became invisible and it was because the 419 // context was modified and not fully stop, then we need to erase 420 // those stars who just became invisible (or they could leave 421 // artefacts in the drawing area in some cases). This problem is 422 // a side effect of the interaction between a complex resizing 423 // case (using 2 B_DIRECT_MODIFY per step), and the dynamic 424 // star count management we are doing. In most case, you never 425 // have to erase things going out of the clipping region... 426 invisible: 427 if ((info->buffer_state & B_DIRECT_MODE_MASK) == B_DIRECT_MODIFY) { 428 if (pixel_depth == 32) 429 draw_ptr32[s->last_draw] = 0; 430 else if (pixel_depth == 16) 431 draw_ptr16[s->last_draw] = 0; 432 else 433 draw_ptr8[s->last_draw] = 0; 434 } 435 // and set its last position as invalid. 436 s->last_draw = INVALID; 437 not_defined: 438 s++; 439 } 440 441 // initialise all the new star (the ones which weren't used but 442 // will be use after that context update) to set their last position 443 // as invalid. 444 s = star_list+star_count; 445 for (i=star_count; i<star_count_new; i++) { 446 s->last_draw = INVALID; 447 s++; 448 } 449 } 450 451 // update the window origin offset. 452 window_offset = row_bytes*(cy-info->window_bounds.top) + (cx-info->window_bounds.left); 453 454 // set the pixel_depth and the pixel data, from the color_space. 455 switch (info->pixel_format) { 456 case B_RGBA32 : 457 case B_RGB32 : 458 pixel_depth = 32; 459 ((uint8*)&pixel32)[0] = 0x20; 460 ((uint8*)&pixel32)[1] = 0xff; 461 ((uint8*)&pixel32)[2] = 0x20; 462 ((uint8*)&pixel32)[3] = 0xff; 463 break; 464 case B_RGB16 : 465 pixel_depth = 16; 466 ((uint8*)&pixel16)[0] = 0xe0; 467 ((uint8*)&pixel16)[1] = 0x07; 468 break; 469 case B_RGB15 : 470 case B_RGBA15 : 471 pixel_depth = 16; 472 ((uint8*)&pixel16)[0] = 0xe0; 473 ((uint8*)&pixel16)[1] = 0x03; 474 break; 475 case B_CMAP8 : 476 pixel_depth = 8; 477 pixel8 = 52; 478 break; 479 case B_RGBA32_BIG : 480 case B_RGB32_BIG : 481 pixel_depth = 32; 482 ((uint8*)&pixel32)[3] = 0x20; 483 ((uint8*)&pixel32)[2] = 0xff; 484 ((uint8*)&pixel32)[1] = 0x20; 485 ((uint8*)&pixel32)[0] = 0xff; 486 break; 487 case B_RGB16_BIG : 488 pixel_depth = 16; 489 ((uint8*)&pixel16)[1] = 0xe0; 490 ((uint8*)&pixel16)[0] = 0x07; 491 break; 492 case B_RGB15_BIG : 493 case B_RGBA15_BIG : 494 pixel_depth = 16; 495 ((uint8*)&pixel16)[1] = 0xe0; 496 ((uint8*)&pixel16)[0] = 0x03; 497 break; 498 default: // unsupported color space? 499 fprintf(stderr, "ERROR - unsupported color space!\n"); 500 exit(1); 501 break; 502 } 503 504 // set the new star count. 505 star_count = star_count_new; 506 507 // save a copy of the variables used to calculate the move of the window center 508 cx_old = cx - info->window_bounds.left; 509 cy_old = cy - info->window_bounds.top; 510 } 511 512 513 // This is the thread doing the star animation itself. It would be easy to 514 // adapt to do any other sort of pixel animation. 515 long 516 StarWindow::StarAnimation(void *data) 517 { 518 star *s; 519 int32 x, y; 520 uint32 i, j; 521 bigtime_t time; 522 StarWindow *w; 523 clipping_rect *r; 524 525 // receive a pointer to the StarWindow object. 526 w = (StarWindow*)data; 527 528 // loop, frame after frame, until asked to quit. 529 while (!w->kill_my_thread) { 530 // we want a frame to take at least 16 ms. 531 time = system_time()+16000; 532 533 // get the right to do direct screen access. 534 while (acquire_sem(w->drawing_lock) == B_INTERRUPTED) 535 ; 536 if (w->kill_my_thread) 537 break; 538 539 // go through the array of star, for all currently used star. 540 s = w->star_list; 541 for (i=0; i<w->star_count; i++) { 542 if (s->count == 0) { 543 // restart the star animation, from a random point close to 544 // the center [-16, +15], both axis. 545 x = s->x = ((w->crc_alea&0x1f00)>>8) - 16; 546 y = s->y = ((w->crc_alea&0x1f0000)>>16) - 16; 547 s->dx = s->dx0; 548 s->dy = s->dy0; 549 // add a small random component to the duration of the star 550 s->count = s->count0 + (w->crc_alea&0x7); 551 w->CrcStep(); 552 } 553 else { 554 // just move the star 555 s->count--; 556 x = s->x += s->dx; 557 y = s->y += s->dy; 558 s->dx += (s->dx>>4); 559 s->dy += (s->dy>>4); 560 } 561 // erase the previous position, if necessary 562 if (s->last_draw != INVALID) { 563 if (w->pixel_depth == 32) 564 w->draw_ptr32[s->last_draw] = 0; 565 else if (w->pixel_depth == 16) 566 w->draw_ptr16[s->last_draw] = 0; 567 else 568 w->draw_ptr8[s->last_draw] = 0; 569 } 570 // check if the new position is visible in the current clipping 571 x >>= 16; 572 y >>= 16; 573 if ((x < w->clipping_bound.left) || (x > w->clipping_bound.right) || 574 (y < w->clipping_bound.top) || (y > w->clipping_bound.bottom)) 575 goto invisible; 576 if (w->clipping_list_count == 1) { 577 visible: 578 // if it's visible, then draw it. 579 s->last_draw = w->window_offset + w->row_bytes*y + x; 580 if (w->pixel_depth == 32) 581 w->draw_ptr32[s->last_draw] = w->pixel32; 582 else if (w->pixel_depth == 16) 583 w->draw_ptr16[s->last_draw] = w->pixel16; 584 else 585 w->draw_ptr8[s->last_draw] = w->pixel8; 586 goto loop; 587 } 588 // handle complex clipping cases 589 r = w->clipping_list; 590 for (j=0; j<w->clipping_list_count; j++) { 591 if ((x >= r->left) && (x <= r->right) && 592 (y >= r->top) && (y <= r->bottom)) 593 goto visible; 594 r++; 595 } 596 invisible: 597 // if not visible, register the fact that the star wasn't draw. 598 s->last_draw = INVALID; 599 loop: 600 s++; 601 } 602 603 // release the direct screen access 604 release_sem(w->drawing_lock); 605 606 // snooze for whatever time is left from the initial allocation done 607 // at the beginning of the loop. 608 time -= system_time(); 609 if (time > 0) 610 snooze(time); 611 } 612 return 0; 613 } 614 615