Simple Directmedia Layer
at main 462 lines 14 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/* testgdk: Basic tests of using task queue/xbl (with simple drawing) in GDK. 13 * NOTE: As of June 2022 GDK, login will only work if MicrosoftGame.config is 14 * configured properly. See README-gdk.md. 15 */ 16 17#include <stdlib.h> 18#include <stdio.h> 19#include <time.h> 20 21#include <SDL3/SDL_test.h> 22#include <SDL3/SDL_test_common.h> 23#include "../src/core/windows/SDL_windows.h" 24#include <SDL3/SDL_main.h> 25 26extern "C" { 27#include "../test/testutils.h" 28} 29 30#include <XGameRuntime.h> 31 32#define NUM_SPRITES 100 33#define MAX_SPEED 1 34 35static SDLTest_CommonState *state; 36static int num_sprites; 37static SDL_Texture **sprites; 38static bool cycle_color; 39static bool cycle_alpha; 40static int cycle_direction = 1; 41static int current_alpha = 0; 42static int current_color = 0; 43static int sprite_w, sprite_h; 44static SDL_BlendMode blendMode = SDL_BLENDMODE_BLEND; 45 46int done; 47 48static struct 49{ 50 SDL_AudioSpec spec; 51 Uint8 *sound; /* Pointer to wave data */ 52 Uint32 soundlen; /* Length of wave data */ 53 int soundpos; /* Current play position */ 54} wave; 55 56static SDL_AudioStream *stream; 57 58/* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */ 59static void 60quit(int rc) 61{ 62 SDL_free(sprites); 63 SDL_DestroyAudioStream(stream); 64 SDL_free(wave.sound); 65 SDLTest_CommonQuit(state); 66 /* If rc is 0, just let main return normally rather than calling exit. 67 * This allows testing of platforms where SDL_main is required and does meaningful cleanup. 68 */ 69 if (rc != 0) { 70 exit(rc); 71 } 72} 73 74static int fillerup(void) 75{ 76 const int minimum = (wave.soundlen / SDL_AUDIO_FRAMESIZE(wave.spec)) / 2; 77 if (SDL_GetAudioStreamQueued(stream) < minimum) { 78 SDL_PutAudioStreamData(stream, wave.sound, wave.soundlen); 79 } 80 return 0; 81} 82 83void 84UserLoggedIn(XUserHandle user) 85{ 86 HRESULT hr; 87 char gamertag[128]; 88 hr = XUserGetGamertag(user, XUserGamertagComponent::UniqueModern, sizeof(gamertag), gamertag, NULL); 89 90 if (SUCCEEDED(hr)) { 91 SDL_Log("User logged in: %s", gamertag); 92 } else { 93 SDL_Log("[GDK] UserLoggedIn -- XUserGetGamertag failed: 0x%08x.", hr); 94 } 95 96 XUserCloseHandle(user); 97} 98 99void 100AddUserUICallback(XAsyncBlock *asyncBlock) 101{ 102 HRESULT hr; 103 XUserHandle user = NULL; 104 105 hr = XUserAddResult(asyncBlock, &user); 106 if (SUCCEEDED(hr)) { 107 uint64_t userId; 108 109 hr = XUserGetId(user, &userId); 110 if (FAILED(hr)) { 111 /* If unable to get the user ID, it means the account is banned, etc. */ 112 SDL_Log("[GDK] AddUserSilentCallback -- XUserGetId failed: 0x%08x.", hr); 113 XUserCloseHandle(user); 114 115 /* Per the docs, likely should call XUserResolveIssueWithUiAsync here. */ 116 } else { 117 UserLoggedIn(user); 118 } 119 } else { 120 SDL_Log("[GDK] AddUserUICallback -- XUserAddAsync failed: 0x%08x.", hr); 121 } 122 123 delete asyncBlock; 124} 125 126void 127AddUserUI() 128{ 129 HRESULT hr; 130 XAsyncBlock *asyncBlock = new XAsyncBlock; 131 132 asyncBlock->context = NULL; 133 asyncBlock->queue = NULL; /* A null queue will use the global process task queue */ 134 asyncBlock->callback = &AddUserUICallback; 135 136 hr = XUserAddAsync(XUserAddOptions::None, asyncBlock); 137 138 if (FAILED(hr)) { 139 delete asyncBlock; 140 SDL_Log("[GDK] AddUserSilent -- failed: 0x%08x", hr); 141 } 142} 143 144void 145AddUserSilentCallback(XAsyncBlock *asyncBlock) 146{ 147 HRESULT hr; 148 XUserHandle user = NULL; 149 150 hr = XUserAddResult(asyncBlock, &user); 151 if (SUCCEEDED(hr)) { 152 uint64_t userId; 153 154 hr = XUserGetId(user, &userId); 155 if (FAILED(hr)) { 156 /* If unable to get the user ID, it means the account is banned, etc. */ 157 SDL_Log("[GDK] AddUserSilentCallback -- XUserGetId failed: 0x%08x. Trying with UI.", hr); 158 XUserCloseHandle(user); 159 AddUserUI(); 160 } else { 161 UserLoggedIn(user); 162 } 163 } else { 164 SDL_Log("[GDK] AddUserSilentCallback -- XUserAddAsync failed: 0x%08x. Trying with UI.", hr); 165 AddUserUI(); 166 } 167 168 delete asyncBlock; 169} 170 171void 172AddUserSilent() 173{ 174 HRESULT hr; 175 XAsyncBlock *asyncBlock = new XAsyncBlock; 176 177 asyncBlock->context = NULL; 178 asyncBlock->queue = NULL; /* A null queue will use the global process task queue */ 179 asyncBlock->callback = &AddUserSilentCallback; 180 181 hr = XUserAddAsync(XUserAddOptions::AddDefaultUserSilently, asyncBlock); 182 183 if (FAILED(hr)) { 184 delete asyncBlock; 185 SDL_Log("[GDK] AddUserSilent -- failed: 0x%08x", hr); 186 } 187} 188 189int 190LoadSprite(const char *file) 191{ 192 int i; 193 194 for (i = 0; i < state->num_windows; ++i) { 195 /* This does the SDL_LoadBMP step repeatedly, but that's OK for test code. */ 196 sprites[i] = LoadTexture(state->renderers[i], file, true, &sprite_w, &sprite_h); 197 if (!sprites[i]) { 198 return -1; 199 } 200 if (!SDL_SetTextureBlendMode(sprites[i], blendMode)) { 201 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't set blend mode: %s\n", SDL_GetError()); 202 SDL_DestroyTexture(sprites[i]); 203 return -1; 204 } 205 } 206 207 /* We're ready to roll. :) */ 208 return 0; 209} 210 211void 212DrawSprites(SDL_Renderer * renderer, SDL_Texture * sprite) 213{ 214 SDL_Rect viewport; 215 SDL_FRect temp; 216 217 /* Query the sizes */ 218 SDL_GetRenderViewport(renderer, &viewport); 219 220 /* Cycle the color and alpha, if desired */ 221 if (cycle_color) { 222 current_color += cycle_direction; 223 if (current_color < 0) { 224 current_color = 0; 225 cycle_direction = -cycle_direction; 226 } 227 if (current_color > 255) { 228 current_color = 255; 229 cycle_direction = -cycle_direction; 230 } 231 SDL_SetTextureColorMod(sprite, 255, (Uint8) current_color, 232 (Uint8) current_color); 233 } 234 if (cycle_alpha) { 235 current_alpha += cycle_direction; 236 if (current_alpha < 0) { 237 current_alpha = 0; 238 cycle_direction = -cycle_direction; 239 } 240 if (current_alpha > 255) { 241 current_alpha = 255; 242 cycle_direction = -cycle_direction; 243 } 244 SDL_SetTextureAlphaMod(sprite, (Uint8) current_alpha); 245 } 246 247 /* Draw a gray background */ 248 SDL_SetRenderDrawColor(renderer, 0xA0, 0xA0, 0xA0, 0xFF); 249 SDL_RenderClear(renderer); 250 251 /* Test points */ 252 SDL_SetRenderDrawColor(renderer, 0xFF, 0x00, 0x00, 0xFF); 253 SDL_RenderPoint(renderer, 0.0f, 0.0f); 254 SDL_RenderPoint(renderer, (float)(viewport.w - 1), 0.0f); 255 SDL_RenderPoint(renderer, 0.0f, (float)(viewport.h - 1)); 256 SDL_RenderPoint(renderer, (float)(viewport.w - 1), (float)(viewport.h - 1)); 257 258 /* Test horizontal and vertical lines */ 259 SDL_SetRenderDrawColor(renderer, 0x00, 0xFF, 0x00, 0xFF); 260 SDL_RenderLine(renderer, 1.0f, 0.0f, (float)(viewport.w - 2), 0.0f); 261 SDL_RenderLine(renderer, 1.0f, (float)(viewport.h - 1), (float)(viewport.w - 2), (float)(viewport.h - 1)); 262 SDL_RenderLine(renderer, 0.0f, 1.0f, 0.0f, (float)(viewport.h - 2)); 263 SDL_RenderLine(renderer, (float)(viewport.w - 1), 1, (float)(viewport.w - 1), (float)(viewport.h - 2)); 264 265 /* Test fill and copy */ 266 SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF); 267 temp.x = 1.0f; 268 temp.y = 1.0f; 269 temp.w = (float)sprite_w; 270 temp.h = (float)sprite_h; 271 SDL_RenderFillRect(renderer, &temp); 272 SDL_RenderTexture(renderer, sprite, NULL, &temp); 273 temp.x = (float)(viewport.w-sprite_w-1); 274 temp.y = 1.0f; 275 temp.w = (float)sprite_w; 276 temp.h = (float)sprite_h; 277 SDL_RenderFillRect(renderer, &temp); 278 SDL_RenderTexture(renderer, sprite, NULL, &temp); 279 temp.x = 1.0f; 280 temp.y = (float)(viewport.h-sprite_h-1); 281 temp.w = (float)sprite_w; 282 temp.h = (float)sprite_h; 283 SDL_RenderFillRect(renderer, &temp); 284 SDL_RenderTexture(renderer, sprite, NULL, &temp); 285 temp.x = (float)(viewport.w-sprite_w-1); 286 temp.y = (float)(viewport.h-sprite_h-1); 287 temp.w = (float)(sprite_w); 288 temp.h = (float)(sprite_h); 289 SDL_RenderFillRect(renderer, &temp); 290 SDL_RenderTexture(renderer, sprite, NULL, &temp); 291 292 /* Test diagonal lines */ 293 SDL_SetRenderDrawColor(renderer, 0x00, 0xFF, 0x00, 0xFF); 294 SDL_RenderLine(renderer, (float)sprite_w, (float)sprite_h, 295 (float)(viewport.w-sprite_w-2), (float)(viewport.h-sprite_h-2)); 296 SDL_RenderLine(renderer, (float)(viewport.w-sprite_w-2), (float)sprite_h, 297 (float)sprite_w, (float)(viewport.h-sprite_h-2)); 298 299 /* Update the screen! */ 300 SDL_RenderPresent(renderer); 301} 302 303void 304loop() 305{ 306 int i; 307 SDL_Event event; 308 309 /* Check for events */ 310 while (SDL_PollEvent(&event)) { 311 if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat) { 312 SDL_Log("Initial SDL_EVENT_KEY_DOWN: %s", SDL_GetScancodeName(event.key.scancode)); 313 } 314#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES) 315 /* On Xbox, ignore the keydown event because the features aren't supported */ 316 if (event.type != SDL_EVENT_KEY_DOWN) { 317 SDLTest_CommonEvent(state, &event, &done); 318 } 319#else 320 SDLTest_CommonEvent(state, &event, &done); 321#endif 322 } 323 for (i = 0; i < state->num_windows; ++i) { 324 if (state->windows[i] == NULL) { 325 continue; 326 } 327 DrawSprites(state->renderers[i], sprites[i]); 328 } 329 fillerup(); 330} 331 332int 333main(int argc, char *argv[]) 334{ 335 int i; 336 const char *icon = "icon.bmp"; 337 char *soundname = NULL; 338 339 /* Initialize parameters */ 340 num_sprites = NUM_SPRITES; 341 342 /* Initialize test framework */ 343 state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO | SDL_INIT_AUDIO); 344 if (!state) { 345 return 1; 346 } 347 348 for (i = 1; i < argc;) { 349 int consumed; 350 351 consumed = SDLTest_CommonArg(state, i); 352 if (consumed == 0) { 353 consumed = -1; 354 if (SDL_strcasecmp(argv[i], "--blend") == 0) { 355 if (argv[i + 1]) { 356 if (SDL_strcasecmp(argv[i + 1], "none") == 0) { 357 blendMode = SDL_BLENDMODE_NONE; 358 consumed = 2; 359 } else if (SDL_strcasecmp(argv[i + 1], "blend") == 0) { 360 blendMode = SDL_BLENDMODE_BLEND; 361 consumed = 2; 362 } else if (SDL_strcasecmp(argv[i + 1], "add") == 0) { 363 blendMode = SDL_BLENDMODE_ADD; 364 consumed = 2; 365 } else if (SDL_strcasecmp(argv[i + 1], "mod") == 0) { 366 blendMode = SDL_BLENDMODE_MOD; 367 consumed = 2; 368 } else if (SDL_strcasecmp(argv[i + 1], "sub") == 0) { 369 blendMode = SDL_ComposeCustomBlendMode(SDL_BLENDFACTOR_SRC_ALPHA, SDL_BLENDFACTOR_ONE, SDL_BLENDOPERATION_SUBTRACT, SDL_BLENDFACTOR_ZERO, SDL_BLENDFACTOR_ONE, SDL_BLENDOPERATION_SUBTRACT); 370 consumed = 2; 371 } 372 } 373 } else if (SDL_strcasecmp(argv[i], "--cyclecolor") == 0) { 374 cycle_color = true; 375 consumed = 1; 376 } else if (SDL_strcasecmp(argv[i], "--cyclealpha") == 0) { 377 cycle_alpha = true; 378 consumed = 1; 379 } else if (SDL_isdigit(*argv[i])) { 380 num_sprites = SDL_atoi(argv[i]); 381 consumed = 1; 382 } else if (argv[i][0] != '-') { 383 icon = argv[i]; 384 consumed = 1; 385 } 386 } 387 if (consumed < 0) { 388 static const char *options[] = { 389 "[--blend none|blend|add|mod]", 390 "[--cyclecolor]", 391 "[--cyclealpha]", 392 "[num_sprites]", 393 "[icon.bmp]", 394 NULL }; 395 SDLTest_CommonLogUsage(state, argv[0], options); 396 quit(1); 397 } 398 i += consumed; 399 } 400 if (!SDLTest_CommonInit(state)) { 401 quit(2); 402 } 403 404 /* Create the windows, initialize the renderers, and load the textures */ 405 sprites = 406 (SDL_Texture **) SDL_malloc(state->num_windows * sizeof(*sprites)); 407 if (!sprites) { 408 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory!\n"); 409 quit(2); 410 } 411 for (i = 0; i < state->num_windows; ++i) { 412 SDL_Renderer *renderer = state->renderers[i]; 413 SDL_SetRenderDrawColor(renderer, 0xA0, 0xA0, 0xA0, 0xFF); 414 SDL_RenderClear(renderer); 415 } 416 if (LoadSprite(icon) < 0) { 417 quit(2); 418 } 419 420 soundname = GetResourceFilename(argc > 1 ? argv[1] : NULL, "sample.wav"); 421 422 if (!soundname) { 423 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s\n", SDL_GetError()); 424 quit(1); 425 } 426 427 /* Load the wave file into memory */ 428 if (!SDL_LoadWAV(soundname, &wave.spec, &wave.sound, &wave.soundlen)) { 429 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't load %s: %s\n", soundname, SDL_GetError()); 430 quit(1); 431 } 432 433 /* Show the list of available drivers */ 434 SDL_Log("Available audio drivers:"); 435 for (i = 0; i < SDL_GetNumAudioDrivers(); ++i) { 436 SDL_Log("%i: %s", i, SDL_GetAudioDriver(i)); 437 } 438 439 SDL_Log("Using audio driver: %s\n", SDL_GetCurrentAudioDriver()); 440 441 stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &wave.spec, NULL, NULL); 442 if (!stream) { 443 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create audio stream: %s\n", SDL_GetError()); 444 return -1; 445 } 446 SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(stream)); 447 448 /* Main render loop */ 449 done = 0; 450 451 /* Try to add the default user silently */ 452 AddUserSilent(); 453 454 while (!done) { 455 loop(); 456 } 457 458 quit(0); 459 460 SDL_free(soundname); 461 return 0; 462}