Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions test/testautomation_fizzy.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Test suites for fizzy's patches to SDL.
*
* Each suite checks one patch on the fizzy-3.4 branch of fizzyedit/SDL and is
* expected to fail on the SDL release the branch is based on. testfizzy.c runs
* them.
*/

#ifndef testautomation_fizzy_h_
#define testautomation_fizzy_h_

#include <SDL3/SDL_test.h>

/* Test collections */
extern SDLTest_TestSuiteReference fizzyGPUTestSuite;

/* Comma-separated GPU drivers ("metal,vulkan") a test must find: when one of
* them is missing the test fails instead of being skipped. Set by testfizzy's
* --require-gpu option, for CI runners that are known to have the driver.
*/
extern const char *fizzyGPURequiredDrivers;

#endif /* testautomation_fizzy_h_ */
195 changes: 195 additions & 0 deletions test/testautomation_fizzy_gpu.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
/**
* GPU test suite for fizzy's patches
*
* "GPU: let each driver decide whether it can claim a transparent window":
* SDL_ClaimWindowForGPUDevice() used to refuse every SDL_WINDOW_TRANSPARENT
* window, though the Metal and Vulkan drivers composite the swapchain's alpha
* for one. These tests claim a transparent window with each driver, clear it to
* a premultiplied 50% red and present it. The opaque claims are the control: a
* driver that cannot claim an opaque window says nothing about the patch.
*/
#include <SDL3/SDL.h>
#include <SDL3/SDL_test.h>
#include "testautomation_fizzy.h"

const char *fizzyGPURequiredDrivers = NULL;

/* Private helpers */

static bool GPUDriverIsRequired(const char *driver)
{
const char *list = fizzyGPURequiredDrivers;
const size_t length = SDL_strlen(driver);

while (list && *list) {
const char *end = SDL_strchr(list, ',');
const size_t item_length = end ? (size_t)(end - list) : SDL_strlen(list);

if (item_length == length && SDL_strncasecmp(list, driver, length) == 0) {
return true;
}
list = end ? end + 1 : NULL;
}
return false;
}

