#include "qs_splash.h" #include #include #include #include #include "xr_session.h" #define QS_SPLASH_HOLD_MS 2500 #define QS_SPLASH_FADE_MS 500 #define QS_SPLASH_TOTAL_MS (QS_SPLASH_HOLD_MS + 2 * QS_SPLASH_FADE_MS) // The XR session only starts once frames are polled, so the display time is // counted from the first submitted frame; this bounds the wait for it. #define QS_SPLASH_SESSION_TIMEOUT_MS 5000 #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "QuestShock", __VA_ARGS__) extern void pump_events(void); // OpenGL.cc (engine patch 11): draws a software frame into the XR game swapchain. extern void android_composite_software_frame(SDL_Surface *ui); static SDL_Surface *s_image = NULL; void qs_splash_set_image(const uint32_t *argb, int width, int height) { if (s_image != NULL) { SDL_FreeSurface(s_image); s_image = NULL; } if (argb == NULL || width <= 0 || height <= 0) return; s_image = SDL_CreateRGBSurfaceWithFormat(0, width, height, 32, SDL_PIXELFORMAT_ARGB8888); if (s_image == NULL) return; for (int y = 0; y < height; y++) memcpy((uint8_t *)s_image->pixels + (size_t)y * s_image->pitch, argb + (size_t)y * width, (size_t)width * sizeof(uint32_t)); SDL_SetSurfaceBlendMode(s_image, SDL_BLENDMODE_BLEND); } // 0-255 brightness at `elapsed` ms after the first frame. static int qs_splash_level(uint32_t elapsed) { uint32_t edge = elapsed < QS_SPLASH_TOTAL_MS - elapsed ? elapsed : QS_SPLASH_TOTAL_MS - elapsed; if (edge >= QS_SPLASH_FADE_MS) return 255; return (int)(255 * edge / QS_SPLASH_FADE_MS); } void qs_splash_show(void) { if (s_image == NULL) { LOGI("Startup splash: no image"); return; } int width, height; xr_get_game_swapchain_size(&width, &height); if (width <= 0 || height <= 0) { LOGI("Startup splash: no XR game swapchain"); return; } // The image is stretched over the whole swapchain image, so it is // letterboxed into a canvas of that size to keep its aspect ratio. SDL_Surface *canvas = SDL_CreateRGBSurfaceWithFormat(0, width, height, 32, SDL_PIXELFORMAT_RGBA32); if (canvas == NULL) return; const float scale = SDL_min((float)width / s_image->w, (float)height / s_image->h); SDL_Rect dst; dst.w = (int)(s_image->w * scale); dst.h = (int)(s_image->h * scale); dst.x = (width - dst.w) / 2; dst.y = (height - dst.h) / 2; const uint32_t waitStart = SDL_GetTicks(); uint32_t shownStart = 0; uint32_t elapsed = 0; int drawnLevel = -1; bool done = false; while (!done) { pump_events(); xr_poll_events(); if (xr_frame_begin()) { if (shownStart == 0) shownStart = SDL_GetTicks(); elapsed = SDL_GetTicks() - shownStart; if (elapsed >= QS_SPLASH_TOTAL_MS) { elapsed = QS_SPLASH_TOTAL_MS; done = true; } const int level = qs_splash_level(elapsed); if (level != drawnLevel) { drawnLevel = level; SDL_FillRect(canvas, NULL, SDL_MapRGB(canvas->format, 0, 0, 0)); SDL_SetSurfaceColorMod(s_image, level, level, level); SDL_BlitScaled(s_image, NULL, canvas, &dst); } android_composite_software_frame(canvas); } else { SDL_Delay(10); if (shownStart == 0 && SDL_GetTicks() - waitStart > QS_SPLASH_SESSION_TIMEOUT_MS) done = true; } xr_frame_end(); } LOGI("Startup splash: %s after %u ms", shownStart != 0 ? "shown" : "XR session did not start", (unsigned)(SDL_GetTicks() - waitStart)); SDL_FreeSurface(canvas); }