Compare commits

...

14 Commits

Author SHA1 Message Date
Katelyn Gadd 6d315b745d
Merge 90707c019c into 9ed83e71f6 2025-06-22 23:37:40 -07:00
Sam Lantinga 9ed83e71f6 Fixed memory leaks in KMSDRM property handling
Also cleaned up the code for consistency so it's easy to see memory leaks here.
2025-06-22 21:33:52 -07:00
SDL Wiki Bot 603118c340 Sync SDL3 wiki -> header
[ci skip]
2025-06-23 04:18:24 +00:00
Sam Lantinga aa4f916b71 Renamed SDL_PROP_AUDIOSTREAM_KEEP_ON_SHUTDOWN_BOOLEAN to SDL_PROP_AUDIOSTREAM_AUTO_CLEANUP_BOOLEAN 2025-06-22 21:17:06 -07:00
SDL Wiki Bot 6cfe211142 Sync SDL3 wiki -> header
[ci skip]
2025-06-23 04:07:09 +00:00
Ryan C. Gordon 274aa0242e
audio: Let apps save an audio stream from destruction during SDL_Quit(). (#13244)
This is a special-case piece of functionality, generally these are expected
to go away during shutdown, but maybe someone is switching between audio
subsystems or something...
2025-06-22 21:06:15 -07:00
Ozkan Sezer af8bee2dd1 alsa: change an SDL_LogError into SDL_LogDebug.
it is informational only and seeing ERROR on the terminal was confusing
2025-06-22 21:04:44 -07:00
Marcin Serwin bbc674b9e7 test: Fix resource paths in testtray
Signed-off-by: Marcin Serwin <marcin@serwin.dev>
2025-06-22 21:03:37 -07:00
mitchellcairns 796961acec
Resolve bug for calibration Nintendo Switch Pro Controller (#13260)
Resolves a bug which prevents the stored calibration data from loading, only allowing loading of factory-installed calibration data
2025-06-22 20:59:15 -07:00
Mitch Cairns 3a6f9e01f8 Fixed Nintendo Switch thumbstick calibration 2025-06-22 15:26:49 -07:00
Katelyn Gadd 90707c019c Fix pointer-size mismatch 2025-01-12 18:25:49 -08:00
Katelyn Gadd 253b424cb4 Okay, that didn't satisfy gcc either 2025-01-12 18:13:47 -08:00
Katelyn Gadd 94b6734a84 Object handles are uint64_t 2025-01-12 18:02:05 -08:00
Katelyn Gadd 54bb24e424 Assign debug names to command pools and command buffers identifying their SDL_GPU pointers and what thread they have affinity to 2025-01-12 17:56:55 -08:00
8 changed files with 178 additions and 197 deletions

View File

@ -1064,6 +1064,17 @@ extern SDL_DECLSPEC SDL_AudioStream * SDLCALL SDL_CreateAudioStream(const SDL_Au
/**
* Get the properties associated with an audio stream.
*
* The application can hang any data it wants here, but the following
* properties are understood by SDL:
*
* - `SDL_PROP_AUDIOSTREAM_AUTO_CLEANUP_BOOLEAN`: if true (the default), the
* stream be automatically cleaned up when the audio subsystem quits. If set
* to false, the streams will persist beyond that. This property is ignored
* for streams created through SDL_OpenAudioDeviceStream(), and will always
* be cleaned up. Streams that are not cleaned up will still be unbound from
* devices when the audio subsystem quits. This property was added in SDL
* 3.4.0.
*
* \param stream the SDL_AudioStream to query.
* \returns a valid property ID on success or 0 on failure; call
* SDL_GetError() for more information.
@ -1074,6 +1085,9 @@ extern SDL_DECLSPEC SDL_AudioStream * SDLCALL SDL_CreateAudioStream(const SDL_Au
*/
extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetAudioStreamProperties(SDL_AudioStream *stream);
#define SDL_PROP_AUDIOSTREAM_AUTO_CLEANUP_BOOLEAN "SDL.audiostream.auto_cleanup"
/**
* Query the current format of an audio stream.
*

View File

@ -1073,9 +1073,16 @@ void SDL_QuitAudio(void)
current_audio.impl.DeinitializeStart();
// Destroy any audio streams that still exist...
while (current_audio.existing_streams) {
SDL_DestroyAudioStream(current_audio.existing_streams);
// Destroy any audio streams that still exist...unless app asked to keep it.
SDL_AudioStream *next = NULL;
for (SDL_AudioStream *i = current_audio.existing_streams; i; i = next) {
next = i->next;
if (i->simplified || SDL_GetBooleanProperty(i->props, SDL_PROP_AUDIOSTREAM_AUTO_CLEANUP_BOOLEAN, true)) {
SDL_DestroyAudioStream(i);
} else {
i->prev = NULL;
i->next = NULL;
}
}
SDL_LockRWLockForWriting(current_audio.device_hash_lock);

View File

@ -1157,7 +1157,7 @@ static bool ALSA_OpenDevice(SDL_AudioDevice *device)
#if SDL_ALSA_DEBUG
snd_pcm_uframes_t bufsize;
ALSA_snd_pcm_hw_params_get_buffer_size(cfg_ctx.hwparams, &bufsize);
SDL_LogError(SDL_LOG_CATEGORY_AUDIO,
SDL_LogDebug(SDL_LOG_CATEGORY_AUDIO,
"ALSA: period size = %ld, periods = %u, buffer size = %lu",
cfg_ctx.persize, cfg_ctx.periods, bufsize);
#endif

View File

@ -1274,6 +1274,27 @@ static inline const char *VkErrorMessages(VkResult code)
// Utility
static void VULKAN_INTERNAL_SetObjectNamePrintf(
VulkanRenderer *renderer, void *object, VkObjectType objectType, const char *format, ...
) {
if (!renderer->debugMode)
return;
va_list args;
char buf[1024] = { 0 };
va_start(args, format);
vsnprintf(buf, sizeof(buf), format, args);
va_end(args);
VkDebugUtilsObjectNameInfoEXT nameInfo = {
.objectHandle = (uint64_t)object,
.objectType = objectType,
.pObjectName = buf,
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
.pNext = NULL,
};
renderer->vkSetDebugUtilsObjectNameEXT(renderer->logicalDevice, &nameInfo);
}
static inline VkPolygonMode SDLToVK_PolygonMode(
VulkanRenderer *renderer,
SDL_GPUFillMode mode)
@ -9397,6 +9418,11 @@ static bool VULKAN_INTERNAL_AllocateCommandBuffer(
// Pool it!
VULKAN_INTERNAL_SetObjectNamePrintf(
renderer, commandBuffer->commandBuffer, VK_OBJECT_TYPE_COMMAND_BUFFER,
"[Thread %p's Command pool %p] Command buffer %p", vulkanCommandPool->threadID, vulkanCommandPool, commandBuffer
);
vulkanCommandPool->inactiveCommandBuffers[vulkanCommandPool->inactiveCommandBufferCount] = commandBuffer;
vulkanCommandPool->inactiveCommandBufferCount += 1;
@ -9441,6 +9467,11 @@ static VulkanCommandPool *VULKAN_INTERNAL_FetchCommandPool(
return NULL;
}
VULKAN_INTERNAL_SetObjectNamePrintf(
renderer, vulkanCommandPool->commandPool, VK_OBJECT_TYPE_COMMAND_POOL,
"[Thread %p] Command pool %p", threadID, vulkanCommandPool
);
vulkanCommandPool->threadID = threadID;
vulkanCommandPool->inactiveCommandBufferCapacity = 0;

View File

@ -928,13 +928,14 @@ static bool SetIMUEnabled(SDL_DriverSwitch_Context *ctx, bool enabled)
static bool LoadStickCalibration(SDL_DriverSwitch_Context *ctx)
{
Uint8 *pLeftStickCal;
Uint8 *pRightStickCal;
Uint8 *pLeftStickCal = NULL;
Uint8 *pRightStickCal = NULL;
size_t stick, axis;
SwitchSubcommandInputPacket_t *user_reply = NULL;
SwitchSubcommandInputPacket_t *factory_reply = NULL;
SwitchSPIOpData_t readUserParams;
SwitchSPIOpData_t readFactoryParams;
Uint8 userParamsReadSuccessCount = 0;
// Read User Calibration Info
readUserParams.unAddress = k_unSPIStickUserCalibrationStartOffset;
@ -947,39 +948,52 @@ static bool LoadStickCalibration(SDL_DriverSwitch_Context *ctx)
readFactoryParams.unAddress = k_unSPIStickFactoryCalibrationStartOffset;
readFactoryParams.ucLength = k_unSPIStickFactoryCalibrationLength;
const int MAX_ATTEMPTS = 3;
for (int attempt = 0; ; ++attempt) {
if (!WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SPIFlashRead, (uint8_t *)&readFactoryParams, sizeof(readFactoryParams), &factory_reply)) {
return false;
}
if (factory_reply->stickFactoryCalibration.opData.unAddress == k_unSPIStickFactoryCalibrationStartOffset) {
// We successfully read the calibration data
break;
}
if (attempt == MAX_ATTEMPTS) {
return false;
}
}
// Automatically select the user calibration if magic bytes are set
if (user_reply && user_reply->stickUserCalibration.rgucLeftMagic[0] == 0xB2 && user_reply->stickUserCalibration.rgucLeftMagic[1] == 0xA1) {
userParamsReadSuccessCount += 1;
pLeftStickCal = user_reply->stickUserCalibration.rgucLeftCalibration;
} else {
pLeftStickCal = factory_reply->stickFactoryCalibration.rgucLeftCalibration;
}
if (user_reply && user_reply->stickUserCalibration.rgucRightMagic[0] == 0xB2 && user_reply->stickUserCalibration.rgucRightMagic[1] == 0xA1) {
userParamsReadSuccessCount += 1;
pRightStickCal = user_reply->stickUserCalibration.rgucRightCalibration;
} else {
pRightStickCal = factory_reply->stickFactoryCalibration.rgucRightCalibration;
}
// Only read the factory calibration info if we failed to receive the correct magic bytes
if (userParamsReadSuccessCount < 2) {
// Read Factory Calibration Info
readFactoryParams.unAddress = k_unSPIStickFactoryCalibrationStartOffset;
readFactoryParams.ucLength = k_unSPIStickFactoryCalibrationLength;
const int MAX_ATTEMPTS = 3;
for (int attempt = 0;; ++attempt) {
if (!WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SPIFlashRead, (uint8_t *)&readFactoryParams, sizeof(readFactoryParams), &factory_reply)) {
return false;
}
if (factory_reply->stickFactoryCalibration.opData.unAddress == k_unSPIStickFactoryCalibrationStartOffset) {
// We successfully read the calibration data
pLeftStickCal = factory_reply->stickFactoryCalibration.rgucLeftCalibration;
pRightStickCal = factory_reply->stickFactoryCalibration.rgucRightCalibration;
break;
}
if (attempt == MAX_ATTEMPTS) {
return false;
}
}
}
// If we still don't have calibration data, return false
if (pLeftStickCal == NULL || pRightStickCal == NULL)
{
return false;
}
/* Stick calibration values are 12-bits each and are packed by bit
* For whatever reason the fields are in a different order for each stick
* Left: X-Max, Y-Max, X-Center, Y-Center, X-Min, Y-Min
* Right: X-Center, Y-Center, X-Max, Y-Max, X-Min, Y-Min
* Right: X-Center, Y-Center, X-Min, Y-Min, X-Max, Y-Max
*/
// Left stick
@ -993,10 +1007,10 @@ static bool LoadStickCalibration(SDL_DriverSwitch_Context *ctx)
// Right stick
ctx->m_StickCalData[1].axis[0].sCenter = ((pRightStickCal[1] << 8) & 0xF00) | pRightStickCal[0]; // X Axis center
ctx->m_StickCalData[1].axis[1].sCenter = (pRightStickCal[2] << 4) | (pRightStickCal[1] >> 4); // Y Axis center
ctx->m_StickCalData[1].axis[0].sMax = ((pRightStickCal[4] << 8) & 0xF00) | pRightStickCal[3]; // X Axis max above center
ctx->m_StickCalData[1].axis[1].sMax = (pRightStickCal[5] << 4) | (pRightStickCal[4] >> 4); // Y Axis max above center
ctx->m_StickCalData[1].axis[0].sMin = ((pRightStickCal[7] << 8) & 0xF00) | pRightStickCal[6]; // X Axis min below center
ctx->m_StickCalData[1].axis[1].sMin = (pRightStickCal[8] << 4) | (pRightStickCal[7] >> 4); // Y Axis min below center
ctx->m_StickCalData[1].axis[0].sMin = ((pRightStickCal[4] << 8) & 0xF00) | pRightStickCal[3]; // X Axis min below center
ctx->m_StickCalData[1].axis[1].sMin = (pRightStickCal[5] << 4) | (pRightStickCal[4] >> 4); // Y Axis min below center
ctx->m_StickCalData[1].axis[0].sMax = ((pRightStickCal[7] << 8) & 0xF00) | pRightStickCal[6]; // X Axis max above center
ctx->m_StickCalData[1].axis[1].sMax = (pRightStickCal[8] << 4) | (pRightStickCal[7] >> 4); // Y Axis max above center
// Filter out any values that were uninitialized (0xFFF) in the SPI read
for (stick = 0; stick < 2; ++stick) {

View File

@ -588,88 +588,51 @@ static void KMSDRM_DeinitDisplays(SDL_VideoDevice *_this)
}
}
static uint32_t KMSDRM_CrtcGetPropId(uint32_t drm_fd,
drmModeObjectPropertiesPtr props,
char const *name)
static bool KMSDRM_ConnectorCheckVrrCapable(uint32_t drm_fd, uint32_t output_id)
{
uint32_t i, prop_id = 0;
for (i = 0; !prop_id && i < props->count_props; ++i) {
drmModePropertyPtr drm_prop =
KMSDRM_drmModeGetProperty(drm_fd, props->props[i]);
if (!drm_prop) {
continue;
}
if (SDL_strcmp(drm_prop->name, name) == 0) {
prop_id = drm_prop->prop_id;
}
KMSDRM_drmModeFreeProperty(drm_prop);
}
return prop_id;
}
static bool KMSDRM_VrrPropId(uint32_t drm_fd, uint32_t crtc_id, uint32_t *vrr_prop_id)
{
drmModeObjectPropertiesPtr drm_props;
drm_props = KMSDRM_drmModeObjectGetProperties(drm_fd,
crtc_id,
DRM_MODE_OBJECT_CRTC);
if (!drm_props) {
return false;
}
*vrr_prop_id = KMSDRM_CrtcGetPropId(drm_fd,
drm_props,
"VRR_ENABLED");
KMSDRM_drmModeFreeObjectProperties(drm_props);
return true;
}
static bool KMSDRM_ConnectorCheckVrrCapable(uint32_t drm_fd,
uint32_t output_id,
char const *name)
{
uint32_t i;
bool found = false;
uint64_t prop_value = 0;
drmModeObjectPropertiesPtr props = KMSDRM_drmModeObjectGetProperties(drm_fd,
output_id,
DRM_MODE_OBJECT_CONNECTOR);
if (!props) {
return false;
}
for (i = 0; !found && i < props->count_props; ++i) {
drmModePropertyPtr drm_prop = KMSDRM_drmModeGetProperty(drm_fd, props->props[i]);
if (!drm_prop) {
continue;
drmModeObjectPropertiesPtr props = KMSDRM_drmModeObjectGetProperties(drm_fd, output_id, DRM_MODE_OBJECT_CONNECTOR);
if (props) {
for (uint32_t i = 0; !found && i < props->count_props; ++i) {
drmModePropertyPtr prop = KMSDRM_drmModeGetProperty(drm_fd, props->props[i]);
if (prop) {
if (SDL_strcasecmp(prop->name, "VRR_CAPABLE") == 0) {
prop_value = props->prop_values[i];
found = true;
}
KMSDRM_drmModeFreeProperty(prop);
}
}
if (SDL_strcasecmp(drm_prop->name, name) == 0) {
prop_value = props->prop_values[i];
found = true;
}
KMSDRM_drmModeFreeProperty(drm_prop);
KMSDRM_drmModeFreeObjectProperties(props);
}
if (found) {
return prop_value ? true : false;
}
return false;
}
static bool KMSDRM_VrrPropId(uint32_t drm_fd, uint32_t crtc_id, uint32_t *vrr_prop_id)
{
bool found = false;
drmModeObjectPropertiesPtr props = KMSDRM_drmModeObjectGetProperties(drm_fd, crtc_id, DRM_MODE_OBJECT_CRTC);
if (props) {
for (uint32_t i = 0; !found && i < props->count_props; ++i) {
drmModePropertyPtr prop = KMSDRM_drmModeGetProperty(drm_fd, props->props[i]);
if (prop) {
if (SDL_strcmp(prop->name, "VRR_ENABLED") == 0) {
*vrr_prop_id = prop->prop_id;
found = true;
}
KMSDRM_drmModeFreeProperty(prop);
}
}
KMSDRM_drmModeFreeObjectProperties(props);
}
return found;
}
static void KMSDRM_CrtcSetVrr(uint32_t drm_fd, uint32_t crtc_id, bool enabled)
{
uint32_t vrr_prop_id;
@ -677,119 +640,67 @@ static void KMSDRM_CrtcSetVrr(uint32_t drm_fd, uint32_t crtc_id, bool enabled)
return;
}
KMSDRM_drmModeObjectSetProperty(drm_fd,
crtc_id,
DRM_MODE_OBJECT_CRTC,
vrr_prop_id,
enabled);
KMSDRM_drmModeObjectSetProperty(drm_fd, crtc_id, DRM_MODE_OBJECT_CRTC, vrr_prop_id, enabled);
}
static bool KMSDRM_CrtcGetVrr(uint32_t drm_fd, uint32_t crtc_id)
{
uint32_t object_prop_id, vrr_prop_id;
drmModeObjectPropertiesPtr props;
bool object_prop_value;
int i;
uint32_t vrr_prop_id = 0;
bool found = false;
uint64_t prop_value = 0;
if (!KMSDRM_VrrPropId(drm_fd, crtc_id, &vrr_prop_id)) {
return false;
}
props = KMSDRM_drmModeObjectGetProperties(drm_fd,
crtc_id,
DRM_MODE_OBJECT_CRTC);
if (!props) {
return false;
drmModeObjectPropertiesPtr props = KMSDRM_drmModeObjectGetProperties(drm_fd, crtc_id, DRM_MODE_OBJECT_CRTC);
if (props) {
for (uint32_t i = 0; !found && i < props->count_props; ++i) {
drmModePropertyPtr prop = KMSDRM_drmModeGetProperty(drm_fd, props->props[i]);
if (prop) {
if (prop->prop_id == vrr_prop_id) {
prop_value = props->prop_values[i];
found = true;
}
KMSDRM_drmModeFreeProperty(prop);
}
}
KMSDRM_drmModeFreeObjectProperties(props);
}
for (i = 0; i < props->count_props; ++i) {
drmModePropertyPtr drm_prop = KMSDRM_drmModeGetProperty(drm_fd, props->props[i]);
if (!drm_prop) {
continue;
}
object_prop_id = drm_prop->prop_id;
object_prop_value = props->prop_values[i] ? true : false;
KMSDRM_drmModeFreeProperty(drm_prop);
if (object_prop_id == vrr_prop_id) {
return object_prop_value;
}
if (found) {
return prop_value ? true : false;
}
return false;
}
static bool KMSDRM_OrientationPropId(uint32_t drm_fd, uint32_t crtc_id, uint32_t *orientation_prop_id)
{
drmModeObjectPropertiesPtr drm_props;
drm_props = KMSDRM_drmModeObjectGetProperties(drm_fd,
crtc_id,
DRM_MODE_OBJECT_CONNECTOR);
if (!drm_props) {
return false;
}
*orientation_prop_id = KMSDRM_CrtcGetPropId(drm_fd,
drm_props,
"panel orientation");
KMSDRM_drmModeFreeObjectProperties(drm_props);
return true;
}
static int KMSDRM_CrtcGetOrientation(uint32_t drm_fd, uint32_t crtc_id)
{
uint32_t orientation_prop_id;
drmModeObjectPropertiesPtr props;
int i;
bool done = false;
bool found = false;
int orientation = 0;
if (!KMSDRM_OrientationPropId(drm_fd, crtc_id, &orientation_prop_id)) {
return orientation;
}
props = KMSDRM_drmModeObjectGetProperties(drm_fd,
crtc_id,
DRM_MODE_OBJECT_CONNECTOR);
if (!props) {
return orientation;
}
for (i = 0; i < props->count_props && !done; ++i) {
drmModePropertyPtr drm_prop = KMSDRM_drmModeGetProperty(drm_fd, props->props[i]);
if (!drm_prop) {
continue;
}
if (drm_prop->prop_id == orientation_prop_id && (drm_prop->flags & DRM_MODE_PROP_ENUM)) {
if (drm_prop->count_enums) {
// "Normal" is the default of no rotation (0 degrees)
if (SDL_strcmp(drm_prop->enums[0].name, "Left Side Up") == 0) {
orientation = 90;
} else if (SDL_strcmp(drm_prop->enums[0].name, "Upside Down") == 0) {
orientation = 180;
} else if (SDL_strcmp(drm_prop->enums[0].name, "Right Side Up") == 0) {
orientation = 270;
drmModeObjectPropertiesPtr props = KMSDRM_drmModeObjectGetProperties(drm_fd, crtc_id, DRM_MODE_OBJECT_CONNECTOR);
if (props) {
for (uint32_t i = 0; !found && i < props->count_props; ++i) {
drmModePropertyPtr prop = KMSDRM_drmModeGetProperty(drm_fd, props->props[i]);
if (prop) {
if (SDL_strcasecmp(prop->name, "panel orientation") == 0 && (prop->flags & DRM_MODE_PROP_ENUM)) {
if (prop->count_enums) {
// "Normal" is the default of no rotation (0 degrees)
if (SDL_strcmp(prop->enums[0].name, "Left Side Up") == 0) {
orientation = 90;
} else if (SDL_strcmp(prop->enums[0].name, "Upside Down") == 0) {
orientation = 180;
} else if (SDL_strcmp(prop->enums[0].name, "Right Side Up") == 0) {
orientation = 270;
}
}
found = true;
}
KMSDRM_drmModeFreeProperty(prop);
}
done = true;
}
KMSDRM_drmModeFreeProperty(drm_prop);
KMSDRM_drmModeFreeObjectProperties(props);
}
KMSDRM_drmModeFreeObjectProperties(props);
return orientation;
}
@ -964,7 +875,7 @@ static void KMSDRM_AddDisplay(SDL_VideoDevice *_this, drmModeConnector *connecto
// save previous vrr state
dispdata->saved_vrr = KMSDRM_CrtcGetVrr(viddata->drm_fd, crtc->crtc_id);
// try to enable vrr
if (KMSDRM_ConnectorCheckVrrCapable(viddata->drm_fd, connector->connector_id, "VRR_CAPABLE")) {
if (KMSDRM_ConnectorCheckVrrCapable(viddata->drm_fd, connector->connector_id)) {
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Enabling VRR");
KMSDRM_CrtcSetVrr(viddata->drm_fd, crtc->crtc_id, true);
}

View File

@ -420,7 +420,7 @@ add_sdl_test_executable(testdialog SOURCES testdialog.c)
add_sdl_test_executable(testtime SOURCES testtime.c)
add_sdl_test_executable(testmanymouse SOURCES testmanymouse.c)
add_sdl_test_executable(testmodal SOURCES testmodal.c)
add_sdl_test_executable(testtray SOURCES testtray.c)
add_sdl_test_executable(testtray NEEDS_RESOURCES TESTUTILS SOURCES testtray.c)
add_sdl_test_executable(testprocess

View File

@ -1,3 +1,4 @@
#include "testutils.h"
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <SDL3/SDL_test.h>
@ -520,14 +521,17 @@ int main(int argc, char **argv)
goto quit;
}
/* TODO: Resource paths? */
SDL_Surface *icon = SDL_LoadBMP("../test/sdl-test_round.bmp");
char *icon1filename = GetResourceFilename(NULL, "sdl-test_round.bmp");
SDL_Surface *icon = SDL_LoadBMP(icon1filename);
SDL_free(icon1filename);
if (!icon) {
SDL_Log("Couldn't load icon 1, proceeding without: %s", SDL_GetError());
}
SDL_Surface *icon2 = SDL_LoadBMP("../test/speaker.bmp");
char *icon2filename = GetResourceFilename(NULL, "speaker.bmp");
SDL_Surface *icon2 = SDL_LoadBMP(icon2filename);
SDL_free(icon2filename);
if (!icon2) {
SDL_Log("Couldn't load icon 2, proceeding without: %s", SDL_GetError());