From d55c3dacaed9ff7ce1acc9579179fe6427d12b96 Mon Sep 17 00:00:00 2001 From: William Candillon Date: Sat, 18 Jul 2026 20:08:04 +0200 Subject: [PATCH 1/2] =?UTF-8?q?fix(=F0=9F=90=9B):=20Route=20surfaces=20con?= =?UTF-8?q?figured=20with=20viewFormats=20through=20the=20blit=20path?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added check for viewFormats in swapchain image usage validation.The Vulkan swapchain creates its images without VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT and wraps them in a texture descriptor that omits the configuration's viewFormats, so the first GetCurrentTexture() fails the viewFormats consistency DAWN_CHECK and aborts. Any non-empty viewFormats now sets needsBlit, like an unsupported extent or usage: the user-facing texture becomes the intermediate blit texture, a regular texture created from the full descriptor, which supports reinterpretation. Metal already builds the swapchain texture from the full descriptor and is unaffected. Adds a SurfaceTests end2end case; the file previously only exercised viewFormatCount = 0, which is why this went unnoticed. Change-Id: If09df3749bb4de9c9ed011bf924141ea1fbfe15b --- src/dawn/native/vulkan/SwapChainVk.cpp | 5 ++++- src/dawn/tests/end2end/SurfaceTests.cpp | 28 +++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/dawn/native/vulkan/SwapChainVk.cpp b/src/dawn/native/vulkan/SwapChainVk.cpp index 7aeb73bc475..e2357fa3fc5 100644 --- a/src/dawn/native/vulkan/SwapChainVk.cpp +++ b/src/dawn/native/vulkan/SwapChainVk.cpp @@ -272,7 +272,10 @@ ResultOrError SwapChain::ChooseConfig( VkImageUsageFlags targetUsages = VulkanImageUsage(GetDevice(), GetUsage(), GetDevice()->GetValidInternalFormat(GetFormat())); VkImageUsageFlags supportedUsages = surfaceInfo.capabilities.supportedUsageFlags; - if (!IsSubset(targetUsages, supportedUsages)) { + // The swapchain images are also unable to satisfy viewFormats: they are + // created without VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT, so they cannot be + // reinterpreted. The blit texture is a regular texture and can. + if (!IsSubset(targetUsages, supportedUsages) || !GetViewFormats().empty()) { config.needsBlit = true; } else { config.usage = targetUsages; diff --git a/src/dawn/tests/end2end/SurfaceTests.cpp b/src/dawn/tests/end2end/SurfaceTests.cpp index 0fbeeaf0249..f153bad8f0a 100644 --- a/src/dawn/tests/end2end/SurfaceTests.cpp +++ b/src/dawn/tests/end2end/SurfaceTests.cpp @@ -719,6 +719,34 @@ TEST_P(SurfaceTests, Storage) { ASSERT_EQ(wgpu::Status::Success, surface.Present()); } +// Test acquiring a texture from a surface configured with viewFormats. +TEST_P(SurfaceTests, ConfigureWithViewFormats) { + wgpu::Surface surface = CreateTestSurface(); + wgpu::SurfaceConfiguration config = GetPreferredConfiguration(surface); + + // Reinterpretation between a format and its srgb counterpart is always + // allowed; pick the counterpart of whatever the surface prefers. + wgpu::TextureFormat viewFormat; + switch (config.format) { + case wgpu::TextureFormat::BGRA8Unorm: + viewFormat = wgpu::TextureFormat::BGRA8UnormSrgb; + break; + case wgpu::TextureFormat::RGBA8Unorm: + viewFormat = wgpu::TextureFormat::RGBA8UnormSrgb; + break; + default: + GTEST_SKIP() << "Preferred surface format has no srgb counterpart"; + } + config.viewFormatCount = 1; + config.viewFormats = &viewFormat; + surface.Configure(&config); + + wgpu::SurfaceTexture surfaceTexture; + surface.GetCurrentTexture(&surfaceTexture); // aborts on Vulkan before the fix + ClearTexture(surfaceTexture.texture, {1.0, 0.0, 0.0, 1.0}); + surface.Present(); +} + // TODO(crbug.com/465183957): Implement swap chain for WebGPUBackend. DAWN_INSTANTIATE_TEST(SurfaceTests, D3D11Backend(), From f58dfb59bc008d8ae75707b1e76f939455d8ba29 Mon Sep 17 00:00:00 2001 From: William Candillon Date: Sun, 19 Jul 2026 14:29:59 +0200 Subject: [PATCH 2/2] Support VK_KHR_swapchain_mutable_format When the device supports VK_KHR_swapchain_mutable_format, a surface configured with viewFormats no longer needs the blit fallback: the swapchain is created with VK_SWAPCHAIN_CREATE_MUTABLE_FORMAT_BIT_KHR and a VkImageFormatListCreateInfo listing the base format and the viewFormats, and the wrapped texture descriptor carries the viewFormats so views can reinterpret the swapchain images directly. The SurfaceTests case is extended to render through the reinterpreted srgb view instead of only acquiring the texture. Change-Id: If6557d1600bb45f18c4889c794b924a8fdc49fe9 --- src/dawn/native/vulkan/SwapChainVk.cpp | 41 +++++++++++++++++++-- src/dawn/native/vulkan/SwapChainVk.h | 5 +++ src/dawn/native/vulkan/VulkanExtensions.cpp | 7 ++++ src/dawn/native/vulkan/VulkanExtensions.h | 1 + src/dawn/tests/end2end/SurfaceTests.cpp | 17 ++++++++- 5 files changed, 66 insertions(+), 5 deletions(-) diff --git a/src/dawn/native/vulkan/SwapChainVk.cpp b/src/dawn/native/vulkan/SwapChainVk.cpp index e2357fa3fc5..aae18ffae51 100644 --- a/src/dawn/native/vulkan/SwapChainVk.cpp +++ b/src/dawn/native/vulkan/SwapChainVk.cpp @@ -195,6 +195,19 @@ MaybeError SwapChain::Initialize(SwapChainBase* previousSwapChain) { createInfo.clipped = VK_FALSE; createInfo.oldSwapchain = previousVkSwapChain; + // Create the swapchain images with VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT so they can be + // reinterpreted to the configuration's viewFormats. VK_KHR_swapchain_mutable_format + // requires the full list of formats to be provided, including the image format itself. + VkImageFormatListCreateInfo imageFormatListInfo; + if (!mConfig.viewFormats.empty()) { + createInfo.flags |= VK_SWAPCHAIN_CREATE_MUTABLE_FORMAT_BIT_KHR; + imageFormatListInfo.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO; + imageFormatListInfo.pNext = nullptr; + imageFormatListInfo.viewFormatCount = static_cast(mConfig.viewFormats.size()); + imageFormatListInfo.pViewFormats = mConfig.viewFormats.data(); + createInfo.pNext = &imageFormatListInfo; + } + DAWN_TRY(CheckVkSuccess( device->fn.CreateSwapchainKHR(device->GetVkDevice(), &createInfo, nullptr, &*mSwapChain), "CreateSwapChain")); @@ -272,10 +285,13 @@ ResultOrError SwapChain::ChooseConfig( VkImageUsageFlags targetUsages = VulkanImageUsage(GetDevice(), GetUsage(), GetDevice()->GetValidInternalFormat(GetFormat())); VkImageUsageFlags supportedUsages = surfaceInfo.capabilities.supportedUsageFlags; - // The swapchain images are also unable to satisfy viewFormats: they are - // created without VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT, so they cannot be - // reinterpreted. The blit texture is a regular texture and can. - if (!IsSubset(targetUsages, supportedUsages) || !GetViewFormats().empty()) { + // Without VK_KHR_swapchain_mutable_format the swapchain images are created + // without VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT, so they cannot be reinterpreted + // to the configuration's viewFormats. The blit texture is a regular texture and can. + const bool viewFormatsRequireBlit = + !GetViewFormats().empty() && + !ToBackend(GetDevice())->GetDeviceInfo().HasExt(DeviceExt::SwapchainMutableFormat); + if (!IsSubset(targetUsages, supportedUsages) || viewFormatsRequireBlit) { config.needsBlit = true; } else { config.usage = targetUsages; @@ -299,6 +315,18 @@ ResultOrError SwapChain::ChooseConfig( "Vulkan SwapChain must support %s with sRGB colorspace.", config.wgpuFormat)); } + // If the swapchain images are exposed to the user directly and need to be reinterpreted + // to the configuration's viewFormats, gather the full list of formats used to create the + // swapchain with VK_SWAPCHAIN_CREATE_MUTABLE_FORMAT_BIT_KHR. + if (!config.needsBlit && !GetViewFormats().empty()) { + DAWN_ASSERT( + ToBackend(GetDevice())->GetDeviceInfo().HasExt(DeviceExt::SwapchainMutableFormat)); + config.viewFormats.push_back(config.format); + for (wgpu::TextureFormat viewFormat : GetViewFormats()) { + config.viewFormats.push_back(VulkanImageFormat(ToBackend(GetDevice()), viewFormat)); + } + } + // Only the identity transform with opaque alpha is supported for now. DAWN_INVALID_IF( (surfaceInfo.capabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) == 0, @@ -554,6 +582,11 @@ ResultOrError SwapChain::GetCurrentTextureInternal(bool is textureDesc.size.height = mConfig.extent.height; textureDesc.format = mConfig.wgpuFormat; textureDesc.usage = mConfig.wgpuUsage; + if (!mConfig.needsBlit) { + // The swapchain images were created mutable so the user-facing texture supports the + // configuration's viewFormats. In the blit case they stay on the blit texture instead. + textureDesc.viewFormats = GetViewFormats(); + } mTexture = SwapChainTexture::Create(device, Unpack(&textureDesc), lastImage.image); diff --git a/src/dawn/native/vulkan/SwapChainVk.h b/src/dawn/native/vulkan/SwapChainVk.h index 3dc1106ec76..0fc8aa6dbb0 100644 --- a/src/dawn/native/vulkan/SwapChainVk.h +++ b/src/dawn/native/vulkan/SwapChainVk.h @@ -66,6 +66,11 @@ class SwapChain : public SwapChainBase { uint32_t targetImageCount; VkSurfaceTransformFlagBitsKHR transform; VkCompositeAlphaFlagBitsKHR alphaMode; + // When non-empty, the swapchain is created with + // VK_SWAPCHAIN_CREATE_MUTABLE_FORMAT_BIT_KHR and this list (which includes `format`) + // is chained as a VkImageFormatListCreateInfo so the images can be reinterpreted to + // the configuration's viewFormats. + std::vector viewFormats; // Redundant information but as WebGPU enums to create the wgpu::Texture that // encapsulates the native swapchain texture. diff --git a/src/dawn/native/vulkan/VulkanExtensions.cpp b/src/dawn/native/vulkan/VulkanExtensions.cpp index 8ce610dc4f4..9f232221c6c 100644 --- a/src/dawn/native/vulkan/VulkanExtensions.cpp +++ b/src/dawn/native/vulkan/VulkanExtensions.cpp @@ -149,6 +149,7 @@ static constexpr std::array sDeviceExtInfos{{ {DeviceExt::DepthClipEnable, "VK_EXT_depth_clip_enable"}, {DeviceExt::ImageDrmFormatModifier, "VK_EXT_image_drm_format_modifier"}, {DeviceExt::Swapchain, "VK_KHR_swapchain"}, + {DeviceExt::SwapchainMutableFormat, "VK_KHR_swapchain_mutable_format"}, {DeviceExt::QueueFamilyForeign, "VK_EXT_queue_family_foreign"}, {DeviceExt::Robustness2, "VK_EXT_robustness2"}, {DeviceExt::DisplayTiming, "VK_GOOGLE_display_timing"}, @@ -253,6 +254,12 @@ DeviceExtSet EnsureDependencies(const DeviceExtSet& advertisedExts, hasDependencies = instanceExts[InstanceExt::Surface]; break; + // Also requires VK_KHR_maintenance2 which is core in Vulkan 1.1. + case DeviceExt::SwapchainMutableFormat: + hasDependencies = + HasDep(DeviceExt::Swapchain) && HasDep(DeviceExt::ImageFormatList); + break; + case DeviceExt::ExternalMemoryAndroidHardwareBuffer: hasDependencies = HasDep(DeviceExt::QueueFamilyForeign); break; diff --git a/src/dawn/native/vulkan/VulkanExtensions.h b/src/dawn/native/vulkan/VulkanExtensions.h index 78d79a4c644..830782263df 100644 --- a/src/dawn/native/vulkan/VulkanExtensions.h +++ b/src/dawn/native/vulkan/VulkanExtensions.h @@ -108,6 +108,7 @@ enum class DeviceExt : uint32_t { DepthClipEnable, ImageDrmFormatModifier, Swapchain, + SwapchainMutableFormat, QueueFamilyForeign, Robustness2, DisplayTiming, diff --git a/src/dawn/tests/end2end/SurfaceTests.cpp b/src/dawn/tests/end2end/SurfaceTests.cpp index f153bad8f0a..32e13801c4d 100644 --- a/src/dawn/tests/end2end/SurfaceTests.cpp +++ b/src/dawn/tests/end2end/SurfaceTests.cpp @@ -743,7 +743,22 @@ TEST_P(SurfaceTests, ConfigureWithViewFormats) { wgpu::SurfaceTexture surfaceTexture; surface.GetCurrentTexture(&surfaceTexture); // aborts on Vulkan before the fix - ClearTexture(surfaceTexture.texture, {1.0, 0.0, 0.0, 1.0}); + + // Render through a view using the reinterpreted format to check the texture really + // supports its viewFormats. + wgpu::TextureViewDescriptor viewDesc; + viewDesc.format = viewFormat; + utils::ComboRenderPassDescriptor renderPassDesc( + {surfaceTexture.texture.CreateView(&viewDesc)}); + renderPassDesc.cColorAttachments[0].loadOp = wgpu::LoadOp::Clear; + renderPassDesc.cColorAttachments[0].clearValue = {1.0, 0.0, 0.0, 1.0}; + + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDesc); + pass.End(); + wgpu::CommandBuffer commands = encoder.Finish(); + queue.Submit(1, &commands); + surface.Present(); }