Merge branch 'windows/dx11' into HEAD

Kate created

Change summary

Cargo.toml                                           |   1 
crates/gpui/Cargo.toml                               |   1 
crates/gpui/src/platform.rs                          |   1 
crates/gpui/src/platform/windows/directx_atlas.rs    |  40 
crates/gpui/src/platform/windows/directx_renderer.rs | 865 +++++++++----
crates/gpui/src/platform/windows/events.rs           |   4 
crates/gpui/src/platform/windows/platform.rs         |   7 
crates/gpui/src/platform/windows/shaders.hlsl        | 686 +++++++---
crates/gpui/src/platform/windows/window.rs           |  88 -
9 files changed, 1,107 insertions(+), 586 deletions(-)

Detailed changes

Cargo.toml 🔗

@@ -666,6 +666,7 @@ features = [
     "Win32_Graphics_Direct3D",
     "Win32_Graphics_Direct3D11",
     "Win32_Graphics_Direct3D_Fxc",
+    "Win32_Graphics_DirectComposition",
     "Win32_Graphics_DirectWrite",
     "Win32_Graphics_Dwm",
     "Win32_Graphics_Dxgi",

crates/gpui/Cargo.toml 🔗

@@ -71,6 +71,7 @@ screen-capture = [
     "scap",
 ]
 windows-manifest = []
+enable-renderdoc = []
 
 [lib]
 path = "src/gpui.rs"

crates/gpui/src/platform.rs 🔗

@@ -13,7 +13,6 @@ mod mac;
         any(target_os = "linux", target_os = "freebsd"),
         any(feature = "x11", feature = "wayland")
     ),
-    target_os = "windows",
     feature = "macos-blade"
 ))]
 mod blade;

crates/gpui/src/platform/windows/directx_atlas.rs 🔗