/**
* Creates a window with the given flags, claims it for a GPU device made with
* the given driver, then clears and presents one frame.
*
* Skips when the driver is unavailable, unless it is required.
*/
static int gpu_claimWindowWithDriver(const char *driver, SDL_WindowFlags flags)
{
const SDL_GPUShaderFormat formats = SDL_GPU_SHADERFORMAT_SPIRV | SDL_GPU_SHADERFORMAT_DXIL | SDL_GPU_SHADERFORMAT_MSL | SDL_GPU_SHADERFORMAT_METALLIB;
SDL_GPUDevice *device;
SDL_Window *window;
SDL_GPUCommandBuffer *cmdbuf;
SDL_GPUTexture *swapchain = NULL;
Uint32 w = 0, h = 0;
bool result;

device = SDL_CreateGPUDevice(formats, false, driver);
SDLTest_AssertPass("Call to SDL_CreateGPUDevice(..., \"%s\")", driver);
if (!device) {
if (GPUDriverIsRequired(driver)) {
SDLTest_AssertCheck(false, "Validate that the required GPU driver '%s' is available, got error: %s", driver, SDL_GetError());
return TEST_COMPLETED;
}
SDLTest_Log("GPU driver '%s' is not available (%s), skipping test", driver, SDL_GetError());
return TEST_SKIPPED;
}
SDLTest_AssertCheck(SDL_strcmp(SDL_GetGPUDeviceDriver(device), driver) == 0,
"Validate the device's driver, expected: %s, got: %s", driver, SDL_GetGPUDeviceDriver(device));

window = SDL_CreateWindow("testautomation_fizzy_gpu", 320, 240, flags);
SDLTest_AssertPass("Call to SDL_CreateWindow('Title',320,240,%" SDL_PRIu64 ")", flags);
SDLTest_AssertCheck(window != NULL, "Validate that returned window is not NULL, got error: %s", window ? "none" : SDL_GetError());
if (!window) {
SDL_DestroyGPUDevice(device);
return TEST_COMPLETED;
}

/* If the video driver quietly dropped the flag, the claim tests nothing */
if (flags & SDL_WINDOW_TRANSPARENT) {
SDLTest_AssertCheck((SDL_GetWindowFlags(window) & SDL_WINDOW_TRANSPARENT) != 0, "Validate that the window is transparent");
}

result = SDL_ClaimWindowForGPUDevice(device, window);
SDLTest_AssertPass("Call to SDL_ClaimWindowForGPUDevice()");
SDLTest_AssertCheck(result, "Validate that the window was claimed, got error: %s", result ? "none" : SDL_GetError());

if (result) {
cmdbuf = SDL_AcquireGPUCommandBuffer(device);
SDLTest_AssertPass("Call to SDL_AcquireGPUCommandBuffer()");
SDLTest_AssertCheck(cmdbuf != NULL, "Validate that returned command buffer is not NULL, got error: %s", cmdbuf ? "none" : SDL_GetError());
if (cmdbuf) {
result = SDL_WaitAndAcquireGPUSwapchainTexture(cmdbuf, window, &swapchain, &w, &h);
SDLTest_AssertPass("Call to SDL_WaitAndAcquireGPUSwapchainTexture()");
SDLTest_AssertCheck(result, "Validate that a swapchain texture was acquired, got error: %s", result ? "none" : SDL_GetError());

if (swapchain) {
SDL_GPUColorTargetInfo target;
SDL_GPURenderPass *pass;

SDLTest_Log("Swapchain texture is %" SDL_PRIu32 "x%" SDL_PRIu32, w, h);
SDL_zero(target);
target.texture = swapchain;
target.clear_color.r = 0.5f;
target.clear_color.a = 0.5f;
target.load_op = SDL_GPU_LOADOP_CLEAR;
target.store_op = SDL_GPU_STOREOP_STORE;
pass = SDL_BeginGPURenderPass(cmdbuf, &target, 1, NULL);
SDLTest_AssertCheck(pass != NULL, "Validate that returned render pass is not NULL");
if (pass) {
SDL_EndGPURenderPass(pass);
}
} else {
/* Allowed, for example while the window is occluded */
SDLTest_Log("No swapchain texture this frame");
}

result = SDL_SubmitGPUCommandBuffer(cmdbuf);
SDLTest_AssertPass("Call to SDL_SubmitGPUCommandBuffer()");
SDLTest_AssertCheck(result, "Validate that the command buffer was submitted, got error: %s", result ? "none" : SDL_GetError());
}

SDL_WaitForGPUIdle(device);
SDL_ReleaseWindowFromGPUDevice(device, window);
SDLTest_AssertPass("Call to SDL_ReleaseWindowFromGPUDevice()");
}

SDL_DestroyWindow(window);
SDL_DestroyGPUDevice(device);
return TEST_COMPLETED;
}

/* Test case functions */

/**
* Claims an opaque window with the Metal driver (the control).
*/
static int SDLCALL gpu_claimOpaqueWindowMetal(void *arg)
{
return gpu_claimWindowWithDriver("metal", 0);
}

/**
* Claims a transparent window with the Metal driver.
*/
static int SDLCALL gpu_claimTransparentWindowMetal(void *arg)
{
return gpu_claimWindowWithDriver("metal", SDL_WINDOW_TRANSPARENT);
}

/**
* Claims an opaque window with the Vulkan driver (the control).
*/
static int SDLCALL gpu_claimOpaqueWindowVulkan(void *arg)
{
return gpu_claimWindowWithDriver("vulkan", 0);
}

/**
* Claims a transparent window with the Vulkan driver.
*/
static int SDLCALL gpu_claimTransparentWindowVulkan(void *arg)
{
return gpu_claimWindowWithDriver("vulkan", SDL_WINDOW_TRANSPARENT);
}

/* ================= Test References ================== */

/* GPU test cases */
static const SDLTest_TestCaseReference gpuTestClaimOpaqueWindowMetal = {
gpu_claimOpaqueWindowMetal, "gpu_claimOpaqueWindowMetal", "Claims an opaque window with the Metal driver and presents a frame", TEST_ENABLED
};

static const SDLTest_TestCaseReference gpuTestClaimTransparentWindowMetal = {
gpu_claimTransparentWindowMetal, "gpu_claimTransparentWindowMetal", "Claims a transparent window with the Metal driver and presents a frame", TEST_ENABLED
};

