1 | /* |
2 | Copyright (C) 1997-2021 Sam Lantinga <slouken@libsdl.org> |
3 | |
4 | This software is provided 'as-is', without any express or implied |
5 | warranty. In no event will the authors be held liable for any damages |
6 | arising from the use of this software. |
7 | |
8 | Permission is granted to anyone to use this software for any purpose, |
9 | including commercial applications, and to alter it and redistribute it |
10 | freely. |
11 | */ |
12 | /* Simple program: Move N sprites around on the screen as fast as possible */ |
13 | |
14 | #include <stdlib.h> |
15 | #include <stdio.h> |
16 | #include <time.h> |
17 | |
18 | #ifdef __EMSCRIPTEN__ |
19 | #include <emscripten/emscripten.h> |
20 | #endif |
21 | |
22 | #include "SDL_test.h" |
23 | #include "SDL_test_common.h" |
24 | |
25 | #define NUM_SPRITES 100 |
26 | #define MAX_SPEED 1 |
27 | |
28 | static SDLTest_CommonState *state; |
29 | static int num_sprites; |
30 | static SDL_Texture **sprites; |
31 | static SDL_bool cycle_color; |
32 | static SDL_bool cycle_alpha; |
33 | static int cycle_direction = 1; |
34 | static int current_alpha = 0; |
35 | static int current_color = 0; |
36 | static SDL_Rect *positions; |
37 | static SDL_Rect *velocities; |
38 | static int sprite_w, sprite_h; |
39 | static SDL_BlendMode blendMode = SDL_BLENDMODE_BLEND; |
40 | static Uint32 next_fps_check, frames; |
41 | static const Uint32 fps_check_delay = 5000; |
42 | |
43 | /* Number of iterations to move sprites - used for visual tests. */ |
44 | /* -1: infinite random moves (default); >=0: enables N deterministic moves */ |
45 | static int iterations = -1; |
46 | |
47 | int done; |
48 | |
49 | /* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */ |
50 | static void |
51 | quit(int rc) |
52 | { |
53 | SDL_free(sprites); |
54 | SDL_free(positions); |
55 | SDL_free(velocities); |
56 | SDLTest_CommonQuit(state); |
57 | exit(rc); |
58 | } |
59 | |
60 | int |
61 | LoadSprite(const char *file) |
62 | { |
63 | int i; |
64 | SDL_Surface *temp; |
65 | |
66 | /* Load the sprite image */ |
67 | temp = SDL_LoadBMP(file); |
68 | if (temp == NULL) { |
69 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't load %s: %s" , file, SDL_GetError()); |
70 | return (-1); |
71 | } |
72 | sprite_w = temp->w; |
73 | sprite_h = temp->h; |
74 | |
75 | /* Set transparent pixel as the pixel at (0,0) */ |
76 | if (temp->format->palette) { |
77 | SDL_SetColorKey(temp, 1, *(Uint8 *) temp->pixels); |
78 | } else { |
79 | switch (temp->format->BitsPerPixel) { |
80 | case 15: |
81 | SDL_SetColorKey(temp, 1, (*(Uint16 *) temp->pixels) & 0x00007FFF); |
82 | break; |
83 | case 16: |
84 | SDL_SetColorKey(temp, 1, *(Uint16 *) temp->pixels); |
85 | break; |
86 | case 24: |
87 | SDL_SetColorKey(temp, 1, (*(Uint32 *) temp->pixels) & 0x00FFFFFF); |
88 | break; |
89 | case 32: |
90 | SDL_SetColorKey(temp, 1, *(Uint32 *) temp->pixels); |
91 | break; |
92 | } |
93 | } |
94 | |
95 | /* Create textures from the image */ |
96 | for (i = 0; i < state->num_windows; ++i) { |
97 | SDL_Renderer *renderer = state->renderers[i]; |
98 | sprites[i] = SDL_CreateTextureFromSurface(renderer, temp); |
99 | if (!sprites[i]) { |
100 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create texture: %s\n" , SDL_GetError()); |
101 | SDL_FreeSurface(temp); |
102 | return (-1); |
103 | } |
104 | if (SDL_SetTextureBlendMode(sprites[i], blendMode) < 0) { |
105 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't set blend mode: %s\n" , SDL_GetError()); |
106 | SDL_FreeSurface(temp); |
107 | SDL_DestroyTexture(sprites[i]); |
108 | return (-1); |
109 | } |
110 | } |
111 | SDL_FreeSurface(temp); |
112 | |
113 | /* We're ready to roll. :) */ |
114 | return (0); |
115 | } |
116 | |
117 | void |
118 | MoveSprites(SDL_Renderer * renderer, SDL_Texture * sprite) |
119 | { |
120 | int i; |
121 | SDL_Rect viewport, temp; |
122 | SDL_Rect *position, *velocity; |
123 | |
124 | /* Query the sizes */ |
125 | SDL_RenderGetViewport(renderer, &viewport); |
126 | |
127 | /* Cycle the color and alpha, if desired */ |
128 | if (cycle_color) { |
129 | current_color += cycle_direction; |
130 | if (current_color < 0) { |
131 | current_color = 0; |
132 | cycle_direction = -cycle_direction; |
133 | } |
134 | if (current_color > 255) { |
135 | current_color = 255; |
136 | cycle_direction = -cycle_direction; |
137 | } |
138 | SDL_SetTextureColorMod(sprite, 255, (Uint8) current_color, |
139 | (Uint8) current_color); |
140 | } |
141 | if (cycle_alpha) { |
142 | current_alpha += cycle_direction; |
143 | if (current_alpha < 0) { |
144 | current_alpha = 0; |
145 | cycle_direction = -cycle_direction; |
146 | } |
147 | if (current_alpha > 255) { |
148 | current_alpha = 255; |
149 | cycle_direction = -cycle_direction; |
150 | } |
151 | SDL_SetTextureAlphaMod(sprite, (Uint8) current_alpha); |
152 | } |
153 | |
154 | /* Draw a gray background */ |
155 | SDL_SetRenderDrawColor(renderer, 0xA0, 0xA0, 0xA0, 0xFF); |
156 | SDL_RenderClear(renderer); |
157 | |
158 | /* Test points */ |
159 | SDL_SetRenderDrawColor(renderer, 0xFF, 0x00, 0x00, 0xFF); |
160 | SDL_RenderDrawPoint(renderer, 0, 0); |
161 | SDL_RenderDrawPoint(renderer, viewport.w-1, 0); |
162 | SDL_RenderDrawPoint(renderer, 0, viewport.h-1); |
163 | SDL_RenderDrawPoint(renderer, viewport.w-1, viewport.h-1); |
164 | |
165 | /* Test horizontal and vertical lines */ |
166 | SDL_SetRenderDrawColor(renderer, 0x00, 0xFF, 0x00, 0xFF); |
167 | SDL_RenderDrawLine(renderer, 1, 0, viewport.w-2, 0); |
168 | SDL_RenderDrawLine(renderer, 1, viewport.h-1, viewport.w-2, viewport.h-1); |
169 | SDL_RenderDrawLine(renderer, 0, 1, 0, viewport.h-2); |
170 | SDL_RenderDrawLine(renderer, viewport.w-1, 1, viewport.w-1, viewport.h-2); |
171 | |
172 | /* Test fill and copy */ |
173 | SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF); |
174 | temp.x = 1; |
175 | temp.y = 1; |
176 | temp.w = sprite_w; |
177 | temp.h = sprite_h; |
178 | SDL_RenderFillRect(renderer, &temp); |
179 | SDL_RenderCopy(renderer, sprite, NULL, &temp); |
180 | temp.x = viewport.w-sprite_w-1; |
181 | temp.y = 1; |
182 | temp.w = sprite_w; |
183 | temp.h = sprite_h; |
184 | SDL_RenderFillRect(renderer, &temp); |
185 | SDL_RenderCopy(renderer, sprite, NULL, &temp); |
186 | temp.x = 1; |
187 | temp.y = viewport.h-sprite_h-1; |
188 | temp.w = sprite_w; |
189 | temp.h = sprite_h; |
190 | SDL_RenderFillRect(renderer, &temp); |
191 | SDL_RenderCopy(renderer, sprite, NULL, &temp); |
192 | temp.x = viewport.w-sprite_w-1; |
193 | temp.y = viewport.h-sprite_h-1; |
194 | temp.w = sprite_w; |
195 | temp.h = sprite_h; |
196 | SDL_RenderFillRect(renderer, &temp); |
197 | SDL_RenderCopy(renderer, sprite, NULL, &temp); |
198 | |
199 | /* Test diagonal lines */ |
200 | SDL_SetRenderDrawColor(renderer, 0x00, 0xFF, 0x00, 0xFF); |
201 | SDL_RenderDrawLine(renderer, sprite_w, sprite_h, |
202 | viewport.w-sprite_w-2, viewport.h-sprite_h-2); |
203 | SDL_RenderDrawLine(renderer, viewport.w-sprite_w-2, sprite_h, |
204 | sprite_w, viewport.h-sprite_h-2); |
205 | |
206 | /* Conditionally move the sprites, bounce at the wall */ |
207 | if (iterations == -1 || iterations > 0) { |
208 | for (i = 0; i < num_sprites; ++i) { |
209 | position = &positions[i]; |
210 | velocity = &velocities[i]; |
211 | position->x += velocity->x; |
212 | if ((position->x < 0) || (position->x >= (viewport.w - sprite_w))) { |
213 | velocity->x = -velocity->x; |
214 | position->x += velocity->x; |
215 | } |
216 | position->y += velocity->y; |
217 | if ((position->y < 0) || (position->y >= (viewport.h - sprite_h))) { |
218 | velocity->y = -velocity->y; |
219 | position->y += velocity->y; |
220 | } |
221 | |
222 | } |
223 | |
224 | /* Countdown sprite-move iterations and disable color changes at iteration end - used for visual tests. */ |
225 | if (iterations > 0) { |
226 | iterations--; |
227 | if (iterations == 0) { |
228 | cycle_alpha = SDL_FALSE; |
229 | cycle_color = SDL_FALSE; |
230 | } |
231 | } |
232 | } |
233 | |
234 | /* Draw sprites */ |
235 | for (i = 0; i < num_sprites; ++i) { |
236 | position = &positions[i]; |
237 | |
238 | /* Blit the sprite onto the screen */ |
239 | SDL_RenderCopy(renderer, sprite, NULL, position); |
240 | } |
241 | |
242 | /* Update the screen! */ |
243 | SDL_RenderPresent(renderer); |
244 | } |
245 | |
246 | void |
247 | loop() |
248 | { |
249 | Uint32 now; |
250 | int i; |
251 | SDL_Event event; |
252 | |
253 | /* Check for events */ |
254 | while (SDL_PollEvent(&event)) { |
255 | SDLTest_CommonEvent(state, &event, &done); |
256 | } |
257 | for (i = 0; i < state->num_windows; ++i) { |
258 | if (state->windows[i] == NULL) |
259 | continue; |
260 | MoveSprites(state->renderers[i], sprites[i]); |
261 | } |
262 | #ifdef __EMSCRIPTEN__ |
263 | if (done) { |
264 | emscripten_cancel_main_loop(); |
265 | } |
266 | #endif |
267 | |
268 | frames++; |
269 | now = SDL_GetTicks(); |
270 | if (SDL_TICKS_PASSED(now, next_fps_check)) { |
271 | /* Print out some timing information */ |
272 | const Uint32 then = next_fps_check - fps_check_delay; |
273 | const double fps = ((double) frames * 1000) / (now - then); |
274 | SDL_Log("%2.2f frames per second\n" , fps); |
275 | next_fps_check = now + fps_check_delay; |
276 | frames = 0; |
277 | } |
278 | |
279 | } |
280 | |
281 | int |
282 | main(int argc, char *argv[]) |
283 | { |
284 | int i; |
285 | Uint64 seed; |
286 | const char *icon = "icon.bmp" ; |
287 | |
288 | /* Initialize parameters */ |
289 | num_sprites = NUM_SPRITES; |
290 | |
291 | /* Initialize test framework */ |
292 | state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO); |
293 | if (!state) { |
294 | return 1; |
295 | } |
296 | |
297 | for (i = 1; i < argc;) { |
298 | int consumed; |
299 | |
300 | consumed = SDLTest_CommonArg(state, i); |
301 | if (consumed == 0) { |
302 | consumed = -1; |
303 | if (SDL_strcasecmp(argv[i], "--blend" ) == 0) { |
304 | if (argv[i + 1]) { |
305 | if (SDL_strcasecmp(argv[i + 1], "none" ) == 0) { |
306 | blendMode = SDL_BLENDMODE_NONE; |
307 | consumed = 2; |
308 | } else if (SDL_strcasecmp(argv[i + 1], "blend" ) == 0) { |
309 | blendMode = SDL_BLENDMODE_BLEND; |
310 | consumed = 2; |
311 | } else if (SDL_strcasecmp(argv[i + 1], "add" ) == 0) { |
312 | blendMode = SDL_BLENDMODE_ADD; |
313 | consumed = 2; |
314 | } else if (SDL_strcasecmp(argv[i + 1], "mod" ) == 0) { |
315 | blendMode = SDL_BLENDMODE_MOD; |
316 | consumed = 2; |
317 | } else if (SDL_strcasecmp(argv[i + 1], "sub" ) == 0) { |
318 | blendMode = SDL_ComposeCustomBlendMode(SDL_BLENDFACTOR_SRC_ALPHA, SDL_BLENDFACTOR_ONE, SDL_BLENDOPERATION_SUBTRACT, SDL_BLENDFACTOR_ZERO, SDL_BLENDFACTOR_ONE, SDL_BLENDOPERATION_SUBTRACT); |
319 | consumed = 2; |
320 | } |
321 | } |
322 | } else if (SDL_strcasecmp(argv[i], "--iterations" ) == 0) { |
323 | if (argv[i + 1]) { |
324 | iterations = SDL_atoi(argv[i + 1]); |
325 | if (iterations < -1) iterations = -1; |
326 | consumed = 2; |
327 | } |
328 | } else if (SDL_strcasecmp(argv[i], "--cyclecolor" ) == 0) { |
329 | cycle_color = SDL_TRUE; |
330 | consumed = 1; |
331 | } else if (SDL_strcasecmp(argv[i], "--cyclealpha" ) == 0) { |
332 | cycle_alpha = SDL_TRUE; |
333 | consumed = 1; |
334 | } else if (SDL_isdigit(*argv[i])) { |
335 | num_sprites = SDL_atoi(argv[i]); |
336 | consumed = 1; |
337 | } else if (argv[i][0] != '-') { |
338 | icon = argv[i]; |
339 | consumed = 1; |
340 | } |
341 | } |
342 | if (consumed < 0) { |
343 | static const char *options[] = { "[--blend none|blend|add|mod]" , "[--cyclecolor]" , "[--cyclealpha]" , "[--iterations N]" , "[num_sprites]" , "[icon.bmp]" , NULL }; |
344 | SDLTest_CommonLogUsage(state, argv[0], options); |
345 | quit(1); |
346 | } |
347 | i += consumed; |
348 | } |
349 | if (!SDLTest_CommonInit(state)) { |
350 | quit(2); |
351 | } |
352 | |
353 | /* Create the windows, initialize the renderers, and load the textures */ |
354 | sprites = |
355 | (SDL_Texture **) SDL_malloc(state->num_windows * sizeof(*sprites)); |
356 | if (!sprites) { |
357 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory!\n" ); |
358 | quit(2); |
359 | } |
360 | for (i = 0; i < state->num_windows; ++i) { |
361 | SDL_Renderer *renderer = state->renderers[i]; |
362 | SDL_SetRenderDrawColor(renderer, 0xA0, 0xA0, 0xA0, 0xFF); |
363 | SDL_RenderClear(renderer); |
364 | } |
365 | if (LoadSprite(icon) < 0) { |
366 | quit(2); |
367 | } |
368 | |
369 | /* Allocate memory for the sprite info */ |
370 | positions = (SDL_Rect *) SDL_malloc(num_sprites * sizeof(SDL_Rect)); |
371 | velocities = (SDL_Rect *) SDL_malloc(num_sprites * sizeof(SDL_Rect)); |
372 | if (!positions || !velocities) { |
373 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory!\n" ); |
374 | quit(2); |
375 | } |
376 | |
377 | /* Position sprites and set their velocities using the fuzzer */ |
378 | if (iterations >= 0) { |
379 | /* Deterministic seed - used for visual tests */ |
380 | seed = (Uint64)iterations; |
381 | } else { |
382 | /* Pseudo-random seed generated from the time */ |
383 | seed = (Uint64)time(NULL); |
384 | } |
385 | SDLTest_FuzzerInit(seed); |
386 | for (i = 0; i < num_sprites; ++i) { |
387 | positions[i].x = SDLTest_RandomIntegerInRange(0, state->window_w - sprite_w); |
388 | positions[i].y = SDLTest_RandomIntegerInRange(0, state->window_h - sprite_h); |
389 | positions[i].w = sprite_w; |
390 | positions[i].h = sprite_h; |
391 | velocities[i].x = 0; |
392 | velocities[i].y = 0; |
393 | while (!velocities[i].x && !velocities[i].y) { |
394 | velocities[i].x = SDLTest_RandomIntegerInRange(-MAX_SPEED, MAX_SPEED); |
395 | velocities[i].y = SDLTest_RandomIntegerInRange(-MAX_SPEED, MAX_SPEED); |
396 | } |
397 | } |
398 | |
399 | /* Main render loop */ |
400 | frames = 0; |
401 | next_fps_check = SDL_GetTicks() + fps_check_delay; |
402 | done = 0; |
403 | |
404 | #ifdef __EMSCRIPTEN__ |
405 | emscripten_set_main_loop(loop, 0, 1); |
406 | #else |
407 | while (!done) { |
408 | loop(); |
409 | } |
410 | #endif |
411 | |
412 | quit(0); |
413 | return 0; |
414 | } |
415 | |
416 | /* vi: set ts=4 sw=4 expandtab: */ |
417 | |