@@ -12,7 +12,7 @@ use windows::Win32::Graphics::{
 
 use crate::{
     AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, DevicePixels, PlatformAtlas,
-    Size, platform::AtlasTextureList,
+    Point, Size, platform::AtlasTextureList,
 };
 
 pub(crate) struct DirectXAtlas(Mutex<DirectXAtlasState>);
@@ -53,25 +53,6 @@ impl DirectXAtlas {
         let tex = lock.texture(id);
         tex.view.clone()
     }
-
-    pub(crate) fn allocate(
-        &self,
-        size: Size<DevicePixels>,
-        texture_kind: AtlasTextureKind,
-    ) -> Option<AtlasTile> {
-        self.0.lock().allocate(size, texture_kind)
-    }
-
-    pub(crate) fn clear_textures(&self, texture_kind: AtlasTextureKind) {
-        let mut lock = self.0.lock();
-        let textures = match texture_kind {
-            AtlasTextureKind::Monochrome => &mut lock.monochrome_textures,
-            AtlasTextureKind::Polychrome => &mut lock.polychrome_textures,
-        };
-        for texture in textures.iter_mut() {
-            texture.clear();
-        }
-    }
 }
 
 impl PlatformAtlas for DirectXAtlas {
@@ -249,10 +230,6 @@ impl DirectXAtlasState {
 }
 
 impl DirectXAtlasTexture {
-    fn clear(&mut self) {
-        self.allocator.clear();
-    }
-
     fn allocate(&mut self, size: Size<DevicePixels>) -> Option<AtlasTile> {
         let allocation = self.allocator.allocate(size.into())?;
         let tile = AtlasTile {
@@ -301,3 +278,18 @@ impl DirectXAtlasTexture {
         self.live_atlas_keys == 0
     }
 }
+
+impl From<Size<DevicePixels>> for etagere::Size {
+    fn from(size: Size<DevicePixels>) -> Self {
+        etagere::Size::new(size.width.into(), size.height.into())
+    }
+}
+
+impl From<etagere::Point> for Point<DevicePixels> {
+    fn from(value: etagere::Point) -> Self {
+        Point {
+            x: DevicePixels::from(value.x),
+            y: DevicePixels::from(value.y),
+        }
+    }
+}

crates/gpui/src/platform/windows/directx_renderer.rs 🔗

@@ -1,65 +1,72 @@
-use std::{collections::HashMap, hash::BuildHasherDefault, sync::Arc};
+use std::{mem::ManuallyDrop, sync::Arc};
 
 use ::util::ResultExt;
 use anyhow::{Context, Result};
-use collections::FxHasher;
-// #[cfg(not(feature = "enable-renderdoc"))]
-// use windows::Win32::Graphics::DirectComposition::*;
-use windows::{
-    Win32::{
-        Foundation::{HMODULE, HWND},
-        Graphics::{
-            Direct3D::*,
-            Direct3D11::*,
-            Dxgi::{Common::*, *},
-        },
+use windows::Win32::{
+    Foundation::{HMODULE, HWND},
+    Graphics::{
+        Direct3D::*,
+        Direct3D11::*,
+        Dxgi::{Common::*, *},
     },
-    core::*,
 };
+#[cfg(not(feature = "enable-renderdoc"))]
+use windows::{Win32::Graphics::DirectComposition::*, core::Interface};
 
 use crate::*;
 
+const RENDER_TARGET_FORMAT: DXGI_FORMAT = DXGI_FORMAT_B8G8R8A8_UNORM;
+// This configuration is used for MSAA rendering, and it's guaranteed to be supported by DirectX 11.
+const MULTISAMPLE_COUNT: u32 = 4;
+
 pub(crate) struct DirectXRenderer {
     atlas: Arc<DirectXAtlas>,
     devices: DirectXDevices,
-    context: DirectXContext,
+    resources: DirectXResources,
     globals: DirectXGlobalElements,
     pipelines: DirectXRenderPipelines,
-    hwnd: HWND,
-    transparent: bool,
+    #[cfg(not(feature = "enable-renderdoc"))]
+    _direct_composition: DirectComposition,
 }
 
+/// Direct3D objects
 #[derive(Clone)]
 pub(crate) struct DirectXDevices {
+    adapter: IDXGIAdapter1,
     dxgi_factory: IDXGIFactory6,
+    #[cfg(not(feature = "enable-renderdoc"))]
     dxgi_device: IDXGIDevice,
     device: ID3D11Device,
     device_context: ID3D11DeviceContext,
 }
 
-struct DirectXContext {
+struct DirectXResources {
+    // Direct3D rendering objects
     swap_chain: IDXGISwapChain1,
-    back_buffer: [Option<ID3D11RenderTargetView>; 1],
+    render_target: ManuallyDrop<ID3D11Texture2D>,
+    render_target_view: [Option<ID3D11RenderTargetView>; 1],
+    msaa_target: ID3D11Texture2D,
+    msaa_view: [Option<ID3D11RenderTargetView>; 1],
+
+    // Cached window size and viewport
+    width: u32,
+    height: u32,
     viewport: [D3D11_VIEWPORT; 1],
-    // #[cfg(not(feature = "enable-renderdoc"))]
-    // direct_composition: DirectComposition,
 }
 
 struct DirectXRenderPipelines {
-    shadow_pipeline: PipelineState,
-    quad_pipeline: PipelineState,
-    paths_pipeline: PipelineState,
-    paths_indirect_draw_buffer: ID3D11Buffer,
-    underline_pipeline: PipelineState,
-    mono_sprites: PipelineState,
-    poly_sprites: PipelineState,
+    shadow_pipeline: PipelineState<Shadow>,
+    quad_pipeline: PipelineState<Quad>,
+    paths_pipeline: PathsPipelineState,
+    underline_pipeline: PipelineState<Underline>,
+    mono_sprites: PipelineState<MonochromeSprite>,
+    poly_sprites: PipelineState<PolychromeSprite>,
 }
 
 struct DirectXGlobalElements {
     global_params_buffer: [Option<ID3D11Buffer>; 1],
     sampler: [Option<ID3D11SamplerState>; 1],
     blend_state: ID3D11BlendState,
-    blend_state_for_pr: ID3D11BlendState,
 }
 
 #[repr(C)]
@@ -70,12 +77,12 @@ struct DrawInstancedIndirectArgs {
     start_instance_location: u32,
 }
 
-// #[cfg(not(feature = "enable-renderdoc"))]
-// struct DirectComposition {
-//     comp_device: IDCompositionDevice,
-//     comp_target: IDCompositionTarget,
-//     comp_visual: IDCompositionVisual,
-// }
+#[cfg(not(feature = "enable-renderdoc"))]
+struct DirectComposition {
+    comp_device: IDCompositionDevice,
+    comp_target: IDCompositionTarget,
+    comp_visual: IDCompositionVisual,
+}
 
 impl DirectXDevices {
     pub(crate) fn new() -> Result<Self> {
@@ -87,10 +94,13 @@ impl DirectXDevices {
             get_device(&adapter, Some(&mut device), Some(&mut context))?;
             (device.unwrap(), context.unwrap())
         };
+        #[cfg(not(feature = "enable-renderdoc"))]
         let dxgi_device: IDXGIDevice = device.cast()?;
 
         Ok(Self {
+            adapter,
             dxgi_factory,
+            #[cfg(not(feature = "enable-renderdoc"))]
             dxgi_device,
             device,
             device_context,
@@ -99,22 +109,33 @@ impl DirectXDevices {
 }
 
 impl DirectXRenderer {
-    pub(crate) fn new(devices: &DirectXDevices, hwnd: HWND, transparent: bool) -> Result<Self> {
+    pub(crate) fn new(devices: &DirectXDevices, hwnd: HWND) -> Result<Self> {
         let atlas = Arc::new(DirectXAtlas::new(
             devices.device.clone(),
             devices.device_context.clone(),
         ));
-        let context = DirectXContext::new(devices, hwnd, transparent)?;
+
+        #[cfg(not(feature = "enable-renderdoc"))]
+        let resources = DirectXResources::new(devices)?;
+        #[cfg(feature = "enable-renderdoc")]
+        let resources = DirectXResources::new(devices, hwnd)?;
+
         let globals = DirectXGlobalElements::new(&devices.device)?;
         let pipelines = DirectXRenderPipelines::new(&devices.device)?;
+
+        #[cfg(not(feature = "enable-renderdoc"))]
+        let direct_composition = DirectComposition::new(&devices.dxgi_device, hwnd)?;
+        #[cfg(not(feature = "enable-renderdoc"))]
+        direct_composition.set_swap_chain(&resources.swap_chain)?;
+
         Ok(DirectXRenderer {
             atlas,
             devices: devices.clone(),
-            context,
+            resources,
             globals,
             pipelines,
-            hwnd,
-            transparent,
+            #[cfg(not(feature = "enable-renderdoc"))]
+            _direct_composition: direct_composition,
         })
     }
 
@@ -122,15 +143,56 @@ impl DirectXRenderer {
         self.atlas.clone()
     }
 
-    pub(crate) fn draw(&mut self, scene: &Scene) -> Result<()> {
-        pre_draw(
+    fn pre_draw(&self) -> Result<()> {
+        update_buffer(
             &self.devices.device_context,
-            &self.globals.global_params_buffer,
-            &self.context.viewport,
-            &self.context.back_buffer,
-            [0.0, 0.0, 0.0, 0.0],
-            &self.globals.blend_state,
+            self.globals.global_params_buffer[0].as_ref().unwrap(),
+            &[GlobalParams {
+                viewport_size: [
+                    self.resources.viewport[0].Width,
+                    self.resources.viewport[0].Height,
+                ],
+                ..Default::default()
+            }],
         )?;
+        unsafe {
+            self.devices
+                .device_context
+                .ClearRenderTargetView(self.resources.msaa_view[0].as_ref().unwrap(), &[0.0; 4]);
+            self.devices
+                .device_context
+                .OMSetRenderTargets(Some(&self.resources.msaa_view), None);
+            self.devices
+                .device_context
+                .RSSetViewports(Some(&self.resources.viewport));
+            self.devices.device_context.OMSetBlendState(
+                &self.globals.blend_state,
+                None,
+                0xFFFFFFFF,
+            );
+        }
+        Ok(())
+    }
+
+    fn present(&self) -> Result<()> {
+        unsafe {
+            self.devices.device_context.ResolveSubresource(
+                &*self.resources.render_target,
+                0,
+                &self.resources.msaa_target,
+                0,
+                RENDER_TARGET_FORMAT,
+            );
+            self.devices
+                .device_context
+                .OMSetRenderTargets(Some(&self.resources.render_target_view), None);
+            self.resources.swap_chain.Present(0, DXGI_PRESENT(0)).ok()?;
+        }
+        Ok(())
+    }
+
+    pub(crate) fn draw(&mut self, scene: &Scene) -> Result<()> {
+        self.pre_draw()?;
         for batch in scene.batches() {
             match batch {
                 PrimitiveBatch::Shadows(shadows) => self.draw_shadows(shadows),
@@ -155,98 +217,51 @@ impl DirectXRenderer {
                     scene.polychrome_sprites.len(),
                     scene.surfaces.len(),))?;
         }
-        unsafe { self.context.swap_chain.Present(0, DXGI_PRESENT(0)) }.ok()?;
-        Ok(())
+        self.present()
     }
 
     pub(crate) fn resize(&mut self, new_size: Size<DevicePixels>) -> Result<()> {
-        unsafe { self.devices.device_context.OMSetRenderTargets(None, None) };
-        drop(self.context.back_buffer[0].take().unwrap());
+        let width = new_size.width.0.max(1) as u32;
+        let height = new_size.height.0.max(1) as u32;
+        if self.resources.width == width && self.resources.height == height {
+            return Ok(());
+        }
         unsafe {
-            self.context.swap_chain.ResizeBuffers(
+            // Clear the render target before resizing
+            self.devices.device_context.OMSetRenderTargets(None, None);
+            ManuallyDrop::drop(&mut self.resources.render_target);
+            drop(self.resources.render_target_view[0].take().unwrap());
+
+            self.resources.swap_chain.ResizeBuffers(
                 BUFFER_COUNT as u32,
-                new_size.width.0 as u32,
-                new_size.height.0 as u32,
-                DXGI_FORMAT_B8G8R8A8_UNORM,
+                width,
+                height,
+                RENDER_TARGET_FORMAT,
                 DXGI_SWAP_CHAIN_FLAG(0),
             )?;
-        }
-        let backbuffer = set_render_target_view(
-            &self.context.swap_chain,
-            &self.devices.device,
-            &self.devices.device_context,
-        )?;
-        self.context.back_buffer[0] = Some(backbuffer);
-        self.context.viewport = set_viewport(
-            &self.devices.device_context,
-            new_size.width.0 as f32,
-            new_size.height.0 as f32,
-        );
-        Ok(())
-    }
 
-    // #[cfg(not(feature = "enable-renderdoc"))]
-    // pub(crate) fn update_transparency(
-    //     &mut self,
-    //     background_appearance: WindowBackgroundAppearance,
-    // ) -> Result<()> {
-    //     // We only support setting `Transparent` and `Opaque` for now.
-    //     match background_appearance {
-    //         WindowBackgroundAppearance::Opaque => {
-    //             if self.transparent {
-    //                 return Err(anyhow::anyhow!(
-    //                     "Set opaque backgroud from transparent background, a restart is required. Or, you can open a new window."
-    //                 ));
-    //             }
-    //         }
-    //         WindowBackgroundAppearance::Transparent | WindowBackgroundAppearance::Blurred => {
-    //             if !self.transparent {
-    //                 return Err(anyhow::anyhow!(
-    //                     "Set transparent backgroud from opaque background, a restart is required. Or, you can open a new window."
-    //                 ));
-    //             }
-    //         }
-    //     }
-    //     Ok(())
-    // }
-
-    // #[cfg(feature = "enable-renderdoc")]
-    pub(crate) fn update_transparency(
-        &mut self,
-        background_appearance: WindowBackgroundAppearance,
-    ) -> Result<()> {
-        if background_appearance != WindowBackgroundAppearance::Opaque {
-            Err(anyhow::anyhow!(
-                "Set transparent background not supported when feature \"enable-renderdoc\" is enabled."
-            ))
-        } else {
-            Ok(())
+            self.resources
+                .recreate_resources(&self.devices, width, height)?;
+            self.devices
+                .device_context
+                .OMSetRenderTargets(Some(&self.resources.render_target_view), None);
         }
+        Ok(())
     }
 
     fn draw_shadows(&mut self, shadows: &[Shadow]) -> Result<()> {
         if shadows.is_empty() {
             return Ok(());
         }
-        update_buffer_capacity(
-            &self.pipelines.shadow_pipeline,
-            std::mem::size_of::<Shadow>(),
-            shadows.len(),
+        self.pipelines.shadow_pipeline.update_buffer(
             &self.devices.device,
-        )
-        .map(|input| update_pipeline(&mut self.pipelines.shadow_pipeline, input));
-        update_buffer(
             &self.devices.device_context,
-            &self.pipelines.shadow_pipeline.buffer,
             shadows,
         )?;
-        draw_normal(
+        self.pipelines.shadow_pipeline.draw(
             &self.devices.device_context,
-            &self.pipelines.shadow_pipeline,
-            &self.context.viewport,
+            &self.resources.viewport,
             &self.globals.global_params_buffer,
-            D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
-            4,
             shadows.len() as u32,
         )
     }
@@ -255,25 +270,15 @@ impl DirectXRenderer {
         if quads.is_empty() {
             return Ok(());
         }
-        update_buffer_capacity(
-            &self.pipelines.quad_pipeline,
-            std::mem::size_of::<Quad>(),
-            quads.len(),
+        self.pipelines.quad_pipeline.update_buffer(
             &self.devices.device,
-        )
-        .map(|input| update_pipeline(&mut self.pipelines.quad_pipeline, input));
-        update_buffer(
             &self.devices.device_context,
-            &self.pipelines.quad_pipeline.buffer,
             quads,
         )?;
-        draw_normal(
+        self.pipelines.quad_pipeline.draw(
             &self.devices.device_context,
-            &self.pipelines.quad_pipeline,
-            &self.context.viewport,
+            &self.resources.viewport,
             &self.globals.global_params_buffer,
-            D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
-            4,
             quads.len() as u32,
         )
     }
@@ -282,7 +287,6 @@ impl DirectXRenderer {
         if paths.is_empty() {
             return Ok(());
         }
-        println!("Drawing {} paths", paths.len());
         let mut vertices = Vec::new();
         let mut sprites = Vec::with_capacity(paths.len());
         let mut draw_indirect_commands = Vec::with_capacity(paths.len());
@@ -296,11 +300,10 @@ impl DirectXRenderer {
             });
             start_vertex_location += path.vertices.len() as u32;
 
-            vertices.extend(path.vertices.iter().map(|v| PathVertex {
+            vertices.extend(path.vertices.iter().map(|v| DirectXPathVertex {
                 xy_position: v.xy_position,
-                content_mask: ContentMask {
-                    bounds: path.content_mask.bounds,
-                },
+                content_mask: path.content_mask.bounds,
+                sprite_index: i as u32,
             }));
 
             sprites.push(PathSprite {
@@ -309,64 +312,34 @@ impl DirectXRenderer {
             });
         }
 
-        update_buffer_capacity(
-            &self.pipelines.paths_pipeline,
-            std::mem::size_of::<PathSprite>(),
-            sprites.len(),
+        self.pipelines.paths_pipeline.update_buffer(
             &self.devices.device,
-        )
-        .map(|input| update_pipeline(&mut self.pipelines.paths_pipeline, input));
-        update_buffer(
             &self.devices.device_context,
-            &self.pipelines.paths_pipeline.buffer,
             &sprites,
-        )?;
-        update_indirect_buffer(
-            &self.devices.device_context,
-            &self.pipelines.paths_indirect_draw_buffer,
+            &vertices,
             &draw_indirect_commands,
         )?;
-        prepare_indirect_draws(
+        self.pipelines.paths_pipeline.draw(
             &self.devices.device_context,
-            &self.pipelines.paths_pipeline,
-            &self.context.viewport,
+            paths.len(),
+            &self.resources.viewport,
             &self.globals.global_params_buffer,
-            D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST,
-        )?;
-
-        for i in 0..paths.len() {
-            draw_indirect(
-                &self.devices.device_context,
-                &self.pipelines.paths_indirect_draw_buffer,
-                (i * std::mem::size_of::<DrawInstancedIndirectArgs>()) as u32,
-            );
-        }
-        Ok(())
+        )
     }
 
     fn draw_underlines(&mut self, underlines: &[Underline]) -> Result<()> {
         if underlines.is_empty() {
             return Ok(());
         }
-        update_buffer_capacity(
-            &self.pipelines.underline_pipeline,
-            std::mem::size_of::<Underline>(),
-            underlines.len(),
+        self.pipelines.underline_pipeline.update_buffer(
             &self.devices.device,
-        )
-        .map(|input| update_pipeline(&mut self.pipelines.underline_pipeline, input));
-        update_buffer(
             &self.devices.device_context,
-            &self.pipelines.underline_pipeline.buffer,
             underlines,
         )?;
-        draw_normal(
+        self.pipelines.underline_pipeline.draw(
             &self.devices.device_context,
-            &self.pipelines.underline_pipeline,
-            &self.context.viewport,
+            &self.resources.viewport,
             &self.globals.global_params_buffer,
-            D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
-            4,
             underlines.len() as u32,
         )
     }
@@ -379,24 +352,16 @@ impl DirectXRenderer {
         if sprites.is_empty() {
             return Ok(());
         }
-        let texture_view = self.atlas.get_texture_view(texture_id);
-        update_buffer_capacity(
-            &self.pipelines.mono_sprites,
-            std::mem::size_of::<MonochromeSprite>(),
-            sprites.len(),
+        self.pipelines.mono_sprites.update_buffer(
             &self.devices.device,
-        )
-        .map(|input| update_pipeline(&mut self.pipelines.mono_sprites, input));
-        update_buffer(
             &self.devices.device_context,
-            &self.pipelines.mono_sprites.buffer,
             sprites,
         )?;
-        draw_with_texture(
+        let texture_view = self.atlas.get_texture_view(texture_id);
+        self.pipelines.mono_sprites.draw_with_texture(
             &self.devices.device_context,
-            &self.pipelines.mono_sprites,
             &texture_view,
-            &self.context.viewport,
+            &self.resources.viewport,
             &self.globals.global_params_buffer,
             &self.globals.sampler,
             sprites.len() as u32,
@@ -411,24 +376,16 @@ impl DirectXRenderer {
         if sprites.is_empty() {
             return Ok(());
         }
-        let texture_view = self.atlas.get_texture_view(texture_id);
-        update_buffer_capacity(
-            &self.pipelines.poly_sprites,
-            std::mem::size_of::<PolychromeSprite>(),
-            sprites.len(),
+        self.pipelines.poly_sprites.update_buffer(
             &self.devices.device,
-        )
-        .map(|input| update_pipeline(&mut self.pipelines.poly_sprites, input));
-        update_buffer(
             &self.devices.device_context,
-            &self.pipelines.poly_sprites.buffer,
             sprites,
         )?;
-        draw_with_texture(
+        let texture_view = self.atlas.get_texture_view(texture_id);
+        self.pipelines.poly_sprites.draw_with_texture(
             &self.devices.device_context,
-            &self.pipelines.poly_sprites,
             &texture_view,
-            &self.context.viewport,
+            &self.resources.viewport,
             &self.globals.global_params_buffer,
             &self.globals.sampler,
             sprites.len() as u32,
@@ -441,88 +398,125 @@ impl DirectXRenderer {
         }
         Ok(())
     }
+
+    pub(crate) fn gpu_specs(&self) -> Result<GpuSpecs> {
+        let desc = unsafe { self.devices.adapter.GetDesc1() }?;
+        let is_software_emulated = (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE.0 as u32) != 0;
+        let device_name = String::from_utf16_lossy(&desc.Description)
+            .trim_matches(char::from(0))
+            .to_string();
+        let driver_name = match desc.VendorId {
+            0x10DE => "NVIDIA Corporation".to_string(),
+            0x1002 => "AMD Corporation".to_string(),
+            0x8086 => "Intel Corporation".to_string(),
+            _ => "Unknown Vendor".to_string(),
+        };
+        let driver_version = match desc.VendorId {
+            0x10DE => nvidia::get_driver_version(),
+            0x1002 => Err(anyhow::anyhow!("AMD driver info not implemented yet")),
+            0x8086 => intel::get_driver_version(&self.devices.adapter),
+            _ => Err(anyhow::anyhow!("Unknown vendor detected.")),
+        }
+        .context("Failed to get gpu driver info")
+        .log_err()
+        .unwrap_or("Unknown Driver".to_string());
+        Ok(GpuSpecs {
+            is_software_emulated,
+            device_name,
+            driver_name,
+            driver_info: driver_version,
+        })
+    }
 }
 
-impl DirectXContext {
-    pub fn new(devices: &DirectXDevices, hwnd: HWND, transparent: bool) -> Result<Self> {
-        // #[cfg(not(feature = "enable-renderdoc"))]
-        // let swap_chain = create_swap_chain(&devices.dxgi_factory, &devices.device, transparent)?;
-        // #[cfg(feature = "enable-renderdoc")]
+impl DirectXResources {
+    pub fn new(
+        devices: &DirectXDevices,
+        #[cfg(feature = "enable-renderdoc")] hwnd: HWND,
+    ) -> Result<Self> {
+        let width = 1;
+        let height = 1;
+
+        #[cfg(not(feature = "enable-renderdoc"))]
+        let swap_chain = create_swap_chain(&devices.dxgi_factory, &devices.device, width, height)?;
+        #[cfg(feature = "enable-renderdoc")]
         let swap_chain =
-            create_swap_chain_default(&devices.dxgi_factory, &devices.device, hwnd, transparent)?;
-        // #[cfg(not(feature = "enable-renderdoc"))]
-        // let direct_composition = DirectComposition::new(&devices.dxgi_device, hwnd)?;
-        // #[cfg(not(feature = "enable-renderdoc"))]
-        // direct_composition.set_swap_chain(&swap_chain)?;
-        let back_buffer = [Some(set_render_target_view(
-            &swap_chain,
-            &devices.device,
-            &devices.device_context,
-        )?)];
-        let viewport = set_viewport(&devices.device_context, 1.0, 1.0);
+            create_swap_chain(&devices.dxgi_factory, &devices.device, hwnd, width, height)?;
+
+        let (render_target, render_target_view, msaa_target, msaa_view, viewport) =
+            create_resources(devices, &swap_chain, width, height)?;
         set_rasterizer_state(&devices.device, &devices.device_context)?;
 
         Ok(Self {
             swap_chain,
-            back_buffer,
+            render_target,
+            render_target_view,
+            msaa_target,
+            msaa_view,
+            width,
+            height,
             viewport,
-            // #[cfg(not(feature = "enable-renderdoc"))]
-            // direct_composition,
         })
     }
+
+    #[inline]
+    fn recreate_resources(
+        &mut self,
+        devices: &DirectXDevices,
+        width: u32,
+        height: u32,
+    ) -> Result<()> {
+        let (render_target, render_target_view, msaa_target, msaa_view, viewport) =
+            create_resources(devices, &self.swap_chain, width, height)?;
+        self.render_target = render_target;
+        self.render_target_view = render_target_view;
+        self.msaa_target = msaa_target;
+        self.msaa_view = msaa_view;
+        self.viewport = viewport;
+        self.width = width;
+        self.height = height;
+        Ok(())
+    }
 }
 
 impl DirectXRenderPipelines {
     pub fn new(device: &ID3D11Device) -> Result<Self> {
-        let shadow_pipeline = create_pipieline(
+        let shadow_pipeline = PipelineState::new(
             device,
+            "shadow_pipeline",
             "shadow_vertex",
             "shadow_fragment",
-            std::mem::size_of::<Shadow>(),
-            32,
-        )?;
-        let quad_pipeline = create_pipieline(
-            device,
-            "quad_vertex",
-            "quad_fragment",
-            std::mem::size_of::<Quad>(),
-            32,
-        )?;
-        let paths_pipeline = create_pipieline(
-            device,
-            "paths_vertex",
-            "paths_fragment",
-            std::mem::size_of::<PathSprite>(),
-            32,
+            4,
         )?;
-        let underline_pipeline = create_pipieline(
+        let quad_pipeline =
+            PipelineState::new(device, "quad_pipeline", "quad_vertex", "quad_fragment", 64)?;
+        let paths_pipeline = PathsPipelineState::new(device)?;
+        let underline_pipeline = PipelineState::new(
             device,
+            "underline_pipeline",
             "underline_vertex",
             "underline_fragment",
-            std::mem::size_of::<Underline>(),
-            32,
+            4,
         )?;
-        let mono_sprites = create_pipieline(
+        let mono_sprites = PipelineState::new(
             device,
+            "monochrome_sprite_pipeline",
             "monochrome_sprite_vertex",
             "monochrome_sprite_fragment",
-            std::mem::size_of::<MonochromeSprite>(),
-            32,
+            512,
         )?;
-        let poly_sprites = create_pipieline(
+        let poly_sprites = PipelineState::new(
             device,
+            "polychrome_sprite_pipeline",
             "polychrome_sprite_vertex",
             "polychrome_sprite_fragment",
-            std::mem::size_of::<PolychromeSprite>(),
-            32,
+            16,
         )?;
-        let paths_indirect_draw_buffer = create_indirect_draw_buffer(device, 32)?;
 
         Ok(Self {
             shadow_pipeline,
             quad_pipeline,
             paths_pipeline,
-            paths_indirect_draw_buffer,
             underline_pipeline,
             mono_sprites,
             poly_sprites,
@@ -530,29 +524,29 @@ impl DirectXRenderPipelines {
     }
 }
 
-// #[cfg(not(feature = "enable-renderdoc"))]
-// impl DirectComposition {
-//     pub fn new(dxgi_device: &IDXGIDevice, hwnd: HWND) -> Result<Self> {
-//         let comp_device = get_comp_device(&dxgi_device)?;
-//         let comp_target = unsafe { comp_device.CreateTargetForHwnd(hwnd, true) }?;
-//         let comp_visual = unsafe { comp_device.CreateVisual() }?;
-
-//         Ok(Self {
-//             comp_device,
-//             comp_target,
-//             comp_visual,
-//         })
-//     }
-
-//     pub fn set_swap_chain(&self, swap_chain: &IDXGISwapChain1) -> Result<()> {
-//         unsafe {
-//             self.comp_visual.SetContent(swap_chain)?;
-//             self.comp_target.SetRoot(&self.comp_visual)?;
-//             self.comp_device.Commit()?;
-//         }
-//         Ok(())
-//     }
-// }
+#[cfg(not(feature = "enable-renderdoc"))]
+impl DirectComposition {
+    pub fn new(dxgi_device: &IDXGIDevice, hwnd: HWND) -> Result<Self> {
+        let comp_device = get_comp_device(&dxgi_device)?;
+        let comp_target = unsafe { comp_device.CreateTargetForHwnd(hwnd, true) }?;
+        let comp_visual = unsafe { comp_device.CreateVisual() }?;
+
+        Ok(Self {
+            comp_device,
+            comp_target,
+            comp_visual,
+        })
+    }
+
+    pub fn set_swap_chain(&self, swap_chain: &IDXGISwapChain1) -> Result<()> {
+        unsafe {
+            self.comp_visual.SetContent(swap_chain)?;
+            self.comp_target.SetRoot(&self.comp_visual)?;
+            self.comp_device.Commit()?;
+        }
+        Ok(())
+    }
+}
 
 impl DirectXGlobalElements {
     pub fn new(device: &ID3D11Device) -> Result<Self> {
@@ -588,13 +582,11 @@ impl DirectXGlobalElements {
         };
 
         let blend_state = create_blend_state(device)?;
-        let blend_state_for_pr = create_blend_state_for_path_raster(device)?;
 
         Ok(Self {
             global_params_buffer,
             sampler,
             blend_state,
-            blend_state_for_pr,
         })
     }
 }
@@ -606,14 +598,345 @@ struct GlobalParams {
     _pad: u64,
 }
 
-struct PipelineState {
+struct PipelineState<T> {
+    label: &'static str,
+    vertex: ID3D11VertexShader,
+    fragment: ID3D11PixelShader,
+    buffer: ID3D11Buffer,
+    buffer_size: usize,
+    view: [Option<ID3D11ShaderResourceView>; 1],
+    _marker: std::marker::PhantomData<T>,
+}
+
+struct PathsPipelineState {
     vertex: ID3D11VertexShader,
     fragment: ID3D11PixelShader,
     buffer: ID3D11Buffer,
     buffer_size: usize,
+    vertex_buffer: Option<ID3D11Buffer>,
+    vertex_buffer_size: usize,
+    indirect_draw_buffer: ID3D11Buffer,
+    indirect_buffer_size: usize,
+    input_layout: ID3D11InputLayout,
     view: [Option<ID3D11ShaderResourceView>; 1],
 }
 
+impl<T> PipelineState<T> {
+    fn new(
+        device: &ID3D11Device,
+        label: &'static str,
+        vertex_entry: &str,
+        fragment_entry: &str,
+        buffer_size: usize,
+    ) -> Result<Self> {
+        let vertex = {
+            let shader_blob = shader_resources::build_shader_blob(vertex_entry, "vs_5_0")?;
+            let bytes = unsafe {
+                std::slice::from_raw_parts(
+                    shader_blob.GetBufferPointer() as *mut u8,
+                    shader_blob.GetBufferSize(),
+                )
+            };
+            create_vertex_shader(device, bytes)?
+        };
+        let fragment = {
+            let shader_blob = shader_resources::build_shader_blob(fragment_entry, "ps_5_0")?;
+            let bytes = unsafe {
+                std::slice::from_raw_parts(
+                    shader_blob.GetBufferPointer() as *mut u8,
+                    shader_blob.GetBufferSize(),
+                )
+            };
+            create_fragment_shader(device, bytes)?
+        };
+        let buffer = create_buffer(device, std::mem::size_of::<T>(), buffer_size)?;
+        let view = create_buffer_view(device, &buffer)?;
+
+        Ok(PipelineState {
+            label,
+            vertex,
+            fragment,
+            buffer,
+            buffer_size,
+            view,
+            _marker: std::marker::PhantomData,
+        })
+    }
+
+    fn update_buffer(
+        &mut self,
+        device: &ID3D11Device,
+        device_context: &ID3D11DeviceContext,
+        data: &[T],
+    ) -> Result<()> {
+        if self.buffer_size < data.len() {
+            let new_buffer_size = data.len().next_power_of_two();
+            log::info!(
+                "Updating {} buffer size from {} to {}",
+                self.label,
+                self.buffer_size,
+                new_buffer_size
+            );
+            let buffer = create_buffer(device, std::mem::size_of::<T>(), new_buffer_size)?;
+            let view = create_buffer_view(device, &buffer)?;
+            self.buffer = buffer;
+            self.view = view;
+            self.buffer_size = new_buffer_size;
+        }
+        update_buffer(device_context, &self.buffer, data)
+    }
+
+    fn draw(
+        &self,
+        device_context: &ID3D11DeviceContext,
+        viewport: &[D3D11_VIEWPORT],
+        global_params: &[Option<ID3D11Buffer>],
+        instance_count: u32,
+    ) -> Result<()> {
+        set_pipeline_state(
+            device_context,
+            &self.view,
+            D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
+            viewport,
+            &self.vertex,
+            &self.fragment,
+            global_params,
+        );
+        unsafe {
+            device_context.DrawInstanced(4, instance_count, 0, 0);
+        }
+        Ok(())
+    }
+
+    fn draw_with_texture(
+        &self,
+        device_context: &ID3D11DeviceContext,
+        texture: &[Option<ID3D11ShaderResourceView>],
+        viewport: &[D3D11_VIEWPORT],
+        global_params: &[Option<ID3D11Buffer>],
+        sampler: &[Option<ID3D11SamplerState>],
+        instance_count: u32,
+    ) -> Result<()> {
+        set_pipeline_state(
+            device_context,
+            &self.view,
+            D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
+            viewport,
+            &self.vertex,
+            &self.fragment,
+            global_params,
+        );
+        unsafe {
+            device_context.PSSetSamplers(0, Some(sampler));
+            device_context.VSSetShaderResources(0, Some(texture));
+            device_context.PSSetShaderResources(0, Some(texture));
+
+            device_context.DrawInstanced(4, instance_count, 0, 0);
+        }
+        Ok(())
+    }
+}
+
+impl PathsPipelineState {
+    fn new(device: &ID3D11Device) -> Result<Self> {
+        let (vertex, vertex_shader) = {
+            let shader_blob = shader_resources::build_shader_blob("paths_vertex", "vs_5_0")?;
+            let bytes = unsafe {
+                std::slice::from_raw_parts(
+                    shader_blob.GetBufferPointer() as *mut u8,
+                    shader_blob.GetBufferSize(),
+                )
+            };
+            (create_vertex_shader(device, bytes)?, shader_blob)
+        };
+        let fragment = {
+            let shader_blob = shader_resources::build_shader_blob("paths_fragment", "ps_5_0")?;
+            let bytes = unsafe {
+                std::slice::from_raw_parts(
+                    shader_blob.GetBufferPointer() as *mut u8,
+                    shader_blob.GetBufferSize(),
+                )
+            };
+            create_fragment_shader(device, bytes)?
+        };
+        let buffer = create_buffer(device, std::mem::size_of::<PathSprite>(), 32)?;
+        let view = create_buffer_view(device, &buffer)?;
+        let vertex_buffer = Some(create_buffer(
+            device,
+            std::mem::size_of::<DirectXPathVertex>(),
+            32,
+        )?);
+        let indirect_draw_buffer = create_indirect_draw_buffer(device, 32)?;
+        // Create input layout
+        let input_layout = unsafe {
+            let shader_bytes = std::slice::from_raw_parts(
+                vertex_shader.GetBufferPointer() as *const u8,
+                vertex_shader.GetBufferSize(),
+            );
+            let mut layout = None;
+            device.CreateInputLayout(
+                &[
+                    D3D11_INPUT_ELEMENT_DESC {
+                        SemanticName: windows::core::s!("POSITION"),
+                        SemanticIndex: 0,
+                        Format: DXGI_FORMAT_R32G32_FLOAT,
+                        InputSlot: 0,
+                        AlignedByteOffset: 0,
+                        InputSlotClass: D3D11_INPUT_PER_VERTEX_DATA,
+                        InstanceDataStepRate: 0,
+                    },
+                    D3D11_INPUT_ELEMENT_DESC {
+                        SemanticName: windows::core::s!("TEXCOORD"),
+                        SemanticIndex: 0,
+                        Format: DXGI_FORMAT_R32G32_FLOAT,
+                        InputSlot: 0,
+                        AlignedByteOffset: 8,
+                        InputSlotClass: D3D11_INPUT_PER_VERTEX_DATA,
+                        InstanceDataStepRate: 0,
+                    },
+                    D3D11_INPUT_ELEMENT_DESC {
+                        SemanticName: windows::core::s!("TEXCOORD"),
+                        SemanticIndex: 1,
+                        Format: DXGI_FORMAT_R32G32_FLOAT,
+                        InputSlot: 0,
+                        AlignedByteOffset: 16,
+                        InputSlotClass: D3D11_INPUT_PER_VERTEX_DATA,
+                        InstanceDataStepRate: 0,
+                    },
+                    D3D11_INPUT_ELEMENT_DESC {
+                        SemanticName: windows::core::s!("GLOBALIDX"),
+                        SemanticIndex: 0,
+                        Format: DXGI_FORMAT_R32_UINT,
+                        InputSlot: 0,
+                        AlignedByteOffset: 24,
+                        InputSlotClass: D3D11_INPUT_PER_VERTEX_DATA,
+                        InstanceDataStepRate: 0,
+                    },
+                ],
+                shader_bytes,
+                Some(&mut layout),
+            )?;
+            layout.unwrap()
+        };
+
+        Ok(Self {
+            vertex,
+            fragment,
+            buffer,
+            buffer_size: 32,
+            vertex_buffer,
+            vertex_buffer_size: 32,
+            indirect_draw_buffer,
+            indirect_buffer_size: 32,
+            input_layout,
+            view,
+        })
+    }
+
+    fn update_buffer(
+        &mut self,
+        device: &ID3D11Device,
+        device_context: &ID3D11DeviceContext,
+        buffer_data: &[PathSprite],
+        vertices_data: &[DirectXPathVertex],
+        draw_commands: &[DrawInstancedIndirectArgs],
+    ) -> Result<()> {
+        if self.buffer_size < buffer_data.len() {
+            let new_buffer_size = buffer_data.len().next_power_of_two();
+            log::info!(
+                "Updating Paths Pipeline buffer size from {} to {}",
+                self.buffer_size,
+                new_buffer_size
+            );
+            let buffer = create_buffer(device, std::mem::size_of::<PathSprite>(), new_buffer_size)?;
+            let view = create_buffer_view(device, &buffer)?;
+            self.buffer = buffer;
+            self.view = view;
+            self.buffer_size = new_buffer_size;
+        }
+        update_buffer(device_context, &self.buffer, buffer_data)?;
+        if self.vertex_buffer_size < vertices_data.len() {
+            let new_vertex_buffer_size = vertices_data.len().next_power_of_two();
+            log::info!(
+                "Updating Paths Pipeline vertex buffer size from {} to {}",
+                self.vertex_buffer_size,
+                new_vertex_buffer_size
+            );
+            let vertex_buffer = create_buffer(
+                device,
+                std::mem::size_of::<DirectXPathVertex>(),
+                new_vertex_buffer_size,
+            )?;
+            self.vertex_buffer = Some(vertex_buffer);
+            self.vertex_buffer_size = new_vertex_buffer_size;
+        }
+        update_buffer(
+            device_context,
+            self.vertex_buffer.as_ref().unwrap(),
+            vertices_data,
+        )?;
+        if self.indirect_buffer_size < draw_commands.len() {
+            let new_indirect_buffer_size = draw_commands.len().next_power_of_two();
+            log::info!(
+                "Updating Paths Pipeline indirect buffer size from {} to {}",
+                self.indirect_buffer_size,
+                new_indirect_buffer_size
+            );
+            let indirect_draw_buffer =
+                create_indirect_draw_buffer(device, new_indirect_buffer_size)?;
+            self.indirect_draw_buffer = indirect_draw_buffer;
+            self.indirect_buffer_size = new_indirect_buffer_size;
+        }
+        update_buffer(device_context, &self.indirect_draw_buffer, draw_commands)?;
+        Ok(())
+    }
+
+    fn draw(
+        &self,
+        device_context: &ID3D11DeviceContext,
+        count: usize,
+        viewport: &[D3D11_VIEWPORT],
+        global_params: &[Option<ID3D11Buffer>],
+    ) -> Result<()> {
+        set_pipeline_state(
+            device_context,
+            &self.view,
+            D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST,
+            viewport,
+            &self.vertex,
+            &self.fragment,
+            global_params,
+        );
+        unsafe {
+            const STRIDE: u32 = std::mem::size_of::<DirectXPathVertex>() as u32;
+            device_context.IASetVertexBuffers(
+                0,
+                1,
+                Some(&self.vertex_buffer),
+                Some(&STRIDE),
+                Some(&0),
+            );
+            device_context.IASetInputLayout(&self.input_layout);
+        }
+        for i in 0..count {
+            unsafe {
+                device_context.DrawInstancedIndirect(
+                    &self.indirect_draw_buffer,
+                    (i * std::mem::size_of::<DrawInstancedIndirectArgs>()) as u32,
+                );
+            }
+        }
+        Ok(())
+    }
+}
+
+#[repr(C)]
+struct DirectXPathVertex {
+    xy_position: Point<ScaledPixels>,
+    content_mask: Bounds<ScaledPixels>,
+    sprite_index: u32,
+}
+
 #[derive(Clone, Debug, Eq, PartialEq)]
 #[repr(C)]
 struct PathSprite {

crates/gpui/src/platform/windows/events.rs 🔗

@@ -181,12 +181,8 @@ fn handle_size_msg(
     let new_size = size(DevicePixels(width), DevicePixels(height));
     let scale_factor = lock.scale_factor;
     if lock.restore_from_minimized.is_some() {
-        // lock.renderer
-        //     .update_drawable_size_even_if_unchanged(new_size);
-        lock.renderer.resize(new_size).log_err();
         lock.callbacks.request_frame = lock.restore_from_minimized.take();
     } else {
-        // lock.renderer.update_drawable_size(new_size);
         lock.renderer.resize(new_size).log_err();
     }
     let new_size = new_size.to_pixels(scale_factor);

crates/gpui/src/platform/windows/platform.rs 🔗

@@ -28,13 +28,12 @@ use windows::{
     core::*,
 };
 
-use crate::{platform::blade::BladeContext, *};
+use crate::*;
 
 pub(crate) struct WindowsPlatform {
     state: RefCell<WindowsPlatformState>,
     raw_window_handles: RwLock<SmallVec<[HWND; 4]>>,
     // The below members will never change throughout the entire lifecycle of the app.
-    // gpu_context: BladeContext,
     directx_devices: DirectXDevices,
     icon: HICON,
     main_receiver: flume::Receiver<Runnable>,
@@ -112,7 +111,6 @@ impl WindowsPlatform {
         let icon = load_icon().unwrap_or_default();
         let state = RefCell::new(WindowsPlatformState::new());
         let raw_window_handles = RwLock::new(SmallVec::new());
-        // let gpu_context = BladeContext::new().context("Unable to init GPU context")?;
         let directx_devices = DirectXDevices::new().context("Unable to init directx devices.")?;
         let windows_version = WindowsVersion::new().context("Error retrieve windows version")?;
 
@@ -462,7 +460,8 @@ impl Platform for WindowsPlatform {
             options,
             self.generate_creation_info(),
             &self.directx_devices,
-        )?;
+        )
+        .inspect_err(|err| show_error("Failed to open new window", err.to_string()))?;
         let handle = window.get_raw_handle();
         self.raw_window_handles.write().push(handle);
 

crates/gpui/src/platform/windows/shaders.hlsl 🔗

@@ -93,10 +93,9 @@ float4 to_device_position(float2 unit_vertex, Bounds bounds) {
 }
 
 float4 distance_from_clip_rect_impl(float2 position, Bounds clip_bounds) {
-    return float4(position.x - clip_bounds.origin.x,
-                    clip_bounds.origin.x + clip_bounds.size.x - position.x,
-                    position.y - clip_bounds.origin.y,
-                    clip_bounds.origin.y + clip_bounds.size.y - position.y);
+    float2 tl = position - clip_bounds.origin;
+    float2 br = clip_bounds.origin + clip_bounds.size - position;
+    return float4(tl.x, br.x, tl.y, br.y);
 }
 
 float4 distance_from_clip_rect(float2 unit_vertex, Bounds bounds, Bounds clip_bounds) {
@@ -240,6 +239,23 @@ float2 to_tile_position(float2 unit_vertex, AtlasTile tile) {
     return (float2(tile.bounds.origin) + unit_vertex * float2(tile.bounds.size)) / atlas_size;
 }
 
+// Selects corner radius based on quadrant.
+float pick_corner_radius(float2 center_to_point, Corners corner_radii) {
+    if (center_to_point.x < 0.) {
+        if (center_to_point.y < 0.) {
+            return corner_radii.top_left;
+        } else {
+            return corner_radii.bottom_left;
+        }
+    } else {
+        if (center_to_point.y < 0.) {
+            return corner_radii.top_right;
+        } else {
+            return corner_radii.bottom_right;
+        }
+    }
+}
+
 float4 to_device_position_transformed(float2 unit_vertex, Bounds bounds, 
                                       TransformationMatrix transformation) {
     float2 position = unit_vertex * bounds.size + bounds.origin;
@@ -248,48 +264,48 @@ float4 to_device_position_transformed(float2 unit_vertex, Bounds bounds,
     return float4(device_position, 0.0, 1.0);
 }
 
+// Implementation of quad signed distance field
+float quad_sdf_impl(float2 corner_center_to_point, float corner_radius) {
+    if (corner_radius == 0.0) {
+        // Fast path for unrounded corners
+        return max(corner_center_to_point.x, corner_center_to_point.y);
+    } else {
+        // Signed distance of the point from a quad that is inset by corner_radius
+        // It is negative inside this quad, and positive outside
+        float signed_distance_to_inset_quad =
+            // 0 inside the inset quad, and positive outside
+            length(max(float2(0.0, 0.0), corner_center_to_point)) +
+            // 0 outside the inset quad, and negative inside
+            min(0.0, max(corner_center_to_point.x, corner_center_to_point.y));
+
+        return signed_distance_to_inset_quad - corner_radius;
+    }
+}
+
 float quad_sdf(float2 pt, Bounds bounds, Corners corner_radii) {
     float2 half_size = bounds.size / 2.;
     float2 center = bounds.origin + half_size;
     float2 center_to_point = pt - center;
-    float corner_radius;
-    if (center_to_point.x < 0.) {
-        if (center_to_point.y < 0.) {
-            corner_radius = corner_radii.top_left;
-        } else {
-            corner_radius = corner_radii.bottom_left;
-        }
-    } else {
-        if (center_to_point.y < 0.) {
-            corner_radius = corner_radii.top_right;
-        } else {
-            corner_radius = corner_radii.bottom_right;
-        }
-    }
-
-    float2 rounded_edge_to_point = abs(center_to_point) - half_size + corner_radius;
-    float distance =
-        length(max(0., rounded_edge_to_point)) +
-        min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) -
-        corner_radius;
-
-    return distance;
+    float corner_radius = pick_corner_radius(center_to_point, corner_radii);
+    float2 corner_to_point = abs(center_to_point) - half_size;
+    float2 corner_center_to_point = corner_to_point + corner_radius;
+    return quad_sdf_impl(corner_center_to_point, corner_radius);
 }
 
-GradientColor prepare_gradient_color(uint tag, uint color_space, Hsla solid, Hsla color0, Hsla color1) {
+GradientColor prepare_gradient_color(uint tag, uint color_space, Hsla solid, LinearColorStop colors[2]) {
     GradientColor output;
-    if (tag == 0) {
+    if (tag == 0 || tag == 2) {
         output.solid = hsla_to_rgba(solid);
     } else if (tag == 1) {
-        output.color0 = hsla_to_rgba(color0);
-        output.color1 = hsla_to_rgba(color1);
+        output.color0 = hsla_to_rgba(colors[0].color);
+        output.color1 = hsla_to_rgba(colors[1].color);
 
         // Prepare color space in vertex for avoid conversion
         // in fragment shader for performance reasons
         if (color_space == 1) {
-        // Oklab
-        output.color0 = srgb_to_oklab(output.color0);
-        output.color1 = srgb_to_oklab(output.color1);
+            // Oklab
+            output.color0 = srgb_to_oklab(output.color0);
+            output.color1 = srgb_to_oklab(output.color1);
         }
     }
 
@@ -326,8 +342,8 @@ float4 gradient_color(Background background,
             }
 
             // Get the t value for the linear gradient with the color stop percentages.
-            float2 half_size = float2(bounds.size.x, bounds.size.y) / 2.;
-            float2 center = float2(bounds.origin.x, bounds.origin.y) + half_size;
+            float2 half_size = bounds.size * 0.5;
+            float2 center = bounds.origin + half_size;
             float2 center_to_point = position - center;
             float t = dot(center_to_point, direction) / length(direction);
             // Check the direct to determine the use x or y
@@ -376,97 +392,55 @@ float4 gradient_color(Background background,
     return color;
 }
 
-/*
-**
-**              Shadows
-**
-*/
-
-struct ShadowVertexOutput {
-    float4 position: SV_Position;
-    float4 color: COLOR;
-    uint shadow_id: FLAT;
-    float4 clip_distance: SV_ClipDistance;
-};
-
-struct ShadowFragmentInput {
-  float4 position: SV_Position;
-  float4 color: COLOR;
-  uint shadow_id: FLAT;
-};
-
-struct Shadow {
-    uint order;
-    float blur_radius;
-    Bounds bounds;
-    Corners corner_radii;
-    Bounds content_mask;
-    Hsla color;
-};
-
-StructuredBuffer<Shadow> shadows: register(t1);
-
-ShadowVertexOutput shadow_vertex(uint vertex_id: SV_VertexID, uint shadow_id: SV_InstanceID) {
-    float2 unit_vertex = float2(float(vertex_id & 1u), 0.5 * float(vertex_id & 2u));
-    Shadow shadow = shadows[shadow_id];
-
-    float margin = 3.0 * shadow.blur_radius;
-    Bounds bounds = shadow.bounds;
-    bounds.origin -= margin;
-    bounds.size += 2.0 * margin;
-
-    float4 device_position = to_device_position(unit_vertex, bounds);
-    float4 clip_distance = distance_from_clip_rect(unit_vertex, bounds, shadow.content_mask);
-    float4 color = hsla_to_rgba(shadow.color);
-
-    ShadowVertexOutput output;
-    output.position = device_position;
-    output.color = color;
-    output.shadow_id = shadow_id;
-    output.clip_distance = clip_distance;
-
-    return output;
-}
-
-float4 shadow_fragment(ShadowFragmentInput input): SV_TARGET {
-    Shadow shadow = shadows[input.shadow_id];
-
-    float2 half_size = shadow.bounds.size / 2.;
-    float2 center = shadow.bounds.origin + half_size;
-    float2 point0 = input.position.xy - center;
-    float corner_radius;
-    if (point0.x < 0.) {
-        if (point0.y < 0.) {
-            corner_radius = shadow.corner_radii.top_left;
-        } else {
-            corner_radius = shadow.corner_radii.bottom_left;
-        }
+// Returns the dash velocity of a corner given the dash velocity of the two
+// sides, by returning the slower velocity (larger dashes).
+//
+// Since 0 is used for dash velocity when the border width is 0 (instead of
+// +inf), this returns the other dash velocity in that case.
+//
+// An alternative to this might be to appropriately interpolate the dash
+// velocity around the corner, but that seems overcomplicated.
+float corner_dash_velocity(float dv1, float dv2) {
+    if (dv1 == 0.0) {
+        return dv2;
+    } else if (dv2 == 0.0) {
+        return dv1;
     } else {
-        if (point0.y < 0.) {
-            corner_radius = shadow.corner_radii.top_right;
-        } else {
-            corner_radius = shadow.corner_radii.bottom_right;
-        }
+        return min(dv1, dv2);
     }
+}
 
-    // The signal is only non-zero in a limited range, so don't waste samples
-    float low = point0.y - half_size.y;
-    float high = point0.y + half_size.y;
-    float start = clamp(-3. * shadow.blur_radius, low, high);
-    float end = clamp(3. * shadow.blur_radius, low, high);
-
-    // Accumulate samples (we can get away with surprisingly few samples)
-    float step = (end - start) / 4.;
-    float y = start + step * 0.5;
-    float alpha = 0.;
-    for (int i = 0; i < 4; i++) {
-        alpha += blur_along_x(point0.x, point0.y - y, shadow.blur_radius,
-                            corner_radius, half_size) *
-                gaussian(y, shadow.blur_radius) * step;
-        y += step;
-    }
+// Returns alpha used to render antialiased dashes.
+// `t` is within the dash when `fmod(t, period) < length`.
+float dash_alpha(
+    float t, float period, float length, float dash_velocity,
+    float antialias_threshold
+) {
+    float half_period = period / 2.0;
+    float half_length = length / 2.0;
+    // Value in [-half_period, half_period]
+    // The dash is in [-half_length, half_length]
+    float centered = fmod(t + half_period - half_length, period) - half_period;
+    // Signed distance for the dash, negative values are inside the dash
+    float signed_distance = abs(centered) - half_length;
+    // Antialiased alpha based on the signed distance
+    return saturate(antialias_threshold - signed_distance / dash_velocity);
+}
 
-    return input.color * float4(1., 1., 1., alpha);
+// This approximates distance to the nearest point to a quarter ellipse in a way
+// that is sufficient for anti-aliasing when the ellipse is not very eccentric.
+// The components of `point` are expected to be positive.
+//
+// Negative on the outside and positive on the inside.
+float quarter_ellipse_sdf(float2 pt, float2 radii) {
+    // Scale the space to treat the ellipse like a unit circle
+    float2 circle_vec = pt / radii;
+    float unit_circle_sdf = length(circle_vec) - 1.0;
+    // Approximate up-scaling of the length by using the average of the radii.
+    //
+    // TODO: A better solution would be to use the gradient of the implicit
+    // function for an ellipse to approximate a scaling factor.
+    return unit_circle_sdf * (radii.x + radii.y) * -0.5;
 }
 
 /*
@@ -477,7 +451,7 @@ float4 shadow_fragment(ShadowFragmentInput input): SV_TARGET {
 
 struct Quad {
     uint order;
-    uint pad;
+    uint border_style;
     Bounds bounds;
     Bounds content_mask;
     Background background;
@@ -487,9 +461,9 @@ struct Quad {
 };
 
 struct QuadVertexOutput {
+    nointerpolation uint quad_id: TEXCOORD0;
     float4 position: SV_Position;
     nointerpolation float4 border_color: COLOR0;
-    nointerpolation uint quad_id: TEXCOORD0;
     nointerpolation float4 background_solid: COLOR1;
     nointerpolation float4 background_color0: COLOR2;
     nointerpolation float4 background_color1: COLOR3;
@@ -511,16 +485,15 @@ QuadVertexOutput quad_vertex(uint vertex_id: SV_VertexID, uint quad_id: SV_Insta
     float2 unit_vertex = float2(float(vertex_id & 1u), 0.5 * float(vertex_id & 2u));
     Quad quad = quads[quad_id];
     float4 device_position = to_device_position(unit_vertex, quad.bounds);
-    float4 clip_distance = distance_from_clip_rect(unit_vertex, quad.bounds, quad.content_mask);
-    float4 border_color = hsla_to_rgba(quad.border_color);
 
     GradientColor gradient = prepare_gradient_color(
         quad.background.tag,
         quad.background.color_space,
         quad.background.solid,
-        quad.background.colors[0].color,
-        quad.background.colors[1].color
+        quad.background.colors
     );
+    float4 clip_distance = distance_from_clip_rect(unit_vertex, quad.bounds, quad.content_mask);
+    float4 border_color = hsla_to_rgba(quad.border_color);
 
     QuadVertexOutput output;
     output.position = device_position;
@@ -535,79 +508,380 @@ QuadVertexOutput quad_vertex(uint vertex_id: SV_VertexID, uint quad_id: SV_Insta
 
 float4 quad_fragment(QuadFragmentInput input): SV_Target {
     Quad quad = quads[input.quad_id];
-    float2 half_size = quad.bounds.size / 2.;
-    float2 center = quad.bounds.origin + half_size;
-    float2 center_to_point = input.position.xy - center;
-    float4 color = gradient_color(quad.background, input.position.xy, quad.bounds,
+    float4 background_color = gradient_color(quad.background, input.position.xy, quad.bounds,
     input.background_solid, input.background_color0, input.background_color1);
 
-    // Fast path when the quad is not rounded and doesn't have any border.
-    if (quad.corner_radii.top_left == 0. && quad.corner_radii.bottom_left == 0. &&
-        quad.corner_radii.top_right == 0. &&
-        quad.corner_radii.bottom_right == 0. && quad.border_widths.top == 0. &&
-        quad.border_widths.left == 0. && quad.border_widths.right == 0. &&
-        quad.border_widths.bottom == 0.) {
-        return color;
+    bool unrounded = quad.corner_radii.top_left == 0.0 &&
+        quad.corner_radii.top_right == 0.0 &&
+        quad.corner_radii.bottom_left == 0.0 &&
+        quad.corner_radii.bottom_right == 0.0;
+
+    // Fast path when the quad is not rounded and doesn't have any border
+    if (quad.border_widths.top == 0.0 &&
+        quad.border_widths.left == 0.0 &&
+        quad.border_widths.right == 0.0 &&
+        quad.border_widths.bottom == 0.0 &&
+        unrounded) {
+        return background_color;
     }
 
-    float corner_radius;
-    if (center_to_point.x < 0.) {
-        if (center_to_point.y < 0.) {
-            corner_radius = quad.corner_radii.top_left;
-        } else {
-            corner_radius = quad.corner_radii.bottom_left;
-        }
-    } else {
-        if (center_to_point.y < 0.) {
-            corner_radius = quad.corner_radii.top_right;
-        } else {
-            corner_radius = quad.corner_radii.bottom_right;
-        }
+    float2 size = quad.bounds.size;
+    float2 half_size = size / 2.;
+    float2 the_point = input.position.xy - quad.bounds.origin;
+    float2 center_to_point = the_point - half_size;
+
+    // Signed distance field threshold for inclusion of pixels. 0.5 is the
+    // minimum distance between the center of the pixel and the edge.
+    const float antialias_threshold = 0.5;
+
+    // Radius of the nearest corner
+    float corner_radius = pick_corner_radius(center_to_point, quad.corner_radii);
+
+    float2 border = float2(
+        center_to_point.x < 0.0 ? quad.border_widths.left : quad.border_widths.right,
+        center_to_point.y < 0.0 ? quad.border_widths.top : quad.border_widths.bottom
+    );
+
+    // 0-width borders are reduced so that `inner_sdf >= antialias_threshold`.
+    // The purpose of this is to not draw antialiasing pixels in this case.
+    float2 reduced_border = float2(
+        border.x == 0.0 ? -antialias_threshold : border.x,
+        border.y == 0.0 ? -antialias_threshold : border.y
+    );
+
+    // Vector from the corner of the quad bounds to the point, after mirroring
+    // the point into the bottom right quadrant. Both components are <= 0.
+    float2 corner_to_point = abs(center_to_point) - half_size;
+
+    // Vector from the point to the center of the rounded corner's circle, also
+    // mirrored into bottom right quadrant.
+    float2 corner_center_to_point = corner_to_point + corner_radius;
+
+    // Whether the nearest point on the border is rounded
+    bool is_near_rounded_corner =
+        corner_center_to_point.x >= 0.0 &&
+        corner_center_to_point.y >= 0.0;
+
+    // Vector from straight border inner corner to point.
+    //
+    // 0-width borders are turned into width -1 so that inner_sdf is > 1.0 near
+    // the border. Without this, antialiasing pixels would be drawn.
+    float2 straight_border_inner_corner_to_point = corner_to_point + reduced_border;
+
+    // Whether the point is beyond the inner edge of the straight border
+    bool is_beyond_inner_straight_border =
+        straight_border_inner_corner_to_point.x > 0.0 ||
+        straight_border_inner_corner_to_point.y > 0.0;
+
+    // Whether the point is far enough inside the quad, such that the pixels are
+    // not affected by the straight border.
+    bool is_within_inner_straight_border =
+        straight_border_inner_corner_to_point.x < -antialias_threshold &&
+        straight_border_inner_corner_to_point.y < -antialias_threshold;
+
+    // Fast path for points that must be part of the background
+    if (is_within_inner_straight_border && !is_near_rounded_corner) {
+        return background_color;
     }
 
-    float2 rounded_edge_to_point = abs(center_to_point) - half_size + corner_radius;
-    float distance =
-        length(max(0., rounded_edge_to_point)) +
-        min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) -
-        corner_radius;
-
-    float vertical_border = center_to_point.x <= 0. ? quad.border_widths.left
-                                                    : quad.border_widths.right;
-    float horizontal_border = center_to_point.y <= 0. ? quad.border_widths.top
-                                                        : quad.border_widths.bottom;
-    float2 inset_size = half_size - corner_radius - float2(vertical_border, horizontal_border);
-    float2 point_to_inset_corner = abs(center_to_point) - inset_size;
-    float border_width;
-    if (point_to_inset_corner.x < 0. && point_to_inset_corner.y < 0.) {
-        border_width = 0.;
-    } else if (point_to_inset_corner.y > point_to_inset_corner.x) {
-        border_width = horizontal_border;
+    // Signed distance of the point to the outside edge of the quad's border
+    float outer_sdf = quad_sdf_impl(corner_center_to_point, corner_radius);
+
+    // Approximate signed distance of the point to the inside edge of the quad's
+    // border. It is negative outside this edge (within the border), and
+    // positive inside.
+    //
+    // This is not always an accurate signed distance:
+    // * The rounded portions with varying border width use an approximation of
+    //   nearest-point-on-ellipse.
+    // * When it is quickly known to be outside the edge, -1.0 is used.
+    float inner_sdf = 0.0;
+    if (corner_center_to_point.x <= 0.0 || corner_center_to_point.y <= 0.0) {
+        // Fast paths for straight borders
+        inner_sdf = -max(straight_border_inner_corner_to_point.x,
+                        straight_border_inner_corner_to_point.y);
+    } else if (is_beyond_inner_straight_border) {
+        // Fast path for points that must be outside the inner edge
+        inner_sdf = -1.0;
+    } else if (reduced_border.x == reduced_border.y) {
+        // Fast path for circular inner edge.
+        inner_sdf = -(outer_sdf + reduced_border.x);
     } else {
-        border_width = vertical_border;
+        float2 ellipse_radii = max(float2(0.0, 0.0), float2(corner_radius, corner_radius) - reduced_border);
+        inner_sdf = quarter_ellipse_sdf(corner_center_to_point, ellipse_radii);
     }
 
-    if (border_width != 0.) {
-        float inset_distance = distance + border_width;
+    // Negative when inside the border
+    float border_sdf = max(inner_sdf, outer_sdf);
+
+    float4 color = background_color;
+    if (border_sdf < antialias_threshold) {
+        float4 border_color = input.border_color;
+        // Dashed border logic when border_style == 1
+        if (quad.border_style == 1) {
+            // Position along the perimeter in "dash space", where each dash
+            // period has length 1
+            float t = 0.0;
+
+            // Total number of dash periods, so that the dash spacing can be
+            // adjusted to evenly divide it
+            float max_t = 0.0;
+
+            // Border width is proportional to dash size. This is the behavior
+            // used by browsers, but also avoids dashes from different segments
+            // overlapping when dash size is smaller than the border width.
+            //
+            // Dash pattern: (2 * border width) dash, (1 * border width) gap
+            const float dash_length_per_width = 2.0;
+            const float dash_gap_per_width = 1.0;
+            const float dash_period_per_width = dash_length_per_width + dash_gap_per_width;
+
+            // Since the dash size is determined by border width, the density of
+            // dashes varies. Multiplying a pixel distance by this returns a
+            // position in dash space - it has units (dash period / pixels). So
+            // a dash velocity of (1 / 10) is 1 dash every 10 pixels.
+            float dash_velocity = 0.0;
+
+            // Dividing this by the border width gives the dash velocity
+            const float dv_numerator = 1.0 / dash_period_per_width;
+
+            if (unrounded) {
+                // When corners aren't rounded, the dashes are separately laid
+                // out on each straight line, rather than around the whole
+                // perimeter. This way each line starts and ends with a dash.
+                bool is_horizontal = corner_center_to_point.x < corner_center_to_point.y;
+                float border_width = is_horizontal ? border.x : border.y;
+                dash_velocity = dv_numerator / border_width;
+                t = is_horizontal ? the_point.x : the_point.y;
+                t *= dash_velocity;
+                max_t = is_horizontal ? size.x : size.y;
+                max_t *= dash_velocity;
+            } else {
+                // When corners are rounded, the dashes are laid out clockwise
+                // around the whole perimeter.
+
+                float r_tr = quad.corner_radii.top_right;
+                float r_br = quad.corner_radii.bottom_right;
+                float r_bl = quad.corner_radii.bottom_left;
+                float r_tl = quad.corner_radii.top_left;
+
+                float w_t = quad.border_widths.top;
+                float w_r = quad.border_widths.right;
+                float w_b = quad.border_widths.bottom;
+                float w_l = quad.border_widths.left;
+
+                // Straight side dash velocities
+                float dv_t = w_t <= 0.0 ? 0.0 : dv_numerator / w_t;
+                float dv_r = w_r <= 0.0 ? 0.0 : dv_numerator / w_r;
+                float dv_b = w_b <= 0.0 ? 0.0 : dv_numerator / w_b;
+                float dv_l = w_l <= 0.0 ? 0.0 : dv_numerator / w_l;
+
+                // Straight side lengths in dash space
+                float s_t = (size.x - r_tl - r_tr) * dv_t;
+                float s_r = (size.y - r_tr - r_br) * dv_r;
+                float s_b = (size.x - r_br - r_bl) * dv_b;
+                float s_l = (size.y - r_bl - r_tl) * dv_l;
+
+                float corner_dash_velocity_tr = corner_dash_velocity(dv_t, dv_r);
+                float corner_dash_velocity_br = corner_dash_velocity(dv_b, dv_r);
+                float corner_dash_velocity_bl = corner_dash_velocity(dv_b, dv_l);
+                float corner_dash_velocity_tl = corner_dash_velocity(dv_t, dv_l);
+
+                // Corner lengths in dash space
+                float c_tr = r_tr * (M_PI_F / 2.0) * corner_dash_velocity_tr;
+                float c_br = r_br * (M_PI_F / 2.0) * corner_dash_velocity_br;
+                float c_bl = r_bl * (M_PI_F / 2.0) * corner_dash_velocity_bl;
+                float c_tl = r_tl * (M_PI_F / 2.0) * corner_dash_velocity_tl;
+
+                // Cumulative dash space upto each segment
+                float upto_tr = s_t;
+                float upto_r = upto_tr + c_tr;
+                float upto_br = upto_r + s_r;
+                float upto_b = upto_br + c_br;
+                float upto_bl = upto_b + s_b;
+                float upto_l = upto_bl + c_bl;
+                float upto_tl = upto_l + s_l;
+                max_t = upto_tl + c_tl;
+
+                if (is_near_rounded_corner) {
+                    float radians = atan2(corner_center_to_point.y, corner_center_to_point.x);
+                    float corner_t = radians * corner_radius;
+
+                    if (center_to_point.x >= 0.0) {
+                        if (center_to_point.y < 0.0) {
+                            dash_velocity = corner_dash_velocity_tr;
+                            // Subtracted because radians is pi/2 to 0 when
+                            // going clockwise around the top right corner,
+                            // since the y axis has been flipped
+                            t = upto_r - corner_t * dash_velocity;
+                        } else {
+                            dash_velocity = corner_dash_velocity_br;
+                            // Added because radians is 0 to pi/2 when going
+                            // clockwise around the bottom-right corner
+                            t = upto_br + corner_t * dash_velocity;
+                        }
+                    } else {
+                        if (center_to_point.y >= 0.0) {
+                            dash_velocity = corner_dash_velocity_bl;
+                            // Subtracted because radians is pi/1 to 0 when
+                            // going clockwise around the bottom-left corner,
+                            // since the x axis has been flipped
+                            t = upto_l - corner_t * dash_velocity;
+                        } else {
+                            dash_velocity = corner_dash_velocity_tl;
+                            // Added because radians is 0 to pi/2 when going
+                            // clockwise around the top-left corner, since both
+                            // axis were flipped
+                            t = upto_tl + corner_t * dash_velocity;
+                        }
+                    }
+                } else {
+                    // Straight borders
+                    bool is_horizontal = corner_center_to_point.x < corner_center_to_point.y;
+                    if (is_horizontal) {
+                        if (center_to_point.y < 0.0) {
+                            dash_velocity = dv_t;
+                            t = (the_point.x - r_tl) * dash_velocity;
+                        } else {
+                            dash_velocity = dv_b;
+                            t = upto_bl - (the_point.x - r_bl) * dash_velocity;
+                        }
+                    } else {
+                        if (center_to_point.x < 0.0) {
+                            dash_velocity = dv_l;
+                            t = upto_tl - (the_point.y - r_tl) * dash_velocity;
+                        } else {
+                            dash_velocity = dv_r;
+                            t = upto_r + (the_point.y - r_tr) * dash_velocity;
+                        }
+                    }
+                }
+            }
+            float dash_length = dash_length_per_width / dash_period_per_width;
+            float desired_dash_gap = dash_gap_per_width / dash_period_per_width;
+
+            // Straight borders should start and end with a dash, so max_t is
+            // reduced to cause this.
+            max_t -= unrounded ? dash_length : 0.0;
+            if (max_t >= 1.0) {
+                // Adjust dash gap to evenly divide max_t
+                float dash_count = floor(max_t);
+                float dash_period = max_t / dash_count;
+                border_color.a *= dash_alpha(t, dash_period, dash_length, dash_velocity, antialias_threshold);
+            } else if (unrounded) {
+                // When there isn't enough space for the full gap between the
+                // two start / end dashes of a straight border, reduce gap to
+                // make them fit.
+                float dash_gap = max_t - dash_length;
+                if (dash_gap > 0.0) {
+                    float dash_period = dash_length + dash_gap;
+                    border_color.a *= dash_alpha(t, dash_period, dash_length, dash_velocity, antialias_threshold);
+                }
+            }
+        }
+
         // Blend the border on top of the background and then linearly interpolate
         // between the two as we slide inside the background.
-        float4 blended_border = over(color, input.border_color);
-        color = lerp(blended_border, color, saturate(0.5 - inset_distance));
+        float4 blended_border = over(background_color, border_color);
+        color = lerp(background_color, blended_border,
+                    saturate(antialias_threshold - inner_sdf));
     }
 
-    return color * float4(1., 1., 1., saturate(0.5 - distance));
+    return color * float4(1.0, 1.0, 1.0, saturate(antialias_threshold - outer_sdf));
 }
 
-struct PathVertex {
-    float2 xy_position;
+/*
+**
+**              Shadows
+**
+*/
+
+struct Shadow {
+    uint order;
+    float blur_radius;
+    Bounds bounds;
+    Corners corner_radii;
     Bounds content_mask;
+    Hsla color;
 };
 
+struct ShadowVertexOutput {
+    nointerpolation uint shadow_id: TEXCOORD0;
+    float4 position: SV_Position;
+    nointerpolation float4 color: COLOR;
+    float4 clip_distance: SV_ClipDistance;
+};
+
+struct ShadowFragmentInput {
+  nointerpolation uint shadow_id: TEXCOORD0;
+  float4 position: SV_Position;
+  nointerpolation float4 color: COLOR;
+};
+
+StructuredBuffer<Shadow> shadows: register(t1);
+
+ShadowVertexOutput shadow_vertex(uint vertex_id: SV_VertexID, uint shadow_id: SV_InstanceID) {
+    float2 unit_vertex = float2(float(vertex_id & 1u), 0.5 * float(vertex_id & 2u));
+    Shadow shadow = shadows[shadow_id];
+
+    float margin = 3.0 * shadow.blur_radius;
+    Bounds bounds = shadow.bounds;
+    bounds.origin -= margin;
+    bounds.size += 2.0 * margin;
+
+    float4 device_position = to_device_position(unit_vertex, bounds);
+    float4 clip_distance = distance_from_clip_rect(unit_vertex, bounds, shadow.content_mask);
+    float4 color = hsla_to_rgba(shadow.color);
+
+    ShadowVertexOutput output;
+    output.position = device_position;
+    output.color = color;
+    output.shadow_id = shadow_id;
+    output.clip_distance = clip_distance;
+
+    return output;
+}
+
+float4 shadow_fragment(ShadowFragmentInput input): SV_TARGET {
+    Shadow shadow = shadows[input.shadow_id];
+
+    float2 half_size = shadow.bounds.size / 2.;
+    float2 center = shadow.bounds.origin + half_size;
+    float2 point0 = input.position.xy - center;
+    float corner_radius = pick_corner_radius(point0, shadow.corner_radii);
+
+    // The signal is only non-zero in a limited range, so don't waste samples
+    float low = point0.y - half_size.y;
+    float high = point0.y + half_size.y;
+    float start = clamp(-3. * shadow.blur_radius, low, high);
+    float end = clamp(3. * shadow.blur_radius, low, high);
+
+    // Accumulate samples (we can get away with surprisingly few samples)
+    float step = (end - start) / 4.;
+    float y = start + step * 0.5;
+    float alpha = 0.;
+    for (int i = 0; i < 4; i++) {
+        alpha += blur_along_x(point0.x, point0.y - y, shadow.blur_radius,
+                            corner_radius, half_size) *
+                gaussian(y, shadow.blur_radius) * step;
+        y += step;
+    }
+
+    return input.color * float4(1., 1., 1., alpha);
+}
+
 /*
 **
 **              Paths
 **
 */
 
+struct PathVertex {
+    float2 xy_position: POSITION;
+    Bounds content_mask: TEXCOORD;
+    uint idx: GLOBALIDX;
+};
+
 struct PathSprite {
     Bounds bounds;
     Background color;
@@ -615,31 +889,36 @@ struct PathSprite {
 
 struct PathVertexOutput {
     float4 position: SV_Position;
+    nointerpolation uint sprite_id: TEXCOORD0;
+    nointerpolation float4 solid_color: COLOR0;
+    nointerpolation float4 color0: COLOR1;
+    nointerpolation float4 color1: COLOR2;
     float4 clip_distance: SV_ClipDistance;
+};
+
+struct PathFragmentInput {
+    float4 position: SV_Position;
     nointerpolation uint sprite_id: TEXCOORD0;
     nointerpolation float4 solid_color: COLOR0;
     nointerpolation float4 color0: COLOR1;
     nointerpolation float4 color1: COLOR2;
 };
 
-StructuredBuffer<PathVertex> path_vertices: register(t1);
-StructuredBuffer<PathSprite> path_sprites: register(t2);
+StructuredBuffer<PathSprite> path_sprites: register(t1);
 
-PathVertexOutput paths_vertex(uint vertex_id: SV_VertexID, uint instance_id: SV_InstanceID) {
-    PathVertex v = path_vertices[vertex_id];
-    PathSprite sprite = path_sprites[instance_id];
+PathVertexOutput paths_vertex(PathVertex input) {
+    PathSprite sprite = path_sprites[input.idx];
 
     PathVertexOutput output;
-    output.position = to_device_position_impl(v.xy_position);
-    output.clip_distance = distance_from_clip_rect_impl(v.xy_position, v.content_mask);
-    output.sprite_id = instance_id;
+    output.position = to_device_position_impl(input.xy_position);
+    output.clip_distance = distance_from_clip_rect_impl(input.xy_position, input.content_mask);
+    output.sprite_id = input.idx;
 
     GradientColor gradient = prepare_gradient_color(
         sprite.color.tag,
         sprite.color.color_space,
         sprite.color.solid,
-        sprite.color.colors[0].color,
-        sprite.color.colors[1].color
+        sprite.color.colors
     );
 
     output.solid_color = gradient.solid;
@@ -648,12 +927,7 @@ PathVertexOutput paths_vertex(uint vertex_id: SV_VertexID, uint instance_id: SV_
     return output;
 }
 
-float4 paths_fragment(PathVertexOutput input): SV_Target {
-    float4 zero = 0.0;
-    if (any(input.clip_distance < zero)) {
-        return zero;
-    }
-    
+float4 paths_fragment(PathFragmentInput input): SV_Target {
     PathSprite sprite = path_sprites[input.sprite_id];
     Background background = sprite.color;
     float4 color = gradient_color(background, input.position.xy, sprite.bounds,
@@ -678,16 +952,16 @@ struct Underline {
 };
 
 struct UnderlineVertexOutput {
+  nointerpolation uint underline_id: TEXCOORD0;
   float4 position: SV_Position;
-  float4 color: COLOR;
-  uint underline_id: FLAT;
+  nointerpolation float4 color: COLOR;
   float4 clip_distance: SV_ClipDistance;
 };
 
 struct UnderlineFragmentInput {
+  nointerpolation uint underline_id: TEXCOORD0;
   float4 position: SV_Position;
-  float4 color: COLOR;
-  uint underline_id: FLAT;
+  nointerpolation float4 color: COLOR;
 };
 
 StructuredBuffer<Underline> underlines: register(t1);
@@ -712,10 +986,8 @@ float4 underline_fragment(UnderlineFragmentInput input): SV_Target {
     Underline underline = underlines[input.underline_id];
     if (underline.wavy) {
         float half_thickness = underline.thickness * 0.5;
-        float2 origin =
-            float2(underline.bounds.origin.x, underline.bounds.origin.y);
-        float2 st = ((input.position.xy - origin) / underline.bounds.size.y) -
-                    float2(0., 0.5);
+        float2 origin = underline.bounds.origin;
+        float2 st = ((input.position.xy - origin) / underline.bounds.size.y) - float2(0., 0.5);
         float frequency = (M_PI_F * (3. * underline.thickness)) / 8.;
         float amplitude = 1. / (2. * underline.thickness);
         float sine = sin(st.x * frequency) * amplitude;
@@ -751,14 +1023,14 @@ struct MonochromeSprite {
 struct MonochromeSpriteVertexOutput {
     float4 position: SV_Position;
     float2 tile_position: POSITION;
-    float4 color: COLOR;
+    nointerpolation float4 color: COLOR;
     float4 clip_distance: SV_ClipDistance;
 };
 
 struct MonochromeSpriteFragmentInput {
     float4 position: SV_Position;
     float2 tile_position: POSITION;
-    float4 color: COLOR;
+    nointerpolation float4 color: COLOR;
 };
 
 StructuredBuffer<MonochromeSprite> mono_sprites: register(t1);
@@ -795,7 +1067,9 @@ float4 monochrome_sprite_fragment(MonochromeSpriteFragmentInput input): SV_Targe
 
 struct PolychromeSprite {
     uint order;
+    uint pad;
     uint grayscale;
+    float opacity;
     Bounds bounds;
     Bounds content_mask;
     Corners corner_radii;
@@ -803,16 +1077,16 @@ struct PolychromeSprite {
 };
 
 struct PolychromeSpriteVertexOutput {
+    nointerpolation uint sprite_id: TEXCOORD0;
     float4 position: SV_Position;
     float2 tile_position: POSITION;
-    uint sprite_id: FLAT;
     float4 clip_distance: SV_ClipDistance;
 };
 
 struct PolychromeSpriteFragmentInput {
+    nointerpolation uint sprite_id: TEXCOORD0;
     float4 position: SV_Position;
     float2 tile_position: POSITION;
-    uint sprite_id: FLAT;
 };
 
 StructuredBuffer<PolychromeSprite> poly_sprites: register(t1);
@@ -843,6 +1117,6 @@ float4 polychrome_sprite_fragment(PolychromeSpriteFragmentInput input): SV_Targe
         float3 grayscale = dot(color.rgb, GRAYSCALE_FACTORS);
         color = float4(grayscale, sample.a);
     }
-    color.a *= saturate(0.5 - distance);
+    color.a *= sprite.opacity * saturate(0.5 - distance);
     return color;
 }

crates/gpui/src/platform/windows/window.rs 🔗

@@ -26,7 +26,6 @@ use windows::{
     core::*,
 };
 
-use crate::platform::blade::{BladeContext, BladeRenderer};
 use crate::*;
 
 pub(crate) struct WindowsWindow(pub Rc<WindowsWindowStatePtr>);
@@ -80,7 +79,6 @@ pub(crate) struct WindowsWindowStatePtr {
 impl WindowsWindowState {
     fn new(
         hwnd: HWND,
-        transparent: bool,
         cs: &CREATESTRUCTW,
         current_cursor: Option<HCURSOR>,
         display: WindowsDisplay,
@@ -103,8 +101,7 @@ impl WindowsWindowState {
         };
         let border_offset = WindowBorderOffset::default();
         let restore_from_minimized = None;
-        // let renderer = windows_renderer::init(gpu_context, hwnd, transparent)?;
-        let renderer = DirectXRenderer::new(gpu_context, hwnd, transparent)?;
+        let renderer = DirectXRenderer::new(gpu_context, hwnd)?;
         let callbacks = Callbacks::default();
         let input_handler = None;
         let pending_surrogate = None;
@@ -207,7 +204,6 @@ impl WindowsWindowStatePtr {
     fn new(context: &WindowCreateContext, hwnd: HWND, cs: &CREATESTRUCTW) -> Result<Rc<Self>> {
         let state = RefCell::new(WindowsWindowState::new(
             hwnd,
-            context.transparent,
             cs,
             context.current_cursor,
             context.display,
@@ -335,7 +331,6 @@ struct WindowCreateContext<'a> {
     handle: AnyWindowHandle,
     hide_title_bar: bool,
     display: WindowsDisplay,
-    transparent: bool,
     is_movable: bool,
     min_size: Option<Size<Pixels>>,
     executor: ForegroundExecutor,
@@ -381,10 +376,13 @@ impl WindowsWindow {
                 .unwrap_or(""),
         );
         let (dwexstyle, mut dwstyle) = if params.kind == WindowKind::PopUp {
-            (WS_EX_TOOLWINDOW | WS_EX_LAYERED, WINDOW_STYLE(0x0))
+            (
+                WS_EX_TOOLWINDOW | WS_EX_NOREDIRECTIONBITMAP,
+                WINDOW_STYLE(0x0),
+            )
         } else {
             (
-                WS_EX_APPWINDOW | WS_EX_LAYERED,
+                WS_EX_APPWINDOW | WS_EX_NOREDIRECTIONBITMAP,
                 WS_THICKFRAME | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX,
             )
         };
@@ -402,7 +400,6 @@ impl WindowsWindow {
             handle,
             hide_title_bar,
             display,
-            transparent: true,
             is_movable: params.is_movable,
             min_size: params.window_min_size,
             executor,
@@ -454,14 +451,6 @@ impl WindowsWindow {
                 state: WindowOpenState::Windowed,
             });
         }
-        // The render pipeline will perform compositing on the GPU when the
-        // swapchain is configured correctly (see downstream of
-        // update_transparency).
-        // The following configuration is a one-time setup to ensure that the
-        // window is going to be composited with per-pixel alpha, but the render
-        // pipeline is responsible for effectively calling UpdateLayeredWindow
-        // at the appropriate time.
-        unsafe { SetLayeredWindowAttributes(hwnd, COLORREF(0), 255, LWA_ALPHA)? };
 
         Ok(Self(state_ptr))
     }
@@ -486,7 +475,6 @@ impl rwh::HasDisplayHandle for WindowsWindow {
 
 impl Drop for WindowsWindow {
     fn drop(&mut self) {
-        // self.0.state.borrow_mut().renderer.destroy();
         // clone this `Rc` to prevent early release of the pointer
         let this = self.0.clone();
         self.0
@@ -706,25 +694,21 @@ impl PlatformWindow for WindowsWindow {
     }
 
     fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance) {
-        let mut window_state = self.0.state.borrow_mut();
-        // todo(zjk)
-        // window_state
-        //     .renderer
-        //     .update_transparency(background_appearance != WindowBackgroundAppearance::Opaque);
+        let hwnd = self.0.hwnd;
 
         match background_appearance {
             WindowBackgroundAppearance::Opaque => {
                 // ACCENT_DISABLED
-                set_window_composition_attribute(window_state.hwnd, None, 0);
+                set_window_composition_attribute(hwnd, None, 0);
             }
             WindowBackgroundAppearance::Transparent => {
                 // Use ACCENT_ENABLE_TRANSPARENTGRADIENT for transparent background
-                set_window_composition_attribute(window_state.hwnd, None, 2);
+                set_window_composition_attribute(hwnd, None, 2);
             }
             WindowBackgroundAppearance::Blurred => {
                 // Enable acrylic blur
                 // ACCENT_ENABLE_ACRYLICBLURBEHIND
-                set_window_composition_attribute(window_state.hwnd, Some((0, 0, 0, 0)), 4);
+                set_window_composition_attribute(hwnd, Some((0, 0, 0, 0)), 4);
             }
         }
     }
@@ -808,13 +792,11 @@ impl PlatformWindow for WindowsWindow {
     }
 
     fn gpu_specs(&self) -> Option<GpuSpecs> {
-        // todo(zjk)
-        // Some(self.0.state.borrow().renderer.gpu_specs())
-        None
+        self.0.state.borrow().renderer.gpu_specs().log_err()
     }
 
     fn update_ime_position(&self, _bounds: Bounds<ScaledPixels>) {
-        // todo(windows)
+        // There is no such thing on Windows.
     }
 }
 
@@ -1310,52 +1292,6 @@ fn set_window_composition_attribute(hwnd: HWND, color: Option<Color>, state: u32
     }
 }
 
-mod windows_renderer {
-    use crate::platform::blade::{BladeContext, BladeRenderer, BladeSurfaceConfig};
-    use raw_window_handle as rwh;
-    use std::num::NonZeroIsize;
-    use windows::Win32::{Foundation::HWND, UI::WindowsAndMessaging::GWLP_HINSTANCE};
-
-    use crate::{get_window_long, show_error};
-
-    pub(super) fn init(
-        context: &BladeContext,
-        hwnd: HWND,
-        transparent: bool,
-    ) -> anyhow::Result<BladeRenderer> {
-        let raw = RawWindow { hwnd };
-        let config = BladeSurfaceConfig {
-            size: Default::default(),
-            transparent,
-        };
-        BladeRenderer::new(context, &raw, config)
-            .inspect_err(|err| show_error("Failed to initialize BladeRenderer", err.to_string()))
-    }
-
-    struct RawWindow {
-        hwnd: HWND,
-    }
-
-    impl rwh::HasWindowHandle for RawWindow {
-        fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
-            Ok(unsafe {
-                let hwnd = NonZeroIsize::new_unchecked(self.hwnd.0 as isize);
-                let mut handle = rwh::Win32WindowHandle::new(hwnd);
-                let hinstance = get_window_long(self.hwnd, GWLP_HINSTANCE);
-                handle.hinstance = NonZeroIsize::new(hinstance);
-                rwh::WindowHandle::borrow_raw(handle.into())
-            })
-        }
-    }
-
-    impl rwh::HasDisplayHandle for RawWindow {
-        fn display_handle(&self) -> Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
-            let handle = rwh::WindowsDisplayHandle::new();
-            Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
-        }
-    }
-}
-
 #[cfg(test)]
 mod tests {
     use super::ClickState;