Simple Directmedia Layer
at main 597 lines 22 kB view raw
1/* 2 Copyright (C) 1997-2024 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#define SDL_MAIN_USE_CALLBACKS 1 15#include <SDL3/SDL_test.h> 16#include <SDL3/SDL_test_common.h> 17#include <SDL3/SDL_main.h> 18#include "testutils.h" 19 20#define NUM_SPRITES 100 21#define MAX_SPEED 1 22 23static SDLTest_CommonState *state; 24static const char *icon = "icon.bmp"; 25static int num_sprites; 26static SDL_Texture **sprites; 27static bool cycle_color; 28static bool cycle_alpha; 29static int cycle_direction = 1; 30static int current_alpha = 0; 31static int current_color = 0; 32static SDL_FRect *positions; 33static SDL_FRect *velocities; 34static float sprite_w, sprite_h; 35static SDL_BlendMode blendMode = SDL_BLENDMODE_BLEND; 36static Uint64 next_fps_check; 37static Uint32 frames; 38static const int fps_check_delay = 5000; 39static int use_rendergeometry = 0; 40static bool suspend_when_occluded; 41 42/* Number of iterations to move sprites - used for visual tests. */ 43/* -1: infinite random moves (default); >=0: enables N deterministic moves */ 44static int iterations = -1; 45 46void SDL_AppQuit(void *appstate, SDL_AppResult result) 47{ 48 SDL_free(sprites); 49 SDL_free(positions); 50 SDL_free(velocities); 51 SDLTest_CommonQuit(state); 52} 53 54static int LoadSprite(const char *file) 55{ 56 int i, w, h; 57 58 for (i = 0; i < state->num_windows; ++i) { 59 /* This does the SDL_LoadBMP step repeatedly, but that's OK for test code. */ 60 if (sprites[i]) { 61 SDL_DestroyTexture(sprites[i]); 62 } 63 sprites[i] = LoadTexture(state->renderers[i], file, true, &w, &h); 64 sprite_w = (float)w; 65 sprite_h = (float)h; 66 if (!sprites[i]) { 67 return -1; 68 } 69 if (!SDL_SetTextureBlendMode(sprites[i], blendMode)) { 70 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't set blend mode: %s\n", SDL_GetError()); 71 SDL_DestroyTexture(sprites[i]); 72 return -1; 73 } 74 } 75 76 /* We're ready to roll. :) */ 77 return 0; 78} 79 80static void MoveSprites(SDL_Renderer *renderer, SDL_Texture *sprite) 81{ 82 int i; 83 SDL_Rect viewport; 84 SDL_FRect temp; 85 SDL_FRect *position, *velocity; 86 87 /* Query the sizes */ 88 SDL_SetRenderViewport(renderer, NULL); 89 SDL_GetRenderSafeArea(renderer, &viewport); 90 SDL_SetRenderViewport(renderer, &viewport); 91 92 /* Cycle the color and alpha, if desired */ 93 if (cycle_color) { 94 current_color += cycle_direction; 95 if (current_color < 0) { 96 current_color = 0; 97 cycle_direction = -cycle_direction; 98 } 99 if (current_color > 255) { 100 current_color = 255; 101 cycle_direction = -cycle_direction; 102 } 103 SDL_SetTextureColorMod(sprite, 255, (Uint8)current_color, 104 (Uint8)current_color); 105 } 106 if (cycle_alpha) { 107 current_alpha += cycle_direction; 108 if (current_alpha < 0) { 109 current_alpha = 0; 110 cycle_direction = -cycle_direction; 111 } 112 if (current_alpha > 255) { 113 current_alpha = 255; 114 cycle_direction = -cycle_direction; 115 } 116 SDL_SetTextureAlphaMod(sprite, (Uint8)current_alpha); 117 } 118 119 /* Draw a gray background */ 120 SDL_SetRenderDrawColor(renderer, 0xA0, 0xA0, 0xA0, 0x00 /* used with --transparent */); 121 SDL_RenderClear(renderer); 122 123 /* Test points */ 124 SDL_SetRenderDrawColor(renderer, 0xFF, 0x00, 0x00, 0xFF); 125 SDL_RenderPoint(renderer, 0.0f, 0.0f); 126 SDL_RenderPoint(renderer, (float)(viewport.w - 1), 0.0f); 127 SDL_RenderPoint(renderer, 0.0f, (float)(viewport.h - 1)); 128 SDL_RenderPoint(renderer, (float)(viewport.w - 1), (float)(viewport.h - 1)); 129 130 /* Test horizontal and vertical lines */ 131 SDL_SetRenderDrawColor(renderer, 0x00, 0xFF, 0x00, 0xFF); 132 SDL_RenderLine(renderer, 1.0f, 0.0f, (float)(viewport.w - 2), 0.0f); 133 SDL_SetRenderDrawColor(renderer, 0xFF, 0x00, 0x00, 0xFF); 134 SDL_RenderLine(renderer, 1.0f, (float)(viewport.h - 1), (float)(viewport.w - 2), (float)(viewport.h - 1)); 135 SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0xFF, 0xFF); 136 SDL_RenderLine(renderer, 0.0f, 1.0f, 0.0f, (float)(viewport.h - 2)); 137 SDL_RenderLine(renderer, (float)(viewport.w - 1), 1.0f, (float)(viewport.w - 1), (float)(viewport.h - 2)); 138 139 /* Test fill and copy */ 140 SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF); 141 temp.x = 1.0f; 142 temp.y = 1.0f; 143 temp.w = sprite_w; 144 temp.h = sprite_h; 145 if (use_rendergeometry == 0) { 146 SDL_RenderFillRect(renderer, &temp); 147 } else { 148 /* Draw two triangles, filled, uniform */ 149 SDL_FColor color; 150 SDL_Vertex verts[3]; 151 SDL_zeroa(verts); 152 color.r = 1.0f; 153 color.g = 1.0f; 154 color.b = 1.0f; 155 color.a = 1.0f; 156 157 verts[0].position.x = temp.x; 158 verts[0].position.y = temp.y; 159 verts[0].color = color; 160 161 verts[1].position.x = temp.x + temp.w; 162 verts[1].position.y = temp.y; 163 verts[1].color = color; 164 165 verts[2].position.x = temp.x + temp.w; 166 verts[2].position.y = temp.y + temp.h; 167 verts[2].color = color; 168 169 SDL_RenderGeometry(renderer, NULL, verts, 3, NULL, 0); 170 171 verts[1].position.x = temp.x; 172 verts[1].position.y = temp.y + temp.h; 173 verts[1].color = color; 174 175 SDL_RenderGeometry(renderer, NULL, verts, 3, NULL, 0); 176 } 177 SDL_RenderTexture(renderer, sprite, NULL, &temp); 178 temp.x = viewport.w - sprite_w - 1; 179 temp.y = 1.0f; 180 temp.w = sprite_w; 181 temp.h = sprite_h; 182 SDL_RenderFillRect(renderer, &temp); 183 SDL_RenderTexture(renderer, sprite, NULL, &temp); 184 temp.x = 1.0f; 185 temp.y = viewport.h - sprite_h - 1; 186 temp.w = sprite_w; 187 temp.h = sprite_h; 188 SDL_RenderFillRect(renderer, &temp); 189 SDL_RenderTexture(renderer, sprite, NULL, &temp); 190 temp.x = viewport.w - sprite_w - 1; 191 temp.y = viewport.h - sprite_h - 1; 192 temp.w = sprite_w; 193 temp.h = sprite_h; 194 SDL_RenderFillRect(renderer, &temp); 195 SDL_RenderTexture(renderer, sprite, NULL, &temp); 196 197 /* Test diagonal lines */ 198 SDL_SetRenderDrawColor(renderer, 0x00, 0xFF, 0x00, 0xFF); 199 SDL_RenderLine(renderer, sprite_w, sprite_h, 200 viewport.w - sprite_w - 2, viewport.h - sprite_h - 2); 201 SDL_RenderLine(renderer, viewport.w - sprite_w - 2, sprite_h, 202 sprite_w, viewport.h - sprite_h - 2); 203 204 /* Conditionally move the sprites, bounce at the wall */ 205 if (iterations == -1 || iterations > 0) { 206 for (i = 0; i < num_sprites; ++i) { 207 position = &positions[i]; 208 velocity = &velocities[i]; 209 position->x += velocity->x; 210 if ((position->x < 0) || (position->x >= (viewport.w - sprite_w))) { 211 velocity->x = -velocity->x; 212 position->x += velocity->x; 213 } 214 position->y += velocity->y; 215 if ((position->y < 0) || (position->y >= (viewport.h - sprite_h))) { 216 velocity->y = -velocity->y; 217 position->y += velocity->y; 218 } 219 } 220 221 /* Countdown sprite-move iterations and disable color changes at iteration end - used for visual tests. */ 222 if (iterations > 0) { 223 iterations--; 224 if (iterations == 0) { 225 cycle_alpha = false; 226 cycle_color = false; 227 } 228 } 229 } 230 231 /* Draw sprites */ 232 if (use_rendergeometry == 0) { 233 for (i = 0; i < num_sprites; ++i) { 234 position = &positions[i]; 235 236 /* Blit the sprite onto the screen */ 237 SDL_RenderTexture(renderer, sprite, NULL, position); 238 } 239 } else if (use_rendergeometry == 1) { 240 /* 241 * 0--1 242 * | /| 243 * |/ | 244 * 3--2 245 * 246 * Draw sprite2 as triangles that can be recombined as rect by software renderer 247 */ 248 SDL_Vertex *verts = (SDL_Vertex *)SDL_malloc(num_sprites * sizeof(SDL_Vertex) * 6); 249 SDL_Vertex *verts2 = verts; 250 if (verts) { 251 SDL_FColor color; 252 SDL_GetTextureColorModFloat(sprite, &color.r, &color.g, &color.b); 253 SDL_GetTextureAlphaModFloat(sprite, &color.a); 254 for (i = 0; i < num_sprites; ++i) { 255 position = &positions[i]; 256 /* 0 */ 257 verts->position.x = position->x; 258 verts->position.y = position->y; 259 verts->color = color; 260 verts->tex_coord.x = 0.0f; 261 verts->tex_coord.y = 0.0f; 262 verts++; 263 /* 1 */ 264 verts->position.x = position->x + position->w; 265 verts->position.y = position->y; 266 verts->color = color; 267 verts->tex_coord.x = 1.0f; 268 verts->tex_coord.y = 0.0f; 269 verts++; 270 /* 2 */ 271 verts->position.x = position->x + position->w; 272 verts->position.y = position->y + position->h; 273 verts->color = color; 274 verts->tex_coord.x = 1.0f; 275 verts->tex_coord.y = 1.0f; 276 verts++; 277 /* 0 */ 278 verts->position.x = position->x; 279 verts->position.y = position->y; 280 verts->color = color; 281 verts->tex_coord.x = 0.0f; 282 verts->tex_coord.y = 0.0f; 283 verts++; 284 /* 2 */ 285 verts->position.x = position->x + position->w; 286 verts->position.y = position->y + position->h; 287 verts->color = color; 288 verts->tex_coord.x = 1.0f; 289 verts->tex_coord.y = 1.0f; 290 verts++; 291 /* 3 */ 292 verts->position.x = position->x; 293 verts->position.y = position->y + position->h; 294 verts->color = color; 295 verts->tex_coord.x = 0.0f; 296 verts->tex_coord.y = 1.0f; 297 verts++; 298 } 299 300 /* Blit sprites as triangles onto the screen */ 301 SDL_RenderGeometry(renderer, sprite, verts2, num_sprites * 6, NULL, 0); 302 SDL_free(verts2); 303 } 304 } else if (use_rendergeometry == 2) { 305 /* 0-----1 306 * |\ A /| 307 * | \ / | 308 * |D 2 B| 309 * | / \ | 310 * |/ C \| 311 * 3-----4 312 * 313 * Draw sprite2 as triangles that can *not* be recombined as rect by software renderer 314 * Use an 'indices' array 315 */ 316 SDL_Vertex *verts = (SDL_Vertex *)SDL_malloc(num_sprites * sizeof(SDL_Vertex) * 5); 317 SDL_Vertex *verts2 = verts; 318 int *indices = (int *)SDL_malloc(num_sprites * sizeof(int) * 4 * 3); 319 int *indices2 = indices; 320 if (verts && indices) { 321 int pos = 0; 322 SDL_FColor color; 323 SDL_GetTextureColorModFloat(sprite, &color.r, &color.g, &color.b); 324 SDL_GetTextureAlphaModFloat(sprite, &color.a); 325 for (i = 0; i < num_sprites; ++i) { 326 position = &positions[i]; 327 /* 0 */ 328 verts->position.x = position->x; 329 verts->position.y = position->y; 330 verts->color = color; 331 verts->tex_coord.x = 0.0f; 332 verts->tex_coord.y = 0.0f; 333 verts++; 334 /* 1 */ 335 verts->position.x = position->x + position->w; 336 verts->position.y = position->y; 337 verts->color = color; 338 verts->tex_coord.x = 1.0f; 339 verts->tex_coord.y = 0.0f; 340 verts++; 341 /* 2 */ 342 verts->position.x = position->x + position->w / 2.0f; 343 verts->position.y = position->y + position->h / 2.0f; 344 verts->color = color; 345 verts->tex_coord.x = 0.5f; 346 verts->tex_coord.y = 0.5f; 347 verts++; 348 /* 3 */ 349 verts->position.x = position->x; 350 verts->position.y = position->y + position->h; 351 verts->color = color; 352 verts->tex_coord.x = 0.0f; 353 verts->tex_coord.y = 1.0f; 354 verts++; 355 /* 4 */ 356 verts->position.x = position->x + position->w; 357 verts->position.y = position->y + position->h; 358 verts->color = color; 359 verts->tex_coord.x = 1.0f; 360 verts->tex_coord.y = 1.0f; 361 verts++; 362 /* A */ 363 *indices++ = pos + 0; 364 *indices++ = pos + 1; 365 *indices++ = pos + 2; 366 /* B */ 367 *indices++ = pos + 1; 368 *indices++ = pos + 2; 369 *indices++ = pos + 4; 370 /* C */ 371 *indices++ = pos + 3; 372 *indices++ = pos + 2; 373 *indices++ = pos + 4; 374 /* D */ 375 *indices++ = pos + 3; 376 *indices++ = pos + 2; 377 *indices++ = pos + 0; 378 pos += 5; 379 } 380 } 381 382 /* Blit sprites as triangles onto the screen */ 383 SDL_RenderGeometry(renderer, sprite, verts2, num_sprites * 5, indices2, num_sprites * 4 * 3); 384 SDL_free(verts2); 385 SDL_free(indices2); 386 } 387 388 /* Update the screen! */ 389 SDL_RenderPresent(renderer); 390} 391 392SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) 393{ 394 SDL_Rect safe_area; 395 int i; 396 Uint64 seed; 397 398 /* Initialize parameters */ 399 num_sprites = NUM_SPRITES; 400 401 /* Initialize test framework */ 402 state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO); 403 if (!state) { 404 return SDL_APP_FAILURE; 405 } 406 407 for (i = 1; i < argc;) { 408 int consumed; 409 410 consumed = SDLTest_CommonArg(state, i); 411 if (consumed == 0) { 412 consumed = -1; 413 if (SDL_strcasecmp(argv[i], "--blend") == 0) { 414 if (argv[i + 1]) { 415 if (SDL_strcasecmp(argv[i + 1], "none") == 0) { 416 blendMode = SDL_BLENDMODE_NONE; 417 consumed = 2; 418 } else if (SDL_strcasecmp(argv[i + 1], "blend") == 0) { 419 blendMode = SDL_BLENDMODE_BLEND; 420 consumed = 2; 421 } else if (SDL_strcasecmp(argv[i + 1], "blend_premultiplied") == 0) { 422 blendMode = SDL_BLENDMODE_BLEND_PREMULTIPLIED; 423 consumed = 2; 424 } else if (SDL_strcasecmp(argv[i + 1], "add") == 0) { 425 blendMode = SDL_BLENDMODE_ADD; 426 consumed = 2; 427 } else if (SDL_strcasecmp(argv[i + 1], "add_premultiplied") == 0) { 428 blendMode = SDL_BLENDMODE_ADD_PREMULTIPLIED; 429 consumed = 2; 430 } else if (SDL_strcasecmp(argv[i + 1], "mod") == 0) { 431 blendMode = SDL_BLENDMODE_MOD; 432 consumed = 2; 433 } else if (SDL_strcasecmp(argv[i + 1], "mul") == 0) { 434 blendMode = SDL_BLENDMODE_MUL; 435 consumed = 2; 436 } else if (SDL_strcasecmp(argv[i + 1], "sub") == 0) { 437 blendMode = SDL_ComposeCustomBlendMode(SDL_BLENDFACTOR_SRC_ALPHA, SDL_BLENDFACTOR_ONE, SDL_BLENDOPERATION_SUBTRACT, SDL_BLENDFACTOR_ZERO, SDL_BLENDFACTOR_ONE, SDL_BLENDOPERATION_SUBTRACT); 438 consumed = 2; 439 } 440 } 441 } else if (SDL_strcasecmp(argv[i], "--iterations") == 0) { 442 if (argv[i + 1]) { 443 iterations = SDL_atoi(argv[i + 1]); 444 if (iterations < -1) { 445 iterations = -1; 446 } 447 consumed = 2; 448 } 449 } else if (SDL_strcasecmp(argv[i], "--cyclecolor") == 0) { 450 cycle_color = true; 451 consumed = 1; 452 } else if (SDL_strcasecmp(argv[i], "--cyclealpha") == 0) { 453 cycle_alpha = true; 454 consumed = 1; 455 } else if (SDL_strcasecmp(argv[i], "--suspend-when-occluded") == 0) { 456 suspend_when_occluded = true; 457 consumed = 1; 458 } else if (SDL_strcasecmp(argv[i], "--use-rendergeometry") == 0) { 459 if (argv[i + 1]) { 460 if (SDL_strcasecmp(argv[i + 1], "mode1") == 0) { 461 /* Draw sprite2 as triangles that can be recombined as rect by software renderer */ 462 use_rendergeometry = 1; 463 } else if (SDL_strcasecmp(argv[i + 1], "mode2") == 0) { 464 /* Draw sprite2 as triangles that can *not* be recombined as rect by software renderer 465 * Use an 'indices' array */ 466 use_rendergeometry = 2; 467 } else { 468 return SDL_APP_FAILURE; 469 } 470 } 471 consumed = 2; 472 } else if (SDL_isdigit(*argv[i])) { 473 num_sprites = SDL_atoi(argv[i]); 474 consumed = 1; 475 } else if (argv[i][0] != '-') { 476 icon = argv[i]; 477 consumed = 1; 478 } 479 } 480 if (consumed < 0) { 481 static const char *options[] = { 482 "[--blend none|blend|blend_premultiplied|add|add_premultiplied|mod|mul|sub]", 483 "[--cyclecolor]", 484 "[--cyclealpha]", 485 "[--suspend-when-occluded]", 486 "[--iterations N]", 487 "[--use-rendergeometry mode1|mode2]", 488 "[num_sprites]", 489 "[icon.bmp]", 490 NULL 491 }; 492 SDLTest_CommonLogUsage(state, argv[0], options); 493 return SDL_APP_FAILURE; 494 } 495 i += consumed; 496 } 497 if (!SDLTest_CommonInit(state)) { 498 return SDL_APP_FAILURE; 499 } 500 501 /* Create the windows, initialize the renderers, and load the textures */ 502 sprites = 503 (SDL_Texture **)SDL_malloc(state->num_windows * sizeof(*sprites)); 504 if (!sprites) { 505 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory!\n"); 506 return SDL_APP_FAILURE; 507 } 508 for (i = 0; i < state->num_windows; ++i) { 509 SDL_Renderer *renderer = state->renderers[i]; 510 SDL_SetRenderDrawColor(renderer, 0xA0, 0xA0, 0xA0, 0xFF); 511 SDL_RenderClear(renderer); 512 } 513 if (LoadSprite(icon) < 0) { 514 return SDL_APP_FAILURE; 515 } 516 517 /* Allocate memory for the sprite info */ 518 positions = (SDL_FRect *)SDL_malloc(num_sprites * sizeof(*positions)); 519 velocities = (SDL_FRect *)SDL_malloc(num_sprites * sizeof(*velocities)); 520 if (!positions || !velocities) { 521 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory!\n"); 522 return SDL_APP_FAILURE; 523 } 524 525 /* Position sprites and set their velocities using the fuzzer */ 526 /* Really we should be using per-window safe area, but this is fine for a simple test */ 527 SDL_GetRenderSafeArea(state->renderers[0], &safe_area); 528 if (iterations >= 0) { 529 /* Deterministic seed - used for visual tests */ 530 seed = (Uint64)iterations; 531 } else { 532 /* Pseudo-random seed generated from the time */ 533 seed = SDL_GetPerformanceCounter(); 534 } 535 SDLTest_FuzzerInit(seed); 536 for (i = 0; i < num_sprites; ++i) { 537 positions[i].x = (float)SDLTest_RandomIntegerInRange(0, (int)(safe_area.w - sprite_w)); 538 positions[i].y = (float)SDLTest_RandomIntegerInRange(0, (int)(safe_area.h - sprite_h)); 539 positions[i].w = sprite_w; 540 positions[i].h = sprite_h; 541 velocities[i].x = 0; 542 velocities[i].y = 0; 543 while (velocities[i].x == 0.f && velocities[i].y == 0.f) { 544 velocities[i].x = (float)SDLTest_RandomIntegerInRange(-MAX_SPEED, MAX_SPEED); 545 velocities[i].y = (float)SDLTest_RandomIntegerInRange(-MAX_SPEED, MAX_SPEED); 546 } 547 } 548 549 /* Main render loop in SDL_AppIterate will begin when this function returns. */ 550 frames = 0; 551 next_fps_check = SDL_GetTicks() + fps_check_delay; 552 553 return SDL_APP_CONTINUE; 554} 555 556 557SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) 558{ 559 if (event->type == SDL_EVENT_RENDER_DEVICE_RESET) { 560 LoadSprite(icon); 561 } 562 return SDLTest_CommonEventMainCallbacks(state, event); 563} 564 565SDL_AppResult SDL_AppIterate(void *appstate) 566{ 567 Uint64 now; 568 int i; 569 int active_windows = 0; 570 571 for (i = 0; i < state->num_windows; ++i) { 572 if (state->windows[i] == NULL || 573 (suspend_when_occluded && (SDL_GetWindowFlags(state->windows[i]) & SDL_WINDOW_OCCLUDED))) { 574 continue; 575 } 576 ++active_windows; 577 MoveSprites(state->renderers[i], sprites[i]); 578 } 579 580 /* If all windows are occluded, throttle the event polling to 15hz. */ 581 if (!active_windows) { 582 SDL_DelayNS(SDL_NS_PER_SECOND / 15); 583 } 584 585 frames++; 586 now = SDL_GetTicks(); 587 if (now >= next_fps_check) { 588 /* Print out some timing information */ 589 const Uint64 then = next_fps_check - fps_check_delay; 590 const double fps = ((double)frames * 1000) / (now - then); 591 SDL_Log("%2.2f frames per second\n", fps); 592 next_fps_check = now + fps_check_delay; 593 frames = 0; 594 } 595 596 return SDL_APP_CONTINUE; 597}