This commit is contained in:
kevin-mai 2026-05-31 11:34:45 +03:00 committed by GitHub
commit 7bdf9e610e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 433 additions and 27 deletions

View file

@ -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"]

View file

@ -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

View file

@ -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 {

View file

@ -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, FontId>,
font_info_cache: HashMap<usize, FontId>,
layout_line_scratch: Vec<u16>,
#[cfg(feature = "win-legacy-compat")]
temp_font_files: Vec<std::path::PathBuf>,
}
impl GPUState {
@ -164,11 +181,13 @@ impl GPUState {
impl DirectWriteTextSystem {
pub(crate) fn new(directx_devices: &DirectXDevices) -> Result<Self> {
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<Cow<'static, [u8]>>,
) -> 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<Option<IDWriteFontFallback>> {
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<IDWriteTypography> {
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<FontInfo> {
@ -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) })),

View file

@ -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<T>(mut f: impl FnMut() -> Result<T>) -> Result<T> {
(0..5)
@ -38,7 +47,7 @@ pub(crate) fn try_to_recover_from_device_lost<T>(mut f: impl FnMut() -> Result<T
#[derive(Clone)]
pub(crate) struct DirectXDevices {
pub(crate) adapter: IDXGIAdapter1,
pub(crate) dxgi_factory: IDXGIFactory6,
pub(crate) dxgi_factory: DxgiFactory,
pub(crate) device: ID3D11Device,
pub(crate) device_context: ID3D11DeviceContext,
}
@ -89,7 +98,7 @@ fn check_debug_layer_available() -> bool {
}
#[inline]
fn get_dxgi_factory(debug_layer_available: bool) -> Result<IDXGIFactory6> {
fn get_dxgi_factory(debug_layer_available: bool) -> Result<DxgiFactory> {
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<IDXGIFactory6> {
#[inline]
fn get_adapter(
dxgi_factory: &IDXGIFactory6,
dxgi_factory: &DxgiFactory,
debug_layer_available: bool,
) -> Result<(
IDXGIAdapter1,

View file

@ -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<IDXGIDevice>,
@ -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<ScaledPixels>]) -> 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<FontInfo> = 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<T> PipelineState<T> {
);
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<IDCompositionDevice> {
}
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<ID3D11BlendState> {
}
#[inline]
#[cfg(not(feature = "win-legacy-compat"))]
fn create_blend_state_for_subpixel_rendering(device: &ID3D11Device) -> Result<ID3D11BlendState> {
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::<OsVersionInfoExW>() 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
}
}

View file

@ -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);

View file

@ -1190,8 +1190,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;
}