Added Vulkan support for palettized textures

This commit is contained in:
Sam Lantinga
2025-09-28 09:33:08 -07:00
parent e2fe23ddab
commit 5d311635cf
5 changed files with 614 additions and 435 deletions

View File

@@ -181,6 +181,12 @@ static const float TONEMAP_NONE = 0;
//static const float TONEMAP_LINEAR = 1;
static const float TONEMAP_CHROME = 2;
//static const float TEXTURETYPE_NONE = 0;
static const float TEXTURETYPE_RGB = 1;
static const float TEXTURETYPE_RGB_PIXELART = 2;
static const float TEXTURETYPE_PALETTE = 3;
static const float TEXTURETYPE_PALETTE_PIXELART = 4;
static const float INPUTTYPE_UNSPECIFIED = 0;
static const float INPUTTYPE_SRGB = 1;
static const float INPUTTYPE_SCRGB = 2;
@@ -190,9 +196,9 @@ static const float INPUTTYPE_HDR10 = 3;
typedef struct
{
float scRGB_output;
float texture_type;
float input_type;
float color_scale;
float pixel_art;
float texel_width;
float texel_height;
@@ -238,6 +244,7 @@ typedef struct
typedef struct
{
VULKAN_Image mainImage;
VULKAN_Image paletteImage;
VkRenderPass mainRenderpasses[VULKAN_RENDERPASS_COUNT];
VkFramebuffer mainFramebuffer;
VULKAN_Buffer stagingBuffer;
@@ -447,6 +454,8 @@ static VkFormat SDLPixelFormatToVkTextureFormat(Uint32 format, Uint32 output_col
return VK_FORMAT_R8G8B8A8_SRGB;
}
return VK_FORMAT_R8G8B8A8_UNORM;
case SDL_PIXELFORMAT_INDEX8:
return VK_FORMAT_R8_UNORM;
case SDL_PIXELFORMAT_YUY2:
return VK_FORMAT_G8B8G8R8_422_UNORM;
case SDL_PIXELFORMAT_UYVY:
@@ -2685,6 +2694,21 @@ static bool VULKAN_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, S
return false;
}
if (texture->format == SDL_PIXELFORMAT_INDEX8) {
VkFormat paletteFormat;
if (renderer->output_colorspace == SDL_COLORSPACE_SRGB_LINEAR) {
paletteFormat = VK_FORMAT_R8G8B8A8_SRGB;
} else {
paletteFormat = VK_FORMAT_R8G8B8A8_UNORM;
}
result = VULKAN_AllocateImage(rendererData, 0, 256, 1, paletteFormat, usage, imageViewSwizzle, textureData->samplerYcbcrConversion, &textureData->paletteImage);
if (result != VK_SUCCESS) {
SET_ERROR_CODE("VULKAN_AllocateImage()", result);
return false;
}
}
SDL_PropertiesID props = SDL_GetTextureProperties(texture);
SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_VULKAN_TEXTURE_NUMBER, (Sint64)textureData->mainImage.image);
@@ -2722,6 +2746,10 @@ static void VULKAN_DestroyTexture(SDL_Renderer *renderer,
VULKAN_DestroyImage(rendererData, &textureData->mainImage);
if (texture->format == SDL_PIXELFORMAT_INDEX8) {
VULKAN_DestroyImage(rendererData, &textureData->paletteImage);
}
#ifdef SDL_HAVE_YUV
if (textureData->samplerYcbcrConversion != VK_NULL_HANDLE) {
vkDestroySamplerYcbcrConversionKHR(rendererData->device, textureData->samplerYcbcrConversion, NULL);
@@ -2849,6 +2877,19 @@ static bool VULKAN_UpdateTextureInternal(VULKAN_RenderData *rendererData, VkImag
}
static bool VULKAN_UpdateTexturePalette(SDL_Renderer *renderer, SDL_Texture *texture)
{
VULKAN_RenderData *rendererData = (VULKAN_RenderData *)renderer->internal;
VULKAN_TextureData *textureData = (VULKAN_TextureData *)texture->internal;
SDL_Palette *palette = texture->palette;
if (!textureData) {
return SDL_SetError("Texture is not currently available");
}
return VULKAN_UpdateTextureInternal(rendererData, textureData->paletteImage.image, textureData->paletteImage.format, 0, 0, 0, palette->ncolors, 1, palette->colors, palette->ncolors * sizeof(*palette->colors), &textureData->paletteImage.imageLayout);
}
static bool VULKAN_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture,
const SDL_Rect *rect, const void *srcPixels,
int srcPitch)
@@ -3369,8 +3410,21 @@ static void VULKAN_SetupShaderConstants(SDL_Renderer *renderer, const SDL_Render
break;
}
if (texture->format == SDL_PIXELFORMAT_INDEX8) {
if (cmd->data.draw.texture_scale_mode == SDL_SCALEMODE_PIXELART) {
constants->texture_type = TEXTURETYPE_PALETTE_PIXELART;
} else {
constants->texture_type = TEXTURETYPE_PALETTE;
}
} else {
if (cmd->data.draw.texture_scale_mode == SDL_SCALEMODE_PIXELART) {
constants->texture_type = TEXTURETYPE_RGB_PIXELART;
} else {
constants->texture_type = TEXTURETYPE_RGB;
}
}
if (cmd->data.draw.texture_scale_mode == SDL_SCALEMODE_PIXELART) {
constants->pixel_art = 1.0f;
constants->texture_width = texture->w;
constants->texture_height = texture->h;
constants->texel_width = 1.0f / constants->texture_width;
@@ -3399,7 +3453,7 @@ static VULKAN_Shader SelectShader(const VULKAN_PixelShaderConstants *shader_cons
return SHADER_SOLID;
}
if (shader_constants->pixel_art == 0.0f &&
if (shader_constants->texture_type == TEXTURETYPE_RGB &&
shader_constants->input_type == INPUTTYPE_UNSPECIFIED &&
shader_constants->tonemap_method == TONEMAP_NONE) {
return SHADER_RGB;
@@ -3445,22 +3499,29 @@ static VkResult VULKAN_CreateDescriptorSetAndPipelineLayout(VULKAN_RenderData *r
VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo = { 0 };
descriptorSetLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
descriptorSetLayoutCreateInfo.flags = 0;
VkDescriptorSetLayoutBinding layoutBindings[2];
VkDescriptorSetLayoutBinding layoutBindings[3];
// PixelShaderConstants
layoutBindings[0].binding = 1;
layoutBindings[0].binding = 0;
layoutBindings[0].descriptorCount = 1;
layoutBindings[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
layoutBindings[0].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
layoutBindings[0].pImmutableSamplers = NULL;
// Combined image/sampler
layoutBindings[1].binding = 0;
layoutBindings[1].binding = 1;
layoutBindings[1].descriptorCount = 1;
layoutBindings[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
layoutBindings[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
layoutBindings[1].pImmutableSamplers = (samplerYcbcr != VK_NULL_HANDLE) ? &samplerYcbcr : NULL;
descriptorSetLayoutCreateInfo.bindingCount = 2;
// Combined image/sampler
layoutBindings[2].binding = 2;
layoutBindings[2].descriptorCount = 1;
layoutBindings[2].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
layoutBindings[2].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
layoutBindings[2].pImmutableSamplers = (samplerYcbcr != VK_NULL_HANDLE) ? &samplerYcbcr : NULL;
descriptorSetLayoutCreateInfo.bindingCount = 3;
descriptorSetLayoutCreateInfo.pBindings = layoutBindings;
result = vkCreateDescriptorSetLayout(rendererData->device, &descriptorSetLayoutCreateInfo, NULL, descriptorSetLayoutOut);
if (result != VK_SUCCESS) {
@@ -3489,7 +3550,7 @@ static VkResult VULKAN_CreateDescriptorSetAndPipelineLayout(VULKAN_RenderData *r
}
static VkDescriptorSet VULKAN_AllocateDescriptorSet(SDL_Renderer *renderer, VULKAN_Shader shader, VkDescriptorSetLayout descriptorSetLayout,
VkSampler sampler, VkBuffer constantBuffer, VkDeviceSize constantBufferOffset, VkImageView imageView)
VkBuffer constantBuffer, VkDeviceSize constantBufferOffset, int numImages, VkImageView *imageViews, int numSamplers, VkSampler *samplers)
{
VULKAN_RenderData *rendererData = (VULKAN_RenderData *)renderer->internal;
uint32_t currentDescriptorPoolIndex = rendererData->currentDescriptorPoolIndex;
@@ -3538,44 +3599,50 @@ static VkDescriptorSet VULKAN_AllocateDescriptorSet(SDL_Renderer *renderer, VULK
rendererData->currentDescriptorSetIndex = 0;
// Call recursively to allocate from the new pool
return VULKAN_AllocateDescriptorSet(renderer, shader, descriptorSetLayout, sampler, constantBuffer, constantBufferOffset, imageView);
return VULKAN_AllocateDescriptorSet(renderer, shader, descriptorSetLayout, constantBuffer, constantBufferOffset, numImages, imageViews, numSamplers, samplers);
}
}
rendererData->currentDescriptorSetIndex++;
VkDescriptorImageInfo combinedImageSamplerDescriptor = { 0 };
VkDescriptorImageInfo combinedImageSamplerDescriptor[2];
VkDescriptorBufferInfo bufferDescriptor = { 0 };
bufferDescriptor.buffer = constantBuffer;
bufferDescriptor.offset = constantBufferOffset;
bufferDescriptor.range = sizeof(VULKAN_PixelShaderConstants);
VkWriteDescriptorSet descriptorWrites[2];
SDL_memset(descriptorWrites, 0, sizeof(descriptorWrites));
VkWriteDescriptorSet descriptorWrites[3];
SDL_zero(descriptorWrites);
uint32_t descriptorCount = 1; // Always have the uniform buffer
descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrites[0].dstSet = descriptorSet;
descriptorWrites[0].dstBinding = 1;
descriptorWrites[0].dstBinding = 0;
descriptorWrites[0].dstArrayElement = 0;
descriptorWrites[0].descriptorCount = 1;
descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
descriptorWrites[0].pBufferInfo = &bufferDescriptor;
if (sampler != VK_NULL_HANDLE && imageView != VK_NULL_HANDLE) {
descriptorCount++;
descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrites[1].dstSet = descriptorSet;
descriptorWrites[1].dstBinding = 0;
descriptorWrites[1].dstArrayElement = 0;
descriptorWrites[1].descriptorCount = 1;
descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
descriptorWrites[1].pImageInfo = &combinedImageSamplerDescriptor;
// Ignore the sampler if we're using YcBcCr data since it will be baked in the descriptor set layout
SDL_assert(numSamplers == numImages);
for (int i = 0; i < numImages; ++i) {
SDL_assert(i < SDL_arraysize(combinedImageSamplerDescriptor));
VkDescriptorImageInfo *pImageInfo = &combinedImageSamplerDescriptor[i];
SDL_zerop(pImageInfo);
if (descriptorSetLayout == rendererData->descriptorSetLayout) {
combinedImageSamplerDescriptor.sampler = sampler;
pImageInfo->sampler = samplers[i];
} else {
// Ignore the sampler if we're using YcBcCr data since it will be baked in the descriptor set layout
}
combinedImageSamplerDescriptor.imageView = imageView;
combinedImageSamplerDescriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
pImageInfo->imageView = imageViews[i];
pImageInfo->imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
SDL_assert(descriptorCount < SDL_arraysize(descriptorWrites));
VkWriteDescriptorSet *pDescriptorSet = &descriptorWrites[descriptorCount++];
pDescriptorSet->sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
pDescriptorSet->dstSet = descriptorSet;
pDescriptorSet->dstBinding = 1 + i;
pDescriptorSet->dstArrayElement = 0;
pDescriptorSet->descriptorCount = 1;
pDescriptorSet->descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
pDescriptorSet->pImageInfo = pImageInfo;
}
vkUpdateDescriptorSets(rendererData->device, descriptorCount, descriptorWrites, 0, NULL);
@@ -3584,7 +3651,7 @@ static VkDescriptorSet VULKAN_AllocateDescriptorSet(SDL_Renderer *renderer, VULK
}
static bool VULKAN_SetDrawState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, VkPipelineLayout pipelineLayout, VkDescriptorSetLayout descriptorSetLayout,
const VULKAN_PixelShaderConstants *shader_constants, VkPrimitiveTopology topology, VkImageView imageView, VkSampler sampler, const Float4X4 *matrix, VULKAN_DrawStateCache *stateCache)
const VULKAN_PixelShaderConstants *shader_constants, VkPrimitiveTopology topology, int numImages, VkImageView *imageViews, int numSamplers, VkSampler *samplers, const Float4X4 *matrix, VULKAN_DrawStateCache *stateCache)
{
VULKAN_RenderData *rendererData = (VULKAN_RenderData *)renderer->internal;
@@ -3718,7 +3785,7 @@ static bool VULKAN_SetDrawState(SDL_Renderer *renderer, const SDL_RenderCommand
}
// Allocate/update descriptor set with the bindings
descriptorSet = VULKAN_AllocateDescriptorSet(renderer, shader, descriptorSetLayout, sampler, constantBuffer, constantBufferOffset, imageView);
descriptorSet = VULKAN_AllocateDescriptorSet(renderer, shader, descriptorSetLayout, constantBuffer, constantBufferOffset, numImages, imageViews, numSamplers, samplers);
if (descriptorSet == VK_NULL_HANDLE) {
return false;
}
@@ -3794,18 +3861,16 @@ static bool VULKAN_SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand
SDL_Texture *texture = cmd->data.draw.texture;
VULKAN_RenderData *rendererData = (VULKAN_RenderData *)renderer->internal;
VULKAN_TextureData *textureData = (VULKAN_TextureData *)texture->internal;
VkSampler textureSampler = VK_NULL_HANDLE;
int numImageViews = 0;
VkImageView imageViews[2];
int numSamplers = 0;
VkSampler samplers[2];
VULKAN_PixelShaderConstants constants;
VkDescriptorSetLayout descriptorSetLayout = (textureData->descriptorSetLayoutYcbcr != VK_NULL_HANDLE) ? textureData->descriptorSetLayoutYcbcr : rendererData->descriptorSetLayout;
VkPipelineLayout pipelineLayout = (textureData->pipelineLayoutYcbcr != VK_NULL_HANDLE) ? textureData->pipelineLayoutYcbcr : rendererData->pipelineLayout;
VULKAN_SetupShaderConstants(renderer, cmd, texture, &constants);
textureSampler = VULKAN_GetSampler(rendererData, cmd->data.draw.texture_scale_mode, cmd->data.draw.texture_address_mode_u, cmd->data.draw.texture_address_mode_v);
if (textureSampler == VK_NULL_HANDLE) {
return false;
}
if (textureData->mainImage.imageLayout != VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
bool stoppedRenderPass = false;
if (rendererData->currentRenderPass != VK_NULL_HANDLE) {
@@ -3827,8 +3892,50 @@ static bool VULKAN_SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand
VULKAN_BeginRenderPass(rendererData, VK_ATTACHMENT_LOAD_OP_LOAD, NULL);
}
}
imageViews[numImageViews++] = textureData->mainImage.imageView;
return VULKAN_SetDrawState(renderer, cmd, pipelineLayout, descriptorSetLayout, &constants, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, textureData->mainImage.imageView, textureSampler, matrix, stateCache);
samplers[numSamplers] = VULKAN_GetSampler(rendererData, cmd->data.draw.texture_scale_mode, cmd->data.draw.texture_address_mode_u, cmd->data.draw.texture_address_mode_v);
if (samplers[numSamplers] == VK_NULL_HANDLE) {
return false;
}
++numSamplers;
if (texture->format == SDL_PIXELFORMAT_INDEX8) {
if (textureData->paletteImage.imageLayout != VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
bool stoppedRenderPass = false;
if (rendererData->currentRenderPass != VK_NULL_HANDLE) {
vkCmdEndRenderPass(rendererData->currentCommandBuffer);
rendererData->currentRenderPass = VK_NULL_HANDLE;
stoppedRenderPass = true;
}
VULKAN_RecordPipelineImageBarrier(rendererData,
VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_ACCESS_SHADER_READ_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
textureData->paletteImage.image,
&textureData->paletteImage.imageLayout);
if (stoppedRenderPass) {
VULKAN_BeginRenderPass(rendererData, VK_ATTACHMENT_LOAD_OP_LOAD, NULL);
}
}
imageViews[numImageViews++] = textureData->paletteImage.imageView;
samplers[numSamplers] = VULKAN_GetSampler(rendererData, SDL_SCALEMODE_NEAREST, SDL_TEXTURE_ADDRESS_CLAMP, SDL_TEXTURE_ADDRESS_CLAMP);
if (samplers[numSamplers] == VK_NULL_HANDLE) {
return false;
}
++numSamplers;
} else {
// We need a valid image view and sampler, but we know we're not going to reference them in the shader
imageViews[numImageViews++] = imageViews[0];
samplers[numSamplers++] = samplers[0];
}
return VULKAN_SetDrawState(renderer, cmd, pipelineLayout, descriptorSetLayout, &constants, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, numImageViews, imageViews, numSamplers, samplers, matrix, stateCache);
}
static void VULKAN_DrawPrimitives(SDL_Renderer *renderer, VkPrimitiveTopology primitiveTopology, const size_t vertexStart, const size_t vertexCount)
@@ -3929,7 +4036,7 @@ static bool VULKAN_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cm
const size_t count = cmd->data.draw.count;
const size_t first = cmd->data.draw.first;
const size_t start = first / sizeof(VULKAN_VertexPositionColor);
VULKAN_SetDrawState(renderer, cmd, rendererData->pipelineLayout, rendererData->descriptorSetLayout, NULL, VK_PRIMITIVE_TOPOLOGY_POINT_LIST, VK_NULL_HANDLE, VK_NULL_HANDLE, NULL, &stateCache);
VULKAN_SetDrawState(renderer, cmd, rendererData->pipelineLayout, rendererData->descriptorSetLayout, NULL, VK_PRIMITIVE_TOPOLOGY_POINT_LIST, 0, NULL, 0, NULL, NULL, &stateCache);
VULKAN_DrawPrimitives(renderer, VK_PRIMITIVE_TOPOLOGY_POINT_LIST, start, count);
break;
}
@@ -3940,10 +4047,10 @@ static bool VULKAN_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cm
const size_t first = cmd->data.draw.first;
const size_t start = first / sizeof(VULKAN_VertexPositionColor);
const VULKAN_VertexPositionColor *verts = (VULKAN_VertexPositionColor *)(((Uint8 *)vertices) + first);
VULKAN_SetDrawState(renderer, cmd, rendererData->pipelineLayout, rendererData->descriptorSetLayout, NULL, VK_PRIMITIVE_TOPOLOGY_LINE_STRIP, VK_NULL_HANDLE, VK_NULL_HANDLE, NULL, &stateCache);
VULKAN_SetDrawState(renderer, cmd, rendererData->pipelineLayout, rendererData->descriptorSetLayout, NULL, VK_PRIMITIVE_TOPOLOGY_LINE_STRIP, 0, NULL, 0, NULL, NULL, &stateCache);
VULKAN_DrawPrimitives(renderer, VK_PRIMITIVE_TOPOLOGY_LINE_STRIP, start, count);
if (verts[0].pos[0] != verts[count - 1].pos[0] || verts[0].pos[1] != verts[count - 1].pos[1]) {
VULKAN_SetDrawState(renderer, cmd, rendererData->pipelineLayout, rendererData->descriptorSetLayout, NULL, VK_PRIMITIVE_TOPOLOGY_POINT_LIST, VK_NULL_HANDLE, VK_NULL_HANDLE, NULL, &stateCache);
VULKAN_SetDrawState(renderer, cmd, rendererData->pipelineLayout, rendererData->descriptorSetLayout, NULL, VK_PRIMITIVE_TOPOLOGY_POINT_LIST, 0, NULL, 0, NULL, NULL, &stateCache);
VULKAN_DrawPrimitives(renderer, VK_PRIMITIVE_TOPOLOGY_POINT_LIST, start + (count - 1), 1);
}
break;
@@ -3968,7 +4075,7 @@ static bool VULKAN_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cm
if (texture) {
VULKAN_SetCopyState(renderer, cmd, NULL, &stateCache);
} else {
VULKAN_SetDrawState(renderer, cmd, rendererData->pipelineLayout, rendererData->descriptorSetLayout, NULL, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, VK_NULL_HANDLE, VK_NULL_HANDLE, NULL, &stateCache);
VULKAN_SetDrawState(renderer, cmd, rendererData->pipelineLayout, rendererData->descriptorSetLayout, NULL, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, NULL, 0, NULL, NULL, &stateCache);
}
VULKAN_DrawPrimitives(renderer, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, start, count);
@@ -4285,6 +4392,7 @@ static bool VULKAN_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SD
renderer->WindowEvent = VULKAN_WindowEvent;
renderer->SupportsBlendMode = VULKAN_SupportsBlendMode;
renderer->CreateTexture = VULKAN_CreateTexture;
renderer->UpdateTexturePalette = VULKAN_UpdateTexturePalette;
renderer->UpdateTexture = VULKAN_UpdateTexture;
#ifdef SDL_HAVE_YUV
renderer->UpdateTextureYUV = VULKAN_UpdateTextureYUV;
@@ -4314,6 +4422,7 @@ static bool VULKAN_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SD
SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_ABGR8888);
SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_ABGR2101010);
SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_RGBA64_FLOAT);
SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_INDEX8);
SDL_SetNumberProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_MAX_TEXTURE_SIZE_NUMBER, 16384);
/* HACK: make sure the SDL_Renderer references the SDL_Window data now, in