/* StarWindow.cpp by Pierre Raynaud-Richard. */ /* Copyright 1999, Be Incorporated. All Rights Reserved. This file may be used under the terms of the Be Sample Code License. */ #include #include "StarWindow.h" #include "Stars.h" #include #include #include #include #include #include #include #include // return the version_info of a file, described by its // name and its generic folder (in find_directory syntax). status_t get_file_version_info( directory_which dir, char *filename, version_info *info) { BPath path; BFile file; status_t res; BAppFileInfo appinfo; // find the directory if ((res = find_directory(dir, &path)) != B_NO_ERROR) return res; // find the file path.Append(filename); file.SetTo(path.Path(), O_RDONLY); if ((res = file.InitCheck()) != B_NO_ERROR) return res; // get the version_info if ((res = appinfo.SetTo(&file)) != B_NO_ERROR) return res; return appinfo.GetVersionInfo(info, B_APP_VERSION_KIND); } enum { // pseudo-random generator parameters (not very good ones, // but good enough for what we do here). CRC_START = 0x56dec231, CRC_KEY = 0x1789feb3 }; StarWindow::StarWindow(BRect frame, const char *name) : BDirectWindow(frame, name, B_TITLED_WINDOW, 0) { uint32 i; int32 x, y, dx, dy, cnt, square; // init the crc pseudo-random generator crc_alea = CRC_START; // allocate the star struct array star_count_max = 8192; star_count = 0; star_list = (star*)malloc(sizeof(star)*star_count_max); // initialise the default state of the star array for (i = 0; i < star_count_max; i++) { // peek a random vector. This is certainly not the nicest way // to do it (the probability and the angle are linked), but that's // simple and doesn't require any trigonometry. do { dx = (crc_alea&0xffff) - 0x8000; CrcStep(); CrcStep(); dy = (crc_alea&0xffff) - 0x8000; CrcStep(); CrcStep(); } while ((dx == 0) && (dy == 0)); // enforce a minimal length by doubling the vector as many times // as needed. square = dx*dx+dy*dy; while (square < 0x08000000) { dx <<= 1; dy <<= 1; square <<= 2; } // save the starting speed vector. star_list[i].dx0 = dx; star_list[i].dy0 = dy; // simulate the animation to see how many moves are needed to // get out by at least 1024 in one direction. That will give us // an minimal value for how long we should wait before restarting // the animation. It wouldn't work if the window was getting // much larger than 2048 pixels in one dimension. cnt = 0; x = 0; y = 0; while ((x<0x4000000) && (x>-0x4000000) && (y<0x4000000) && (y>-0x4000000)) { x += dx; y += dy; dx += (dx>>4); dy += (dy>>4); cnt++; } // add a random compenent [0 to 15] to the minimal count before // restart. star_list[i].count0 = cnt + ((crc_alea&0xf0000)>>16); // make the star initialy invisible and fixed, then spread the // value of their restart countdown so that they won't start all // at the same time, but progressively star_list[i].last_draw = INVALID; star_list[i].x = 0x40000000; star_list[i].y = 0x40000000; star_list[i].dx = 0; star_list[i].dy = 0; star_list[i].count = (i&255); } // allocate the semaphore used to synchronise the star animation drawing access. drawing_lock = create_sem(0, "star locker"); // spawn the star animation thread (we have better set the force quit flag to // false first). kill_my_thread = false; my_thread = spawn_thread(StarWindow::StarAnimation, "StarAnimation", B_DISPLAY_PRIORITY, (void*)this); resume_thread(my_thread); // add a view in the background to insure that the content area will // be properly erased in black. This erase mechanism is not synchronised // with the star animaton, which means that from time to time, some // stars will be erreneously erased by the view redraw. But as every // single star is erased and redraw every frame, that graphic glitch // will last less than a frame, and that just in the area being redraw // because of a window resize, move... Which means the glitch won't // be noticeable. The other solution for erasing the background would // have been to do it on our own (which means some serious region // calculation and handling all color_space). Better to use the kit // to do it, as it gives us access to hardware acceleration... frame.OffsetTo(0.0, 0.0); //view = new BView(frame, "", B_FOLLOW_ALL, B_WILL_DRAW); // The only think we want from the view mechanism is to // erase the background in black. Because of the way the // star animation is done, this erasing operation doesn't // need to be synchronous with the animation. That the // reason why we use both the direct access and the view // mechanism to draw in the same area of the StarWindow. // Such thing is usualy not recommended as synchronisation // is generally an issue (drawing in random order usualy // gives remanent incorrect result). // set the view color to be black (nicer update). //view->SetViewColor(0, 0, 0); //AddChild(view); // Add a shortcut to switch in and out of fullscreen mode. AddShortcut('f', B_COMMAND_KEY, new BMessage('full')); // As we said before, the window shouldn't get wider than 2048 in any // direction, so those limits will do. SetSizeLimits(40.0, 2000.0, 40.0, 2000.0); // If the graphic card/graphic driver we use doesn't support directwindow // in window mode, then we need to switch to fullscreen immediately, or // the user won't see anything, as long as it doesn't used the undocumented // shortcut. That would be bad behavior... if (!BDirectWindow::SupportsWindowMode()) { bool sSwapped; char *buf; BAlert *quit_alert; key_map *map; get_key_map(&map, &buf); if (map != NULL) { sSwapped = (map->left_control_key == 0x5d) && (map->left_command_key == 0x5c); } else sSwapped = false; free(map); free(buf); quit_alert = new BAlert("QuitAlert", sSwapped ? "This demo runs only in full screen mode.\n" "While running, press 'Ctrl-Q' to quit.": "This demo runs only in full screen mode.\n" "While running, press 'Alt-Q' to quit.", "Quit", "Start demo", NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT); if (quit_alert->Go() == 0) ((StarsApp*)be_app)->abort_required = true; else SetFullScreen(true); } } StarWindow::~StarWindow() { // force the drawing_thread to quit. This is the easiest way to deal // with potential closing problem. When it's not practical, we // recommand to use Hide() and Sync() to force the disconnection of // the direct access, and use some other flag to guarantee that your // drawing thread won't draw anymore. After that, you can pursue the // window destructor and kill your drawing thread... kill_my_thread = true; delete_sem(drawing_lock); status_t result; wait_for_thread(my_thread, &result); // Free window resources. As they're used by the drawing thread, we // need to terminate that thread before freeing them, or we could crash. free(star_list); } bool StarWindow::QuitRequested() { be_app->PostMessage(B_QUIT_REQUESTED); return true; } void StarWindow::MessageReceived(BMessage *message) { int8 key_code; switch (message->what) { // Switch between full-screen mode and windowed mode. case 'full': SetFullScreen(!IsFullScreen()); break; case B_KEY_DOWN: if (!IsFullScreen()) break; if (message->FindInt8("byte", &key_code) != B_OK) break; if (key_code == B_ESCAPE) PostMessage(B_QUIT_REQUESTED); break; default: BDirectWindow::MessageReceived(message); break; } } void StarWindow::CrcStep() { // basic crc pseudo-random generator crc_alea <<= 1; if (crc_alea < 0) crc_alea ^= CRC_KEY; } void StarWindow::DirectConnected(direct_buffer_info *info) { // you need to use that mask to read the buffer state. switch (info->buffer_state & B_DIRECT_MODE_MASK) { // start a direct screen connection. case B_DIRECT_START: SwitchContext(info); // update the direct screen infos. release_sem(drawing_lock); // unblock the animation thread. break; // stop a direct screen connection. case B_DIRECT_STOP: acquire_sem(drawing_lock); // block the animation thread. break; // modify the state of a direct screen connection. case B_DIRECT_MODIFY: acquire_sem(drawing_lock); // block the animation thread. SwitchContext(info); // update the direct screen infos. release_sem(drawing_lock); // unblock the animation thread. break; default: break; } } // This function update the internal graphic context of the StarWindow // object to reflect the infos send through the DirectConnected API. // It also update the state of stars (and erase some of them) to // insure a clean transition during resize. As this function is called // in DirectConnected, it's a bad idea to do any heavy drawing (long) // operation. But as we only update the stars (the background will be // updated a little later by the view system), it's not a big deal. void StarWindow::SwitchContext(direct_buffer_info *info) { star *s; int32 x, y, deltax, deltay; uint32 i, j, window_area, cx, cy; uint32 star_count_new; clipping_rect *r; // calculate the new star count, depending the size of the window frame. // we do that because we want to keep the star count proportionnal to // the size of the window, to keep an similar overall star density feeling window_area = (info->window_bounds.right-info->window_bounds.left+1)* (info->window_bounds.bottom-info->window_bounds.top+1); // max out beyond 1M pixels. if (window_area > (1<<20)) window_area = (1<<20); star_count_new = (star_count_max*(window_area>>10))>>10; if (star_count_new > star_count_max) star_count_new = star_count_max; // set the position of the new center of the window (in case of move or resize) cx = (info->window_bounds.right+info->window_bounds.left+1)/2; cy = (info->window_bounds.bottom+info->window_bounds.top+1)/2; // update to the new clipping region. The local copy is kept relative // to the center of the animation (origin of the star coordinate). clipping_bound.left = info->clip_bounds.left - cx; clipping_bound.right = info->clip_bounds.right - cx; clipping_bound.top = info->clip_bounds.top - cy; clipping_bound.bottom = info->clip_bounds.bottom - cy; // the clipping count is bounded (see comment in header file). clipping_list_count = info->clip_list_count; if (clipping_list_count > MAX_CLIPPING_RECT_COUNT) clipping_list_count = MAX_CLIPPING_RECT_COUNT; for (i=0; iclip_list[i].left - cx; clipping_list[i].right = info->clip_list[i].right - cx; clipping_list[i].top = info->clip_list[i].top - cy; clipping_list[i].bottom = info->clip_list[i].bottom - cy; } // update the new rowbyte // NOTE: "row_bytes" is completely misnamed, and was misused too row_bytes = info->bytes_per_row / (info->bits_per_pixel / 8); // update the screen bases (only one of the 3 will be really used). draw_ptr8 = (uint8*)info->bits + info->bytes_per_row * info->window_bounds.top + info->window_bounds.left * (info->bits_per_pixel / 8); // Note: parenthesis around "info->bits_per_pixel / 8" // are needed to avoid an overflow when info->window_bounds.left // becomes negative. draw_ptr16 = (uint16*)draw_ptr8; draw_ptr32 = (uint32*)draw_ptr8; // cancel the erasing of all stars if the buffer has been reset. // Because of a bug in the R3 direct window protocol, B_BUFFER_RESET is not set // whew showing a previously hidden window. The second test is a reasonnable // way to work around that bug... if ((info->buffer_state & B_BUFFER_RESET) || (need_r3_buffer_reset_work_around && ((info->buffer_state & (B_DIRECT_MODE_MASK|B_BUFFER_MOVED)) == B_DIRECT_START))) { s = star_list; for (i=0; ilast_draw = INVALID; s++; } } // in the other case, update the stars that will stay visible. else { // calculate the delta vector due to window resize or move. deltax = cx_old - (cx - info->window_bounds.left); deltay = cy_old - (cy - info->window_bounds.top); // check all the stars previously used. s = star_list; for (i=0; ilast_draw == INVALID) goto not_defined; // convert the old position into the new referential. x = (s->x>>16) + deltax; y = (s->y>>16) + deltay; // check if the old position is still visible in the new clipping if ((x < clipping_bound.left) || (x > clipping_bound.right) || (y < clipping_bound.top) || (y > clipping_bound.bottom)) goto invisible; if (clipping_list_count == 1) goto visible; r = clipping_list; for (j=0; j= r->left) && (x <= r->right) && (y >= r->top) && (y <= r->bottom)) goto visible; r++; } goto invisible; // if it's still visible... visible: if (i >= star_count_new) { // ...and the star won't be used anylonger, then we erase it. if (pixel_depth == 32) draw_ptr32[s->last_draw] = 0; else if (pixel_depth == 16) draw_ptr16[s->last_draw] = 0; else draw_ptr8[s->last_draw] = 0; } goto not_defined; // if the star just became invisible and it was because the // context was modified and not fully stop, then we need to erase // those stars who just became invisible (or they could leave // artefacts in the drawing area in some cases). This problem is // a side effect of the interaction between a complex resizing // case (using 2 B_DIRECT_MODIFY per step), and the dynamic // star count management we are doing. In most case, you never // have to erase things going out of the clipping region... invisible: if ((info->buffer_state & B_DIRECT_MODE_MASK) == B_DIRECT_MODIFY) { if (pixel_depth == 32) draw_ptr32[s->last_draw] = 0; else if (pixel_depth == 16) draw_ptr16[s->last_draw] = 0; else draw_ptr8[s->last_draw] = 0; } // and set its last position as invalid. s->last_draw = INVALID; not_defined: s++; } // initialise all the new star (the ones which weren't used but // will be use after that context update) to set their last position // as invalid. s = star_list+star_count; for (i=star_count; ilast_draw = INVALID; s++; } } // update the window origin offset. window_offset = row_bytes*(cy-info->window_bounds.top) + (cx-info->window_bounds.left); // set the pixel_depth and the pixel data, from the color_space. switch (info->pixel_format) { case B_RGBA32 : case B_RGB32 : pixel_depth = 32; ((uint8*)&pixel32)[0] = 0x20; ((uint8*)&pixel32)[1] = 0xff; ((uint8*)&pixel32)[2] = 0x20; ((uint8*)&pixel32)[3] = 0xff; break; case B_RGB16 : pixel_depth = 16; ((uint8*)&pixel16)[0] = 0xe0; ((uint8*)&pixel16)[1] = 0x07; break; case B_RGB15 : case B_RGBA15 : pixel_depth = 16; ((uint8*)&pixel16)[0] = 0xe0; ((uint8*)&pixel16)[1] = 0x03; break; case B_CMAP8 : pixel_depth = 8; pixel8 = 52; break; case B_RGBA32_BIG : case B_RGB32_BIG : pixel_depth = 32; ((uint8*)&pixel32)[3] = 0x20; ((uint8*)&pixel32)[2] = 0xff; ((uint8*)&pixel32)[1] = 0x20; ((uint8*)&pixel32)[0] = 0xff; break; case B_RGB16_BIG : pixel_depth = 16; ((uint8*)&pixel16)[1] = 0xe0; ((uint8*)&pixel16)[0] = 0x07; break; case B_RGB15_BIG : case B_RGBA15_BIG : pixel_depth = 16; ((uint8*)&pixel16)[1] = 0xe0; ((uint8*)&pixel16)[0] = 0x03; break; default: // unsupported color space? fprintf(stderr, "ERROR - unsupported color space!\n"); exit(1); break; } // set the new star count. star_count = star_count_new; // save a copy of the variables used to calculate the move of the window center cx_old = cx - info->window_bounds.left; cy_old = cy - info->window_bounds.top; } // This is the thread doing the star animation itself. It would be easy to // adapt to do any other sort of pixel animation. status_t StarWindow::StarAnimation(void *data) { star *s; int32 x, y; uint32 i, j; bigtime_t time; StarWindow *w; clipping_rect *r; // receive a pointer to the StarWindow object. w = (StarWindow*)data; // loop, frame after frame, until asked to quit. while (!w->kill_my_thread) { // we want a frame to take at least 16 ms. time = system_time()+16000; // get the right to do direct screen access. while (acquire_sem(w->drawing_lock) == B_INTERRUPTED) ; if (w->kill_my_thread) break; // go through the array of star, for all currently used star. s = w->star_list; for (i=0; istar_count; i++) { if (s->count == 0) { // restart the star animation, from a random point close to // the center [-16, +15], both axis. x = s->x = ((w->crc_alea&0x1f00)>>8) - 16; y = s->y = ((w->crc_alea&0x1f0000)>>16) - 16; s->dx = s->dx0; s->dy = s->dy0; // add a small random component to the duration of the star s->count = s->count0 + (w->crc_alea&0x7); w->CrcStep(); } else { // just move the star s->count--; x = s->x += s->dx; y = s->y += s->dy; s->dx += (s->dx>>4); s->dy += (s->dy>>4); } // erase the previous position, if necessary if (s->last_draw != INVALID) { if (w->pixel_depth == 32) w->draw_ptr32[s->last_draw] = 0; else if (w->pixel_depth == 16) w->draw_ptr16[s->last_draw] = 0; else w->draw_ptr8[s->last_draw] = 0; } // check if the new position is visible in the current clipping x >>= 16; y >>= 16; if ((x < w->clipping_bound.left) || (x > w->clipping_bound.right) || (y < w->clipping_bound.top) || (y > w->clipping_bound.bottom)) goto invisible; if (w->clipping_list_count == 1) { visible: // if it's visible, then draw it. s->last_draw = w->window_offset + w->row_bytes*y + x; if (w->pixel_depth == 32) w->draw_ptr32[s->last_draw] = w->pixel32; else if (w->pixel_depth == 16) w->draw_ptr16[s->last_draw] = w->pixel16; else w->draw_ptr8[s->last_draw] = w->pixel8; goto loop; } // handle complex clipping cases r = w->clipping_list; for (j=0; jclipping_list_count; j++) { if ((x >= r->left) && (x <= r->right) && (y >= r->top) && (y <= r->bottom)) goto visible; r++; } invisible: // if not visible, register the fact that the star wasn't draw. s->last_draw = INVALID; loop: s++; } // release the direct screen access release_sem(w->drawing_lock); // snooze for whatever time is left from the initial allocation done // at the beginning of the loop. time -= system_time(); if (time > 0) snooze(time); } return 0; }