xref: /haiku/src/tests/kits/game/direct_window_test/StarWindow.cpp (revision 21258e2674226d6aa732321b6f8494841895af5f)
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