diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cd8a2ae6cf..9a22c089365 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -109,6 +109,7 @@ Bottom level categories: #### Vulkan +- Work around Arm proprietary drivers (observed on r54p2, Mali-G715 / Pixel 9 Pro) ignoring negative viewport heights, which flipped every render pass vertically: on those drivers Y is now flipped in the vertex shader epilogue with positive-height viewports and inverted front-face winding. By @lexoliu in [#10057](https://github.com/gfx-rs/wgpu/issues/10057). - Add OpenHarmony surface support via `VK_OHOS_surface`. Previously the Vulkan backend could not create a surface on OpenHarmony, leaving GLES as the only usable backend. By @ozongzi in [#9908](https://github.com/gfx-rs/wgpu/pull/9908). - Stop passing an un-waited fence to `vkAcquireNextImageKHR` on non-Windows platforms, which triggered `VUID-vkAcquireNextImageKHR-fence-10066` validation errors every frame since v30.0.0. By @ErichDonGubler in [#9855](https://github.com/gfx-rs/wgpu/issues/9855). diff --git a/wgpu-hal/src/vulkan/adapter.rs b/wgpu-hal/src/vulkan/adapter.rs index 53682bca493..7f1115336a3 100644 --- a/wgpu-hal/src/vulkan/adapter.rs +++ b/wgpu-hal/src/vulkan/adapter.rs @@ -2305,6 +2305,13 @@ impl super::Instance { super::Workarounds::FORCE_FILL_BUFFER_WITH_SIZE_GREATER_4096_ALIGNED_OFFSET_16, phd_capabilities.properties.vendor_id == db::nvidia::VENDOR, ); + workarounds.set( + super::Workarounds::IGNORED_NEGATIVE_VIEWPORT_HEIGHT, + phd_capabilities + .driver + .as_ref() + .is_some_and(|driver| driver.driver_id == vk::DriverId::ARM_PROPRIETARY), + ); }; if let Some(driver) = phd_capabilities.driver { @@ -2787,6 +2794,11 @@ impl super::Adapter { spv::WriterFlags::LABEL_VARYINGS, self.phd_capabilities.properties.vendor_id != crate::auxil::db::qualcomm::VENDOR, ); + flags.set( + spv::WriterFlags::ADJUST_COORDINATE_SPACE, + self.workarounds + .contains(super::Workarounds::IGNORED_NEGATIVE_VIEWPORT_HEIGHT), + ); flags.set( spv::WriterFlags::FORCE_POINT_SIZE, //Note: we could technically disable this when we are compiling separate entry points, diff --git a/wgpu-hal/src/vulkan/command.rs b/wgpu-hal/src/vulkan/command.rs index 8ce12045321..1fab83a1996 100644 --- a/wgpu-hal/src/vulkan/command.rs +++ b/wgpu-hal/src/vulkan/command.rs @@ -862,14 +862,31 @@ impl crate::CommandEncoder for super::CommandEncoder { height: desc.extent.height, }, }; - let vk_viewports = [vk::Viewport { - x: 0.0, - y: desc.extent.height as f32, - width: desc.extent.width as f32, - height: -(desc.extent.height as f32), - min_depth: 0.0, - max_depth: 1.0, - }]; + let vk_viewports = if self + .device + .workarounds + .contains(super::Workarounds::IGNORED_NEGATIVE_VIEWPORT_HEIGHT) + { + // The Y-flip happens in the vertex shader epilogue instead; see the + // workaround's documentation. + [vk::Viewport { + x: 0.0, + y: 0.0, + width: desc.extent.width as f32, + height: desc.extent.height as f32, + min_depth: 0.0, + max_depth: 1.0, + }] + } else { + [vk::Viewport { + x: 0.0, + y: desc.extent.height as f32, + width: desc.extent.width as f32, + height: -(desc.extent.height as f32), + min_depth: 0.0, + max_depth: 1.0, + }] + }; let raw_pass = self.device.make_render_pass(rp_key).unwrap(); fb_key.raw_pass = raw_pass; @@ -1027,14 +1044,31 @@ impl crate::CommandEncoder for super::CommandEncoder { }; } unsafe fn set_viewport(&mut self, rect: &crate::Rect, depth_range: Range) { - let vk_viewports = [vk::Viewport { - x: rect.x, - y: rect.y + rect.h, - width: rect.w, - height: -rect.h, // flip Y - min_depth: depth_range.start, - max_depth: depth_range.end, - }]; + let vk_viewports = if self + .device + .workarounds + .contains(super::Workarounds::IGNORED_NEGATIVE_VIEWPORT_HEIGHT) + { + // The Y-flip happens in the vertex shader epilogue instead; see the + // workaround's documentation. + [vk::Viewport { + x: rect.x, + y: rect.y, + width: rect.w, + height: rect.h, + min_depth: depth_range.start, + max_depth: depth_range.end, + }] + } else { + [vk::Viewport { + x: rect.x, + y: rect.y + rect.h, + width: rect.w, + height: -rect.h, // flip Y + min_depth: depth_range.start, + max_depth: depth_range.end, + }] + }; unsafe { self.device .raw diff --git a/wgpu-hal/src/vulkan/device.rs b/wgpu-hal/src/vulkan/device.rs index 22bca0c664a..4d39af209d2 100644 --- a/wgpu-hal/src/vulkan/device.rs +++ b/wgpu-hal/src/vulkan/device.rs @@ -2026,9 +2026,23 @@ impl crate::Device for super::Device { None => None, }; + // Flipping Y in the vertex shader instead of the viewport inverts the + // winding of every triangle, so the front face must be inverted too. + let flip_winding = self + .shared + .workarounds + .contains(super::Workarounds::IGNORED_NEGATIVE_VIEWPORT_HEIGHT); + let front_face = if flip_winding { + match desc.primitive.front_face { + wgt::FrontFace::Ccw => wgt::FrontFace::Cw, + wgt::FrontFace::Cw => wgt::FrontFace::Ccw, + } + } else { + desc.primitive.front_face + }; let mut vk_rasterization = vk::PipelineRasterizationStateCreateInfo::default() .polygon_mode(conv::map_polygon_mode(desc.primitive.polygon_mode)) - .front_face(conv::map_front_face(desc.primitive.front_face)) + .front_face(conv::map_front_face(front_face)) .line_width(1.0) .depth_clamp_enable(desc.primitive.unclipped_depth); if let Some(face) = desc.primitive.cull_mode { @@ -2066,6 +2080,11 @@ impl crate::Device for super::Device { let s = &ds.stencil; let front = conv::map_stencil_face(&s.front, s.read_mask, s.write_mask); let back = conv::map_stencil_face(&s.back, s.read_mask, s.write_mask); + let (front, back) = if flip_winding { + (back, front) + } else { + (front, back) + }; vk_depth_stencil = vk_depth_stencil .stencil_test_enable(true) .front(front) diff --git a/wgpu-hal/src/vulkan/mod.rs b/wgpu-hal/src/vulkan/mod.rs index e8a58e6fe39..444563ed749 100644 --- a/wgpu-hal/src/vulkan/mod.rs +++ b/wgpu-hal/src/vulkan/mod.rs @@ -461,6 +461,13 @@ bitflags::bitflags!( /// As such, we need to make sure all calls to vkCmdFillBuffer are aligned to 16 bytes /// if they cover a range of 4096 bytes or more. const FORCE_FILL_BUFFER_WITH_SIZE_GREATER_4096_ALIGNED_OFFSET_16 = 0x4; + /// Arm's proprietary driver (observed on r54p2, Mali-G715 / Pixel 9 Pro, + /// Android 16) rasterizes as if the viewport height were positive even when + /// a negative-height viewport is submitted, undoing the Y-flip wgpu relies + /// on to map WebGPU clip space onto Vulkan's. Flip Y in the vertex shader + /// epilogue instead, submit positive-height viewports, and invert the + /// front-face winding to keep culling and stencil facing correct. + const IGNORED_NEGATIVE_VIEWPORT_HEIGHT = 0x8; } );