SDL/test/testsprite.c

598 lines
21 KiB
C
Raw Permalink Normal View History

/*
2024-01-02 05:15:26 +08:00
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely.
*/
/* Simple program: Move N sprites around on the screen as fast as possible */
main: Added _optional_ callback entry points. This lets apps optionally have a handful of callbacks for their entry points instead of a single main function. If used, the actual main/SDL_main/whatever entry point will be implemented in the single-header library SDL_main.h and the app will implement four separate functions: First: int SDL_AppInit(int argc, char **argv); This will be called once before anything else. argc/argv work like they always do. If this returns 0, the app runs. If it returns < 0, the app calls SDL_AppQuit and terminates with an exit code that reports an error to the platform. If it returns > 0, the app calls SDL_AppQuit and terminates with an exit code that reports success to the platform. This function should not go into an infinite mainloop; it should do any one-time startup it requires and then return. Then: int SDL_AppIterate(void); This is called over and over, possibly at the refresh rate of the display or some other metric that the platform dictates. This is where the heart of your app runs. It should return as quickly as reasonably possible, but it's not a "run one memcpy and that's all the time you have" sort of thing. The app should do any game updates, and render a frame of video. If it returns < 0, SDL will call SDL_AppQuit and terminate the process with an exit code that reports an error to the platform. If it returns > 0, the app calls SDL_AppQuit and terminates with an exit code that reports success to the platform. If it returns 0, then SDL_AppIterate will be called again at some regular frequency. The platform may choose to run this more or less (perhaps less in the background, etc), or it might just call this function in a loop as fast as possible. You do not check the event queue in this function (SDL_AppEvent exists for that). Next: int SDL_AppEvent(const SDL_Event *event); This will be called once for each event pushed into the SDL queue. This may be called from any thread, and possibly in parallel to SDL_AppIterate. The fields in event do not need to be free'd (as you would normally need to do for SDL_EVENT_DROP_FILE, etc), and your app should not call SDL_PollEvent, SDL_PumpEvent, etc, as SDL will manage this for you. Return values are the same as from SDL_AppIterate(), so you can terminate in response to SDL_EVENT_QUIT, etc. Finally: void SDL_AppQuit(void); This is called once before terminating the app--assuming the app isn't being forcibly killed or crashed--as a last chance to clean up. After this returns, SDL will call SDL_Quit so the app doesn't have to (but it's safe for the app to call it, too). Process termination proceeds as if the app returned normally from main(), so atexit handles will run, if your platform supports that. The app does not implement SDL_main if using this. To turn this on, define SDL_MAIN_USE_CALLBACKS before including SDL_main.h. Defines like SDL_MAIN_HANDLED and SDL_MAIN_NOIMPL are also respected for callbacks, if the app wants to do some sort of magic main implementation thing. In theory, on most platforms these can be implemented in the app itself, but this saves some #ifdefs in the app and lets everyone struggle less against some platforms, and might be more efficient in the long run, too. On some platforms, it's possible this is the only reasonable way to go, but we haven't actually hit one that 100% requires it yet (but we will, if we want to write a RetroArch backend, for example). Using the callback entry points works on every platform, because on platforms that don't require them, we can fake them with a simple loop in an internal implementation of the usual SDL_main. The primary way we expect people to write SDL apps is with SDL_main, and this is not intended to replace it. If the app chooses to use this, it just removes some platform-specific details they might have to otherwise manage, and maybe removes a barrier to entry on some future platform. Fixes #6785. Reference PR #8247.
2023-11-02 06:40:41 +08:00
#define SDL_MAIN_USE_CALLBACKS 1
#include <SDL3/SDL_test.h>
#include <SDL3/SDL_test_common.h>
#include <SDL3/SDL_main.h>
#include "testutils.h"
#define NUM_SPRITES 100
#define MAX_SPEED 1
static SDLTest_CommonState *state;
static const char *icon = "icon.bmp";
static int num_sprites;
static SDL_Texture **sprites;
static bool cycle_color;
static bool cycle_alpha;
static int cycle_direction = 1;
static int current_alpha = 0;
static int current_color = 0;
static SDL_FRect *positions;
static SDL_FRect *velocities;
static float sprite_w, sprite_h;
static SDL_BlendMode blendMode = SDL_BLENDMODE_BLEND;
static Uint64 next_fps_check;
static Uint32 frames;
static const int fps_check_delay = 5000;
static int use_rendergeometry = 0;
static bool suspend_when_occluded;
/* Number of iterations to move sprites - used for visual tests. */
/* -1: infinite random moves (default); >=0: enables N deterministic moves */
static int iterations = -1;
void SDL_AppQuit(void *appstate, SDL_AppResult result)
{
SDL_free(sprites);
SDL_free(positions);
SDL_free(velocities);
SDLTest_CommonQuit(state);
}
static int LoadSprite(const char *file)
{
int i, w, h;
for (i = 0; i < state->num_windows; ++i) {
/* This does the SDL_LoadBMP step repeatedly, but that's OK for test code. */
if (sprites[i]) {
SDL_DestroyTexture(sprites[i]);
}
sprites[i] = LoadTexture(state->renderers[i], file, true, &w, &h);
sprite_w = (float)w;
sprite_h = (float)h;
if (!sprites[i]) {
return -1;
}
Use SDL_bool instead an int return code in the SDL API Most SDL functions used to indicate success or failure using an int return code. These functions have been changed to return SDL_bool. Here is a coccinelle patch to change code that previously compared the return value to 0 and changes it to a boolean test: @ bool_return_type @ identifier func =~ "^(SDL_AddEventWatch|SDL_AddHintCallback|SDL_AddSurfaceAlternateImage|SDL_AddVulkanRenderSemaphores|SDL_BindAudioStream|SDL_BindAudioStreams|SDL_BlitSurface|SDL_BlitSurface9Grid|SDL_BlitSurfaceScaled|SDL_BlitSurfaceTiled|SDL_BlitSurfaceTiledWithScale|SDL_BlitSurfaceUnchecked|SDL_BlitSurfaceUncheckedScaled|SDL_CaptureMouse|SDL_ClearAudioStream|SDL_ClearClipboardData|SDL_ClearComposition|SDL_ClearError|SDL_ClearProperty|SDL_ClearSurface|SDL_CloseIO|SDL_CloseStorage|SDL_ConvertAudioSamples|SDL_ConvertEventToRenderCoordinates|SDL_ConvertPixels|SDL_ConvertPixelsAndColorspace|SDL_CopyFile|SDL_CopyProperties|SDL_CopyStorageFile|SDL_CreateDirectory|SDL_CreateStorageDirectory|SDL_CreateWindowAndRenderer|SDL_DateTimeToTime|SDL_DestroyWindowSurface|SDL_DetachVirtualJoystick|SDL_DisableScreenSaver|SDL_EnableScreenSaver|SDL_EnumerateDirectory|SDL_EnumerateProperties|SDL_EnumerateStorageDirectory|SDL_FillSurfaceRect|SDL_FillSurfaceRects|SDL_FlashWindow|SDL_FlipSurface|SDL_FlushAudioStream|SDL_FlushRenderer|SDL_GL_DestroyContext|SDL_GL_GetAttribute|SDL_GL_GetSwapInterval|SDL_GL_LoadLibrary|SDL_GL_MakeCurrent|SDL_GL_SetAttribute|SDL_GL_SetSwapInterval|SDL_GL_SwapWindow|SDL_GetAudioDeviceFormat|SDL_GetAudioStreamFormat|SDL_GetCameraFormat|SDL_GetClosestFullscreenDisplayMode|SDL_GetCurrentRenderOutputSize|SDL_GetCurrentTime|SDL_GetDXGIOutputInfo|SDL_GetDateTimeLocalePreferences|SDL_GetDisplayBounds|SDL_GetDisplayUsableBounds|SDL_GetGDKDefaultUser|SDL_GetGDKTaskQueue|SDL_GetGamepadSensorData|SDL_GetGamepadTouchpadFinger|SDL_GetHapticEffectStatus|SDL_GetJoystickBall|SDL_GetMasksForPixelFormat|SDL_GetPathInfo|SDL_GetRectUnion|SDL_GetRectUnionFloat|SDL_GetRenderClipRect|SDL_GetRenderColorScale|SDL_GetRenderDrawBlendMode|SDL_GetRenderDrawColor|SDL_GetRenderDrawColorFloat|SDL_GetRenderLogicalPresentation|SDL_GetRenderLogicalPresentationRect|SDL_GetRenderOutputSize|SDL_GetRenderSafeArea|SDL_GetRenderScale|SDL_GetRenderVSync|SDL_GetRenderViewport|SDL_GetSensorData|SDL_GetStorageFileSize|SDL_GetStoragePathInfo|SDL_GetSurfaceAlphaMod|SDL_GetSurfaceBlendMode|SDL_GetSurfaceClipRect|SDL_GetSurfaceColorKey|SDL_GetSurfaceColorMod|SDL_GetTextInputArea|SDL_GetTextureAlphaMod|SDL_GetTextureAlphaModFloat|SDL_GetTextureBlendMode|SDL_GetTextureColorMod|SDL_GetTextureColorModFloat|SDL_GetTextureScaleMode|SDL_GetTextureSize|SDL_GetWindowAspectRatio|SDL_GetWindowBordersSize|SDL_GetWindowMaximumSize|SDL_GetWindowMinimumSize|SDL_GetWindowPosition|SDL_GetWindowRelativeMouseMode|SDL_GetWindowSafeArea|SDL_GetWindowSize|SDL_GetWindowSizeInPixels|SDL_GetWindowSurfaceVSync|SDL_HideCursor|SDL_HideWindow|SDL_Init|SDL_InitHapticRumble|SDL_InitSubSystem|SDL_LoadWAV|SDL_LoadWAV_IO|SDL_LockAudioStream|SDL_LockProperties|SDL_LockSurface|SDL_LockTexture|SDL_LockTextureToSurface|SDL_MaximizeWindow|SDL_MinimizeWindow|SDL_MixAudio|SDL_OpenURL|SDL_OutOfMemory|SDL_PauseAudioDevice|SDL_PauseAudioStreamDevice|SDL_PauseHaptic|SDL_PlayHapticRumble|SDL_PremultiplyAlpha|SDL_PremultiplySurfaceAlpha|SDL_PushEvent|SDL_PutAudioStreamData|SDL_RaiseWindow|SDL_ReadStorageFile|SDL_ReadSurfacePixel|SDL_ReadSurfacePixelFloat|SDL_RegisterApp|SDL_ReloadGamepadMappings|SDL_RemovePath|SDL_RemoveStoragePath|SDL_RemoveTimer|SDL_RenamePath|SDL_RenameStoragePath|SDL_RenderClear|SDL_RenderCoordinatesFromWindow|SDL_RenderCoordinatesToWindow|SDL_RenderFillRect|SDL_RenderFillRects|SDL_RenderGeometry|SDL_RenderGeometryRaw|SDL_RenderLine|SDL_RenderLines|SDL_RenderPoint|SDL_RenderPoints|SDL_RenderPresent|SDL_RenderRect|SDL_RenderRects|SDL_RenderTexture|SDL_RenderTexture9Grid|SDL_RenderTextureRotated|SDL_RenderTextureTiled|SDL_RequestAndroidPermission|SDL_RestoreWindow|SDL_ResumeAudioDevice|SDL_ResumeAudioStreamDevice|SDL_ResumeHaptic|SDL_RumbleGamepad|SDL_RumbleGamepadTriggers|SDL_RumbleJoystick|SDL_RumbleJoystickTriggers|SDL_RunHapticEffect|SDL_SaveBMP|SDL_SaveBMP_IO|SDL_SendAndroidMessage|SDL_SendGamepadEffect|SDL_SendJoystickEffect|SDL_SendJoystickVirtualSensorData|SDL_SetAppMetadata|SDL_SetAppMetadataProperty|SDL_SetAudioDeviceGain|SDL_SetAudioPostmixCallback|SDL_SetAudioStreamFormat|SDL_SetAudioStreamFrequencyRatio|SDL_SetAudioStreamGain|SDL_SetAudioStreamGetCallback|SDL_SetAudioStreamInputChannelMap|SDL_SetAudioStreamOutputChannelMap|SDL_SetAudioStreamPutCallback|SDL_SetBooleanProperty|SDL_SetClipboardData|SDL_SetClipboardText|SDL_SetCursor|SDL_SetFloatProperty|SDL_SetGamepadLED|SDL_SetGamepadMapping|SDL_SetGamepadPlayerIndex|SDL_SetGamepadSensorEnabled|SDL_SetHapticAutocenter|SDL_SetHapticGain|SDL_SetJoystickLED|SDL_SetJoystickPlayerIndex|SDL_SetJoystickVirtualAxis|SDL_SetJoystickVirtualBall|SDL_SetJoystickVirtualButton|SDL_SetJoystickVirtualHat|SDL_SetJoystickVirtualTouchpad|SDL_SetLinuxThreadPriority|SDL_SetLinuxThreadPriorityAndPolicy|SDL_SetLogPriorityPrefix|SDL_SetMemoryFunctions|SDL_SetNumberProperty|SDL_SetPaletteColors|SDL_SetPointerProperty|SDL_SetPointerPropertyWithCleanup|SDL_SetPrimarySelectionText|SDL_SetRenderClipRect|SDL_SetRenderColorScale|SDL_SetRenderDrawBlendMode|SDL_SetRenderDrawColor|SDL_SetRenderDrawColorFloat|SDL_SetRenderLogicalPresentation|SDL_SetRenderScale|SDL_SetRenderTarget|SDL_SetRenderVSync|SDL_SetRenderViewport|SDL_SetScancodeName|SDL_SetStringProperty|SDL_SetSurfaceAlphaMod|SDL_SetSurfaceBlendMode|SDL_SetSurfaceColorKey|SDL_SetSurfaceColorMod|SDL_SetSurfaceColorspace|SDL_SetSurfacePalette|SDL_SetSurfaceRLE|SDL_SetTLS|SDL_SetTextInputArea|SDL_SetTextureAlphaMod|SDL_SetTextureAlphaModFloat|SDL_SetTextureBlendMode|SDL_SetTextureColorMod|SDL_SetTextureColorModFloat|SDL_SetTextureScaleMode|SDL_SetThreadPriority|SDL_SetWindowAlwaysOnTop|SDL_SetWindowAspectRatio|SDL_SetWindowBordered|SDL_SetWindowFocusable|SDL_SetWindowFullscreen|SDL_SetWindowFullscreenMode|SDL_SetWindowHitTest|SDL_SetWindowIcon|SDL_SetWindowKeyboardGrab|SDL_SetWindowMaximumSize|SDL_SetWindowMinimumSize|SDL_SetWindowModalFor|SDL_SetWindowMouseGrab|SDL_SetWindowMouseRect|SDL_SetWindowOpacity|SDL_SetWindowPosition|SDL_SetWindowRelativeMouseMode|SDL_SetWindowResizable|SDL_SetWindowShape|SDL_SetWindowSize|SDL_SetWindowSurfaceVSync|SDL_SetWindowTitle|SDL_SetiOSAnimationCallback|SDL_ShowAndroidToast|SDL_ShowCursor|SDL_ShowMessageBox|SDL_ShowSimpleMessageBox|SDL_ShowWindow|SDL_ShowWindowSystemMenu|SDL_StartTextInput|SDL_StartTextInputWithProperties|SDL_StopHapticEffect|SDL_StopHapticEffects|SDL_StopHapticRumble|SDL_StopTextInput|SDL_SyncWindow|SDL_TimeToDateTime|SDL_TryLockMutex|SDL_TryLockRWLockForReading|SDL_TryLockRWLockForWriting|SDL_TryWaitSemaphore|SDL_UnlockAudioStream|SDL_UpdateHapticEffect|SDL_UpdateNVTexture|SDL_UpdateTexture|SDL_UpdateWindowSurface|SDL_UpdateWindowSurfaceRects|SDL_UpdateYUVTexture|SDL_Vulkan_CreateSurface|SDL_Vulkan_LoadLibrary|SDL_WaitConditionTimeout|SDL_WaitSemaphoreTimeout|SDL_WarpMouseGlobal|SDL_WriteStorageFile|SDL_WriteSurfacePixel|SDL_WriteSurfacePixelFloat)$"; @@ ( func( ... ) - == 0 | - func( + !func( ... ) - < 0 | - func( + !func( ... ) - != 0 | - func( + !func( ... ) - == -1 )
2024-08-23 08:33:49 +08:00
if (!SDL_SetTextureBlendMode(sprites[i], blendMode)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't set blend mode: %s\n", SDL_GetError());
SDL_DestroyTexture(sprites[i]);
return -1;
}
}
/* We're ready to roll. :) */
return 0;
}
static void MoveSprites(SDL_Renderer *renderer, SDL_Texture *sprite)
{
int i;
SDL_Rect viewport;
SDL_FRect temp;
SDL_FRect *position, *velocity;
/* Query the sizes */
2024-07-25 08:14:00 +08:00
SDL_SetRenderViewport(renderer, NULL);
SDL_GetRenderSafeArea(renderer, &viewport);
SDL_SetRenderViewport(renderer, &viewport);
/* Cycle the color and alpha, if desired */
if (cycle_color) {
current_color += cycle_direction;
if (current_color < 0) {
current_color = 0;
cycle_direction = -cycle_direction;
}
if (current_color > 255) {
current_color = 255;
cycle_direction = -cycle_direction;
}
SDL_SetTextureColorMod(sprite, 255, (Uint8)current_color,
(Uint8)current_color);
}
if (cycle_alpha) {
current_alpha += cycle_direction;
if (current_alpha < 0) {
current_alpha = 0;
cycle_direction = -cycle_direction;
}
if (current_alpha > 255) {
current_alpha = 255;
cycle_direction = -cycle_direction;
}
SDL_SetTextureAlphaMod(sprite, (Uint8)current_alpha);
}
/* Draw a gray background */
SDL_SetRenderDrawColor(renderer, 0xA0, 0xA0, 0xA0, 0x00 /* used with --transparent */);
SDL_RenderClear(renderer);
/* Test points */
SDL_SetRenderDrawColor(renderer, 0xFF, 0x00, 0x00, 0xFF);
SDL_RenderPoint(renderer, 0.0f, 0.0f);
SDL_RenderPoint(renderer, (float)(viewport.w - 1), 0.0f);
SDL_RenderPoint(renderer, 0.0f, (float)(viewport.h - 1));
SDL_RenderPoint(renderer, (float)(viewport.w - 1), (float)(viewport.h - 1));
/* Test horizontal and vertical lines */
SDL_SetRenderDrawColor(renderer, 0x00, 0xFF, 0x00, 0xFF);
SDL_RenderLine(renderer, 1.0f, 0.0f, (float)(viewport.w - 2), 0.0f);
SDL_SetRenderDrawColor(renderer, 0xFF, 0x00, 0x00, 0xFF);
SDL_RenderLine(renderer, 1.0f, (float)(viewport.h - 1), (float)(viewport.w - 2), (float)(viewport.h - 1));
SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0xFF, 0xFF);
SDL_RenderLine(renderer, 0.0f, 1.0f, 0.0f, (float)(viewport.h - 2));
SDL_RenderLine(renderer, (float)(viewport.w - 1), 1.0f, (float)(viewport.w - 1), (float)(viewport.h - 2));
/* Test fill and copy */
SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF);
temp.x = 1.0f;
temp.y = 1.0f;
temp.w = sprite_w;
temp.h = sprite_h;
if (use_rendergeometry == 0) {
SDL_RenderFillRect(renderer, &temp);
} else {
/* Draw two triangles, filled, uniform */
SDL_FColor color;
SDL_Vertex verts[3];
SDL_zeroa(verts);
color.r = 1.0f;
color.g = 1.0f;
color.b = 1.0f;
color.a = 1.0f;
verts[0].position.x = temp.x;
verts[0].position.y = temp.y;
verts[0].color = color;
verts[1].position.x = temp.x + temp.w;
verts[1].position.y = temp.y;
verts[1].color = color;
verts[2].position.x = temp.x + temp.w;
verts[2].position.y = temp.y + temp.h;
verts[2].color = color;
SDL_RenderGeometry(renderer, NULL, verts, 3, NULL, 0);
verts[1].position.x = temp.x;
verts[1].position.y = temp.y + temp.h;
verts[1].color = color;
SDL_RenderGeometry(renderer, NULL, verts, 3, NULL, 0);
}
SDL_RenderTexture(renderer, sprite, NULL, &temp);
temp.x = viewport.w - sprite_w - 1;
temp.y = 1.0f;
temp.w = sprite_w;
temp.h = sprite_h;
SDL_RenderFillRect(renderer, &temp);
SDL_RenderTexture(renderer, sprite, NULL, &temp);
temp.x = 1.0f;
temp.y = viewport.h - sprite_h - 1;
temp.w = sprite_w;
temp.h = sprite_h;
SDL_RenderFillRect(renderer, &temp);
SDL_RenderTexture(renderer, sprite, NULL, &temp);
temp.x = viewport.w - sprite_w - 1;
temp.y = viewport.h - sprite_h - 1;
temp.w = sprite_w;
temp.h = sprite_h;
SDL_RenderFillRect(renderer, &temp);
SDL_RenderTexture(renderer, sprite, NULL, &temp);
/* Test diagonal lines */
SDL_SetRenderDrawColor(renderer, 0x00, 0xFF, 0x00, 0xFF);
SDL_RenderLine(renderer, sprite_w, sprite_h,
viewport.w - sprite_w - 2, viewport.h - sprite_h - 2);
SDL_RenderLine(renderer, viewport.w - sprite_w - 2, sprite_h,
sprite_w, viewport.h - sprite_h - 2);
/* Conditionally move the sprites, bounce at the wall */
if (iterations == -1 || iterations > 0) {
for (i = 0; i < num_sprites; ++i) {
position = &positions[i];
velocity = &velocities[i];
position->x += velocity->x;
if ((position->x < 0) || (position->x >= (viewport.w - sprite_w))) {
velocity->x = -velocity->x;
position->x += velocity->x;
}
position->y += velocity->y;
if ((position->y < 0) || (position->y >= (viewport.h - sprite_h))) {
velocity->y = -velocity->y;
position->y += velocity->y;
}
}
/* Countdown sprite-move iterations and disable color changes at iteration end - used for visual tests. */
if (iterations > 0) {
iterations--;
if (iterations == 0) {
cycle_alpha = false;
cycle_color = false;
}
}
}
/* Draw sprites */
if (use_rendergeometry == 0) {
for (i = 0; i < num_sprites; ++i) {
position = &positions[i];
/* Blit the sprite onto the screen */
SDL_RenderTexture(renderer, sprite, NULL, position);
}
} else if (use_rendergeometry == 1) {
/*
* 0--1
* | /|
* |/ |
* 3--2
*
* Draw sprite2 as triangles that can be recombined as rect by software renderer
*/
SDL_Vertex *verts = (SDL_Vertex *)SDL_malloc(num_sprites * sizeof(SDL_Vertex) * 6);
SDL_Vertex *verts2 = verts;
if (verts) {
SDL_FColor color;
SDL_GetTextureColorModFloat(sprite, &color.r, &color.g, &color.b);
SDL_GetTextureAlphaModFloat(sprite, &color.a);
for (i = 0; i < num_sprites; ++i) {
position = &positions[i];
/* 0 */
verts->position.x = position->x;
verts->position.y = position->y;
verts->color = color;
verts->tex_coord.x = 0.0f;
verts->tex_coord.y = 0.0f;
verts++;
/* 1 */
verts->position.x = position->x + position->w;
verts->position.y = position->y;
verts->color = color;
verts->tex_coord.x = 1.0f;
verts->tex_coord.y = 0.0f;
verts++;
/* 2 */
verts->position.x = position->x + position->w;
verts->position.y = position->y + position->h;
verts->color = color;
verts->tex_coord.x = 1.0f;
verts->tex_coord.y = 1.0f;
verts++;
/* 0 */
verts->position.x = position->x;
verts->position.y = position->y;
verts->color = color;
verts->tex_coord.x = 0.0f;
verts->tex_coord.y = 0.0f;
verts++;
/* 2 */
verts->position.x = position->x + position->w;
verts->position.y = position->y + position->h;
verts->color = color;
verts->tex_coord.x = 1.0f;
verts->tex_coord.y = 1.0f;
verts++;
/* 3 */
verts->position.x = position->x;
verts->position.y = position->y + position->h;
verts->color = color;
verts->tex_coord.x = 0.0f;
verts->tex_coord.y = 1.0f;
verts++;
}
/* Blit sprites as triangles onto the screen */
SDL_RenderGeometry(renderer, sprite, verts2, num_sprites * 6, NULL, 0);
SDL_free(verts2);
}
} else if (use_rendergeometry == 2) {
/* 0-----1
* |\ A /|
* | \ / |
* |D 2 B|
* | / \ |
* |/ C \|
* 3-----4
*
* Draw sprite2 as triangles that can *not* be recombined as rect by software renderer
* Use an 'indices' array
*/
SDL_Vertex *verts = (SDL_Vertex *)SDL_malloc(num_sprites * sizeof(SDL_Vertex) * 5);
SDL_Vertex *verts2 = verts;
int *indices = (int *)SDL_malloc(num_sprites * sizeof(int) * 4 * 3);
int *indices2 = indices;
if (verts && indices) {
int pos = 0;
SDL_FColor color;
SDL_GetTextureColorModFloat(sprite, &color.r, &color.g, &color.b);
SDL_GetTextureAlphaModFloat(sprite, &color.a);
for (i = 0; i < num_sprites; ++i) {
position = &positions[i];
/* 0 */
verts->position.x = position->x;
verts->position.y = position->y;
verts->color = color;
verts->tex_coord.x = 0.0f;
verts->tex_coord.y = 0.0f;
verts++;
/* 1 */
verts->position.x = position->x + position->w;
verts->position.y = position->y;
verts->color = color;
verts->tex_coord.x = 1.0f;
verts->tex_coord.y = 0.0f;
verts++;
/* 2 */
verts->position.x = position->x + position->w / 2.0f;
verts->position.y = position->y + position->h / 2.0f;
verts->color = color;
verts->tex_coord.x = 0.5f;
verts->tex_coord.y = 0.5f;
verts++;
/* 3 */
verts->position.x = position->x;
verts->position.y = position->y + position->h;
verts->color = color;
verts->tex_coord.x = 0.0f;
verts->tex_coord.y = 1.0f;
verts++;
/* 4 */
verts->position.x = position->x + position->w;
verts->position.y = position->y + position->h;
verts->color = color;
verts->tex_coord.x = 1.0f;
verts->tex_coord.y = 1.0f;
verts++;
/* A */
*indices++ = pos + 0;
*indices++ = pos + 1;
*indices++ = pos + 2;
/* B */
*indices++ = pos + 1;
*indices++ = pos + 2;
*indices++ = pos + 4;
/* C */
*indices++ = pos + 3;
*indices++ = pos + 2;
*indices++ = pos + 4;
/* D */
*indices++ = pos + 3;
*indices++ = pos + 2;
*indices++ = pos + 0;
pos += 5;
}
}
/* Blit sprites as triangles onto the screen */
SDL_RenderGeometry(renderer, sprite, verts2, num_sprites * 5, indices2, num_sprites * 4 * 3);
SDL_free(verts2);
SDL_free(indices2);
}
/* Update the screen! */
SDL_RenderPresent(renderer);
}
SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
{
2024-07-25 08:14:00 +08:00
SDL_Rect safe_area;
int i;
Uint64 seed;
/* Initialize parameters */
num_sprites = NUM_SPRITES;
/* Initialize test framework */
state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO);
2023-11-10 05:29:15 +08:00
if (!state) {
return SDL_APP_FAILURE;
}
for (i = 1; i < argc;) {
int consumed;
consumed = SDLTest_CommonArg(state, i);
if (consumed == 0) {
consumed = -1;
if (SDL_strcasecmp(argv[i], "--blend") == 0) {
if (argv[i + 1]) {
if (SDL_strcasecmp(argv[i + 1], "none") == 0) {
blendMode = SDL_BLENDMODE_NONE;
consumed = 2;
} else if (SDL_strcasecmp(argv[i + 1], "blend") == 0) {
blendMode = SDL_BLENDMODE_BLEND;
consumed = 2;
} else if (SDL_strcasecmp(argv[i + 1], "blend_premultiplied") == 0) {
blendMode = SDL_BLENDMODE_BLEND_PREMULTIPLIED;
consumed = 2;
} else if (SDL_strcasecmp(argv[i + 1], "add") == 0) {
blendMode = SDL_BLENDMODE_ADD;
consumed = 2;
} else if (SDL_strcasecmp(argv[i + 1], "add_premultiplied") == 0) {
blendMode = SDL_BLENDMODE_ADD_PREMULTIPLIED;
consumed = 2;
} else if (SDL_strcasecmp(argv[i + 1], "mod") == 0) {
blendMode = SDL_BLENDMODE_MOD;
consumed = 2;
} else if (SDL_strcasecmp(argv[i + 1], "mul") == 0) {
blendMode = SDL_BLENDMODE_MUL;
consumed = 2;
} else if (SDL_strcasecmp(argv[i + 1], "sub") == 0) {
blendMode = SDL_ComposeCustomBlendMode(SDL_BLENDFACTOR_SRC_ALPHA, SDL_BLENDFACTOR_ONE, SDL_BLENDOPERATION_SUBTRACT, SDL_BLENDFACTOR_ZERO, SDL_BLENDFACTOR_ONE, SDL_BLENDOPERATION_SUBTRACT);
consumed = 2;
}
}
} else if (SDL_strcasecmp(argv[i], "--iterations") == 0) {
if (argv[i + 1]) {
iterations = SDL_atoi(argv[i + 1]);
if (iterations < -1) {
iterations = -1;
}
consumed = 2;
}
} else if (SDL_strcasecmp(argv[i], "--cyclecolor") == 0) {
cycle_color = true;
consumed = 1;
} else if (SDL_strcasecmp(argv[i], "--cyclealpha") == 0) {
cycle_alpha = true;
consumed = 1;
} else if (SDL_strcasecmp(argv[i], "--suspend-when-occluded") == 0) {
suspend_when_occluded = true;
consumed = 1;
} else if (SDL_strcasecmp(argv[i], "--use-rendergeometry") == 0) {
if (argv[i + 1]) {
if (SDL_strcasecmp(argv[i + 1], "mode1") == 0) {
/* Draw sprite2 as triangles that can be recombined as rect by software renderer */
use_rendergeometry = 1;
} else if (SDL_strcasecmp(argv[i + 1], "mode2") == 0) {
/* Draw sprite2 as triangles that can *not* be recombined as rect by software renderer
* Use an 'indices' array */
use_rendergeometry = 2;
} else {
return SDL_APP_FAILURE;
}
}
consumed = 2;
} else if (SDL_isdigit(*argv[i])) {
num_sprites = SDL_atoi(argv[i]);
consumed = 1;
} else if (argv[i][0] != '-') {
icon = argv[i];
consumed = 1;
}
}
if (consumed < 0) {
static const char *options[] = {
"[--blend none|blend|blend_premultiplied|add|add_premultiplied|mod|mul|sub]",
"[--cyclecolor]",
"[--cyclealpha]",
"[--suspend-when-occluded]",
"[--iterations N]",
"[--use-rendergeometry mode1|mode2]",
"[num_sprites]",
"[icon.bmp]",
NULL
};
SDLTest_CommonLogUsage(state, argv[0], options);
return SDL_APP_FAILURE;
}
i += consumed;
}
if (!SDLTest_CommonInit(state)) {
return SDL_APP_FAILURE;
}
/* Create the windows, initialize the renderers, and load the textures */
sprites =
(SDL_Texture **)SDL_malloc(state->num_windows * sizeof(*sprites));
2023-11-10 05:29:15 +08:00
if (!sprites) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory!\n");
return SDL_APP_FAILURE;
}
for (i = 0; i < state->num_windows; ++i) {
SDL_Renderer *renderer = state->renderers[i];
SDL_SetRenderDrawColor(renderer, 0xA0, 0xA0, 0xA0, 0xFF);
SDL_RenderClear(renderer);
}
if (LoadSprite(icon) < 0) {
return SDL_APP_FAILURE;
}
/* Allocate memory for the sprite info */
positions = (SDL_FRect *)SDL_malloc(num_sprites * sizeof(*positions));
velocities = (SDL_FRect *)SDL_malloc(num_sprites * sizeof(*velocities));
2023-11-10 05:29:15 +08:00
if (!positions || !velocities) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory!\n");
return SDL_APP_FAILURE;
}
/* Position sprites and set their velocities using the fuzzer */
2024-07-25 08:14:00 +08:00
/* Really we should be using per-window safe area, but this is fine for a simple test */
SDL_GetRenderSafeArea(state->renderers[0], &safe_area);
if (iterations >= 0) {
/* Deterministic seed - used for visual tests */
seed = (Uint64)iterations;
} else {
/* Pseudo-random seed generated from the time */
seed = SDL_GetPerformanceCounter();
}
SDLTest_FuzzerInit(seed);
for (i = 0; i < num_sprites; ++i) {
2024-07-25 08:14:00 +08:00
positions[i].x = (float)SDLTest_RandomIntegerInRange(0, (int)(safe_area.w - sprite_w));
positions[i].y = (float)SDLTest_RandomIntegerInRange(0, (int)(safe_area.h - sprite_h));
positions[i].w = sprite_w;
positions[i].h = sprite_h;
velocities[i].x = 0;
velocities[i].y = 0;
while (velocities[i].x == 0.f && velocities[i].y == 0.f) {
velocities[i].x = (float)SDLTest_RandomIntegerInRange(-MAX_SPEED, MAX_SPEED);
velocities[i].y = (float)SDLTest_RandomIntegerInRange(-MAX_SPEED, MAX_SPEED);
}
}
main: Added _optional_ callback entry points. This lets apps optionally have a handful of callbacks for their entry points instead of a single main function. If used, the actual main/SDL_main/whatever entry point will be implemented in the single-header library SDL_main.h and the app will implement four separate functions: First: int SDL_AppInit(int argc, char **argv); This will be called once before anything else. argc/argv work like they always do. If this returns 0, the app runs. If it returns < 0, the app calls SDL_AppQuit and terminates with an exit code that reports an error to the platform. If it returns > 0, the app calls SDL_AppQuit and terminates with an exit code that reports success to the platform. This function should not go into an infinite mainloop; it should do any one-time startup it requires and then return. Then: int SDL_AppIterate(void); This is called over and over, possibly at the refresh rate of the display or some other metric that the platform dictates. This is where the heart of your app runs. It should return as quickly as reasonably possible, but it's not a "run one memcpy and that's all the time you have" sort of thing. The app should do any game updates, and render a frame of video. If it returns < 0, SDL will call SDL_AppQuit and terminate the process with an exit code that reports an error to the platform. If it returns > 0, the app calls SDL_AppQuit and terminates with an exit code that reports success to the platform. If it returns 0, then SDL_AppIterate will be called again at some regular frequency. The platform may choose to run this more or less (perhaps less in the background, etc), or it might just call this function in a loop as fast as possible. You do not check the event queue in this function (SDL_AppEvent exists for that). Next: int SDL_AppEvent(const SDL_Event *event); This will be called once for each event pushed into the SDL queue. This may be called from any thread, and possibly in parallel to SDL_AppIterate. The fields in event do not need to be free'd (as you would normally need to do for SDL_EVENT_DROP_FILE, etc), and your app should not call SDL_PollEvent, SDL_PumpEvent, etc, as SDL will manage this for you. Return values are the same as from SDL_AppIterate(), so you can terminate in response to SDL_EVENT_QUIT, etc. Finally: void SDL_AppQuit(void); This is called once before terminating the app--assuming the app isn't being forcibly killed or crashed--as a last chance to clean up. After this returns, SDL will call SDL_Quit so the app doesn't have to (but it's safe for the app to call it, too). Process termination proceeds as if the app returned normally from main(), so atexit handles will run, if your platform supports that. The app does not implement SDL_main if using this. To turn this on, define SDL_MAIN_USE_CALLBACKS before including SDL_main.h. Defines like SDL_MAIN_HANDLED and SDL_MAIN_NOIMPL are also respected for callbacks, if the app wants to do some sort of magic main implementation thing. In theory, on most platforms these can be implemented in the app itself, but this saves some #ifdefs in the app and lets everyone struggle less against some platforms, and might be more efficient in the long run, too. On some platforms, it's possible this is the only reasonable way to go, but we haven't actually hit one that 100% requires it yet (but we will, if we want to write a RetroArch backend, for example). Using the callback entry points works on every platform, because on platforms that don't require them, we can fake them with a simple loop in an internal implementation of the usual SDL_main. The primary way we expect people to write SDL apps is with SDL_main, and this is not intended to replace it. If the app chooses to use this, it just removes some platform-specific details they might have to otherwise manage, and maybe removes a barrier to entry on some future platform. Fixes #6785. Reference PR #8247.
2023-11-02 06:40:41 +08:00
/* Main render loop in SDL_AppIterate will begin when this function returns. */
frames = 0;
next_fps_check = SDL_GetTicks() + fps_check_delay;
return SDL_APP_CONTINUE;
}
main: Added _optional_ callback entry points. This lets apps optionally have a handful of callbacks for their entry points instead of a single main function. If used, the actual main/SDL_main/whatever entry point will be implemented in the single-header library SDL_main.h and the app will implement four separate functions: First: int SDL_AppInit(int argc, char **argv); This will be called once before anything else. argc/argv work like they always do. If this returns 0, the app runs. If it returns < 0, the app calls SDL_AppQuit and terminates with an exit code that reports an error to the platform. If it returns > 0, the app calls SDL_AppQuit and terminates with an exit code that reports success to the platform. This function should not go into an infinite mainloop; it should do any one-time startup it requires and then return. Then: int SDL_AppIterate(void); This is called over and over, possibly at the refresh rate of the display or some other metric that the platform dictates. This is where the heart of your app runs. It should return as quickly as reasonably possible, but it's not a "run one memcpy and that's all the time you have" sort of thing. The app should do any game updates, and render a frame of video. If it returns < 0, SDL will call SDL_AppQuit and terminate the process with an exit code that reports an error to the platform. If it returns > 0, the app calls SDL_AppQuit and terminates with an exit code that reports success to the platform. If it returns 0, then SDL_AppIterate will be called again at some regular frequency. The platform may choose to run this more or less (perhaps less in the background, etc), or it might just call this function in a loop as fast as possible. You do not check the event queue in this function (SDL_AppEvent exists for that). Next: int SDL_AppEvent(const SDL_Event *event); This will be called once for each event pushed into the SDL queue. This may be called from any thread, and possibly in parallel to SDL_AppIterate. The fields in event do not need to be free'd (as you would normally need to do for SDL_EVENT_DROP_FILE, etc), and your app should not call SDL_PollEvent, SDL_PumpEvent, etc, as SDL will manage this for you. Return values are the same as from SDL_AppIterate(), so you can terminate in response to SDL_EVENT_QUIT, etc. Finally: void SDL_AppQuit(void); This is called once before terminating the app--assuming the app isn't being forcibly killed or crashed--as a last chance to clean up. After this returns, SDL will call SDL_Quit so the app doesn't have to (but it's safe for the app to call it, too). Process termination proceeds as if the app returned normally from main(), so atexit handles will run, if your platform supports that. The app does not implement SDL_main if using this. To turn this on, define SDL_MAIN_USE_CALLBACKS before including SDL_main.h. Defines like SDL_MAIN_HANDLED and SDL_MAIN_NOIMPL are also respected for callbacks, if the app wants to do some sort of magic main implementation thing. In theory, on most platforms these can be implemented in the app itself, but this saves some #ifdefs in the app and lets everyone struggle less against some platforms, and might be more efficient in the long run, too. On some platforms, it's possible this is the only reasonable way to go, but we haven't actually hit one that 100% requires it yet (but we will, if we want to write a RetroArch backend, for example). Using the callback entry points works on every platform, because on platforms that don't require them, we can fake them with a simple loop in an internal implementation of the usual SDL_main. The primary way we expect people to write SDL apps is with SDL_main, and this is not intended to replace it. If the app chooses to use this, it just removes some platform-specific details they might have to otherwise manage, and maybe removes a barrier to entry on some future platform. Fixes #6785. Reference PR #8247.
2023-11-02 06:40:41 +08:00
SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
{
if (event->type == SDL_EVENT_RENDER_DEVICE_RESET) {
LoadSprite(icon);
}
return SDLTest_CommonEventMainCallbacks(state, event);
}
SDL_AppResult SDL_AppIterate(void *appstate)
{
Uint64 now;
int i;
int active_windows = 0;
for (i = 0; i < state->num_windows; ++i) {
if (state->windows[i] == NULL ||
(suspend_when_occluded && (SDL_GetWindowFlags(state->windows[i]) & SDL_WINDOW_OCCLUDED))) {
continue;
}
++active_windows;
MoveSprites(state->renderers[i], sprites[i]);
}
/* If all windows are occluded, throttle the event polling to 15hz. */
if (!active_windows) {
SDL_DelayNS(SDL_NS_PER_SECOND / 15);
}
frames++;
now = SDL_GetTicks();
if (now >= next_fps_check) {
/* Print out some timing information */
const Uint64 then = next_fps_check - fps_check_delay;
const double fps = ((double)frames * 1000) / (now - then);
SDL_Log("%2.2f frames per second\n", fps);
next_fps_check = now + fps_check_delay;
frames = 0;
}
return SDL_APP_CONTINUE;
}