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 StarWindow::StarWindow(BRect frame, const char *name) 64 : BDirectWindow(frame, name, B_TITLED_WINDOW, 0) 65 { 66 uint32 i; 67 int32 x, y, dx, dy, cnt, square; 68 69 // init the crc pseudo-random generator 70 crc_alea = CRC_START; 71 72 // allocate the star struct array 73 star_count_max = 8192; 74 star_count = 0; 75 star_list = (star*)malloc(sizeof(star)*star_count_max); 76 77 // initialise the default state of the star array 78 for (i=0; i<star_count_max; i++) { 79 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 immediatly, 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 key_map *map; 189 190 get_key_map(&map, &buf); 191 sSwapped = (map->left_control_key==0x5d) && (map->left_command_key==0x5c); 192 free(map); 193 free(buf); 194 quit_alert = new BAlert("QuitAlert", sSwapped ? 195 "This demo runs only in full screen mode.\n" 196 "While running, press 'Ctrl-Q' to quit.": 197 "This demo runs only in full screen mode.\n" 198 "While running, press 'Alt-Q' to quit.", 199 "Quit", "Start demo", NULL, 200 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 201 if (quit_alert->Go() == 0) 202 ((StarsApp*)be_app)->abort_required = true; 203 else 204 SetFullScreen(true); 205 } 206 } 207 208 StarWindow::~StarWindow() 209 { 210 long result; 211 212 // force the drawing_thread to quit. This is the easiest way to deal 213 // with potential closing problem. When it's not practical, we 214 // recommand to use Hide() and Sync() to force the disconnection of 215 // the direct access, and use some other flag to guarantee that your 216 // drawing thread won't draw anymore. After that, you can pursue the 217 // window destructor and kill your drawing thread... 218 kill_my_thread = true; 219 delete_sem(drawing_lock); 220 wait_for_thread(my_thread, &result); 221 222 // Free window resources. As they're used by the drawing thread, we 223 // need to terminate that thread before freeing them, or we could crash. 224 free(star_list); 225 } 226 227 bool StarWindow::QuitRequested() 228 { 229 be_app->PostMessage(B_QUIT_REQUESTED); 230 return(TRUE); 231 } 232 233 void StarWindow::MessageReceived(BMessage *message) 234 { 235 int8 key_code; 236 237 switch(message->what) { 238 // Switch between full-screen mode and windowed mode. 239 case 'full' : 240 SetFullScreen(!IsFullScreen()); 241 break; 242 case B_KEY_DOWN : 243 if (!IsFullScreen()) 244 break; 245 if (message->FindInt8("byte", &key_code) != B_OK) 246 break; 247 if (key_code == B_ESCAPE) 248 PostMessage(B_QUIT_REQUESTED); 249 break; 250 default : 251 BDirectWindow::MessageReceived(message); 252 break; 253 } 254 } 255 256 void StarWindow::CrcStep() 257 { 258 // basic crc pseudo-random generator 259 crc_alea <<= 1; 260 if (crc_alea < 0) 261 crc_alea ^= CRC_KEY; 262 } 263 264 void StarWindow::DirectConnected(direct_buffer_info *info) 265 { 266 // you need to use that mask to read the buffer state. 267 switch (info->buffer_state & B_DIRECT_MODE_MASK) { 268 // start a direct screen connection. 269 case B_DIRECT_START : 270 SwitchContext(info); // update the direct screen infos. 271 release_sem(drawing_lock); // unblock the animation thread. 272 break; 273 // stop a direct screen connection. 274 case B_DIRECT_STOP : 275 acquire_sem(drawing_lock); // block the animation thread. 276 break; 277 // modify the state of a direct screen connection. 278 case B_DIRECT_MODIFY : 279 acquire_sem(drawing_lock); // block the animation thread. 280 SwitchContext(info); // update the direct screen infos. 281 release_sem(drawing_lock); // unblock the animation thread. 282 break; 283 default : 284 break; 285 } 286 } 287 288 // This function update the internal graphic context of the StarWindow 289 // object to reflect the infos send through the DirectConnected API. 290 // It also update the state of stars (and erase some of them) to 291 // insure a clean transition during resize. As this function is called 292 // in DirectConnected, it's a bad idea to do any heavy drawing (long) 293 // operation. But as we only update the stars (the background will be 294 // updated a little later by the view system), it's not a big deal. 295 void StarWindow::SwitchContext(direct_buffer_info *info) 296 { 297 star *s; 298 int32 x, y, deltax, deltay; 299 uint32 i, j, window_area, cx, cy; 300 uint32 star_count_new; 301 clipping_rect *r; 302 303 // calculate the new star count, depending the size of the window frame. 304 // we do that because we want to keep the star count proportionnal to 305 // the size of the window, to keep an similar overall star density feeling 306 window_area = (info->window_bounds.right-info->window_bounds.left+1)* 307 (info->window_bounds.bottom-info->window_bounds.top+1); 308 // max out beyond 1M pixels. 309 if (window_area > (1<<20)) 310 window_area = (1<<20); 311 star_count_new = (star_count_max*(window_area>>10))>>10; 312 if (star_count_new > star_count_max) 313 star_count_new = star_count_max; 314 315 // set the position of the new center of the window (in case of move or resize) 316 cx = (info->window_bounds.right+info->window_bounds.left+1)/2; 317 cy = (info->window_bounds.bottom+info->window_bounds.top+1)/2; 318 319 // update to the new clipping region. The local copy is kept relative 320 // to the center of the animation (origin of the star coordinate). 321 clipping_bound.left = info->clip_bounds.left - cx; 322 clipping_bound.right = info->clip_bounds.right - cx; 323 clipping_bound.top = info->clip_bounds.top - cy; 324 clipping_bound.bottom = info->clip_bounds.bottom - cy; 325 // the clipping count is bounded (see comment in header file). 326 clipping_list_count = info->clip_list_count; 327 if (clipping_list_count > MAX_CLIPPING_RECT_COUNT) 328 clipping_list_count = MAX_CLIPPING_RECT_COUNT; 329 for (i=0; i<clipping_list_count; i++) { 330 clipping_list[i].left = info->clip_list[i].left - cx; 331 clipping_list[i].right = info->clip_list[i].right - cx; 332 clipping_list[i].top = info->clip_list[i].top - cy; 333 clipping_list[i].bottom = info->clip_list[i].bottom - cy; 334 } 335 336 // update the new rowbyte 337 row_bytes = info->bytes_per_row/(info->bits_per_pixel/8); 338 339 // update the screen bases (only one of the 3 will be really used). 340 draw_ptr8 = ((uint8*)info->bits) + row_bytes*info->window_bounds.top + info->window_bounds.left; 341 draw_ptr16 = ((uint16*)info->bits) + row_bytes*info->window_bounds.top + info->window_bounds.left; 342 draw_ptr32 = ((uint32*)info->bits) + row_bytes*info->window_bounds.top + info->window_bounds.left; 343 344 // cancel the erasing of all stars if the buffer has been reset. 345 // Because of a bug in the R3 direct window protocol, B_BUFFER_RESET is not set 346 // whew showing a previously hidden window. The second test is a reasonnable 347 // way to work around that bug... 348 if ((info->buffer_state & B_BUFFER_RESET) || 349 (need_r3_buffer_reset_work_around && 350 ((info->buffer_state & (B_DIRECT_MODE_MASK|B_BUFFER_MOVED)) == B_DIRECT_START))) { 351 s = star_list; 352 for (i=0; i<star_count_max; i++) { 353 s->last_draw = INVALID; 354 s++; 355 } 356 } 357 // in the other case, update the stars that will stay visible. 358 else { 359 // calculate the delta vector due to window resize or move. 360 deltax = cx_old - (cx - info->window_bounds.left); 361 deltay = cy_old - (cy - info->window_bounds.top); 362 // check all the stars previously used. 363 s = star_list; 364 for (i=0; i<star_count; i++) { 365 // if the star wasn't visible before, then no more question. 366 if (s->last_draw == INVALID) 367 goto not_defined; 368 // convert the old position into the new referential. 369 x = (s->x>>16) + deltax; 370 y = (s->y>>16) + deltay; 371 // check if the old position is still visible in the new clipping 372 if ((x < clipping_bound.left) || (x > clipping_bound.right) || 373 (y < clipping_bound.top) || (y > clipping_bound.bottom)) 374 goto invisible; 375 if (clipping_list_count == 1) 376 goto visible; 377 r = clipping_list; 378 for (j=0; j<clipping_list_count; j++) { 379 if ((x >= r->left) && (x <= r->right) && 380 (y >= r->top) && (y <= r->bottom)) 381 goto visible; 382 r++; 383 } 384 goto invisible; 385 386 // if it's still visible... 387 visible: 388 if (i >= star_count_new) { 389 // ...and the star won't be used anylonger, then we erase it. 390 if (pixel_depth == 32) 391 draw_ptr32[s->last_draw] = 0; 392 else if (pixel_depth == 16) 393 draw_ptr16[s->last_draw] = 0; 394 else 395 draw_ptr8[s->last_draw] = 0; 396 } 397 goto not_defined; 398 399 // if the star just became invisible and it was because the 400 // context was modified and not fully stop, then we need to erase 401 // those stars who just became invisible (or they could leave 402 // artefacts in the drawing area in some cases). This problem is 403 // a side effect of the interaction between a complex resizing 404 // case (using 2 B_DIRECT_MODIFY per step), and the dynamic 405 // star count management we are doing. In most case, you never 406 // have to erase things going out of the clipping region... 407 invisible: 408 if ((info->buffer_state & B_DIRECT_MODE_MASK) == B_DIRECT_MODIFY) { 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 // and set its last position as invalid. 417 s->last_draw = INVALID; 418 not_defined: 419 s++; 420 } 421 422 // initialise all the new star (the ones which weren't used but 423 // will be use after that context update) to set their last position 424 // as invalid. 425 s = star_list+star_count; 426 for (i=star_count; i<star_count_new; i++) { 427 s->last_draw = INVALID; 428 s++; 429 } 430 } 431 432 // update the window origin offset. 433 window_offset = row_bytes*(cy-info->window_bounds.top) + (cx-info->window_bounds.left); 434 435 // set the pixel_depth and the pixel data, from the color_space. 436 switch (info->pixel_format) { 437 case B_RGBA32 : 438 case B_RGB32 : 439 pixel_depth = 32; 440 ((uint8*)&pixel32)[0] = 0x20; 441 ((uint8*)&pixel32)[1] = 0xff; 442 ((uint8*)&pixel32)[2] = 0x20; 443 ((uint8*)&pixel32)[3] = 0xff; 444 break; 445 case B_RGB16 : 446 pixel_depth = 16; 447 ((uint8*)&pixel16)[0] = 0xe0; 448 ((uint8*)&pixel16)[1] = 0x07; 449 break; 450 case B_RGB15 : 451 case B_RGBA15 : 452 pixel_depth = 16; 453 ((uint8*)&pixel16)[0] = 0xe0; 454 ((uint8*)&pixel16)[1] = 0x03; 455 break; 456 case B_CMAP8 : 457 pixel_depth = 8; 458 pixel8 = 52; 459 break; 460 case B_RGBA32_BIG : 461 case B_RGB32_BIG : 462 pixel_depth = 32; 463 ((uint8*)&pixel32)[3] = 0x20; 464 ((uint8*)&pixel32)[2] = 0xff; 465 ((uint8*)&pixel32)[1] = 0x20; 466 ((uint8*)&pixel32)[0] = 0xff; 467 break; 468 case B_RGB16_BIG : 469 pixel_depth = 16; 470 ((uint8*)&pixel16)[1] = 0xe0; 471 ((uint8*)&pixel16)[0] = 0x07; 472 break; 473 case B_RGB15_BIG : 474 case B_RGBA15_BIG : 475 pixel_depth = 16; 476 ((uint8*)&pixel16)[1] = 0xe0; 477 ((uint8*)&pixel16)[0] = 0x03; 478 break; 479 default: // unsupported color space? 480 fprintf(stderr, "ERROR - unsupported color space!\n"); 481 exit(1); 482 break; 483 } 484 485 // set the new star count. 486 star_count = star_count_new; 487 488 // save a copy of the variables used to calculate the move of the window center 489 cx_old = cx - info->window_bounds.left; 490 cy_old = cy - info->window_bounds.top; 491 } 492 493 // This is the thread doing the star animation itself. It would be easy to 494 // adapt to do any other sort of pixel animation. 495 long StarWindow::StarAnimation(void *data) 496 { 497 star *s; 498 int32 x, y; 499 uint32 i, j; 500 bigtime_t time; 501 StarWindow *w; 502 clipping_rect *r; 503 504 // receive a pointer to the StarWindow object. 505 w = (StarWindow*)data; 506 507 // loop, frame after frame, until asked to quit. 508 while (!w->kill_my_thread) { 509 510 // we want a frame to take at least 16 ms. 511 time = system_time()+16000; 512 513 // get the right to do direct screen access. 514 acquire_sem(w->drawing_lock); 515 if (w->kill_my_thread) break; 516 517 // go through the array of star, for all currently used star. 518 s = w->star_list; 519 for (i=0; i<w->star_count; i++) { 520 if (s->count == 0) { 521 // restart the star animation, from a random point close to 522 // the center [-16, +15], both axis. 523 x = s->x = ((w->crc_alea&0x1f00)>>8) - 16; 524 y = s->y = ((w->crc_alea&0x1f0000)>>16) - 16; 525 s->dx = s->dx0; 526 s->dy = s->dy0; 527 // add a small random component to the duration of the star 528 s->count = s->count0 + (w->crc_alea&0x7); 529 w->CrcStep(); 530 } 531 else { 532 // just move the star 533 s->count--; 534 x = s->x += s->dx; 535 y = s->y += s->dy; 536 s->dx += (s->dx>>4); 537 s->dy += (s->dy>>4); 538 } 539 // erase the previous position, if necessary 540 if (s->last_draw != INVALID) { 541 if (w->pixel_depth == 32) 542 w->draw_ptr32[s->last_draw] = 0; 543 else if (w->pixel_depth == 16) 544 w->draw_ptr16[s->last_draw] = 0; 545 else 546 w->draw_ptr8[s->last_draw] = 0; 547 } 548 // check if the new position is visible in the current clipping 549 x >>= 16; 550 y >>= 16; 551 if ((x < w->clipping_bound.left) || (x > w->clipping_bound.right) || 552 (y < w->clipping_bound.top) || (y > w->clipping_bound.bottom)) 553 goto invisible; 554 if (w->clipping_list_count == 1) { 555 visible: 556 // if it's visible, then draw it. 557 s->last_draw = w->window_offset + w->row_bytes*y + x; 558 if (w->pixel_depth == 32) 559 w->draw_ptr32[s->last_draw] = w->pixel32; 560 else if (w->pixel_depth == 16) 561 w->draw_ptr16[s->last_draw] = w->pixel16; 562 else 563 w->draw_ptr8[s->last_draw] = w->pixel8; 564 goto loop; 565 } 566 // handle complex clipping cases 567 r = w->clipping_list; 568 for (j=0; j<w->clipping_list_count; j++) { 569 if ((x >= r->left) && (x <= r->right) && 570 (y >= r->top) && (y <= r->bottom)) 571 goto visible; 572 r++; 573 } 574 invisible: 575 // if not visible, register the fact that the star wasn't draw. 576 s->last_draw = INVALID; 577 loop: 578 s++; 579 } 580 581 // release the direct screen access 582 release_sem(w->drawing_lock); 583 584 // snooze for whatever time is left from the initial allocation done 585 // at the beginning of the loop. 586 time -= system_time(); 587 if (time > 0) 588 snooze(time); 589 } 590 return 0; 591 } 592 593