SDL: GPU: Rework MSAA (#10859)

From 9416917353cf54fc6753f7f5e557f55214ed86f6 Mon Sep 17 00:00:00 2001
From: Caleb Cornett <[EMAIL REDACTED]>
Date: Mon, 16 Sep 2024 12:19:09 -0500
Subject: [PATCH] GPU: Rework MSAA (#10859)

---
 include/SDL3/SDL_gpu.h          |   39 +-
 src/gpu/SDL_gpu.c               |   66 +-
 src/gpu/d3d11/SDL_gpu_d3d11.c   |  158 ++--
 src/gpu/d3d12/SDL_gpu_d3d12.c   |  140 ++--
 src/gpu/metal/SDL_gpu_metal.m   |   94 +--
 src/gpu/vulkan/SDL_gpu_vulkan.c | 1291 +++++++++++--------------------
 test/testgpu_spinning_cube.c    |  101 ++-
 7 files changed, 777 insertions(+), 1112 deletions(-)

diff --git a/include/SDL3/SDL_gpu.h b/include/SDL3/SDL_gpu.h
index 6ad4532ba1e80..c411eacc07634 100644
--- a/include/SDL3/SDL_gpu.h
+++ b/include/SDL3/SDL_gpu.h
@@ -282,8 +282,10 @@ typedef enum SDL_GPULoadOp
  */
 typedef enum SDL_GPUStoreOp
 {
-    SDL_GPU_STOREOP_STORE,     /**< The contents generated during the render pass will be written to memory. */
-    SDL_GPU_STOREOP_DONT_CARE  /**< The contents generated during the render pass are not needed and may be discarded. The contents will be undefined. */
+    SDL_GPU_STOREOP_STORE,             /**< The contents generated during the render pass will be written to memory. */
+    SDL_GPU_STOREOP_DONT_CARE,         /**< The contents generated during the render pass are not needed and may be discarded. The contents will be undefined. */
+    SDL_GPU_STOREOP_RESOLVE,           /**< The multisample contents generated during the render pass will be resolved to a non-multisample texture. The contents in the multisample texture may then be discarded and will be undefined. */
+    SDL_GPU_STOREOP_RESOLVE_AND_STORE  /**< The multisample contents generated during the render pass will be resolved to a non-multisample texture. The contents in the multisample texture will be written to memory. */
 } SDL_GPUStoreOp;
 
 /**
@@ -1499,7 +1501,8 @@ typedef struct SDL_GPUComputePipelineCreateInfo
  * The load_op field determines what is done with the texture at the beginning
  * of the render pass.
  *
- * - LOAD: Loads the data currently in the texture.
+ * - LOAD: Loads the data currently in the texture. Not recommended for
+ *   multisample textures as it requires significant memory bandwidth.
  * - CLEAR: Clears the texture to a single color.
  * - DONT_CARE: The driver will do whatever it wants with the texture memory.
  *   This is a good option if you know that every single pixel will be touched
@@ -1508,9 +1511,16 @@ typedef struct SDL_GPUComputePipelineCreateInfo
  * The store_op field determines what is done with the color results of the
  * render pass.
  *
- * - STORE: Stores the results of the render pass in the texture.
+ * - STORE: Stores the results of the render pass in the texture. Not recommended
+ *   for multisample textures as it requires significant memory bandwidth.
  * - DONT_CARE: The driver will do whatever it wants with the texture memory.
  *   This is often a good option for depth/stencil textures.
+ * - RESOLVE: Resolves a multisample texture into resolve_texture, which must have
+ *   a sample count of 1. Then the driver may discard the multisample texture memory.
+ *   This is the most performant method of resolving a multisample target.
+ * - RESOLVE_AND_STORE: Resolves a multisample texture into the resolve_texture,
+ *   which must have a sample count of 1. Then the driver stores the multisample
+ *   texture's contents. Not recommended as it requires significant memory bandwidth.
  *
  * \since This struct is available since SDL 3.0.0
  *
@@ -1518,16 +1528,19 @@ typedef struct SDL_GPUComputePipelineCreateInfo
  */
 typedef struct SDL_GPUColorTargetInfo
 {
-    SDL_GPUTexture *texture;      /**< The texture that will be used as a color target by a render pass. */
-    Uint32 mip_level;             /**< The mip level to use as a color target. */
-    Uint32 layer_or_depth_plane;  /**< The layer index or depth plane to use as a color target. This value is treated as a layer index on 2D array and cube textures, and as a depth plane on 3D textures. */
-    SDL_FColor clear_color;       /**< The color to clear the color target to at the start of the render pass. Ignored if SDL_GPU_LOADOP_CLEAR is not used. */
-    SDL_GPULoadOp load_op;        /**< What is done with the contents of the color target at the beginning of the render pass. */
-    SDL_GPUStoreOp store_op;      /**< What is done with the results of the render pass. */
-    SDL_bool cycle;               /**< SDL_TRUE cycles the texture if the texture is bound and load_op is not LOAD */
+    SDL_GPUTexture *texture;         /**< The texture that will be used as a color target by a render pass. */
+    Uint32 mip_level;                /**< The mip level to use as a color target. */
+    Uint32 layer_or_depth_plane;     /**< The layer index or depth plane to use as a color target. This value is treated as a layer index on 2D array and cube textures, and as a depth plane on 3D textures. */
+    SDL_FColor clear_color;          /**< The color to clear the color target to at the start of the render pass. Ignored if SDL_GPU_LOADOP_CLEAR is not used. */
+    SDL_GPULoadOp load_op;           /**< What is done with the contents of the color target at the beginning of the render pass. */
+    SDL_GPUStoreOp store_op;         /**< What is done with the results of the render pass. */
+    SDL_GPUTexture *resolve_texture; /**< The texture that will receive the results of a multisample resolve operation. Ignored if a RESOLVE* store_op is not used. */
+    Uint32 resolve_mip_level;        /**< The mip level of the resolve texture to use for the resolve operation. Ignored if a RESOLVE* store_op is not used. */
+    Uint32 resolve_layer;            /**< The layer index of the resolve texture to use for the resolve operation. Ignored if a RESOLVE* store_op is not used. */
+    SDL_bool cycle;                  /**< SDL_TRUE cycles the texture if the texture is bound and load_op is not LOAD */
+    SDL_bool cycle_resolve_texture;  /**< SDL_TRUE cycles the resolve texture if the resolve texture is bound. Ignored if a RESOLVE* store_op is not used. */
     Uint8 padding1;
     Uint8 padding2;
-    Uint8 padding3;
 } SDL_GPUColorTargetInfo;
 
 /**
@@ -1568,6 +1581,8 @@ typedef struct SDL_GPUColorTargetInfo
  *   This is often a good option for depth/stencil textures that don't need to
  *   be reused again.
  *
+ * Note that depth/stencil targets do not support multisample resolves.
+ *
  * \since This struct is available since SDL 3.0.0
  *
  * \sa SDL_BeginGPURenderPass
diff --git a/src/gpu/SDL_gpu.c b/src/gpu/SDL_gpu.c
index b458894b71b6a..29698506e590e 100644
--- a/src/gpu/SDL_gpu.c
+++ b/src/gpu/SDL_gpu.c
@@ -867,6 +867,13 @@ SDL_GPUTexture *SDL_CreateGPUTexture(
             SDL_assert_release(!"For any texture: usage cannot contain both GRAPHICS_STORAGE_READ and SAMPLER");
             failed = true;
         }
+        if (createinfo->sample_count > 1 && (createinfo->usage & (SDL_GPU_TEXTUREUSAGE_SAMPLER |
+                                                                  SDL_GPU_TEXTUREUSAGE_GRAPHICS_STORAGE_READ |
+                                                                  SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_READ |
+                                                                  SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_WRITE))) {
+            SDL_assert_release(!"For multisample textures: usage cannot contain SAMPLER or STORAGE flags");
+            failed = true;
+        }
         if (IsDepthFormat(createinfo->format) && (createinfo->usage & ~(SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET | SDL_GPU_TEXTUREUSAGE_SAMPLER))) {
             SDL_assert_release(!"For depth textures: usage cannot contain any flags except for DEPTH_STENCIL_TARGET and SAMPLER");
             failed = true;
@@ -945,16 +952,10 @@ SDL_GPUTexture *SDL_CreateGPUTexture(
                     SDL_assert_release(!"For array textures: usage must not contain DEPTH_STENCIL_TARGET");
                     failed = true;
                 }
-                if (createinfo->sample_count > SDL_GPU_SAMPLECOUNT_1) {
-                    SDL_assert_release(!"For array textures: sample_count must be SDL_GPU_SAMPLECOUNT_1");
-                    failed = true;
-                }
-            } else {
-                // 2D Texture Validation
-                if (createinfo->sample_count > SDL_GPU_SAMPLECOUNT_1 && createinfo->num_levels > 1) {
-                    SDL_assert_release(!"For 2D textures: if sample_count is >= SDL_GPU_SAMPLECOUNT_1, then num_levels must be 1");
-                    failed = true;
-                }
+            }
+            if (createinfo->sample_count > SDL_GPU_SAMPLECOUNT_1 && createinfo->num_levels > 1) {
+                SDL_assert_release(!"For 2D multisample textures: num_levels must be 1");
+                failed = true;
             }
             if (!SDL_GPUTextureSupportsFormat(device, createinfo->format, SDL_GPU_TEXTURETYPE_2D, createinfo->usage)) {
                 SDL_assert_release(!"For 2D textures: the format is unsupported for the given usage");
@@ -1347,13 +1348,50 @@ SDL_GPURenderPass *SDL_BeginGPURenderPass(
         CHECK_ANY_PASS_IN_PROGRESS("Cannot begin render pass during another pass!", NULL)
 
         for (Uint32 i = 0; i < num_color_targets; i += 1) {
+            TextureCommonHeader *textureHeader = (TextureCommonHeader *)color_target_infos[i].texture;
+
             if (color_target_infos[i].cycle && color_target_infos[i].load_op == SDL_GPU_LOADOP_LOAD) {
                 SDL_assert_release(!"Cannot cycle color target when load op is LOAD!");
             }
+
+            if (color_target_infos[i].store_op == SDL_GPU_STOREOP_RESOLVE || color_target_infos[i].store_op == SDL_GPU_STOREOP_RESOLVE_AND_STORE) {
+                if (color_target_infos[i].resolve_texture == NULL) {
+                    SDL_assert_release(!"Store op is RESOLVE or RESOLVE_AND_STORE but resolve_texture is NULL!");
+                } else {
+                    TextureCommonHeader *resolveTextureHeader = (TextureCommonHeader *)color_target_infos[i].resolve_texture;
+                    if (textureHeader->info.sample_count == SDL_GPU_SAMPLECOUNT_1) {
+                        SDL_assert_release(!"Store op is RESOLVE or RESOLVE_AND_STORE but texture is not multisample!");
+                    }
+                    if (resolveTextureHeader->info.sample_count != SDL_GPU_SAMPLECOUNT_1) {
+                        SDL_assert_release(!"Resolve texture must have a sample count of 1!");
+                    }
+                    if (resolveTextureHeader->info.format != textureHeader->info.format) {
+                        SDL_assert_release(!"Resolve texture must have the same format as its corresponding color target!");
+                    }
+                    if (resolveTextureHeader->info.type == SDL_GPU_TEXTURETYPE_3D) {
+                        SDL_assert_release(!"Resolve texture must not be of TEXTURETYPE_3D!");
+                    }
+                }
+            }
         }
 
-        if (depth_stencil_target_info != NULL && depth_stencil_target_info->cycle && (depth_stencil_target_info->load_op == SDL_GPU_LOADOP_LOAD || depth_stencil_target_info->load_op == SDL_GPU_LOADOP_LOAD)) {
-            SDL_assert_release(!"Cannot cycle depth target when load op or stencil load op is LOAD!");
+        if (depth_stencil_target_info != NULL) {
+
+            TextureCommonHeader *textureHeader = (TextureCommonHeader *)depth_stencil_target_info->texture;
+            if (!(textureHeader->info.usage & SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET)) {
+                SDL_assert_release(!"Depth target must have been created with the DEPTH_STENCIL_TARGET usage flag!");
+            }
+
+            if (depth_stencil_target_info->cycle && (depth_stencil_target_info->load_op == SDL_GPU_LOADOP_LOAD || depth_stencil_target_info->stencil_load_op == SDL_GPU_LOADOP_LOAD)) {
+                SDL_assert_release(!"Cannot cycle depth target when load op or stencil load op is LOAD!");
+            }
+
+            if (depth_stencil_target_info->store_op == SDL_GPU_STOREOP_RESOLVE ||
+                depth_stencil_target_info->stencil_store_op == SDL_GPU_STOREOP_RESOLVE ||
+                depth_stencil_target_info->store_op == SDL_GPU_STOREOP_RESOLVE_AND_STORE ||
+                depth_stencil_target_info->stencil_store_op == SDL_GPU_STOREOP_RESOLVE_AND_STORE) {
+                SDL_assert_release(!"RESOLVE store ops are not supported for depth-stencil targets!");
+            }
         }
     }
 
@@ -2397,6 +2435,10 @@ void SDL_BlitGPUTexture(
             SDL_assert_release(!"Blit destination texture must be non-NULL");
             return; // attempting to proceed will crash
         }
+        if (srcHeader->info.sample_count != SDL_GPU_SAMPLECOUNT_1) {
+            SDL_assert_release(!"Blit source texture must have a sample count of 1");
+            failed = true;
+        }
         if ((srcHeader->info.usage & SDL_GPU_TEXTUREUSAGE_SAMPLER) == 0) {
             SDL_assert_release(!"Blit source texture must be created with the SAMPLER usage flag");
             failed = true;
diff --git a/src/gpu/d3d11/SDL_gpu_d3d11.c b/src/gpu/d3d11/SDL_gpu_d3d11.c
index ef18ce6aebc1c..b78c00c54eb52 100644
--- a/src/gpu/d3d11/SDL_gpu_d3d11.c
+++ b/src/gpu/d3d11/SDL_gpu_d3d11.c
@@ -438,9 +438,6 @@ typedef struct D3D11TextureSubresource
 
     ID3D11UnorderedAccessView *uav;                 // NULL if not a storage texture
     ID3D11DepthStencilView *depthStencilTargetView; // NULL if not a depth stencil target
-
-    ID3D11Resource *msaaHandle;             // NULL if not using MSAA
-    ID3D11RenderTargetView *msaaTargetView; // NULL if not an MSAA color target
 } D3D11TextureSubresource;
 
 struct D3D11Texture
@@ -637,12 +634,8 @@ typedef struct D3D11CommandBuffer
     D3D11GraphicsPipeline *graphicsPipeline;
     Uint8 stencilRef;
     SDL_FColor blendConstants;
-
-    // Render Pass MSAA resolve
-    D3D11Texture *colorTargetResolveTexture[MAX_COLOR_TARGET_BINDINGS];
-    Uint32 colorTargetResolveSubresourceIndex[MAX_COLOR_TARGET_BINDINGS];
-    ID3D11Resource *colorTargetMsaaHandle[MAX_COLOR_TARGET_BINDINGS];
-    DXGI_FORMAT colorTargetMsaaFormat[MAX_COLOR_TARGET_BINDINGS];
+    D3D11TextureSubresource *colorTargetSubresources[MAX_COLOR_TARGET_BINDINGS];
+    D3D11TextureSubresource *colorResolveSubresources[MAX_COLOR_TARGET_BINDINGS];
 
     // Compute Pass
     D3D11ComputePipeline *computePipeline;
@@ -1090,14 +1083,6 @@ static void D3D11_INTERNAL_DestroyTexture(D3D11Texture *d3d11Texture)
     }
 
     for (Uint32 subresourceIndex = 0; subresourceIndex < d3d11Texture->subresourceCount; subresourceIndex += 1) {
-        if (d3d11Texture->subresources[subresourceIndex].msaaHandle != NULL) {
-            ID3D11Resource_Release(d3d11Texture->subresources[subresourceIndex].msaaHandle);
-        }
-
-        if (d3d11Texture->subresources[subresourceIndex].msaaTargetView != NULL) {
-            ID3D11RenderTargetView_Release(d3d11Texture->subresources[subresourceIndex].msaaTargetView);
-        }
-
         if (d3d11Texture->subresources[subresourceIndex].colorTargetViews != NULL) {
             for (Uint32 depthIndex = 0; depthIndex < d3d11Texture->subresources[subresourceIndex].depth; depthIndex += 1) {
                 ID3D11RenderTargetView_Release(d3d11Texture->subresources[subresourceIndex].colorTargetViews[depthIndex]);
@@ -1933,8 +1918,8 @@ static D3D11Texture *D3D11_INTERNAL_CreateTexture(
         desc2D.Format = format;
         desc2D.MipLevels = createInfo->num_levels;
         desc2D.MiscFlags = 0;
-        desc2D.SampleDesc.Count = 1;
-        desc2D.SampleDesc.Quality = 0;
+        desc2D.SampleDesc.Count = SDLToD3D11_SampleCount[createInfo->sample_count];
+        desc2D.SampleDesc.Quality = isMultisample ? D3D11_STANDARD_MULTISAMPLE_PATTERN : 0;
         desc2D.Usage = isStaging ? D3D11_USAGE_STAGING : D3D11_USAGE_DEFAULT;
 
         if (createInfo->type == SDL_GPU_TEXTURETYPE_CUBE || createInfo->type == SDL_GPU_TEXTURETYPE_CUBE_ARRAY) {
@@ -2067,54 +2052,10 @@ static D3D11Texture *D3D11_INTERNAL_CreateTexture(
             d3d11Texture->subresources[subresourceIndex].colorTargetViews = NULL;
             d3d11Texture->subresources[subresourceIndex].uav = NULL;
             d3d11Texture->subresources[subresourceIndex].depthStencilTargetView = NULL;
-            d3d11Texture->subresources[subresourceIndex].msaaHandle = NULL;
-            d3d11Texture->subresources[subresourceIndex].msaaTargetView = NULL;
-
-            if (isMultisample) {
-                D3D11_TEXTURE2D_DESC desc2D;
-
-                if (isColorTarget) {
-                    desc2D.BindFlags = D3D11_BIND_RENDER_TARGET;
-                } else if (isDepthStencil) {
-                    desc2D.BindFlags = D3D11_BIND_DEPTH_STENCIL;
-                }
-
-                desc2D.Width = createInfo->width;
-                desc2D.Height = createInfo->height;
-                desc2D.ArraySize = 1;
-                desc2D.CPUAccessFlags = 0;
-                desc2D.Format = format;
-                desc2D.MipLevels = 1;
-                desc2D.MiscFlags = 0;
-                desc2D.SampleDesc.Count = SDLToD3D11_SampleCount[createInfo->sample_count];
-                desc2D.SampleDesc.Quality = (UINT)D3D11_STANDARD_MULTISAMPLE_PATTERN;
-                desc2D.Usage = D3D11_USAGE_DEFAULT;
-
-                res = ID3D11Device_CreateTexture2D(
-                    renderer->device,
-                    &desc2D,
-                    NULL,
-                    (ID3D11Texture2D **)&d3d11Texture->subresources[subresourceIndex].msaaHandle);
-                ERROR_CHECK_RETURN("Could not create MSAA texture!", NULL);
-
-                if (!isDepthStencil) {
-                    D3D11_RENDER_TARGET_VIEW_DESC rtvDesc;
-
-                    rtvDesc.Format = format;
-                    rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DMS;
-
-                    res = ID3D11Device_CreateRenderTargetView(
-                        renderer->device,
-                        d3d11Texture->subresources[subresourceIndex].msaaHandle,
-                        &rtvDesc,
-                        &d3d11Texture->subresources[subresourceIndex].msaaTargetView);
-                    ERROR_CHECK_RETURN("Could not create MSAA RTV!", NULL);
-                }
-            }
 
             if (isDepthStencil) {
-                D3D11_DEPTH_STENCIL_VIEW_DESC dsvDesc;
 
+                D3D11_DEPTH_STENCIL_VIEW_DESC dsvDesc;
                 dsvDesc.Format = SDLToD3D11_TextureFormat[createInfo->format];
                 dsvDesc.Flags = 0;
 
@@ -2127,31 +2068,42 @@ static D3D11Texture *D3D11_INTERNAL_CreateTexture(
 
                 res = ID3D11Device_CreateDepthStencilView(
                     renderer->device,
-                    isMultisample ? d3d11Texture->subresources[subresourceIndex].msaaHandle : d3d11Texture->handle,
+                    d3d11Texture->handle,
                     &dsvDesc,
                     &d3d11Texture->subresources[subresourceIndex].depthStencilTargetView);
                 ERROR_CHECK_RETURN("Could not create DSV!", NULL);
+
             } else if (isColorTarget) {
+
                 d3d11Texture->subresources[subresourceIndex].colorTargetViews = SDL_calloc(depth, sizeof(ID3D11RenderTargetView *));
 
                 for (Uint32 depthIndex = 0; depthIndex < depth; depthIndex += 1) {
                     D3D11_RENDER_TARGET_VIEW_DESC rtvDesc;
-
                     rtvDesc.Format = SDLToD3D11_TextureFormat[createInfo->format];
 
-                    if (createInfo->type == SDL_GPU_TEXTURETYPE_2D_ARRAY || createInfo->type == SDL_GPU_TEXTURETYPE_CUBE || createInfo->type == SDL_GPU_TEXTURETYPE_CUBE_ARRAY) {
-                        rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DARRAY;
-                        rtvDesc.Texture2DArray.MipSlice = levelIndex;
-                        rtvDesc.Texture2DArray.FirstArraySlice = layerIndex;
-                        rtvDesc.Texture2DArray.ArraySize = 1;
-                    } else if (createInfo->type == SDL_GPU_TEXTURETYPE_3D) {
-                        rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE3D;
-                        rtvDesc.Texture3D.MipSlice = levelIndex;
-                        rtvDesc.Texture3D.FirstWSlice = depthIndex;
-                        rtvDesc.Texture3D.WSize = 1;
+                    if (isMultisample) {
+                        if (createInfo->type == SDL_GPU_TEXTURETYPE_2D) {
+                            rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DMS;
+                        } else if (createInfo->type == SDL_GPU_TEXTURETYPE_2D_ARRAY) {
+                            rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DMSARRAY;
+                            rtvDesc.Texture2DMSArray.FirstArraySlice = layerIndex;
+                            rtvDesc.Texture2DMSArray.ArraySize = 1;
+                        }
                     } else {
-                        rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
-                        rtvDesc.Texture2D.MipSlice = levelIndex;
+                        if (createInfo->type == SDL_GPU_TEXTURETYPE_2D_ARRAY || createInfo->type == SDL_GPU_TEXTURETYPE_CUBE || createInfo->type == SDL_GPU_TEXTURETYPE_CUBE_ARRAY) {
+                            rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DARRAY;
+                            rtvDesc.Texture2DArray.MipSlice = levelIndex;
+                            rtvDesc.Texture2DArray.FirstArraySlice = layerIndex;
+                            rtvDesc.Texture2DArray.ArraySize = 1;
+                        } else if (createInfo->type == SDL_GPU_TEXTURETYPE_3D) {
+                            rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE3D;
+                            rtvDesc.Texture3D.MipSlice = levelIndex;
+                            rtvDesc.Texture3D.FirstWSlice = depthIndex;
+                            rtvDesc.Texture3D.WSize = 1;
+                        } else {
+                            rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
+                            rtvDesc.Texture2D.MipSlice = levelIndex;
+                        }
                     }
 
                     res = ID3D11Device_CreateRenderTargetView(
@@ -3238,10 +3190,8 @@ static SDL_GPUCommandBuffer *D3D11_AcquireCommandBuffer(
     commandBuffer->blendConstants.a = 1.0f;
     commandBuffer->computePipeline = NULL;
     for (i = 0; i < MAX_COLOR_TARGET_BINDINGS; i += 1) {
-        commandBuffer->colorTargetResolveTexture[i] = NULL;
-        commandBuffer->colorTargetResolveSubresourceIndex[i] = 0;
-        commandBuffer->colorTargetMsaaHandle[i] = NULL;
-        commandBuffer->colorTargetMsaaFormat[i] = DXGI_FORMAT_UNKNOWN;
+        commandBuffer->colorTargetSubresources[i] = NULL;
+        commandBuffer->colorResolveSubresources[i] = NULL;
     }
 
     for (i = 0; i < MAX_UNIFORM_BUFFERS_PER_STAGE; i += 1) {
@@ -3507,10 +3457,8 @@ static void D3D11_BeginRenderPass(
 
     // Clear the bound targets for the current command buffer
     for (Uint32 i = 0; i < MAX_COLOR_TARGET_BINDINGS; i += 1) {
-        d3d11CommandBuffer->colorTargetResolveTexture[i] = NULL;
-        d3d11CommandBuffer->colorTargetResolveSubresourceIndex[i] = 0;
-        d3d11CommandBuffer->colorTargetMsaaHandle[i] = NULL;
-        d3d11CommandBuffer->colorTargetMsaaFormat[i] = DXGI_FORMAT_UNKNOWN;
+        d3d11CommandBuffer->colorTargetSubresources[i] = NULL;
+        d3d11CommandBuffer->colorResolveSubresources[i] = NULL;
     }
 
     // Set up the new color target bindings
@@ -3523,16 +3471,20 @@ static void D3D11_BeginRenderPass(
             colorTargetInfos[i].mip_level,
             colorTargetInfos[i].cycle);
 
-        if (subresource->msaaHandle != NULL) {
-            d3d11CommandBuffer->colorTargetResolveTexture[i] = subresource->parent;
-            d3d11CommandBuffer->colorTargetResolveSubresourceIndex[i] = subresource->index;
-            d3d11CommandBuffer->colorTargetMsaaHandle[i] = subresource->msaaHandle;
-            d3d11CommandBuffer->colorTargetMsaaFormat[i] = SDLToD3D11_TextureFormat[container->header.info.format];
+        Uint32 rtvIndex = container->header.info.type == SDL_GPU_TEXTURETYPE_3D ? colorTargetInfos[i].layer_or_depth_plane : 0;
+        rtvs[i] = subresource->colorTargetViews[rtvIndex];
+        d3d11CommandBuffer->colorTargetSubresources[i] = subresource;
 
-            rtvs[i] = subresource->msaaTargetView;
-        } else {
-            Uint32 rtvIndex = container->header.info.type == SDL_GPU_TEXTURETYPE_3D ? colorTargetInfos[i].layer_or_depth_plane : 0;
-            rtvs[i] = subresource->colorTargetViews[rtvIndex];
+        if (colorTargetInfos[i].store_op == SDL_GPU_STOREOP_RESOLVE || colorTargetInfos[i].store_op == SDL_GPU_STOREOP_RESOLVE_AND_STORE) {
+            D3D11TextureContainer *resolveContainer = (D3D11TextureContainer *)colorTargetInfos[i].resolve_texture;
+            D3D11TextureSubresource *resolveSubresource = D3D11_INTERNAL_PrepareTextureSubresourceForWrite(
+                renderer,
+                resolveContainer,
+                colorTargetInfos[i].resolve_layer,
+                colorTargetInfos[i].resolve_mip_level,
+                colorTargetInfos[i].cycle_resolve_texture);
+
+            d3d11CommandBuffer->colorResolveSubresources[i] = resolveSubresource;
         }
 
         if (colorTargetInfos[i].load_op == SDL_GPU_LOADOP_CLEAR) {
@@ -4220,14 +4172,14 @@ static void D3D11_EndRenderPass(
 
     // Resolve MSAA color render targets
     for (i = 0; i < MAX_COLOR_TARGET_BINDINGS; i += 1) {
-        if (d3d11CommandBuffer->colorTargetMsaaHandle[i] != NULL) {
+        if (d3d11CommandBuffer->colorResolveSubresources[i] != NULL) {
             ID3D11DeviceContext_ResolveSubresource(
                 d3d11CommandBuffer->context,
-                d3d11CommandBuffer->colorTargetResolveTexture[i]->handle,
-                d3d11CommandBuffer->colorTargetResolveSubresourceIndex[i],
-                d3d11CommandBuffer->colorTargetMsaaHandle[i],
-                0,
-                d3d11CommandBuffer->colorTargetMsaaFormat[i]);
+                d3d11CommandBuffer->colorResolveSubresources[i]->parent->handle,
+                d3d11CommandBuffer->colorResolveSubresources[i]->index,
+                d3d11CommandBuffer->colorTargetSubresources[i]->parent->handle,
+                d3d11CommandBuffer->colorTargetSubresources[i]->index,
+                SDLToD3D11_TextureFormat[d3d11CommandBuffer->colorTargetSubresources[i]->parent->container->header.info.format]);
         }
     }
 
@@ -5083,8 +5035,6 @@ static bool D3D11_INTERNAL_InitializeSwapchainTexture(
         return false;
     }
 
-    // Create container
-
     // Fill out the texture struct
     pTexture->handle = NULL;     // This will be set in AcquireSwapchainTexture.
     pTexture->shaderView = NULL; // We don't allow swapchain texture to be sampled
@@ -5095,8 +5045,6 @@ static bool D3D11_INTERNAL_InitializeSwapchainTexture(
     pTexture->subresources[0].colorTargetViews[0] = rtv;
     pTexture->subresources[0].uav = NULL;
     pTexture->subresources[0].depthStencilTargetView = NULL;
-    pTexture->subresources[0].msaaHandle = NULL;
-    pTexture->subresources[0].msaaTargetView = NULL;
     pTexture->subresources[0].layer = 0;
     pTexture->subresources[0].level = 0;
     pTexture->subresources[0].depth = 1;
diff --git a/src/gpu/d3d12/SDL_gpu_d3d12.c b/src/gpu/d3d12/SDL_gpu_d3d12.c
index f3c9e18e9920f..06e60744fda18 100644
--- a/src/gpu/d3d12/SDL_gpu_d3d12.c
+++ b/src/gpu/d3d12/SDL_gpu_d3d12.c
@@ -673,8 +673,8 @@ struct D3D12CommandBuffer
     Uint32 presentDataCount;
     Uint32 presentDataCapacity;
 
-    Uint32 colorTargetTextureSubresourceCount;
-    D3D12TextureSubresource *colorTargetTextureSubresources[MAX_COLOR_TARGET_BINDINGS];
+    D3D12TextureSubresource *colorTargetSubresources[MAX_COLOR_TARGET_BINDINGS];
+    D3D12TextureSubresource *colorResolveSubresources[MAX_COLOR_TARGET_BINDINGS];
     D3D12TextureSubresource *depthStencilTextureSubresource;
     D3D12GraphicsPipeline *currentGraphicsPipeline;
     D3D12ComputePipeline *currentComputePipeline;
@@ -2619,7 +2619,7 @@ static SDL_GPUGraphicsPipeline *D3D12_CreateGraphicsPipeline(
 
     psoDesc.SampleMask = sampleMask;
     psoDesc.SampleDesc.Count = SDLToD3D12_SampleCount[createinfo->multisample_state.sample_count];
-    psoDesc.SampleDesc.Quality = 0;
+    psoDesc.SampleDesc.Quality = (createinfo->multisample_state.sample_count > SDL_GPU_SAMPLECOUNT_1) ? D3D12_STANDARD_MULTISAMPLE_PATTERN : 0;
 
     psoDesc.DSVFormat = SDLToD3D12_TextureFormat[createinfo->target_info.depth_stencil_format];
     psoDesc.NumRenderTargets = createinfo->target_info.num_color_targets;
@@ -2787,6 +2787,7 @@ static D3D12Texture *D3D12_INTERNAL_CreateTexture(
 
     Uint32 layerCount = createinfo->type == SDL_GPU_TEXTURETYPE_3D ? 1 : createinfo->layer_count_or_depth;
     Uint32 depth = createinfo->type == SDL_GPU_TEXTURETYPE_3D ? createinfo->layer_count_or_depth : 1;
+    bool isMultisample = createinfo->sample_count > SDL_GPU_SAMPLECOUNT_1;
 
     if (createinfo->usage & SDL_GPU_TEXTUREUSAGE_COLOR_TARGET) {
         resourceFlags |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
@@ -2824,8 +2825,8 @@ static D3D12Texture *D3D12_INTERNAL_CreateTexture(
         desc.DepthOrArraySize = (UINT16)createinfo->layer_count_or_depth;
         desc.MipLevels = (UINT16)createinfo->num_levels;
         desc.Format = SDLToD3D12_TextureFormat[createinfo->format];
-        desc.SampleDesc.Count = 1;
-        desc.SampleDesc.Quality = 0;
+        desc.SampleDesc.Count = SDLToD3D12_SampleCount[createinfo->sample_count];
+        desc.SampleDesc.Quality = isMultisample ? D3D12_STANDARD_MULTISAMPLE_PATTERN : 0;
         desc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; // Apparently this is the most efficient choice
         desc.Flags = resourceFlags;
     } else {
@@ -2956,21 +2957,31 @@ static D3D12Texture *D3D12_INTERNAL_CreateTexture(
 
                     rtvDesc.Format = SDLToD3D12_TextureFormat[createinfo->format];
 
-                    if (createinfo->type == SDL_GPU_TEXTURETYPE_2D_ARRAY || createinfo->type == SDL_GPU_TEXTURETYPE_CUBE || createinfo->type == SDL_GPU_TEXTURETYPE_CUBE_ARRAY) {
-                        rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DARRAY;
-                        rtvDesc.Texture2DArray.MipSlice = levelIndex;
-                        rtvDesc.Texture2DArray.FirstArraySlice = layerIndex;
-                        rtvDesc.Texture2DArray.ArraySize = 1;
-                        rtvDesc.Texture2DArray.PlaneSlice = 0;
-                    } else if (createinfo->type == SDL_GPU_TEXTURETYPE_3D) {
-                        rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE3D;
-                        rtvDesc.Texture3D.MipSlice = levelIndex;
-                        rtvDesc.Texture3D.FirstWSlice = depthIndex;
-                        rtvDesc.Texture3D.WSize = 1;
+                    if (isMultisample) {
+                        if (createinfo->type == SDL_GPU_TEXTURETYPE_2D) {
+                            rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DMS;
+                        } else if (createinfo->type == SDL_GPU_TEXTURETYPE_2D_ARRAY) {
+                            rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DMSARRAY;
+                            rtvDesc.Texture2DMSArray.FirstArraySlice = layerIndex;
+                            rtvDesc.Texture2DMSArray.ArraySize = 1;
+                        }
                     } else {
-                        rtvDesc.ViewDimension = D3D1

(Patch may be truncated, please check the link at the top of this post.)