static const SDLTest_TestCaseReference gpuTestClaimOpaqueWindowVulkan = {
gpu_claimOpaqueWindowVulkan, "gpu_claimOpaqueWindowVulkan", "Claims an opaque window with the Vulkan driver and presents a frame", TEST_ENABLED
};

static const SDLTest_TestCaseReference gpuTestClaimTransparentWindowVulkan = {
gpu_claimTransparentWindowVulkan, "gpu_claimTransparentWindowVulkan", "Claims a transparent window with the Vulkan driver and presents a frame", TEST_ENABLED
};

/* Sequence of GPU test cases */
static const SDLTest_TestCaseReference *gpuTests[] = {
&gpuTestClaimOpaqueWindowMetal,
&gpuTestClaimTransparentWindowMetal,
&gpuTestClaimOpaqueWindowVulkan,
&gpuTestClaimTransparentWindowVulkan,
NULL
};

/* GPU test suite (global) */
SDLTest_TestSuiteReference fizzyGPUTestSuite = {
"FizzyGPU",
NULL,
gpuTests,
NULL
};
127 changes: 127 additions & 0 deletions test/testfizzy.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
Copyright (C) 1997-2026 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.
*/

/* Runs the suites for fizzy's patches (testautomation_fizzy*.c), the way
* testautomation runs SDL's own: --filter, --seed and --list work the same.
*/

#include <stdlib.h>

#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <SDL3/SDL_test.h>

#include "testautomation_fizzy.h"

static SDLTest_CommonState *state;
static SDLTest_TestSuiteRunner *runner;

/* All test suites */
static SDLTest_TestSuiteReference *testSuites[] = {
&fizzyGPUTestSuite,
NULL
};

/* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */
static void
quit(int rc)
{
SDLTest_DestroyTestSuiteRunner(runner);
SDLTest_CommonQuit(state);
/* Let 'main()' return normally */
if (rc != 0) {
exit(rc);
}
}

int main(int argc, char *argv[])
{
int result;
int i, done;
SDL_Event event;
int list = 0;

/* Initialize test framework */
state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO);
if (!state) {
return 1;
}

/* The suites create the windows they need */
state->num_windows = 0;

runner = SDLTest_CreateTestSuiteRunner(state, testSuites);

/* Parse commandline */
for (i = 1; i < argc;) {
int consumed;

consumed = SDLTest_CommonArg(state, i);
if (consumed == 0) {
consumed = -1;

if (SDL_strcasecmp(argv[i], "--list") == 0) {
consumed = 1;
list = 1;
} else if (SDL_strcasecmp(argv[i], "--require-gpu") == 0 && argv[i + 1]) {
consumed = 2;
fizzyGPURequiredDrivers = argv[i + 1];
}
}
if (consumed < 0) {
static const char *options[] = {
"[--list]",
"[--require-gpu driver[,driver]]",
NULL };
SDLTest_CommonLogUsage(state, argv[0], options);
quit(1);
}

i += consumed;
}

/* List all suites. */
if (list) {
int suiteCounter;
for (suiteCounter = 0; testSuites[suiteCounter]; ++suiteCounter) {
int testCounter;
SDLTest_TestSuiteReference *testSuite = testSuites[suiteCounter];
SDL_Log("Test suite: %s", testSuite->name);
for (testCounter = 0; testSuite->testCases[testCounter]; ++testCounter) {
const SDLTest_TestCaseReference *testCase = testSuite->testCases[testCounter];
SDL_Log(" test: %s%s", testCase->name, testCase->enabled ? "" : " (disabled)");
}
}
return 0;
}

/* Initialize common state */
if (!SDLTest_CommonInit(state)) {
quit(2);
}

/* Call Harness */
result = SDLTest_ExecuteTestSuiteRunner(runner);

/* Empty event queue */
done = 0;
for (i = 0; i < 100; i++) {
while (SDL_PollEvent(&event)) {
SDLTest_CommonEvent(state, &event, &done);
}
SDL_Delay(10);
}

/* Shutdown everything */
quit(0);
return result;
}