From 3f1e4afd775dc593b376a793780dd03239c421dd Mon Sep 17 00:00:00 2001 From: kevin-mai Date: Thu, 14 May 2026 16:42:27 +0800 Subject: [PATCH] gpui_windows: add win-legacy-compat feature flag for Windows Server 2016 / pre-1809 back-compat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces a `win-legacy-compat` Cargo feature (forwarded from `gpui_platform`) that gates all Windows Server 2016 / pre-Windows 10 1809 workarounds behind a compile-time flag, preserving exact upstream behaviour and performance when the flag is absent. Changes when `win-legacy-compat` is enabled: - directx_devices.rs: use IDXGIFactory2 (DXGI 1.2, Win8+) instead of IDXGIFactory6 - direct_write.rs: use IDWriteFactory4 + IDWriteFontSetBuilder (Server 2016) instead of Factory5 + in-memory font loader; fall back to temp files for custom font loading - directx_renderer.rs: runtime is_pre_1809 check gates SRV FirstElement workaround; draw_* functions accept scene: &Scene for per-batch buffer re-upload on pre-1809; standard alpha blend replaces dual-source SRC1_COLOR for subpixel pipeline; MSAA quality 0 replaces D3D11_STANDARD_MULTISAMPLE_PATTERN (D3D11.1 sentinel); SM_REMOTESESSION check disables DirectComposition on RDP sessions - shaders.hlsl: #ifdef WIN_LEGACY_COMPAT collapses per-channel ClearType mask to scalar alpha for standard SRC_ALPHA/INV_SRC_ALPHA blending - build.rs: pass /D WIN_LEGACY_COMPAT to fxc (release); pass D3D_SHADER_MACRO define to D3DCompileFromFile (debug); fix /D argument order (must precede input filename); add rerun-if-env-changed for CARGO_FEATURE_WIN_LEGACY_COMPAT - gpui_platform/Cargo.toml: forward win-legacy-compat → gpui_windows/win-legacy-compat --- crates/gpui_platform/Cargo.toml | 1 + crates/gpui_windows/Cargo.toml | 5 + crates/gpui_windows/build.rs | 42 ++- crates/gpui_windows/src/direct_write.rs | 85 +++++- crates/gpui_windows/src/directx_devices.rs | 17 +- crates/gpui_windows/src/directx_renderer.rs | 298 +++++++++++++++++++- crates/gpui_windows/src/platform.rs | 3 + crates/gpui_windows/src/shaders.hlsl | 9 + 8 files changed, 433 insertions(+), 27 deletions(-) diff --git a/crates/gpui_platform/Cargo.toml b/crates/gpui_platform/Cargo.toml index 22d44a96b21..a9efec7fcc2 100644 --- a/crates/gpui_platform/Cargo.toml +++ b/crates/gpui_platform/Cargo.toml @@ -16,6 +16,7 @@ default = [] font-kit = ["gpui_macos/font-kit"] test-support = ["gpui/test-support", "gpui_macos/test-support"] screen-capture = ["gpui/screen-capture", "gpui_macos/screen-capture", "gpui_windows/screen-capture", "gpui_linux/screen-capture"] +win-legacy-compat = ["gpui_windows/win-legacy-compat"] runtime_shaders = ["gpui_macos/runtime_shaders"] wayland = ["gpui_linux/wayland"] x11 = ["gpui_linux/x11"] diff --git a/crates/gpui_windows/Cargo.toml b/crates/gpui_windows/Cargo.toml index 992a47dce9e..ccad10bf54d 100644 --- a/crates/gpui_windows/Cargo.toml +++ b/crates/gpui_windows/Cargo.toml @@ -15,6 +15,11 @@ path = "src/gpui_windows.rs" default = ["gpui/default"] test-support = ["gpui/test-support"] screen-capture = ["gpui/screen-capture", "scap"] +# Enable compatibility workarounds for Windows Server 2016 / Windows 10 builds +# before 1809 (build 17763). Without this flag the crate behaves exactly like +# upstream: Factory6/Factory5 APIs, dual-source ClearType blending, standard +# MSAA quality sentinel, and no RDP/WDDM-1.x runtime detection. +win-legacy-compat = [] [dependencies] gpui.workspace = true diff --git a/crates/gpui_windows/build.rs b/crates/gpui_windows/build.rs index 1db95715e26..a1fb1a0694a 100644 --- a/crates/gpui_windows/build.rs +++ b/crates/gpui_windows/build.rs @@ -24,10 +24,20 @@ mod shader_compilation { let out_dir = std::env::var("OUT_DIR").unwrap(); println!("cargo:rerun-if-changed={}", shader_path.display()); + // Rerun when the feature flag changes so cached shader bytes are invalidated. + println!("cargo:rerun-if-env-changed=CARGO_FEATURE_WIN_LEGACY_COMPAT"); // Check if fxc.exe is available let fxc_path = find_fxc_compiler(); + // Propagate Cargo feature to HLSL preprocessor so #ifdef WIN_LEGACY_COMPAT works. + let extra_defines: Vec<&str> = + if std::env::var("CARGO_FEATURE_WIN_LEGACY_COMPAT").is_ok() { + vec!["WIN_LEGACY_COMPAT"] + } else { + vec![] + }; + // Define all modules let modules = [ "quad", @@ -52,6 +62,7 @@ mod shader_compilation { &fxc_path, shader_path.to_str().unwrap(), &rust_binding_path, + &extra_defines, ); } @@ -64,6 +75,7 @@ mod shader_compilation { &fxc_path, shader_path.to_str().unwrap(), &rust_binding_path, + &extra_defines, ); } } @@ -144,6 +156,7 @@ mod shader_compilation { fxc_path: &str, shader_path: &str, rust_binding_path: &str, + extra_defines: &[&str], ) { // Compile vertex shader let output_file = format!("{}/{}_vs.h", out_dir, module); @@ -155,6 +168,7 @@ mod shader_compilation { &const_name, shader_path, "vs_4_1", + extra_defines, ); generate_rust_binding(&const_name, &output_file, rust_binding_path); @@ -168,6 +182,7 @@ mod shader_compilation { &const_name, shader_path, "ps_4_1", + extra_defines, ); generate_rust_binding(&const_name, &output_file, rust_binding_path); } @@ -179,20 +194,23 @@ mod shader_compilation { var_name: &str, shader_path: &str, target: &str, + extra_defines: &[&str], ) { + // All options (including /D defines) must come before the input filename. + let mut args = vec![ + "/T", target, + "/E", entry_point, + "/Fh", output_path, + "/Vn", var_name, + "/O3", + ]; + for define in extra_defines { + args.push("/D"); + args.push(define); + } + args.push(shader_path); let output = Command::new(fxc_path) - .args([ - "/T", - target, - "/E", - entry_point, - "/Fh", - output_path, - "/Vn", - var_name, - "/O3", - shader_path, - ]) + .args(&args) .output(); match output { diff --git a/crates/gpui_windows/src/direct_write.rs b/crates/gpui_windows/src/direct_write.rs index 5cdd01f70af..aca15a0f977 100644 --- a/crates/gpui_windows/src/direct_write.rs +++ b/crates/gpui_windows/src/direct_write.rs @@ -26,6 +26,19 @@ use windows_numerics::Vector2; use crate::*; use gpui::*; +/// DWrite factory: IDWriteFactory5 on upstream builds (Win10 1703+ for in-memory font loader), +/// IDWriteFactory4 when `win-legacy-compat` is enabled (Win10 1607 / Server 2016+). +#[cfg(not(feature = "win-legacy-compat"))] +type DWriteFactory = IDWriteFactory5; +#[cfg(feature = "win-legacy-compat")] +type DWriteFactory = IDWriteFactory4; + +/// Font-set builder: IDWriteFontSetBuilder1 (upstream) vs IDWriteFontSetBuilder (legacy). +#[cfg(not(feature = "win-legacy-compat"))] +type DWriteFontSetBuilder = IDWriteFontSetBuilder1; +#[cfg(feature = "win-legacy-compat")] +type DWriteFontSetBuilder = IDWriteFontSetBuilder; + #[derive(Debug)] struct FontInfo { font_family_h: HSTRING, @@ -42,14 +55,16 @@ pub(crate) struct DirectWriteTextSystem { struct DirectWriteComponents { locale: HSTRING, - factory: IDWriteFactory5, + factory: DWriteFactory, + #[cfg(not(feature = "win-legacy-compat"))] in_memory_loader: IDWriteInMemoryFontFileLoader, - builder: IDWriteFontSetBuilder1, + builder: DWriteFontSetBuilder, text_renderer: TextRendererWrapper, system_ui_font_name: SharedString, system_subpixel_rendering: bool, } +#[cfg(not(feature = "win-legacy-compat"))] impl Drop for DirectWriteComponents { fn drop(&mut self) { unsafe { @@ -77,6 +92,8 @@ struct DirectWriteState { font_to_font_id: HashMap, font_info_cache: HashMap, layout_line_scratch: Vec, + #[cfg(feature = "win-legacy-compat")] + temp_font_files: Vec, } impl GPUState { @@ -164,11 +181,13 @@ impl GPUState { impl DirectWriteTextSystem { pub(crate) fn new(directx_devices: &DirectXDevices) -> Result { - let factory: IDWriteFactory5 = unsafe { DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED)? }; + let factory: DWriteFactory = unsafe { DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED)? }; // The `IDWriteInMemoryFontFileLoader` here is supported starting from // Windows 10 Creators Update, which consequently requires the entire // `DirectWriteTextSystem` to run on `win10 1703`+. + #[cfg(not(feature = "win-legacy-compat"))] let in_memory_loader = unsafe { factory.CreateInMemoryFontFileLoader()? }; + #[cfg(not(feature = "win-legacy-compat"))] unsafe { factory.RegisterFontFileLoader(&in_memory_loader)? }; let builder = unsafe { factory.CreateFontSetBuilder()? }; let mut locale = [0u16; LOCALE_NAME_MAX_LENGTH as usize]; @@ -183,6 +202,7 @@ impl DirectWriteTextSystem { let components = DirectWriteComponents { locale, factory, + #[cfg(not(feature = "win-legacy-compat"))] in_memory_loader, builder, text_renderer, @@ -214,6 +234,8 @@ impl DirectWriteTextSystem { font_to_font_id: HashMap::default(), font_info_cache: HashMap::default(), layout_line_scratch: Vec::new(), + #[cfg(feature = "win-legacy-compat")] + temp_font_files: Vec::new(), }), }) } @@ -347,6 +369,7 @@ impl DirectWriteState { Some(font_id) } + #[cfg(not(feature = "win-legacy-compat"))] fn add_fonts( &mut self, components: &DirectWriteComponents, @@ -385,9 +408,57 @@ impl DirectWriteState { Ok(()) } + #[cfg(feature = "win-legacy-compat")] + fn add_fonts( + &mut self, + components: &DirectWriteComponents, + fonts: Vec>, + ) -> Result<()> { + for font_data in fonts { + let mut temp_path = std::env::temp_dir(); + temp_path.push(format!( + "gpui_font_{}_{}.ttf", + std::process::id(), + self.temp_font_files.len() + )); + std::fs::write(&temp_path, font_data.as_ref())?; + unsafe { + let path_hstr = + HSTRING::from(temp_path.to_str().context("non-UTF8 temp font path")?); + let font_file = components.factory.CreateFontFileReference(&path_hstr, None)?; + let mut is_supported = BOOL(0); + let mut _file_type = DWRITE_FONT_FILE_TYPE::default(); + let mut face_count = 0u32; + font_file.Analyze( + &mut is_supported, + &mut _file_type, + None, + &mut face_count, + )?; + if is_supported.as_bool() { + for face_index in 0..face_count { + let face_ref = components.factory.CreateFontFaceReference2( + &path_hstr, + None, + face_index, + DWRITE_FONT_SIMULATIONS_NONE, + )?; + components.builder.AddFontFaceReference2(&face_ref)?; + } + } + } + self.temp_font_files.push(temp_path); + } + let set = unsafe { components.builder.CreateFontSet()? }; + let collection = unsafe { components.factory.CreateFontCollectionFromFontSet(&set)? }; + self.custom_font_collection = collection; + + Ok(()) + } + fn generate_font_fallbacks( fallbacks: &FontFallbacks, - factory: &IDWriteFactory5, + factory: &DWriteFactory, system_font_collection: &IDWriteFontCollection1, ) -> Result> { let fallback_list = fallbacks.fallback_list(); @@ -444,7 +515,7 @@ impl DirectWriteState { } unsafe fn generate_font_features( - factory: &IDWriteFactory5, + factory: &DWriteFactory, font_features: &FontFeatures, ) -> Result { let direct_write_features = unsafe { factory.CreateTypography()? }; @@ -461,7 +532,7 @@ impl DirectWriteState { style, }: &Font, collection: &IDWriteFontCollection1, - factory: &IDWriteFactory5, + factory: &DWriteFactory, system_font_collection: &IDWriteFontCollection1, system_ui_font_name: &SharedString, ) -> Option { @@ -1842,7 +1913,7 @@ fn get_system_ui_font_name() -> SharedString { fn is_color_glyph( font_face: &IDWriteFontFace3, glyph_id: GlyphId, - factory: &IDWriteFactory5, + factory: &DWriteFactory, ) -> bool { let glyph_run = DWRITE_GLYPH_RUN { fontFace: ManuallyDrop::new(Some(unsafe { std::ptr::read(&****font_face) })), diff --git a/crates/gpui_windows/src/directx_devices.rs b/crates/gpui_windows/src/directx_devices.rs index 882e404a56c..a342bce6b70 100644 --- a/crates/gpui_windows/src/directx_devices.rs +++ b/crates/gpui_windows/src/directx_devices.rs @@ -15,11 +15,20 @@ use windows::Win32::{ }, Dxgi::{ CreateDXGIFactory2, DXGI_CREATE_FACTORY_DEBUG, DXGI_CREATE_FACTORY_FLAGS, - IDXGIAdapter1, IDXGIFactory6, + IDXGIAdapter1, }, }, }; use windows::core::Interface; +#[cfg(not(feature = "win-legacy-compat"))] +use windows::Win32::Graphics::Dxgi::IDXGIFactory6; +#[cfg(feature = "win-legacy-compat")] +use windows::Win32::Graphics::Dxgi::IDXGIFactory2; + +#[cfg(not(feature = "win-legacy-compat"))] +pub(crate) type DxgiFactory = IDXGIFactory6; +#[cfg(feature = "win-legacy-compat")] +pub(crate) type DxgiFactory = IDXGIFactory2; pub(crate) fn try_to_recover_from_device_lost(mut f: impl FnMut() -> Result) -> Result { (0..5) @@ -38,7 +47,7 @@ pub(crate) fn try_to_recover_from_device_lost(mut f: impl FnMut() -> Result bool { } #[inline] -fn get_dxgi_factory(debug_layer_available: bool) -> Result { +fn get_dxgi_factory(debug_layer_available: bool) -> Result { let factory_flag = if debug_layer_available { DXGI_CREATE_FACTORY_DEBUG } else { @@ -104,7 +113,7 @@ fn get_dxgi_factory(debug_layer_available: bool) -> Result { #[inline] fn get_adapter( - dxgi_factory: &IDXGIFactory6, + dxgi_factory: &DxgiFactory, debug_layer_available: bool, ) -> Result<( IDXGIAdapter1, diff --git a/crates/gpui_windows/src/directx_renderer.rs b/crates/gpui_windows/src/directx_renderer.rs index b5c4d3bf34a..2b7189ae0c2 100644 --- a/crates/gpui_windows/src/directx_renderer.rs +++ b/crates/gpui_windows/src/directx_renderer.rs @@ -53,13 +53,20 @@ pub(crate) struct DirectXRenderer { /// In that case we want to discard the first frame that we draw as we got reset in the middle of a frame /// meaning we lost all the allocated gpu textures and scene resources. skip_draws: bool, + + /// True on Windows builds < 17763 (Server 2016 / Windows 10 pre-1809). + /// On these builds, D3D11_BUFFER_SRV FirstElement is unreliable on WDDM 1.x + /// drivers; each batch's data must be re-uploaded to buffer offset 0 and drawn + /// with first_instance=0 instead of relying on SRV range offsets. + #[cfg(feature = "win-legacy-compat")] + is_pre_1809: bool, } /// Direct3D objects #[derive(Clone)] pub(crate) struct DirectXRendererDevices { pub(crate) adapter: IDXGIAdapter1, - pub(crate) dxgi_factory: IDXGIFactory6, + pub(crate) dxgi_factory: DxgiFactory, pub(crate) device: ID3D11Device, pub(crate) device_context: ID3D11DeviceContext, dxgi_device: Option, @@ -148,6 +155,8 @@ impl DirectXRenderer { .context("Creating DirectX resources")?; let globals = DirectXGlobalElements::new(&devices.device) .context("Creating DirectX global elements")?; + #[cfg(feature = "win-legacy-compat")] + let is_pre_1809 = is_pre_1809_build(); let pipelines = DirectXRenderPipelines::new(&devices.device) .context("Creating DirectX render pipelines")?; @@ -174,6 +183,8 @@ impl DirectXRenderer { width: 1, height: 1, skip_draws: false, + #[cfg(feature = "win-legacy-compat")] + is_pre_1809, }) } @@ -272,6 +283,8 @@ impl DirectXRenderer { .context("Creating DirectX resources")?; let globals = DirectXGlobalElements::new(&devices.device) .context("Creating DirectXGlobalElements")?; + #[cfg(feature = "win-legacy-compat")] + let is_pre_1809 = is_pre_1809_build(); let pipelines = DirectXRenderPipelines::new(&devices.device) .context("Creating DirectXRenderPipelines")?; @@ -297,6 +310,8 @@ impl DirectXRenderer { self.globals = globals; self.pipelines = pipelines; self.direct_composition = direct_composition; + #[cfg(feature = "win-legacy-compat")] + { self.is_pre_1809 = is_pre_1809; } self.skip_draws = true; Ok(()) } @@ -320,23 +335,47 @@ impl DirectXRenderer { for batch in scene.batches() { match batch { + #[cfg(not(feature = "win-legacy-compat"))] PrimitiveBatch::Shadows(range) => self.draw_shadows(range.start, range.len()), + #[cfg(feature = "win-legacy-compat")] + PrimitiveBatch::Shadows(range) => self.draw_shadows(scene, range.start, range.len()), + #[cfg(not(feature = "win-legacy-compat"))] PrimitiveBatch::Quads(range) => self.draw_quads(range.start, range.len()), + #[cfg(feature = "win-legacy-compat")] + PrimitiveBatch::Quads(range) => self.draw_quads(scene, range.start, range.len()), PrimitiveBatch::Paths(range) => { let paths = &scene.paths[range]; self.draw_paths_to_intermediate(paths)?; self.draw_paths_from_intermediate(paths) } + #[cfg(not(feature = "win-legacy-compat"))] PrimitiveBatch::Underlines(range) => self.draw_underlines(range.start, range.len()), + #[cfg(feature = "win-legacy-compat")] + PrimitiveBatch::Underlines(range) => self.draw_underlines(scene, range.start, range.len()), + #[cfg(not(feature = "win-legacy-compat"))] PrimitiveBatch::MonochromeSprites { texture_id, range } => { self.draw_monochrome_sprites(texture_id, range.start, range.len()) } + #[cfg(feature = "win-legacy-compat")] + PrimitiveBatch::MonochromeSprites { texture_id, range } => { + self.draw_monochrome_sprites(scene, texture_id, range.start, range.len()) + } + #[cfg(not(feature = "win-legacy-compat"))] PrimitiveBatch::SubpixelSprites { texture_id, range } => { self.draw_subpixel_sprites(texture_id, range.start, range.len()) } + #[cfg(feature = "win-legacy-compat")] + PrimitiveBatch::SubpixelSprites { texture_id, range } => { + self.draw_subpixel_sprites(scene, texture_id, range.start, range.len()) + } + #[cfg(not(feature = "win-legacy-compat"))] PrimitiveBatch::PolychromeSprites { texture_id, range } => { self.draw_polychrome_sprites(texture_id, range.start, range.len()) } + #[cfg(feature = "win-legacy-compat")] + PrimitiveBatch::PolychromeSprites { texture_id, range } => { + self.draw_polychrome_sprites(scene, texture_id, range.start, range.len()) + } PrimitiveBatch::Surfaces(range) => self.draw_surfaces(&scene.surfaces[range]), } .context(format!( @@ -453,6 +492,7 @@ impl DirectXRenderer { Ok(()) } + #[cfg(not(feature = "win-legacy-compat"))] fn draw_shadows(&mut self, start: usize, len: usize) -> Result<()> { if len == 0 { return Ok(()); @@ -475,6 +515,33 @@ impl DirectXRenderer { ) } + #[cfg(feature = "win-legacy-compat")] + fn draw_shadows(&mut self, scene: &Scene, start: usize, len: usize) -> Result<()> { + if len == 0 { + return Ok(()); + } + let devices = self.devices.as_ref().context("devices missing")?; + if self.is_pre_1809 { + self.pipelines.shadow_pipeline.update_buffer(&devices.device, &devices.device_context, &scene.shadows[start..start + len])?; + } + self.pipelines.shadow_pipeline.draw_range( + &devices.device, + &devices.device_context, + slice::from_ref( + &self + .resources + .as_ref() + .context("resources missing")? + .viewport, + ), + slice::from_ref(&self.globals.global_params_buffer), + 4, + if !self.is_pre_1809 { start as u32 } else { 0 }, + len as u32, + ) + } + + #[cfg(not(feature = "win-legacy-compat"))] fn draw_quads(&mut self, start: usize, len: usize) -> Result<()> { if len == 0 { return Ok(()); @@ -497,6 +564,32 @@ impl DirectXRenderer { ) } + #[cfg(feature = "win-legacy-compat")] + fn draw_quads(&mut self, scene: &Scene, start: usize, len: usize) -> Result<()> { + if len == 0 { + return Ok(()); + } + let devices = self.devices.as_ref().context("devices missing")?; + if self.is_pre_1809 { + self.pipelines.quad_pipeline.update_buffer(&devices.device, &devices.device_context, &scene.quads[start..start + len])?; + } + self.pipelines.quad_pipeline.draw_range( + &devices.device, + &devices.device_context, + slice::from_ref( + &self + .resources + .as_ref() + .context("resources missing")? + .viewport, + ), + slice::from_ref(&self.globals.global_params_buffer), + 4, + if !self.is_pre_1809 { start as u32 } else { 0 }, + len as u32, + ) + } + fn draw_paths_to_intermediate(&mut self, paths: &[Path]) -> Result<()> { if paths.is_empty() { return Ok(()); @@ -608,6 +701,7 @@ impl DirectXRenderer { ) } + #[cfg(not(feature = "win-legacy-compat"))] fn draw_underlines(&mut self, start: usize, len: usize) -> Result<()> { if len == 0 { return Ok(()); @@ -625,6 +719,28 @@ impl DirectXRenderer { ) } + #[cfg(feature = "win-legacy-compat")] + fn draw_underlines(&mut self, scene: &Scene, start: usize, len: usize) -> Result<()> { + if len == 0 { + return Ok(()); + } + let devices = self.devices.as_ref().context("devices missing")?; + if self.is_pre_1809 { + self.pipelines.underline_pipeline.update_buffer(&devices.device, &devices.device_context, &scene.underlines[start..start + len])?; + } + let resources = self.resources.as_ref().context("resources missing")?; + self.pipelines.underline_pipeline.draw_range( + &devices.device, + &devices.device_context, + slice::from_ref(&resources.viewport), + slice::from_ref(&self.globals.global_params_buffer), + 4, + if !self.is_pre_1809 { start as u32 } else { 0 }, + len as u32, + ) + } + + #[cfg(not(feature = "win-legacy-compat"))] fn draw_monochrome_sprites( &mut self, texture_id: AtlasTextureId, @@ -649,6 +765,36 @@ impl DirectXRenderer { ) } + #[cfg(feature = "win-legacy-compat")] + fn draw_monochrome_sprites( + &mut self, + scene: &Scene, + texture_id: AtlasTextureId, + start: usize, + len: usize, + ) -> Result<()> { + if len == 0 { + return Ok(()); + } + let devices = self.devices.as_ref().context("devices missing")?; + if self.is_pre_1809 { + self.pipelines.mono_sprites.update_buffer(&devices.device, &devices.device_context, &scene.monochrome_sprites[start..start + len])?; + } + let resources = self.resources.as_ref().context("resources missing")?; + let texture_view = self.atlas.get_texture_view(texture_id); + self.pipelines.mono_sprites.draw_range_with_texture( + &devices.device, + &devices.device_context, + &texture_view, + slice::from_ref(&resources.viewport), + slice::from_ref(&self.globals.global_params_buffer), + slice::from_ref(&self.globals.sampler), + if !self.is_pre_1809 { start as u32 } else { 0 }, + len as u32, + ) + } + + #[cfg(not(feature = "win-legacy-compat"))] fn draw_subpixel_sprites( &mut self, texture_id: AtlasTextureId, @@ -673,6 +819,36 @@ impl DirectXRenderer { ) } + #[cfg(feature = "win-legacy-compat")] + fn draw_subpixel_sprites( + &mut self, + scene: &Scene, + texture_id: AtlasTextureId, + start: usize, + len: usize, + ) -> Result<()> { + if len == 0 { + return Ok(()); + } + let devices = self.devices.as_ref().context("devices missing")?; + if self.is_pre_1809 { + self.pipelines.subpixel_sprites.update_buffer(&devices.device, &devices.device_context, &scene.subpixel_sprites[start..start + len])?; + } + let resources = self.resources.as_ref().context("resources missing")?; + let texture_view = self.atlas.get_texture_view(texture_id); + self.pipelines.subpixel_sprites.draw_range_with_texture( + &devices.device, + &devices.device_context, + &texture_view, + slice::from_ref(&resources.viewport), + slice::from_ref(&self.globals.global_params_buffer), + slice::from_ref(&self.globals.sampler), + if !self.is_pre_1809 { start as u32 } else { 0 }, + len as u32, + ) + } + + #[cfg(not(feature = "win-legacy-compat"))] fn draw_polychrome_sprites( &mut self, texture_id: AtlasTextureId, @@ -697,6 +873,35 @@ impl DirectXRenderer { ) } + #[cfg(feature = "win-legacy-compat")] + fn draw_polychrome_sprites( + &mut self, + scene: &Scene, + texture_id: AtlasTextureId, + start: usize, + len: usize, + ) -> Result<()> { + if len == 0 { + return Ok(()); + } + let devices = self.devices.as_ref().context("devices missing")?; + if self.is_pre_1809 { + self.pipelines.poly_sprites.update_buffer(&devices.device, &devices.device_context, &scene.polychrome_sprites[start..start + len])?; + } + let resources = self.resources.as_ref().context("resources missing")?; + let texture_view = self.atlas.get_texture_view(texture_id); + self.pipelines.poly_sprites.draw_range_with_texture( + &devices.device, + &devices.device_context, + &texture_view, + slice::from_ref(&resources.viewport), + slice::from_ref(&self.globals.global_params_buffer), + slice::from_ref(&self.globals.sampler), + if !self.is_pre_1809 { start as u32 } else { 0 }, + len as u32, + ) + } + fn draw_surfaces(&mut self, surfaces: &[PaintSurface]) -> Result<()> { if surfaces.is_empty() { return Ok(()); @@ -737,7 +942,10 @@ impl DirectXRenderer { pub(crate) fn get_font_info() -> &'static FontInfo { static CACHED_FONT_INFO: OnceLock = OnceLock::new(); CACHED_FONT_INFO.get_or_init(|| unsafe { + #[cfg(not(feature = "win-legacy-compat"))] let factory: IDWriteFactory5 = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED).unwrap(); + #[cfg(feature = "win-legacy-compat")] + let factory: IDWriteFactory4 = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED).unwrap(); let render_params: IDWriteRenderingParams1 = factory.CreateRenderingParams().unwrap().cast().unwrap(); FontInfo { @@ -872,6 +1080,11 @@ impl DirectXRenderPipelines { "subpixel_sprite_pipeline", ShaderModule::SubpixelSprite, 512, + // win-legacy-compat: standard alpha blending (dual-source broken on WDDM 1.x). + // Upstream: dual-source (D3D11_BLEND_SRC1_COLOR) for per-channel ClearType. + #[cfg(feature = "win-legacy-compat")] + create_blend_state(device)?, + #[cfg(not(feature = "win-legacy-compat"))] create_blend_state_for_subpixel_rendering(device)?, )?; let poly_sprites = PipelineState::new( @@ -1080,6 +1293,7 @@ impl PipelineState { ); unsafe { device_context.PSSetSamplers(0, Some(sampler)); + #[cfg(not(feature = "win-legacy-compat"))] device_context.VSSetShaderResources(0, Some(texture)); device_context.PSSetShaderResources(0, Some(texture)); @@ -1177,7 +1391,7 @@ fn get_comp_device(dxgi_device: &IDXGIDevice) -> Result { } fn create_swap_chain_for_composition( - dxgi_factory: &IDXGIFactory6, + dxgi_factory: &DxgiFactory, device: &ID3D11Device, width: u32, height: u32, @@ -1203,7 +1417,7 @@ fn create_swap_chain_for_composition( } fn create_swap_chain( - dxgi_factory: &IDXGIFactory6, + dxgi_factory: &DxgiFactory, device: &ID3D11Device, hwnd: HWND, width: u32, @@ -1326,7 +1540,10 @@ fn create_path_intermediate_msaa_texture_and_view( Format: RENDER_TARGET_FORMAT, SampleDesc: DXGI_SAMPLE_DESC { Count: PATH_MULTISAMPLE_COUNT, + #[cfg(not(feature = "win-legacy-compat"))] Quality: D3D11_STANDARD_MULTISAMPLE_PATTERN.0 as u32, + #[cfg(feature = "win-legacy-compat")] + Quality: 0, // quality 0 is universally supported; D3D11_STANDARD_MULTISAMPLE_PATTERN requires D3D11.1 }, Usage: D3D11_USAGE_DEFAULT, BindFlags: D3D11_BIND_RENDER_TARGET.0 as u32, @@ -1398,6 +1615,7 @@ fn create_blend_state(device: &ID3D11Device) -> Result { } #[inline] +#[cfg(not(feature = "win-legacy-compat"))] fn create_blend_state_for_subpixel_rendering(device: &ID3D11Device) -> Result { let mut desc = D3D11_BLEND_DESC::default(); desc.RenderTarget[0].BlendEnable = true.into(); @@ -1587,6 +1805,7 @@ pub(crate) mod shader_resources { #[cfg(debug_assertions)] use windows::{ Win32::Graphics::Direct3D::{ + D3D_SHADER_MACRO, Fxc::{D3DCOMPILE_DEBUG, D3DCOMPILE_SKIP_OPTIMIZATION, D3DCompileFromFile}, ID3DBlob, }, @@ -1726,9 +1945,23 @@ pub(crate) mod shader_resources { D3D_COMPILE_STANDARD_FILE_INCLUDE as usize, ); + // Build a null-terminated D3D_SHADER_MACRO list for any active features. + #[cfg(feature = "win-legacy-compat")] + let defines_storage = [ + D3D_SHADER_MACRO { + Name: windows::core::s!("WIN_LEGACY_COMPAT"), + Definition: windows::core::s!("1"), + }, + D3D_SHADER_MACRO { Name: PCSTR::null(), Definition: PCSTR::null() }, + ]; + #[cfg(feature = "win-legacy-compat")] + let defines_ptr: Option<*const D3D_SHADER_MACRO> = Some(defines_storage.as_ptr()); + #[cfg(not(feature = "win-legacy-compat"))] + let defines_ptr: Option<*const D3D_SHADER_MACRO> = None; + let ret = D3DCompileFromFile( &HSTRING::from(shader_path.to_str().unwrap()), - None, + defines_ptr, include_handler, entry_point, target_cstr, @@ -1955,3 +2188,60 @@ mod dxgi { )) } } + +#[cfg(feature = "win-legacy-compat")] +/// Returns true on Windows builds before 17763 (Windows 10 1809 / Windows Server 2019). +/// +/// On these older builds, D3D11_BUFFER_SRV FirstElement and DrawInstanced +/// StartInstanceLocation are unreliable on WDDM 1.x virtual GPU drivers +/// (e.g. Windows Server 2016 RDP WARP). Re-uploading each batch at buffer +/// offset 0 and drawing with first_instance=0 works around the issue. +/// +/// Uses RtlGetVersion via ntdll.dll to bypass the GetVersionEx lie introduced +/// in Windows 8.1 (manifested applications always see 6.2 without this). +fn is_pre_1809_build() -> bool { + use std::mem; + use windows::Win32::System::LibraryLoader::{GetModuleHandleA, GetProcAddress}; + + #[repr(C)] + struct OsVersionInfoExW { + dw_size: u32, + dw_major_version: u32, + dw_minor_version: u32, + dw_build_number: u32, + dw_platform_id: u32, + sz_csd_version: [u16; 128], + w_service_pack_major: u16, + w_service_pack_minor: u16, + w_suite_mask: u16, + w_product_type: u8, + w_reserved: u8, + } + + unsafe { + let ntdll = GetModuleHandleA(windows::core::s!("ntdll.dll")); + let Ok(ntdll) = ntdll else { + return false; + }; + let proc = GetProcAddress(ntdll, windows::core::s!("RtlGetVersion")); + let Some(proc) = proc else { + return false; + }; + + type RtlGetVersionFn = unsafe extern "system" fn(*mut OsVersionInfoExW) -> i32; + let rtl_get_version: RtlGetVersionFn = mem::transmute(proc); + + let mut info: OsVersionInfoExW = mem::zeroed(); + info.dw_size = mem::size_of::() as u32; + rtl_get_version(&mut info); + + let is_pre_1809 = info.dw_build_number < 17763; + if is_pre_1809 { + log::info!( + "OS build {} < 17763: enabling WDDM 1.x SRV FirstElement workaround.", + info.dw_build_number + ); + } + is_pre_1809 + } +} diff --git a/crates/gpui_windows/src/platform.rs b/crates/gpui_windows/src/platform.rs index 7b76c4d12a8..a228457d65c 100644 --- a/crates/gpui_windows/src/platform.rs +++ b/crates/gpui_windows/src/platform.rs @@ -166,6 +166,9 @@ impl WindowsPlatform { let disable_direct_composition = std::env::var(DISABLE_DIRECT_COMPOSITION) .is_ok_and(|value| value == "true" || value == "1"); + #[cfg(feature = "win-legacy-compat")] + let disable_direct_composition = + disable_direct_composition || unsafe { GetSystemMetrics(SM_REMOTESESSION) != 0 }; let background_executor = BackgroundExecutor::new(dispatcher.clone()); let foreground_executor = ForegroundExecutor::new(dispatcher); diff --git a/crates/gpui_windows/src/shaders.hlsl b/crates/gpui_windows/src/shaders.hlsl index d40c7241bd0..d917acd8b02 100644 --- a/crates/gpui_windows/src/shaders.hlsl +++ b/crates/gpui_windows/src/shaders.hlsl @@ -1165,8 +1165,17 @@ SubpixelSpriteFragmentOutput subpixel_sprite_fragment(MonochromeSpriteFragmentIn float3 alpha_corrected = apply_contrast_and_gamma_correction3(sample, input.color.rgb, subpixel_enhanced_contrast, gamma_ratios); SubpixelSpriteFragmentOutput output; +#ifdef WIN_LEGACY_COMPAT + // Scalar alpha blending: collapses per-channel ClearType mask to a luminance + // scalar for use with standard SRC_ALPHA / INV_SRC_ALPHA blending. + // Required on pre-1809 WARP/virtual GPU where dual-source blending is broken. + float scalar_alpha = dot(alpha_corrected, float3(1.0f / 3.0f, 1.0f / 3.0f, 1.0f / 3.0f)); + output.foreground = float4(input.color.rgb, input.color.a * scalar_alpha); + output.alpha = float4(0.0f, 0.0f, 0.0f, 0.0f); +#else output.foreground = float4(input.color.rgb, 1.0f); output.alpha = float4(input.color.a * alpha_corrected, 1.0f); +#endif return output; }