Add support for subpixel text rendering (#45423)
Subpixel text rendering is now implemented on Windows and Linux. Comparison screenshots: |Before|After| | ------------- | ------------- | | <img width="400" src="https://github.com/user-attachments/assets/9d720d2c-2ec4-4adf-a83f-7c2d81d30025" /> | <img width="400" src="https://github.com/user-attachments/assets/8fd7dc2a-8ca0-4f71-86cd-55460f568f7a" /> | Release Notes: - Added support for subpixel (ClearType-style) text rendering. This improves the legibility of text on standard DPI displays. Subpixel rendering is enabled by default on Windows and Linux and can be configured using the `text_rendering_mode` setting. --------- Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
This commit is contained in:
1 parent
797276bc73
commit
47d2694dcc
37 files changed
+820
-111
No files matched your search
Generated
+4
-6
@@ -2187,8 +2187,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "blade-graphics"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4deb8f595ce7f00dee3543ebf6fd9a20ea86fc421ab79600dac30876250bdae"
|
||||
source = "git+https://github.com/kvark/blade?rev=e3cf011ca18a6dfd907d1dedd93e85e21f005fe3#e3cf011ca18a6dfd907d1dedd93e85e21f005fe3"
|
||||
dependencies = [
|
||||
"ash",
|
||||
"ash-window",
|
||||
@@ -2222,8 +2221,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "blade-macros"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27142319e2f4c264581067eaccb9f80acccdde60d8b4bf57cc50cd3152f109ca"
|
||||
source = "git+https://github.com/kvark/blade?rev=e3cf011ca18a6dfd907d1dedd93e85e21f005fe3#e3cf011ca18a6dfd907d1dedd93e85e21f005fe3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2233,8 +2231,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "blade-util"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a6be3a82c001ba7a17b6f8e413ede5d1004e6047213f8efaf0ffc15b5c4904c"
|
||||
source = "git+https://github.com/kvark/blade?rev=e3cf011ca18a6dfd907d1dedd93e85e21f005fe3#e3cf011ca18a6dfd907d1dedd93e85e21f005fe3"
|
||||
dependencies = [
|
||||
"blade-graphics",
|
||||
"bytemuck",
|
||||
@@ -7392,6 +7389,7 @@ dependencies = [
|
||||
"stacksafe",
|
||||
"strum 0.27.2",
|
||||
"sum_tree",
|
||||
"swash",
|
||||
"taffy",
|
||||
"thiserror 2.0.17",
|
||||
"unicode-segmentation",
|
||||
|
||||
+3
-3
@@ -473,9 +473,9 @@ backtrace = "0.3"
|
||||
base64 = "0.22"
|
||||
bincode = "1.2.1"
|
||||
bitflags = "2.6.0"
|
||||
blade-graphics = { version = "0.7.0" }
|
||||
blade-macros = { version = "0.3.0" }
|
||||
blade-util = { version = "0.3.0" }
|
||||
blade-graphics = { git = "https://github.com/kvark/blade", rev = "e3cf011ca18a6dfd907d1dedd93e85e21f005fe3" }
|
||||
blade-macros = { git = "https://github.com/kvark/blade", rev = "e3cf011ca18a6dfd907d1dedd93e85e21f005fe3" }
|
||||
blade-util = { git = "https://github.com/kvark/blade", rev = "e3cf011ca18a6dfd907d1dedd93e85e21f005fe3" }
|
||||
brotli = "8.0.2"
|
||||
bytes = "1.0"
|
||||
cargo_metadata = "0.19"
|
||||
|
||||
@@ -169,6 +169,15 @@
|
||||
// 2. Always quit the application
|
||||
// "on_last_window_closed": "quit_app",
|
||||
"on_last_window_closed": "platform_default",
|
||||
// The text rendering mode to use.
|
||||
// May take 3 values:
|
||||
// 1. Use platform default behavior:
|
||||
// "text_rendering_mode": "platform_default"
|
||||
// 2. Use subpixel (ClearType-style) text rendering:
|
||||
// "text_rendering_mode": "subpixel"
|
||||
// 3. Use grayscale text rendering:
|
||||
// "text_rendering_mode": "grayscale"
|
||||
"text_rendering_mode": "platform_default",
|
||||
// Whether to show padding for zoomed panels.
|
||||
// When enabled, zoomed center panels (e.g. code editor) will have padding all around,
|
||||
// while zoomed bottom/left/right panels will have padding to the top/right/left (respectively).
|
||||
|
||||
@@ -183,6 +183,7 @@ blade-macros = { workspace = true, optional = true }
|
||||
blade-util = { workspace = true, optional = true }
|
||||
bytemuck = { version = "1", optional = true }
|
||||
cosmic-text = { version = "0.14.0", optional = true }
|
||||
swash = { version = "0.2.6" }
|
||||
# WARNING: If you change this, you must also publish a new version of zed-font-kit to crates.io
|
||||
font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "110523127440aefb11ce0cf280ae7c5071337ec5", package = "zed-font-kit", version = "0.14.1-zed", features = [
|
||||
"source-fontconfig-dlopen",
|
||||
|
||||
@@ -297,6 +297,7 @@ mod windows {
|
||||
"path_sprite",
|
||||
"underline",
|
||||
"monochrome_sprite",
|
||||
"subpixel_sprite",
|
||||
"polychrome_sprite",
|
||||
];
|
||||
|
||||
|
||||
+15
-3
@@ -1,6 +1,6 @@
|
||||
use std::{
|
||||
any::{TypeId, type_name},
|
||||
cell::{BorrowMutError, Ref, RefCell, RefMut},
|
||||
cell::{BorrowMutError, Cell, Ref, RefCell, RefMut},
|
||||
marker::PhantomData,
|
||||
mem,
|
||||
ops::{Deref, DerefMut},
|
||||
@@ -43,8 +43,8 @@ use crate::{
|
||||
PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper, Point, Priority,
|
||||
PromptBuilder, PromptButton, PromptHandle, PromptLevel, Render, RenderImage,
|
||||
RenderablePromptHandle, Reservation, ScreenCaptureSource, SharedString, SubscriberSet,
|
||||
Subscription, SvgRenderer, Task, TextSystem, Window, WindowAppearance, WindowHandle, WindowId,
|
||||
WindowInvalidator,
|
||||
Subscription, SvgRenderer, Task, TextRenderingMode, TextSystem, Window, WindowAppearance,
|
||||
WindowHandle, WindowId, WindowInvalidator,
|
||||
colors::{Colors, GlobalColors},
|
||||
current_platform, hash, init_app_menus,
|
||||
};
|
||||
@@ -637,6 +637,7 @@ pub struct App {
|
||||
pub(crate) inspector_element_registry: InspectorElementRegistry,
|
||||
#[cfg(any(test, feature = "test-support", debug_assertions))]
|
||||
pub(crate) name: Option<&'static str>,
|
||||
pub(crate) text_rendering_mode: Rc<Cell<TextRenderingMode>>,
|
||||
quit_mode: QuitMode,
|
||||
quitting: bool,
|
||||
}
|
||||
@@ -666,6 +667,7 @@ impl App {
|
||||
liveness: std::sync::Arc::new(()),
|
||||
platform: platform.clone(),
|
||||
text_system,
|
||||
text_rendering_mode: Rc::new(Cell::new(TextRenderingMode::default())),
|
||||
mode: GpuiMode::Production,
|
||||
actions: Rc::new(ActionRegistry::default()),
|
||||
flushing_effects: false,
|
||||
@@ -1088,6 +1090,16 @@ impl App {
|
||||
self.platform.read_from_clipboard()
|
||||
}
|
||||
|
||||
/// Sets the text rendering mode for the application.
|
||||
pub fn set_text_rendering_mode(&mut self, mode: TextRenderingMode) {
|
||||
self.text_rendering_mode.set(mode);
|
||||
}
|
||||
|
||||
/// Returns the current text rendering mode for the application.
|
||||
pub fn text_rendering_mode(&self) -> TextRenderingMode {
|
||||
self.text_rendering_mode.get()
|
||||
}
|
||||
|
||||
/// Writes data to the platform clipboard.
|
||||
pub fn write_to_clipboard(&self, item: ClipboardItem) {
|
||||
self.platform.write_to_clipboard(item)
|
||||
|
||||
@@ -499,6 +499,7 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
|
||||
fn activate(&self);
|
||||
fn is_active(&self) -> bool;
|
||||
fn is_hovered(&self) -> bool;
|
||||
fn background_appearance(&self) -> WindowBackgroundAppearance;
|
||||
fn set_title(&mut self, title: &str);
|
||||
fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance);
|
||||
fn minimize(&self);
|
||||
@@ -518,6 +519,7 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
|
||||
fn draw(&self, scene: &Scene);
|
||||
fn completed_frame(&self) {}
|
||||
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
|
||||
fn is_subpixel_rendering_supported(&self) -> bool;
|
||||
|
||||
// macOS specific methods
|
||||
fn get_title(&self) -> String {
|
||||
@@ -654,6 +656,8 @@ pub(crate) trait PlatformTextSystem: Send + Sync {
|
||||
raster_bounds: Bounds<DevicePixels>,
|
||||
) -> Result<(Size<DevicePixels>, Vec<u8>)>;
|
||||
fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
|
||||
fn recommended_rendering_mode(&self, _font_id: FontId, _font_size: Pixels)
|
||||
-> TextRenderingMode;
|
||||
}
|
||||
|
||||
pub(crate) struct NoopTextSystem;
|
||||
@@ -774,6 +778,14 @@ impl PlatformTextSystem for NoopTextSystem {
|
||||
len: text.len(),
|
||||
}
|
||||
}
|
||||
|
||||
fn recommended_rendering_mode(
|
||||
&self,
|
||||
_font_id: FontId,
|
||||
_font_size: Pixels,
|
||||
) -> TextRenderingMode {
|
||||
TextRenderingMode::Grayscale
|
||||
}
|
||||
}
|
||||
|
||||
// Adapted from https://github.com/microsoft/terminal/blob/1283c0f5b99a2961673249fa77c6b986efb5086c/src/renderer/atlas/dwrite.cpp
|
||||
@@ -831,6 +843,8 @@ impl AtlasKey {
|
||||
AtlasKey::Glyph(params) => {
|
||||
if params.is_emoji {
|
||||
AtlasTextureKind::Polychrome
|
||||
} else if params.subpixel_rendering {
|
||||
AtlasTextureKind::Subpixel
|
||||
} else {
|
||||
AtlasTextureKind::Monochrome
|
||||
}
|
||||
@@ -932,6 +946,7 @@ pub(crate) struct AtlasTextureId {
|
||||
pub(crate) enum AtlasTextureKind {
|
||||
Monochrome = 0,
|
||||
Polychrome = 1,
|
||||
Subpixel = 2,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
@@ -1443,6 +1458,18 @@ pub enum WindowBackgroundAppearance {
|
||||
MicaAltBackdrop,
|
||||
}
|
||||
|
||||
/// The text rendering mode to use for drawing glyphs.
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub enum TextRenderingMode {
|
||||
/// Use the platform's default text rendering mode.
|
||||
#[default]
|
||||
PlatformDefault,
|
||||
/// Use subpixel (ClearType-style) text rendering.
|
||||
Subpixel,
|
||||
/// Use grayscale text rendering.
|
||||
Grayscale,
|
||||
}
|
||||
|
||||
/// The options that can be configured for a file dialog prompt
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PathPromptOptions {
|
||||
|
||||
@@ -162,6 +162,10 @@ impl BladeAtlasState {
|
||||
format = gpu::TextureFormat::R8Unorm;
|
||||
usage = gpu::TextureUsage::COPY | gpu::TextureUsage::RESOURCE;
|
||||
}
|
||||
AtlasTextureKind::Subpixel => {
|
||||
format = gpu::TextureFormat::Bgra8Unorm;
|
||||
usage = gpu::TextureUsage::COPY | gpu::TextureUsage::RESOURCE;
|
||||
}
|
||||
AtlasTextureKind::Polychrome => {
|
||||
format = gpu::TextureFormat::Bgra8Unorm;
|
||||
usage = gpu::TextureUsage::COPY | gpu::TextureUsage::RESOURCE;
|
||||
@@ -263,6 +267,7 @@ impl BladeAtlasState {
|
||||
#[derive(Default)]
|
||||
struct BladeAtlasStorage {
|
||||
monochrome_textures: AtlasTextureList<BladeAtlasTexture>,
|
||||
subpixel_textures: AtlasTextureList<BladeAtlasTexture>,
|
||||
polychrome_textures: AtlasTextureList<BladeAtlasTexture>,
|
||||
}
|
||||
|
||||
@@ -271,6 +276,7 @@ impl ops::Index<AtlasTextureKind> for BladeAtlasStorage {
|
||||
fn index(&self, kind: AtlasTextureKind) -> &Self::Output {
|
||||
match kind {
|
||||
crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
|
||||
crate::AtlasTextureKind::Subpixel => &self.subpixel_textures,
|
||||
crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
|
||||
}
|
||||
}
|
||||
@@ -280,6 +286,7 @@ impl ops::IndexMut<AtlasTextureKind> for BladeAtlasStorage {
|
||||
fn index_mut(&mut self, kind: AtlasTextureKind) -> &mut Self::Output {
|
||||
match kind {
|
||||
crate::AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
|
||||
crate::AtlasTextureKind::Subpixel => &mut self.subpixel_textures,
|
||||
crate::AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
|
||||
}
|
||||
}
|
||||
@@ -290,6 +297,7 @@ impl ops::Index<AtlasTextureId> for BladeAtlasStorage {
|
||||
fn index(&self, id: AtlasTextureId) -> &Self::Output {
|
||||
let textures = match id.kind {
|
||||
crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
|
||||
crate::AtlasTextureKind::Subpixel => &self.subpixel_textures,
|
||||
crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
|
||||
};
|
||||
textures[id.index as usize].as_ref().unwrap()
|
||||
@@ -301,6 +309,9 @@ impl BladeAtlasStorage {
|
||||
for mut texture in self.monochrome_textures.drain().flatten() {
|
||||
texture.destroy(gpu);
|
||||
}
|
||||
for mut texture in self.subpixel_textures.drain().flatten() {
|
||||
texture.destroy(gpu);
|
||||
}
|
||||
for mut texture in self.polychrome_textures.drain().flatten() {
|
||||
texture.destroy(gpu);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,11 @@ impl BladeContext {
|
||||
);
|
||||
Ok(Self { gpu })
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn supports_dual_source_blending(&self) -> bool {
|
||||
self.gpu.capabilities().dual_source_blending
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_pci_id(id: &str) -> anyhow::Result<u32> {
|
||||
|
||||
@@ -95,6 +95,16 @@ struct ShaderMonoSpritesData {
|
||||
b_mono_sprites: gpu::BufferPiece,
|
||||
}
|
||||
|
||||
#[derive(blade_macros::ShaderData)]
|
||||
struct ShaderSubpixelSpritesData {
|
||||
globals: GlobalParams,
|
||||
gamma_ratios: [f32; 4],
|
||||
subpixel_enhanced_contrast: f32,
|
||||
t_sprite: gpu::TextureView,
|
||||
s_sprite: gpu::Sampler,
|
||||
b_subpixel_sprites: gpu::BufferPiece,
|
||||
}
|
||||
|
||||
#[derive(blade_macros::ShaderData)]
|
||||
struct ShaderPolySpritesData {
|
||||
globals: GlobalParams,
|
||||
@@ -134,6 +144,7 @@ struct BladePipelines {
|
||||
paths: gpu::RenderPipeline,
|
||||
underlines: gpu::RenderPipeline,
|
||||
mono_sprites: gpu::RenderPipeline,
|
||||
subpixel_sprites: gpu::RenderPipeline,
|
||||
poly_sprites: gpu::RenderPipeline,
|
||||
surfaces: gpu::RenderPipeline,
|
||||
}
|
||||
@@ -277,6 +288,31 @@ impl BladePipelines {
|
||||
color_targets,
|
||||
multisample_state: gpu::MultisampleState::default(),
|
||||
}),
|
||||
subpixel_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
|
||||
name: "subpixel-sprites",
|
||||
data_layouts: &[&ShaderSubpixelSpritesData::layout()],
|
||||
vertex: shader.at("vs_subpixel_sprite"),
|
||||
vertex_fetches: &[],
|
||||
primitive: gpu::PrimitiveState {
|
||||
topology: gpu::PrimitiveTopology::TriangleStrip,
|
||||
..Default::default()
|
||||
},
|
||||
depth_stencil: None,
|
||||
fragment: Some(shader.at("fs_subpixel_sprite")),
|
||||
color_targets: &[gpu::ColorTargetState {
|
||||
format: surface_info.format,
|
||||
blend: Some(gpu::BlendState {
|
||||
color: gpu::BlendComponent {
|
||||
src_factor: gpu::BlendFactor::Src1,
|
||||
dst_factor: gpu::BlendFactor::OneMinusSrc1,
|
||||
operation: gpu::BlendOperation::Add,
|
||||
},
|
||||
alpha: gpu::BlendComponent::OVER,
|
||||
}),
|
||||
write_mask: gpu::ColorWrites::COLOR,
|
||||
}],
|
||||
multisample_state: gpu::MultisampleState::default(),
|
||||
}),
|
||||
poly_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
|
||||
name: "poly-sprites",
|
||||
data_layouts: &[&ShaderPolySpritesData::layout()],
|
||||
@@ -315,6 +351,7 @@ impl BladePipelines {
|
||||
gpu.destroy_render_pipeline(&mut self.paths);
|
||||
gpu.destroy_render_pipeline(&mut self.underlines);
|
||||
gpu.destroy_render_pipeline(&mut self.mono_sprites);
|
||||
gpu.destroy_render_pipeline(&mut self.subpixel_sprites);
|
||||
gpu.destroy_render_pipeline(&mut self.poly_sprites);
|
||||
gpu.destroy_render_pipeline(&mut self.surfaces);
|
||||
}
|
||||
@@ -672,7 +709,11 @@ impl BladeRenderer {
|
||||
gpu::RenderTargetSet {
|
||||
colors: &[gpu::RenderTarget {
|
||||
view: frame.texture_view(),
|
||||
init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
|
||||
init_op: gpu::InitOp::Clear(if self.surface_config.transparent {
|
||||
gpu::TextureColor::TransparentBlack
|
||||
} else {
|
||||
gpu::TextureColor::White
|
||||
}),
|
||||
finish_op: gpu::FinishOp::Store,
|
||||
}],
|
||||
depth_stencil: None,
|
||||
@@ -818,6 +859,29 @@ impl BladeRenderer {
|
||||
);
|
||||
encoder.draw(0, 4, 0, sprites.len() as u32);
|
||||
}
|
||||
PrimitiveBatch::SubpixelSprites {
|
||||
texture_id,
|
||||
sprites,
|
||||
} => {
|
||||
let tex_info = self.atlas.get_texture_info(texture_id);
|
||||
let instance_buf =
|
||||
unsafe { self.instance_belt.alloc_typed(sprites, &self.gpu) };
|
||||
let mut encoder = pass.with(&self.pipelines.subpixel_sprites);
|
||||
encoder.bind(
|
||||
0,
|
||||
&ShaderSubpixelSpritesData {
|
||||
globals,
|
||||
gamma_ratios: self.rendering_parameters.gamma_ratios,
|
||||
subpixel_enhanced_contrast: self
|
||||
.rendering_parameters
|
||||
.subpixel_enhanced_contrast,
|
||||
t_sprite: tex_info.raw_view,
|
||||
s_sprite: self.atlas_sampler,
|
||||
b_subpixel_sprites: instance_buf,
|
||||
},
|
||||
);
|
||||
encoder.draw(0, 4, 0, sprites.len() as u32);
|
||||
}
|
||||
PrimitiveBatch::Surfaces(surfaces) => {
|
||||
let mut _encoder = pass.with(&self.pipelines.surfaces);
|
||||
|
||||
@@ -1016,6 +1080,10 @@ struct RenderingParameters {
|
||||
// Allowed range: [0.0, ..), other values are clipped
|
||||
// Default: 1.0
|
||||
grayscale_enhanced_contrast: f32,
|
||||
// Env var: ZED_FONTS_SUBPIXEL_ENHANCED_CONTRAST
|
||||
// Allowed range: [0.0, ..), other values are clipped
|
||||
// Default: 0.5
|
||||
subpixel_enhanced_contrast: f32,
|
||||
}
|
||||
|
||||
impl RenderingParameters {
|
||||
@@ -1042,11 +1110,17 @@ impl RenderingParameters {
|
||||
.and_then(|v| v.parse().ok())
|
||||
.unwrap_or(1.0_f32)
|
||||
.max(0.0);
|
||||
let subpixel_enhanced_contrast = env::var("ZED_FONTS_SUBPIXEL_ENHANCED_CONTRAST")
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok())
|
||||
.unwrap_or(0.5_f32)
|
||||
.max(0.0);
|
||||
|
||||
Self {
|
||||
path_sample_count,
|
||||
gamma_ratios,
|
||||
grayscale_enhanced_contrast,
|
||||
subpixel_enhanced_contrast,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
enable dual_source_blending;
|
||||
/* Functions useful for debugging:
|
||||
|
||||
// A heat map color for debugging (blue -> cyan -> green -> yellow -> red).
|
||||
@@ -46,12 +47,22 @@ fn enhance_contrast(alpha: f32, k: f32) -> f32 {
|
||||
return alpha * (k + 1.0) / (alpha * k + 1.0);
|
||||
}
|
||||
|
||||
fn enhance_contrast3(alpha: vec3<f32>, k: f32) -> vec3<f32> {
|
||||
return alpha * (k + 1.0) / (alpha * k + 1.0);
|
||||
}
|
||||
|
||||
fn apply_alpha_correction(a: f32, b: f32, g: vec4<f32>) -> f32 {
|
||||
let brightness_adjustment = g.x * b + g.y;
|
||||
let correction = brightness_adjustment * a + (g.z * b + g.w);
|
||||
return a + a * (1.0 - a) * correction;
|
||||
}
|
||||
|
||||
fn apply_alpha_correction3(a: vec3<f32>, b: vec3<f32>, g: vec4<f32>) -> vec3<f32> {
|
||||
let brightness_adjustment = g.x * b + g.y;
|
||||
let correction = brightness_adjustment * a + (g.z * b + g.w);
|
||||
return a + a * (1.0 - a) * correction;
|
||||
}
|
||||
|
||||
fn apply_contrast_and_gamma_correction(sample: f32, color: vec3<f32>, enhanced_contrast_factor: f32, gamma_ratios: vec4<f32>) -> f32 {
|
||||
let enhanced_contrast = light_on_dark_contrast(enhanced_contrast_factor, color);
|
||||
let brightness = color_brightness(color);
|
||||
@@ -60,6 +71,13 @@ fn apply_contrast_and_gamma_correction(sample: f32, color: vec3<f32>, enhanced_c
|
||||
return apply_alpha_correction(contrasted, brightness, gamma_ratios);
|
||||
}
|
||||
|
||||
fn apply_contrast_and_gamma_correction3(sample: vec3<f32>, color: vec3<f32>, enhanced_contrast_factor: f32, gamma_ratios: vec4<f32>) -> vec3<f32> {
|
||||
let enhanced_contrast = light_on_dark_contrast(enhanced_contrast_factor, color);
|
||||
|
||||
let contrasted = enhance_contrast3(sample, enhanced_contrast);
|
||||
return apply_alpha_correction3(contrasted, color, gamma_ratios);
|
||||
}
|
||||
|
||||
struct GlobalParams {
|
||||
viewport_size: vec2<f32>,
|
||||
premultiplied_alpha: u32,
|
||||
@@ -69,6 +87,7 @@ struct GlobalParams {
|
||||
var<uniform> globals: GlobalParams;
|
||||
var<uniform> gamma_ratios: vec4<f32>;
|
||||
var<uniform> grayscale_enhanced_contrast: f32;
|
||||
var<uniform> subpixel_enhanced_contrast: f32;
|
||||
var t_sprite: texture_2d<f32>;
|
||||
var s_sprite: sampler;
|
||||
|
||||
@@ -1190,7 +1209,6 @@ fn fs_mono_sprite(input: MonoSpriteVarying) -> @location(0) vec4<f32> {
|
||||
return vec4<f32>(0.0);
|
||||
}
|
||||
|
||||
// convert to srgb space as the rest of the code (output swapchain) expects that
|
||||
return blend_color(input.color, alpha_corrected);
|
||||
}
|
||||
|
||||
@@ -1297,3 +1315,57 @@ fn fs_surface(input: SurfaceVarying) -> @location(0) vec4<f32> {
|
||||
|
||||
return ycbcr_to_RGB * y_cb_cr;
|
||||
}
|
||||
|
||||
// --- subpixel sprites --- //
|
||||
|
||||
struct SubpixelSprite {
|
||||
order: u32,
|
||||
pad: u32,
|
||||
bounds: Bounds,
|
||||
content_mask: Bounds,
|
||||
color: Hsla,
|
||||
tile: AtlasTile,
|
||||
transformation: TransformationMatrix,
|
||||
}
|
||||
var<storage, read> b_subpixel_sprites: array<SubpixelSprite>;
|
||||
|
||||
struct SubpixelSpriteOutput {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) tile_position: vec2<f32>,
|
||||
@location(1) @interpolate(flat) color: vec4<f32>,
|
||||
@location(3) clip_distances: vec4<f32>,
|
||||
}
|
||||
|
||||
struct SubpixelSpriteFragmentOutput {
|
||||
@location(0) @blend_src(0) foreground: vec4<f32>,
|
||||
@location(0) @blend_src(1) alpha: vec4<f32>,
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn vs_subpixel_sprite(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) instance_id: u32) -> SubpixelSpriteOutput {
|
||||
let unit_vertex = vec2<f32>(f32(vertex_id & 1u), 0.5 * f32(vertex_id & 2u));
|
||||
let sprite = b_subpixel_sprites[instance_id];
|
||||
|
||||
var out = SubpixelSpriteOutput();
|
||||
out.position = to_device_position_transformed(unit_vertex, sprite.bounds, sprite.transformation);
|
||||
out.tile_position = to_tile_position(unit_vertex, sprite.tile);
|
||||
out.color = hsla_to_rgba(sprite.color);
|
||||
out.clip_distances = distance_from_clip_rect_transformed(unit_vertex, sprite.bounds, sprite.content_mask, sprite.transformation);
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_subpixel_sprite(input: SubpixelSpriteOutput) -> SubpixelSpriteFragmentOutput {
|
||||
let sample = textureSample(t_sprite, s_sprite, input.tile_position).rgb;
|
||||
let alpha_corrected = apply_contrast_and_gamma_correction3(sample, input.color.rgb, subpixel_enhanced_contrast, gamma_ratios);
|
||||
|
||||
// Alpha clip after using the derivatives.
|
||||
if (any(input.clip_distances < vec4<f32>(0.0))) {
|
||||
return SubpixelSpriteFragmentOutput(vec4<f32>(0.0), vec4<f32>(0.0));
|
||||
}
|
||||
|
||||
var out = SubpixelSpriteFragmentOutput();
|
||||
out.foreground = vec4<f32>(input.color.rgb, 1.0);
|
||||
out.alpha = vec4<f32>(input.color.a * alpha_corrected, 1.0);
|
||||
return out;
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
use crate::{
|
||||
Bounds, DevicePixels, Font, FontFeatures, FontId, FontMetrics, FontRun, FontStyle, FontWeight,
|
||||
GlyphId, LineLayout, Pixels, PlatformTextSystem, Point, RenderGlyphParams, SUBPIXEL_VARIANTS_X,
|
||||
SUBPIXEL_VARIANTS_Y, ShapedGlyph, ShapedRun, SharedString, Size, point, size,
|
||||
SUBPIXEL_VARIANTS_Y, ShapedGlyph, ShapedRun, SharedString, Size, TextRenderingMode, point,
|
||||
size,
|
||||
};
|
||||
use anyhow::{Context as _, Ok, Result};
|
||||
use collections::HashMap;
|
||||
use cosmic_text::{
|
||||
Attrs, AttrsList, CacheKey, Family, Font as CosmicTextFont, FontFeatures as CosmicFontFeatures,
|
||||
FontSystem, ShapeBuffer, ShapeLine, SwashCache,
|
||||
Attrs, AttrsList, Family, Font as CosmicTextFont, FontFeatures as CosmicFontFeatures,
|
||||
FontSystem, ShapeBuffer, ShapeLine,
|
||||
};
|
||||
|
||||
use itertools::Itertools;
|
||||
@@ -18,6 +19,10 @@ use pathfinder_geometry::{
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
use std::{borrow::Cow, sync::Arc};
|
||||
use swash::{
|
||||
scale::{Render, ScaleContext, Source, StrikeWith},
|
||||
zeno::{Format, Transform, Vector},
|
||||
};
|
||||
|
||||
pub(crate) struct CosmicTextSystem(RwLock<CosmicTextSystemState>);
|
||||
|
||||
@@ -34,9 +39,9 @@ impl FontKey {
|
||||
}
|
||||
|
||||
struct CosmicTextSystemState {
|
||||
swash_cache: SwashCache,
|
||||
font_system: FontSystem,
|
||||
scratch: ShapeBuffer,
|
||||
swash_scale_context: ScaleContext,
|
||||
/// Contains all already loaded fonts, including all faces. Indexed by `FontId`.
|
||||
loaded_fonts: Vec<LoadedFont>,
|
||||
/// Caches the `FontId`s associated with a specific family to avoid iterating the font database
|
||||
@@ -57,8 +62,8 @@ impl CosmicTextSystem {
|
||||
|
||||
Self(RwLock::new(CosmicTextSystemState {
|
||||
font_system,
|
||||
swash_cache: SwashCache::new(),
|
||||
scratch: ShapeBuffer::default(),
|
||||
swash_scale_context: ScaleContext::new(),
|
||||
loaded_fonts: Vec::new(),
|
||||
font_ids_by_family_cache: HashMap::default(),
|
||||
}))
|
||||
@@ -183,6 +188,15 @@ impl PlatformTextSystem for CosmicTextSystem {
|
||||
fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout {
|
||||
self.0.write().layout_line(text, font_size, runs)
|
||||
}
|
||||
|
||||
fn recommended_rendering_mode(
|
||||
&self,
|
||||
_font_id: FontId,
|
||||
_font_size: Pixels,
|
||||
) -> TextRenderingMode {
|
||||
// Ideally, we'd use fontconfig to read the user preference.
|
||||
TextRenderingMode::Subpixel
|
||||
}
|
||||
}
|
||||
|
||||
impl CosmicTextSystemState {
|
||||
@@ -273,26 +287,7 @@ impl CosmicTextSystemState {
|
||||
}
|
||||
|
||||
fn raster_bounds(&mut self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
|
||||
let font = &self.loaded_fonts[params.font_id.0].font;
|
||||
let subpixel_shift = point(
|
||||
params.subpixel_variant.x as f32 / SUBPIXEL_VARIANTS_X as f32 / params.scale_factor,
|
||||
params.subpixel_variant.y as f32 / SUBPIXEL_VARIANTS_Y as f32 / params.scale_factor,
|
||||
);
|
||||
let image = self
|
||||
.swash_cache
|
||||
.get_image(
|
||||
&mut self.font_system,
|
||||
CacheKey::new(
|
||||
font.id(),
|
||||
params.glyph_id.0 as u16,
|
||||
(params.font_size * params.scale_factor).into(),
|
||||
(subpixel_shift.x, subpixel_shift.y.trunc()),
|
||||
cosmic_text::CacheKeyFlags::empty(),
|
||||
)
|
||||
.0,
|
||||
)
|
||||
.clone()
|
||||
.with_context(|| format!("no image for {params:?} in font {font:?}"))?;
|
||||
let image = self.render_glyph_image(params)?;
|
||||
Ok(Bounds {
|
||||
origin: point(image.placement.left.into(), (-image.placement.top).into()),
|
||||
size: size(image.placement.width.into(), image.placement.height.into()),
|
||||
@@ -307,40 +302,77 @@ impl CosmicTextSystemState {
|
||||
) -> Result<(Size<DevicePixels>, Vec<u8>)> {
|
||||
if glyph_bounds.size.width.0 == 0 || glyph_bounds.size.height.0 == 0 {
|
||||
anyhow::bail!("glyph bounds are empty");
|
||||
} else {
|
||||
let bitmap_size = glyph_bounds.size;
|
||||
let font = &self.loaded_fonts[params.font_id.0].font;
|
||||
let subpixel_shift = point(
|
||||
params.subpixel_variant.x as f32 / SUBPIXEL_VARIANTS_X as f32 / params.scale_factor,
|
||||
params.subpixel_variant.y as f32 / SUBPIXEL_VARIANTS_Y as f32 / params.scale_factor,
|
||||
);
|
||||
let mut image = self
|
||||
.swash_cache
|
||||
.get_image(
|
||||
&mut self.font_system,
|
||||
CacheKey::new(
|
||||
font.id(),
|
||||
params.glyph_id.0 as u16,
|
||||
(params.font_size * params.scale_factor).into(),
|
||||
(subpixel_shift.x, subpixel_shift.y.trunc()),
|
||||
cosmic_text::CacheKeyFlags::empty(),
|
||||
)
|
||||
.0,
|
||||
)
|
||||
.clone()
|
||||
.with_context(|| format!("no image for {params:?} in font {font:?}"))?;
|
||||
}
|
||||
|
||||
if params.is_emoji {
|
||||
let mut image = self.render_glyph_image(params)?;
|
||||
let bitmap_size = glyph_bounds.size;
|
||||
match image.content {
|
||||
swash::scale::image::Content::Color | swash::scale::image::Content::SubpixelMask => {
|
||||
// Convert from RGBA to BGRA.
|
||||
for pixel in image.data.chunks_exact_mut(4) {
|
||||
pixel.swap(0, 2);
|
||||
}
|
||||
Ok((bitmap_size, image.data))
|
||||
}
|
||||
|
||||
Ok((bitmap_size, image.data))
|
||||
swash::scale::image::Content::Mask => Ok((bitmap_size, image.data)),
|
||||
}
|
||||
}
|
||||
|
||||
fn render_glyph_image(
|
||||
&mut self,
|
||||
params: &RenderGlyphParams,
|
||||
) -> Result<swash::scale::image::Image> {
|
||||
let loaded_font = &self.loaded_fonts[params.font_id.0];
|
||||
let font_ref = loaded_font.font.as_swash();
|
||||
let pixel_size = params.font_size.0;
|
||||
|
||||
let subpixel_offset = Vector::new(
|
||||
params.subpixel_variant.x as f32 / SUBPIXEL_VARIANTS_X as f32 / params.scale_factor,
|
||||
params.subpixel_variant.y as f32 / SUBPIXEL_VARIANTS_Y as f32 / params.scale_factor,
|
||||
);
|
||||
|
||||
let mut scaler = self
|
||||
.swash_scale_context
|
||||
.builder(font_ref)
|
||||
.size(pixel_size)
|
||||
.hint(true)
|
||||
.build();
|
||||
|
||||
let sources: &[Source] = if params.is_emoji {
|
||||
&[
|
||||
Source::ColorOutline(0),
|
||||
Source::ColorBitmap(StrikeWith::BestFit),
|
||||
Source::Outline,
|
||||
]
|
||||
} else {
|
||||
&[Source::Outline]
|
||||
};
|
||||
|
||||
let mut renderer = Render::new(sources);
|
||||
renderer.transform(Some(Transform {
|
||||
xx: params.scale_factor,
|
||||
xy: 0.0,
|
||||
yx: 0.0,
|
||||
yy: params.scale_factor,
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
}));
|
||||
|
||||
if params.subpixel_rendering {
|
||||
// There seems to be a bug in Swash where the B and R values are swapped.
|
||||
renderer
|
||||
.format(Format::subpixel_bgra())
|
||||
.offset(subpixel_offset);
|
||||
} else {
|
||||
renderer.format(Format::Alpha).offset(subpixel_offset);
|
||||
}
|
||||
|
||||
let glyph_id: u16 = params.glyph_id.0.try_into()?;
|
||||
renderer
|
||||
.render(&mut scaler, glyph_id)
|
||||
.with_context(|| format!("unable to render glyph via swash for {params:?}"))
|
||||
}
|
||||
|
||||
/// This is used when cosmic_text has chosen a fallback font instead of using the requested
|
||||
/// font, typically to handle some unicode characters. When this happens, `loaded_fonts` may not
|
||||
/// yet have an entry for this fallback font, and so one is added.
|
||||
|
||||
@@ -204,7 +204,7 @@ pub struct Output {
|
||||
pub(crate) struct WaylandClientState {
|
||||
serial_tracker: SerialTracker,
|
||||
globals: Globals,
|
||||
gpu_context: BladeContext,
|
||||
pub gpu_context: BladeContext,
|
||||
wl_seat: wl_seat::WlSeat, // TODO: Multi seat support
|
||||
wl_pointer: Option<wl_pointer::WlPointer>,
|
||||
wl_keyboard: Option<wl_keyboard::WlKeyboard>,
|
||||
@@ -247,7 +247,7 @@ pub(crate) struct WaylandClientState {
|
||||
cursor: Cursor,
|
||||
pending_activation: Option<PendingActivation>,
|
||||
event_loop: Option<EventLoop<'static, WaylandClientStatePtr>>,
|
||||
common: LinuxCommon,
|
||||
pub common: LinuxCommon,
|
||||
}
|
||||
|
||||
pub struct DragState {
|
||||
|
||||
@@ -1220,6 +1220,16 @@ impl PlatformWindow for WaylandWindow {
|
||||
update_window(state);
|
||||
}
|
||||
|
||||
fn background_appearance(&self) -> WindowBackgroundAppearance {
|
||||
self.borrow().background_appearance
|
||||
}
|
||||
|
||||
fn is_subpixel_rendering_supported(&self) -> bool {
|
||||
let client = self.borrow().client.get_client();
|
||||
let state = client.borrow();
|
||||
state.gpu_context.supports_dual_source_blending()
|
||||
}
|
||||
|
||||
fn minimize(&self) {
|
||||
if let Some(toplevel) = self.borrow().surface_state.toplevel() {
|
||||
toplevel.set_minimized();
|
||||
|
||||
@@ -177,7 +177,7 @@ pub struct X11ClientState {
|
||||
pub(crate) last_location: Point<Pixels>,
|
||||
pub(crate) current_count: usize,
|
||||
|
||||
gpu_context: BladeContext,
|
||||
pub(crate) gpu_context: BladeContext,
|
||||
|
||||
pub(crate) scale_factor: f32,
|
||||
|
||||
|
||||
@@ -1459,6 +1459,24 @@ impl PlatformWindow for X11Window {
|
||||
state.renderer.update_transparency(transparent);
|
||||
}
|
||||
|
||||
fn background_appearance(&self) -> WindowBackgroundAppearance {
|
||||
self.0.state.borrow().background_appearance
|
||||
}
|
||||
|
||||
fn is_subpixel_rendering_supported(&self) -> bool {
|
||||
self.0
|
||||
.state
|
||||
.borrow()
|
||||
.client
|
||||
.0
|
||||
.upgrade()
|
||||
.map(|ref_cell| {
|
||||
let state = ref_cell.borrow();
|
||||
state.gpu_context.supports_dual_source_blending()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn minimize(&self) {
|
||||
let state = self.0.state.borrow();
|
||||
const WINDOW_ICONIC_STATE: u32 = 3;
|
||||
|
||||
@@ -66,6 +66,7 @@ impl PlatformAtlas for MetalAtlas {
|
||||
let textures = match id.kind {
|
||||
AtlasTextureKind::Monochrome => &mut lock.monochrome_textures,
|
||||
AtlasTextureKind::Polychrome => &mut lock.polychrome_textures,
|
||||
AtlasTextureKind::Subpixel => unreachable!(),
|
||||
};
|
||||
|
||||
let Some(texture_slot) = textures
|
||||
@@ -99,6 +100,7 @@ impl MetalAtlasState {
|
||||
let textures = match texture_kind {
|
||||
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
|
||||
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
|
||||
AtlasTextureKind::Subpixel => unreachable!(),
|
||||
};
|
||||
|
||||
if let Some(tile) = textures
|
||||
@@ -143,6 +145,7 @@ impl MetalAtlasState {
|
||||
pixel_format = metal::MTLPixelFormat::BGRA8Unorm;
|
||||
usage = metal::MTLTextureUsage::ShaderRead;
|
||||
}
|
||||
AtlasTextureKind::Subpixel => unreachable!(),
|
||||
}
|
||||
texture_descriptor.set_pixel_format(pixel_format);
|
||||
texture_descriptor.set_usage(usage);
|
||||
@@ -151,6 +154,7 @@ impl MetalAtlasState {
|
||||
let texture_list = match kind {
|
||||
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
|
||||
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
|
||||
AtlasTextureKind::Subpixel => unreachable!(),
|
||||
};
|
||||
|
||||
let index = texture_list.free_list.pop();
|
||||
@@ -181,6 +185,7 @@ impl MetalAtlasState {
|
||||
let textures = match id.kind {
|
||||
crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
|
||||
crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
|
||||
crate::AtlasTextureKind::Subpixel => unreachable!(),
|
||||
};
|
||||
textures[id.index as usize].as_ref().unwrap()
|
||||
}
|
||||
|
||||
@@ -630,6 +630,7 @@ impl MetalRenderer {
|
||||
viewport_size,
|
||||
command_encoder,
|
||||
),
|
||||
PrimitiveBatch::SubpixelSprites { .. } => unreachable!(),
|
||||
};
|
||||
if !ok {
|
||||
command_encoder.end_encoding();
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::{
|
||||
Bounds, DevicePixels, Font, FontFallbacks, FontFeatures, FontId, FontMetrics, FontRun,
|
||||
FontStyle, FontWeight, GlyphId, LineLayout, Pixels, PlatformTextSystem, Point,
|
||||
RenderGlyphParams, Result, SUBPIXEL_VARIANTS_X, ShapedGlyph, ShapedRun, SharedString, Size,
|
||||
point, px, size, swap_rgba_pa_to_bgra,
|
||||
TextRenderingMode, point, px, size, swap_rgba_pa_to_bgra,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use cocoa::appkit::CGFloat;
|
||||
@@ -204,6 +204,14 @@ impl PlatformTextSystem for MacTextSystem {
|
||||
fn layout_line(&self, text: &str, font_size: Pixels, font_runs: &[FontRun]) -> LineLayout {
|
||||
self.0.write().layout_line(text, font_size, font_runs)
|
||||
}
|
||||
|
||||
fn recommended_rendering_mode(
|
||||
&self,
|
||||
_font_id: FontId,
|
||||
_font_size: Pixels,
|
||||
) -> TextRenderingMode {
|
||||
TextRenderingMode::Grayscale
|
||||
}
|
||||
}
|
||||
|
||||
impl MacTextSystemState {
|
||||
|
||||
@@ -402,6 +402,7 @@ struct MacWindowState {
|
||||
native_window: id,
|
||||
native_view: NonNull<Object>,
|
||||
blurred_view: Option<id>,
|
||||
background_appearance: WindowBackgroundAppearance,
|
||||
display_link: Option<DisplayLink>,
|
||||
renderer: renderer::Renderer,
|
||||
request_frame_callback: Option<Box<dyn FnMut(RequestFrameOptions)>>,
|
||||
@@ -706,6 +707,7 @@ impl MacWindow {
|
||||
native_window,
|
||||
native_view: NonNull::new_unchecked(native_view),
|
||||
blurred_view: None,
|
||||
background_appearance: WindowBackgroundAppearance::Opaque,
|
||||
display_link: None,
|
||||
renderer: renderer::new_renderer(
|
||||
renderer_context,
|
||||
@@ -1304,6 +1306,7 @@ impl PlatformWindow for MacWindow {
|
||||
|
||||
fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance) {
|
||||
let mut this = self.0.as_ref().lock();
|
||||
this.background_appearance = background_appearance;
|
||||
|
||||
let opaque = background_appearance == WindowBackgroundAppearance::Opaque;
|
||||
this.renderer.update_transparency(!opaque);
|
||||
@@ -1358,6 +1361,14 @@ impl PlatformWindow for MacWindow {
|
||||
}
|
||||
}
|
||||
|
||||
fn background_appearance(&self) -> WindowBackgroundAppearance {
|
||||
self.0.as_ref().lock().background_appearance
|
||||
}
|
||||
|
||||
fn is_subpixel_rendering_supported(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn set_edited(&mut self, edited: bool) {
|
||||
unsafe {
|
||||
let window = self.0.lock().native_window;
|
||||
|
||||
@@ -199,6 +199,14 @@ impl PlatformWindow for TestWindow {
|
||||
false
|
||||
}
|
||||
|
||||
fn background_appearance(&self) -> WindowBackgroundAppearance {
|
||||
WindowBackgroundAppearance::Opaque
|
||||
}
|
||||
|
||||
fn is_subpixel_rendering_supported(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn set_title(&mut self, title: &str) {
|
||||
self.0.lock().title = Some(title.to_owned());
|
||||
}
|
||||
|
||||
@@ -17,12 +17,22 @@ float enhance_contrast(float alpha, float k) {
|
||||
return alpha * (k + 1.0f) / (alpha * k + 1.0f);
|
||||
}
|
||||
|
||||
float3 enhance_contrast3(float3 alpha, float k) {
|
||||
return alpha * (k + 1.0f) / (alpha * k + 1.0f);
|
||||
}
|
||||
|
||||
float apply_alpha_correction(float a, float b, float4 g) {
|
||||
float brightness_adjustment = g.x * b + g.y;
|
||||
float correction = brightness_adjustment * a + (g.z * b + g.w);
|
||||
return a + a * (1.0f - a) * correction;
|
||||
}
|
||||
|
||||
float3 apply_alpha_correction3(float3 a, float3 b, float4 g) {
|
||||
float3 brightness_adjustment = g.x * b + g.y;
|
||||
float3 correction = brightness_adjustment * a + (g.z * b + g.w);
|
||||
return a + a * (1.0f - a) * correction;
|
||||
}
|
||||
|
||||
float apply_contrast_and_gamma_correction(float sample, float3 color, float enhanced_contrast_factor, float4 gamma_ratios) {
|
||||
float enhanced_contrast = light_on_dark_contrast(enhanced_contrast_factor, color);
|
||||
float brightness = color_brightness(color);
|
||||
@@ -30,3 +40,10 @@ float apply_contrast_and_gamma_correction(float sample, float3 color, float enha
|
||||
float contrasted = enhance_contrast(sample, enhanced_contrast);
|
||||
return apply_alpha_correction(contrasted, brightness, gamma_ratios);
|
||||
}
|
||||
|
||||
float3 apply_contrast_and_gamma_correction3(float3 sample, float3 color, float enhanced_contrast_factor, float4 gamma_ratios) {
|
||||
float enhanced_contrast = light_on_dark_contrast(enhanced_contrast_factor, color);
|
||||
|
||||
float3 contrasted = enhance_contrast3(sample, enhanced_contrast);
|
||||
return apply_alpha_correction3(contrasted, color, gamma_ratios);
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
use std::{borrow::Cow, sync::Arc};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
ffi::{c_uint, c_void},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use ::util::ResultExt;
|
||||
use anyhow::{Context, Result};
|
||||
@@ -60,6 +64,7 @@ struct DirectWriteState {
|
||||
fonts: Vec<FontInfo>,
|
||||
font_selections: HashMap<Font, FontId>,
|
||||
font_id_by_identifier: HashMap<FontIdentifier, FontId>,
|
||||
system_subpixel_rendering: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
@@ -199,6 +204,7 @@ impl DirectWriteTextSystem {
|
||||
.CreateFontCollectionFromFontSet(&custom_font_set)?
|
||||
};
|
||||
let system_ui_font_name = get_system_ui_font_name();
|
||||
let system_subpixel_rendering = get_system_subpixel_rendering();
|
||||
|
||||
Ok(Self(RwLock::new(DirectWriteState {
|
||||
components,
|
||||
@@ -208,6 +214,7 @@ impl DirectWriteTextSystem {
|
||||
fonts: Vec::new(),
|
||||
font_selections: HashMap::default(),
|
||||
font_id_by_identifier: HashMap::default(),
|
||||
system_subpixel_rendering,
|
||||
})))
|
||||
}
|
||||
|
||||
@@ -280,6 +287,18 @@ impl PlatformTextSystem for DirectWriteTextSystem {
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn recommended_rendering_mode(
|
||||
&self,
|
||||
_font_id: FontId,
|
||||
_font_size: Pixels,
|
||||
) -> TextRenderingMode {
|
||||
if self.0.read().system_subpixel_rendering {
|
||||
TextRenderingMode::Subpixel
|
||||
} else {
|
||||
TextRenderingMode::Grayscale
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DirectWriteState {
|
||||
@@ -759,6 +778,12 @@ impl DirectWriteState {
|
||||
m => m,
|
||||
};
|
||||
|
||||
let antialias_mode = if params.subpixel_rendering {
|
||||
DWRITE_TEXT_ANTIALIAS_MODE_CLEARTYPE
|
||||
} else {
|
||||
DWRITE_TEXT_ANTIALIAS_MODE_GRAYSCALE
|
||||
};
|
||||
|
||||
let glyph_analysis = unsafe {
|
||||
self.components.factory.CreateGlyphRunAnalysis(
|
||||
&glyph_run,
|
||||
@@ -766,7 +791,7 @@ impl DirectWriteState {
|
||||
rendering_mode,
|
||||
DWRITE_MEASURING_MODE_NATURAL,
|
||||
grid_fit_mode,
|
||||
DWRITE_TEXT_ANTIALIAS_MODE_GRAYSCALE,
|
||||
antialias_mode,
|
||||
baseline_origin_x,
|
||||
baseline_origin_y,
|
||||
)
|
||||
@@ -777,7 +802,13 @@ impl DirectWriteState {
|
||||
fn raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
|
||||
let glyph_analysis = self.create_glyph_run_analysis(params)?;
|
||||
|
||||
let bounds = unsafe { glyph_analysis.GetAlphaTextureBounds(DWRITE_TEXTURE_ALIASED_1x1)? };
|
||||
let texture_type = if params.subpixel_rendering {
|
||||
DWRITE_TEXTURE_CLEARTYPE_3x1
|
||||
} else {
|
||||
DWRITE_TEXTURE_ALIASED_1x1
|
||||
};
|
||||
|
||||
let bounds = unsafe { glyph_analysis.GetAlphaTextureBounds(texture_type)? };
|
||||
|
||||
if bounds.right < bounds.left {
|
||||
Ok(Bounds {
|
||||
@@ -839,23 +870,64 @@ impl DirectWriteState {
|
||||
params: &RenderGlyphParams,
|
||||
glyph_bounds: Bounds<DevicePixels>,
|
||||
) -> Result<Vec<u8>> {
|
||||
let mut bitmap_data =
|
||||
vec![0u8; glyph_bounds.size.width.0 as usize * glyph_bounds.size.height.0 as usize];
|
||||
if !params.subpixel_rendering {
|
||||
let mut bitmap_data =
|
||||
vec![0u8; glyph_bounds.size.width.0 as usize * glyph_bounds.size.height.0 as usize];
|
||||
|
||||
let glyph_analysis = self.create_glyph_run_analysis(params)?;
|
||||
unsafe {
|
||||
glyph_analysis.CreateAlphaTexture(
|
||||
DWRITE_TEXTURE_ALIASED_1x1,
|
||||
&RECT {
|
||||
left: glyph_bounds.origin.x.0,
|
||||
top: glyph_bounds.origin.y.0,
|
||||
right: glyph_bounds.size.width.0 + glyph_bounds.origin.x.0,
|
||||
bottom: glyph_bounds.size.height.0 + glyph_bounds.origin.y.0,
|
||||
},
|
||||
&mut bitmap_data,
|
||||
)?;
|
||||
}
|
||||
|
||||
return Ok(bitmap_data);
|
||||
}
|
||||
|
||||
let width = glyph_bounds.size.width.0 as usize;
|
||||
let height = glyph_bounds.size.height.0 as usize;
|
||||
let pixel_count = width * height;
|
||||
|
||||
let mut bitmap_data = vec![0u8; pixel_count * 4];
|
||||
|
||||
let glyph_analysis = self.create_glyph_run_analysis(params)?;
|
||||
unsafe {
|
||||
glyph_analysis.CreateAlphaTexture(
|
||||
DWRITE_TEXTURE_ALIASED_1x1,
|
||||
DWRITE_TEXTURE_CLEARTYPE_3x1,
|
||||
&RECT {
|
||||
left: glyph_bounds.origin.x.0,
|
||||
top: glyph_bounds.origin.y.0,
|
||||
right: glyph_bounds.size.width.0 + glyph_bounds.origin.x.0,
|
||||
bottom: glyph_bounds.size.height.0 + glyph_bounds.origin.y.0,
|
||||
},
|
||||
&mut bitmap_data,
|
||||
&mut bitmap_data[..pixel_count * 3],
|
||||
)?;
|
||||
}
|
||||
|
||||
// The output buffer expects RGBA data, so pad the alpha channel with zeros.
|
||||
for pixel_ix in (0..pixel_count).rev() {
|
||||
let src = pixel_ix * 3;
|
||||
let dst = pixel_ix * 4;
|
||||
(
|
||||
bitmap_data[dst],
|
||||
bitmap_data[dst + 1],
|
||||
bitmap_data[dst + 2],
|
||||
bitmap_data[dst + 3],
|
||||
) = (
|
||||
bitmap_data[src],
|
||||
bitmap_data[src + 1],
|
||||
bitmap_data[src + 2],
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(bitmap_data)
|
||||
}
|
||||
|
||||
@@ -1076,6 +1148,7 @@ impl DirectWriteState {
|
||||
let crate::FontInfo {
|
||||
gamma_ratios,
|
||||
grayscale_enhanced_contrast,
|
||||
..
|
||||
} = DirectXRenderer::get_font_info();
|
||||
|
||||
for layer in glyph_layers {
|
||||
@@ -1820,6 +1893,23 @@ fn get_name(string: IDWriteLocalizedStrings, locale: &str) -> Result<String> {
|
||||
Ok(String::from_utf16_lossy(&name_vec[..name_length]))
|
||||
}
|
||||
|
||||
fn get_system_subpixel_rendering() -> bool {
|
||||
let mut value = c_uint::default();
|
||||
let result = unsafe {
|
||||
SystemParametersInfoW(
|
||||
SPI_GETFONTSMOOTHINGTYPE,
|
||||
0,
|
||||
Some((&mut value) as *mut c_uint as *mut c_void),
|
||||
SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS::default(),
|
||||
)
|
||||
};
|
||||
if result.log_err().is_some() {
|
||||
value == FE_FONTSMOOTHINGCLEARTYPE
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
fn get_system_ui_font_name() -> SharedString {
|
||||
unsafe {
|
||||
let mut info: LOGFONTW = std::mem::zeroed();
|
||||
|
||||
@@ -21,6 +21,7 @@ struct DirectXAtlasState {
|
||||
device_context: ID3D11DeviceContext,
|
||||
monochrome_textures: AtlasTextureList<DirectXAtlasTexture>,
|
||||
polychrome_textures: AtlasTextureList<DirectXAtlasTexture>,
|
||||
subpixel_textures: AtlasTextureList<DirectXAtlasTexture>,
|
||||
tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
|
||||
}
|
||||
|
||||
@@ -40,6 +41,7 @@ impl DirectXAtlas {
|
||||
device_context: device_context.clone(),
|
||||
monochrome_textures: Default::default(),
|
||||
polychrome_textures: Default::default(),
|
||||
subpixel_textures: Default::default(),
|
||||
tiles_by_key: Default::default(),
|
||||
}))
|
||||
}
|
||||
@@ -63,6 +65,7 @@ impl DirectXAtlas {
|
||||
lock.device_context = device_context.clone();
|
||||
lock.monochrome_textures = AtlasTextureList::default();
|
||||
lock.polychrome_textures = AtlasTextureList::default();
|
||||
lock.subpixel_textures = AtlasTextureList::default();
|
||||
lock.tiles_by_key.clear();
|
||||
}
|
||||
}
|
||||
@@ -102,6 +105,7 @@ impl PlatformAtlas for DirectXAtlas {
|
||||
let textures = match id.kind {
|
||||
AtlasTextureKind::Monochrome => &mut lock.monochrome_textures,
|
||||
AtlasTextureKind::Polychrome => &mut lock.polychrome_textures,
|
||||
AtlasTextureKind::Subpixel => &mut lock.subpixel_textures,
|
||||
};
|
||||
|
||||
let Some(texture_slot) = textures.textures.get_mut(id.index as usize) else {
|
||||
@@ -130,6 +134,7 @@ impl DirectXAtlasState {
|
||||
let textures = match texture_kind {
|
||||
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
|
||||
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
|
||||
AtlasTextureKind::Subpixel => &mut self.subpixel_textures,
|
||||
};
|
||||
|
||||
if let Some(tile) = textures
|
||||
@@ -175,6 +180,11 @@ impl DirectXAtlasState {
|
||||
bind_flag = D3D11_BIND_SHADER_RESOURCE;
|
||||
bytes_per_pixel = 4;
|
||||
}
|
||||
AtlasTextureKind::Subpixel => {
|
||||
pixel_format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
bind_flag = D3D11_BIND_SHADER_RESOURCE;
|
||||
bytes_per_pixel = 4;
|
||||
}
|
||||
}
|
||||
let texture_desc = D3D11_TEXTURE2D_DESC {
|
||||
Width: size.width.0 as u32,
|
||||
@@ -204,6 +214,7 @@ impl DirectXAtlasState {
|
||||
let texture_list = match kind {
|
||||
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
|
||||
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
|
||||
AtlasTextureKind::Subpixel => &mut self.subpixel_textures,
|
||||
};
|
||||
let index = texture_list.free_list.pop();
|
||||
let view = unsafe {
|
||||
@@ -241,6 +252,9 @@ impl DirectXAtlasState {
|
||||
crate::AtlasTextureKind::Polychrome => &self.polychrome_textures[id.index as usize]
|
||||
.as_ref()
|
||||
.unwrap(),
|
||||
crate::AtlasTextureKind::Subpixel => {
|
||||
&self.subpixel_textures[id.index as usize].as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ const PATH_MULTISAMPLE_COUNT: u32 = 4;
|
||||
pub(crate) struct FontInfo {
|
||||
pub gamma_ratios: [f32; 4],
|
||||
pub grayscale_enhanced_contrast: f32,
|
||||
pub subpixel_enhanced_contrast: f32,
|
||||
}
|
||||
|
||||
pub(crate) struct DirectXRenderer {
|
||||
@@ -89,6 +90,7 @@ struct DirectXRenderPipelines {
|
||||
path_sprite_pipeline: PipelineState<PathSprite>,
|
||||
underline_pipeline: PipelineState<Underline>,
|
||||
mono_sprites: PipelineState<MonochromeSprite>,
|
||||
subpixel_sprites: PipelineState<SubpixelSprite>,
|
||||
poly_sprites: PipelineState<PolychromeSprite>,
|
||||
}
|
||||
|
||||
@@ -181,7 +183,7 @@ impl DirectXRenderer {
|
||||
self.atlas.clone()
|
||||
}
|
||||
|
||||
fn pre_draw(&self) -> Result<()> {
|
||||
fn pre_draw(&self, clear_color: &[f32; 4]) -> Result<()> {
|
||||
let resources = self.resources.as_ref().expect("resources missing");
|
||||
let device_context = &self
|
||||
.devices
|
||||
@@ -195,7 +197,7 @@ impl DirectXRenderer {
|
||||
gamma_ratios: self.font_info.gamma_ratios,
|
||||
viewport_size: [resources.viewport.Width, resources.viewport.Height],
|
||||
grayscale_enhanced_contrast: self.font_info.grayscale_enhanced_contrast,
|
||||
_pad: 0,
|
||||
subpixel_enhanced_contrast: self.font_info.subpixel_enhanced_contrast,
|
||||
}],
|
||||
)?;
|
||||
unsafe {
|
||||
@@ -204,7 +206,7 @@ impl DirectXRenderer {
|
||||
.render_target_view
|
||||
.as_ref()
|
||||
.context("missing render target view")?,
|
||||
&[0.0; 4],
|
||||
clear_color,
|
||||
);
|
||||
device_context
|
||||
.OMSetRenderTargets(Some(slice::from_ref(&resources.render_target_view)), None);
|
||||
@@ -299,13 +301,20 @@ impl DirectXRenderer {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn draw(&mut self, scene: &Scene) -> Result<()> {
|
||||
pub(crate) fn draw(
|
||||
&mut self,
|
||||
scene: &Scene,
|
||||
background_appearance: WindowBackgroundAppearance,
|
||||
) -> Result<()> {
|
||||
if self.skip_draws {
|
||||
// skip drawing this frame, we just recovered from a device lost event
|
||||
// and so likely do not have the textures anymore that are required for drawing
|
||||
return Ok(());
|
||||
}
|
||||
self.pre_draw()?;
|
||||
self.pre_draw(&match background_appearance {
|
||||
WindowBackgroundAppearance::Opaque => [1.0f32; 4],
|
||||
_ => [0.0f32; 4],
|
||||
})?;
|
||||
for batch in scene.batches() {
|
||||
match batch {
|
||||
PrimitiveBatch::Shadows(shadows) => self.draw_shadows(shadows),
|
||||
@@ -319,6 +328,10 @@ impl DirectXRenderer {
|
||||
texture_id,
|
||||
sprites,
|
||||
} => self.draw_monochrome_sprites(texture_id, sprites),
|
||||
PrimitiveBatch::SubpixelSprites {
|
||||
texture_id,
|
||||
sprites,
|
||||
} => self.draw_subpixel_sprites(texture_id, sprites),
|
||||
PrimitiveBatch::PolychromeSprites {
|
||||
texture_id,
|
||||
sprites,
|
||||
@@ -327,12 +340,13 @@ impl DirectXRenderer {
|
||||
}
|
||||
.context(format!(
|
||||
"scene too large:\
|
||||
{} paths, {} shadows, {} quads, {} underlines, {} mono, {} poly, {} surfaces",
|
||||
{} paths, {} shadows, {} quads, {} underlines, {} mono, {} subpixel, {} poly, {} surfaces",
|
||||
scene.paths.len(),
|
||||
scene.shadows.len(),
|
||||
scene.quads.len(),
|
||||
scene.underlines.len(),
|
||||
scene.monochrome_sprites.len(),
|
||||
scene.subpixel_sprites.len(),
|
||||
scene.polychrome_sprites.len(),
|
||||
scene.surfaces.len(),
|
||||
))?;
|
||||
@@ -578,6 +592,7 @@ impl DirectXRenderer {
|
||||
}
|
||||
let devices = self.devices.as_ref().context("devices missing")?;
|
||||
let resources = self.resources.as_ref().context("resources missing")?;
|
||||
|
||||
self.pipelines.mono_sprites.update_buffer(
|
||||
&devices.device,
|
||||
&devices.device_context,
|
||||
@@ -594,6 +609,33 @@ impl DirectXRenderer {
|
||||
)
|
||||
}
|
||||
|
||||
fn draw_subpixel_sprites(
|
||||
&mut self,
|
||||
texture_id: AtlasTextureId,
|
||||
sprites: &[SubpixelSprite],
|
||||
) -> Result<()> {
|
||||
if sprites.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let devices = self.devices.as_ref().context("devices missing")?;
|
||||
let resources = self.resources.as_ref().context("resources missing")?;
|
||||
|
||||
self.pipelines.subpixel_sprites.update_buffer(
|
||||
&devices.device,
|
||||
&devices.device_context,
|
||||
&sprites,
|
||||
)?;
|
||||
let texture_view = self.atlas.get_texture_view(texture_id);
|
||||
self.pipelines.subpixel_sprites.draw_with_texture(
|
||||
&devices.device_context,
|
||||
&texture_view,
|
||||
slice::from_ref(&resources.viewport),
|
||||
slice::from_ref(&self.globals.global_params_buffer),
|
||||
slice::from_ref(&self.globals.sampler),
|
||||
sprites.len() as u32,
|
||||
)
|
||||
}
|
||||
|
||||
fn draw_polychrome_sprites(
|
||||
&mut self,
|
||||
texture_id: AtlasTextureId,
|
||||
@@ -667,6 +709,7 @@ impl DirectXRenderer {
|
||||
FontInfo {
|
||||
gamma_ratios: get_gamma_correction_ratios(render_params.GetGamma()),
|
||||
grayscale_enhanced_contrast: render_params.GetGrayscaleEnhancedContrast(),
|
||||
subpixel_enhanced_contrast: render_params.GetEnhancedContrast(),
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -789,6 +832,13 @@ impl DirectXRenderPipelines {
|
||||
512,
|
||||
create_blend_state(device)?,
|
||||
)?;
|
||||
let subpixel_sprites = PipelineState::new(
|
||||
device,
|
||||
"subpixel_sprite_pipeline",
|
||||
ShaderModule::SubpixelSprite,
|
||||
512,
|
||||
create_blend_state_for_subpixel_rendering(device)?,
|
||||
)?;
|
||||
let poly_sprites = PipelineState::new(
|
||||
device,
|
||||
"polychrome_sprite_pipeline",
|
||||
@@ -804,6 +854,7 @@ impl DirectXRenderPipelines {
|
||||
path_sprite_pipeline,
|
||||
underline_pipeline,
|
||||
mono_sprites,
|
||||
subpixel_sprites,
|
||||
poly_sprites,
|
||||
})
|
||||
}
|
||||
@@ -878,7 +929,7 @@ struct GlobalParams {
|
||||
gamma_ratios: [f32; 4],
|
||||
viewport_size: [f32; 2],
|
||||
grayscale_enhanced_contrast: f32,
|
||||
_pad: u32,
|
||||
subpixel_enhanced_contrast: f32,
|
||||
}
|
||||
|
||||
struct PipelineState<T> {
|
||||
@@ -1235,8 +1286,6 @@ fn set_rasterizer_state(device: &ID3D11Device, device_context: &ID3D11DeviceCont
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/d3d11/ns-d3d11-d3d11_blend_desc
|
||||
#[inline]
|
||||
fn create_blend_state(device: &ID3D11Device) -> Result<ID3D11BlendState> {
|
||||
// If the feature level is set to greater than D3D_FEATURE_LEVEL_9_3, the display
|
||||
// device performs the blend in linear space, which is ideal.
|
||||
let mut desc = D3D11_BLEND_DESC::default();
|
||||
desc.RenderTarget[0].BlendEnable = true.into();
|
||||
desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
|
||||
@@ -1253,6 +1302,27 @@ fn create_blend_state(device: &ID3D11Device) -> Result<ID3D11BlendState> {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn create_blend_state_for_subpixel_rendering(device: &ID3D11Device) -> Result<ID3D11BlendState> {
|
||||
let mut desc = D3D11_BLEND_DESC::default();
|
||||
desc.RenderTarget[0].BlendEnable = true.into();
|
||||
desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
|
||||
desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
|
||||
desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC1_COLOR;
|
||||
desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC1_COLOR;
|
||||
// It does not make sense to draw transparent subpixel-rendered text, since it cannot be meaningfully alpha-blended onto anything else.
|
||||
desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
|
||||
desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO;
|
||||
desc.RenderTarget[0].RenderTargetWriteMask =
|
||||
D3D11_COLOR_WRITE_ENABLE_ALL.0 as u8 & !D3D11_COLOR_WRITE_ENABLE_ALPHA.0 as u8;
|
||||
|
||||
unsafe {
|
||||
let mut state = None;
|
||||
device.CreateBlendState(&desc, Some(&mut state))?;
|
||||
Ok(state.unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn create_blend_state_for_path_rasterization(device: &ID3D11Device) -> Result<ID3D11BlendState> {
|
||||
// If the feature level is set to greater than D3D_FEATURE_LEVEL_9_3, the display
|
||||
@@ -1410,6 +1480,7 @@ pub(crate) mod shader_resources {
|
||||
PathRasterization,
|
||||
PathSprite,
|
||||
MonochromeSprite,
|
||||
SubpixelSprite,
|
||||
PolychromeSprite,
|
||||
EmojiRasterization,
|
||||
}
|
||||
@@ -1477,6 +1548,10 @@ pub(crate) mod shader_resources {
|
||||
ShaderTarget::Vertex => MONOCHROME_SPRITE_VERTEX_BYTES,
|
||||
ShaderTarget::Fragment => MONOCHROME_SPRITE_FRAGMENT_BYTES,
|
||||
},
|
||||
ShaderModule::SubpixelSprite => match target {
|
||||
ShaderTarget::Vertex => SUBPIXEL_SPRITE_VERTEX_BYTES,
|
||||
ShaderTarget::Fragment => SUBPIXEL_SPRITE_FRAGMENT_BYTES,
|
||||
},
|
||||
ShaderModule::PolychromeSprite => match target {
|
||||
ShaderTarget::Vertex => POLYCHROME_SPRITE_VERTEX_BYTES,
|
||||
ShaderTarget::Fragment => POLYCHROME_SPRITE_FRAGMENT_BYTES,
|
||||
@@ -1561,7 +1636,7 @@ pub(crate) mod shader_resources {
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
impl ShaderModule {
|
||||
pub fn as_str(&self) -> &str {
|
||||
pub fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
ShaderModule::Quad => "quad",
|
||||
ShaderModule::Shadow => "shadow",
|
||||
@@ -1569,6 +1644,7 @@ pub(crate) mod shader_resources {
|
||||
ShaderModule::PathRasterization => "path_rasterization",
|
||||
ShaderModule::PathSprite => "path_sprite",
|
||||
ShaderModule::MonochromeSprite => "monochrome_sprite",
|
||||
ShaderModule::SubpixelSprite => "subpixel_sprite",
|
||||
ShaderModule::PolychromeSprite => "polychrome_sprite",
|
||||
ShaderModule::EmojiRasterization => "emoji_rasterization",
|
||||
}
|
||||
|
||||
@@ -4,12 +4,17 @@ cbuffer GlobalParams: register(b0) {
|
||||
float4 gamma_ratios;
|
||||
float2 global_viewport_size;
|
||||
float grayscale_enhanced_contrast;
|
||||
uint _pad;
|
||||
float subpixel_enhanced_contrast;
|
||||
};
|
||||
|
||||
Texture2D<float4> t_sprite: register(t0);
|
||||
SamplerState s_sprite: register(s0);
|
||||
|
||||
struct SubpixelSpriteFragmentOutput {
|
||||
float4 foreground : SV_Target0;
|
||||
float4 alpha : SV_Target1;
|
||||
};
|
||||
|
||||
struct Bounds {
|
||||
float2 origin;
|
||||
float2 size;
|
||||
@@ -1119,6 +1124,20 @@ float4 monochrome_sprite_fragment(MonochromeSpriteFragmentInput input): SV_Targe
|
||||
return float4(input.color.rgb, input.color.a * alpha_corrected);
|
||||
}
|
||||
|
||||
MonochromeSpriteVertexOutput subpixel_sprite_vertex(uint vertex_id: SV_VertexID, uint sprite_id: SV_InstanceID) {
|
||||
return monochrome_sprite_vertex(vertex_id, sprite_id);
|
||||
}
|
||||
|
||||
SubpixelSpriteFragmentOutput subpixel_sprite_fragment(MonochromeSpriteFragmentInput input) {
|
||||
float3 sample = t_sprite.Sample(s_sprite, input.tile_position).rgb;
|
||||
float3 alpha_corrected = apply_contrast_and_gamma_correction3(sample, input.color.rgb, subpixel_enhanced_contrast, gamma_ratios);
|
||||
|
||||
SubpixelSpriteFragmentOutput output;
|
||||
output.foreground = float4(input.color.rgb, 1.0f);
|
||||
output.alpha = float4(input.color.a * alpha_corrected, 1.0f);
|
||||
return output;
|
||||
}
|
||||
|
||||
/*
|
||||
**
|
||||
** Polychrome sprites
|
||||
|
||||
@@ -7,8 +7,8 @@ use ::util::ResultExt;
|
||||
use windows::Win32::UI::{
|
||||
Shell::{ABM_GETSTATE, ABM_GETTASKBARPOS, ABS_AUTOHIDE, APPBARDATA, SHAppBarMessage},
|
||||
WindowsAndMessaging::{
|
||||
SPI_GETWHEELSCROLLCHARS, SPI_GETWHEELSCROLLLINES, SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS,
|
||||
SystemParametersInfoW,
|
||||
SPI_GETWHEELSCROLLCHARS, SPI_GETWHEELSCROLLLINES, SPI_SETWORKAREA,
|
||||
SYSTEM_PARAMETERS_INFO_ACTION, SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS, SystemParametersInfoW,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -39,18 +39,16 @@ impl WindowsSystemSettings {
|
||||
settings
|
||||
}
|
||||
|
||||
fn init(&self, display: WindowsDisplay) {
|
||||
fn init(&mut self, display: WindowsDisplay) {
|
||||
self.mouse_wheel_settings.update();
|
||||
self.auto_hide_taskbar_position
|
||||
.set(AutoHideTaskbarPosition::new(display).log_err().flatten());
|
||||
}
|
||||
|
||||
pub(crate) fn update(&self, display: WindowsDisplay, wparam: usize) {
|
||||
match wparam {
|
||||
// SPI_SETWORKAREA
|
||||
47 => self.update_taskbar_position(display),
|
||||
// SPI_GETWHEELSCROLLLINES, SPI_GETWHEELSCROLLCHARS
|
||||
104 | 108 => self.update_mouse_wheel_settings(),
|
||||
match SYSTEM_PARAMETERS_INFO_ACTION(wparam as u32) {
|
||||
SPI_SETWORKAREA => self.update_taskbar_position(display),
|
||||
SPI_GETWHEELSCROLLLINES | SPI_GETWHEELSCROLLCHARS => self.update_mouse_wheel_settings(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ pub struct WindowsWindowState {
|
||||
pub fullscreen_restore_bounds: Cell<Bounds<Pixels>>,
|
||||
pub border_offset: WindowBorderOffset,
|
||||
pub appearance: Cell<WindowAppearance>,
|
||||
pub background_appearance: Cell<WindowBackgroundAppearance>,
|
||||
pub scale_factor: Cell<f32>,
|
||||
pub restore_from_minimized: Cell<Option<Box<dyn FnMut(RequestFrameOptions)>>>,
|
||||
|
||||
@@ -135,6 +136,7 @@ impl WindowsWindowState {
|
||||
fullscreen_restore_bounds: Cell::new(fullscreen_restore_bounds),
|
||||
border_offset,
|
||||
appearance: Cell::new(appearance),
|
||||
background_appearance: Cell::new(WindowBackgroundAppearance::Opaque),
|
||||
scale_factor: Cell::new(scale_factor),
|
||||
restore_from_minimized: Cell::new(restore_from_minimized),
|
||||
min_size,
|
||||
@@ -788,6 +790,14 @@ impl PlatformWindow for WindowsWindow {
|
||||
self.state.hovered.get()
|
||||
}
|
||||
|
||||
fn background_appearance(&self) -> WindowBackgroundAppearance {
|
||||
self.state.background_appearance.get()
|
||||
}
|
||||
|
||||
fn is_subpixel_rendering_supported(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn set_title(&mut self, title: &str) {
|
||||
unsafe { SetWindowTextW(self.0.hwnd, &HSTRING::from(title)) }
|
||||
.inspect_err(|e| log::error!("Set title failed: {e}"))
|
||||
@@ -795,6 +805,7 @@ impl PlatformWindow for WindowsWindow {
|
||||
}
|
||||
|
||||
fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance) {
|
||||
self.state.background_appearance.set(background_appearance);
|
||||
let hwnd = self.0.hwnd;
|
||||
|
||||
// using Dwm APIs for Mica and MicaAlt backdrops.
|
||||
@@ -905,7 +916,11 @@ impl PlatformWindow for WindowsWindow {
|
||||
}
|
||||
|
||||
fn draw(&self, scene: &Scene) {
|
||||
self.state.renderer.borrow_mut().draw(scene).log_err();
|
||||
self.state
|
||||
.renderer
|
||||
.borrow_mut()
|
||||
.draw(scene, self.state.background_appearance.get())
|
||||
.log_err();
|
||||
}
|
||||
|
||||
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
|
||||
|
||||
@@ -30,6 +30,7 @@ pub(crate) struct Scene {
|
||||
pub(crate) paths: Vec<Path<ScaledPixels>>,
|
||||
pub(crate) underlines: Vec<Underline>,
|
||||
pub(crate) monochrome_sprites: Vec<MonochromeSprite>,
|
||||
pub(crate) subpixel_sprites: Vec<SubpixelSprite>,
|
||||
pub(crate) polychrome_sprites: Vec<PolychromeSprite>,
|
||||
pub(crate) surfaces: Vec<PaintSurface>,
|
||||
}
|
||||
@@ -44,6 +45,7 @@ impl Scene {
|
||||
self.quads.clear();
|
||||
self.underlines.clear();
|
||||
self.monochrome_sprites.clear();
|
||||
self.subpixel_sprites.clear();
|
||||
self.polychrome_sprites.clear();
|
||||
self.surfaces.clear();
|
||||
}
|
||||
@@ -101,6 +103,10 @@ impl Scene {
|
||||
sprite.order = order;
|
||||
self.monochrome_sprites.push(sprite.clone());
|
||||
}
|
||||
Primitive::SubpixelSprite(sprite) => {
|
||||
sprite.order = order;
|
||||
self.subpixel_sprites.push(sprite.clone());
|
||||
}
|
||||
Primitive::PolychromeSprite(sprite) => {
|
||||
sprite.order = order;
|
||||
self.polychrome_sprites.push(sprite.clone());
|
||||
@@ -131,6 +137,8 @@ impl Scene {
|
||||
self.underlines.sort_by_key(|underline| underline.order);
|
||||
self.monochrome_sprites
|
||||
.sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id));
|
||||
self.subpixel_sprites
|
||||
.sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id));
|
||||
self.polychrome_sprites
|
||||
.sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id));
|
||||
self.surfaces.sort_by_key(|surface| surface.order);
|
||||
@@ -160,6 +168,9 @@ impl Scene {
|
||||
monochrome_sprites: &self.monochrome_sprites,
|
||||
monochrome_sprites_start: 0,
|
||||
monochrome_sprites_iter: self.monochrome_sprites.iter().peekable(),
|
||||
subpixel_sprites: &self.subpixel_sprites,
|
||||
subpixel_sprites_start: 0,
|
||||
subpixel_sprites_iter: self.subpixel_sprites.iter().peekable(),
|
||||
polychrome_sprites: &self.polychrome_sprites,
|
||||
polychrome_sprites_start: 0,
|
||||
polychrome_sprites_iter: self.polychrome_sprites.iter().peekable(),
|
||||
@@ -185,6 +196,7 @@ pub(crate) enum PrimitiveKind {
|
||||
Path,
|
||||
Underline,
|
||||
MonochromeSprite,
|
||||
SubpixelSprite,
|
||||
PolychromeSprite,
|
||||
Surface,
|
||||
}
|
||||
@@ -202,6 +214,7 @@ pub(crate) enum Primitive {
|
||||
Path(Path<ScaledPixels>),
|
||||
Underline(Underline),
|
||||
MonochromeSprite(MonochromeSprite),
|
||||
SubpixelSprite(SubpixelSprite),
|
||||
PolychromeSprite(PolychromeSprite),
|
||||
Surface(PaintSurface),
|
||||
}
|
||||
@@ -214,6 +227,7 @@ impl Primitive {
|
||||
Primitive::Path(path) => &path.bounds,
|
||||
Primitive::Underline(underline) => &underline.bounds,
|
||||
Primitive::MonochromeSprite(sprite) => &sprite.bounds,
|
||||
Primitive::SubpixelSprite(sprite) => &sprite.bounds,
|
||||
Primitive::PolychromeSprite(sprite) => &sprite.bounds,
|
||||
Primitive::Surface(surface) => &surface.bounds,
|
||||
}
|
||||
@@ -226,6 +240,7 @@ impl Primitive {
|
||||
Primitive::Path(path) => &path.content_mask,
|
||||
Primitive::Underline(underline) => &underline.content_mask,
|
||||
Primitive::MonochromeSprite(sprite) => &sprite.content_mask,
|
||||
Primitive::SubpixelSprite(sprite) => &sprite.content_mask,
|
||||
Primitive::PolychromeSprite(sprite) => &sprite.content_mask,
|
||||
Primitive::Surface(surface) => &surface.content_mask,
|
||||
}
|
||||
@@ -255,6 +270,9 @@ struct BatchIterator<'a> {
|
||||
monochrome_sprites: &'a [MonochromeSprite],
|
||||
monochrome_sprites_start: usize,
|
||||
monochrome_sprites_iter: Peekable<slice::Iter<'a, MonochromeSprite>>,
|
||||
subpixel_sprites: &'a [SubpixelSprite],
|
||||
subpixel_sprites_start: usize,
|
||||
subpixel_sprites_iter: Peekable<slice::Iter<'a, SubpixelSprite>>,
|
||||
polychrome_sprites: &'a [PolychromeSprite],
|
||||
polychrome_sprites_start: usize,
|
||||
polychrome_sprites_iter: Peekable<slice::Iter<'a, PolychromeSprite>>,
|
||||
@@ -282,6 +300,10 @@ impl<'a> Iterator for BatchIterator<'a> {
|
||||
self.monochrome_sprites_iter.peek().map(|s| s.order),
|
||||
PrimitiveKind::MonochromeSprite,
|
||||
),
|
||||
(
|
||||
self.subpixel_sprites_iter.peek().map(|s| s.order),
|
||||
PrimitiveKind::SubpixelSprite,
|
||||
),
|
||||
(
|
||||
self.polychrome_sprites_iter.peek().map(|s| s.order),
|
||||
PrimitiveKind::PolychromeSprite,
|
||||
@@ -383,6 +405,27 @@ impl<'a> Iterator for BatchIterator<'a> {
|
||||
sprites: &self.monochrome_sprites[sprites_start..sprites_end],
|
||||
})
|
||||
}
|
||||
PrimitiveKind::SubpixelSprite => {
|
||||
let texture_id = self.subpixel_sprites_iter.peek().unwrap().tile.texture_id;
|
||||
let sprites_start = self.subpixel_sprites_start;
|
||||
let mut sprites_end = sprites_start + 1;
|
||||
self.subpixel_sprites_iter.next();
|
||||
while self
|
||||
.subpixel_sprites_iter
|
||||
.next_if(|sprite| {
|
||||
(sprite.order, batch_kind) < max_order_and_kind
|
||||
&& sprite.tile.texture_id == texture_id
|
||||
})
|
||||
.is_some()
|
||||
{
|
||||
sprites_end += 1;
|
||||
}
|
||||
self.subpixel_sprites_start = sprites_end;
|
||||
Some(PrimitiveBatch::SubpixelSprites {
|
||||
texture_id,
|
||||
sprites: &self.subpixel_sprites[sprites_start..sprites_end],
|
||||
})
|
||||
}
|
||||
PrimitiveKind::PolychromeSprite => {
|
||||
let texture_id = self.polychrome_sprites_iter.peek().unwrap().tile.texture_id;
|
||||
let sprites_start = self.polychrome_sprites_start;
|
||||
@@ -441,6 +484,11 @@ pub(crate) enum PrimitiveBatch<'a> {
|
||||
texture_id: AtlasTextureId,
|
||||
sprites: &'a [MonochromeSprite],
|
||||
},
|
||||
#[cfg_attr(target_os = "macos", allow(dead_code))]
|
||||
SubpixelSprites {
|
||||
texture_id: AtlasTextureId,
|
||||
sprites: &'a [SubpixelSprite],
|
||||
},
|
||||
PolychromeSprites {
|
||||
texture_id: AtlasTextureId,
|
||||
sprites: &'a [PolychromeSprite],
|
||||
@@ -634,6 +682,24 @@ impl From<MonochromeSprite> for Primitive {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub(crate) struct SubpixelSprite {
|
||||
pub order: DrawOrder,
|
||||
pub pad: u32, // align to 8 bytes
|
||||
pub bounds: Bounds<ScaledPixels>,
|
||||
pub content_mask: ContentMask<ScaledPixels>,
|
||||
pub color: Hsla,
|
||||
pub tile: AtlasTile,
|
||||
pub transformation: TransformationMatrix,
|
||||
}
|
||||
|
||||
impl From<SubpixelSprite> for Primitive {
|
||||
fn from(sprite: SubpixelSprite) -> Self {
|
||||
Primitive::SubpixelSprite(sprite)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub(crate) struct PolychromeSprite {
|
||||
|
||||
@@ -14,7 +14,7 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
Bounds, DevicePixels, Hsla, Pixels, PlatformTextSystem, Point, Result, SharedString, Size,
|
||||
StrikethroughStyle, UnderlineStyle, px,
|
||||
StrikethroughStyle, TextRenderingMode, UnderlineStyle, px,
|
||||
};
|
||||
use anyhow::{Context as _, anyhow};
|
||||
use collections::FxHashMap;
|
||||
@@ -326,6 +326,17 @@ impl TextSystem {
|
||||
self.platform_text_system
|
||||
.rasterize_glyph(params, raster_bounds)
|
||||
}
|
||||
|
||||
/// Returns the text rendering mode recommended by the platform for the given font and size.
|
||||
/// The return value will never be [`TextRenderingMode::PlatformDefault`].
|
||||
pub(crate) fn recommended_rendering_mode(
|
||||
&self,
|
||||
font_id: FontId,
|
||||
font_size: Pixels,
|
||||
) -> TextRenderingMode {
|
||||
self.platform_text_system
|
||||
.recommended_rendering_mode(font_id, font_size)
|
||||
}
|
||||
}
|
||||
|
||||
/// The GPUI text layout subsystem.
|
||||
@@ -775,6 +786,7 @@ pub(crate) struct RenderGlyphParams {
|
||||
pub(crate) subpixel_variant: Point<u8>,
|
||||
pub(crate) scale_factor: f32,
|
||||
pub(crate) is_emoji: bool,
|
||||
pub(crate) subpixel_rendering: bool,
|
||||
}
|
||||
|
||||
impl Eq for RenderGlyphParams {}
|
||||
@@ -787,6 +799,7 @@ impl Hash for RenderGlyphParams {
|
||||
self.subpixel_variant.hash(state);
|
||||
self.scale_factor.to_bits().hash(state);
|
||||
self.is_emoji.hash(state);
|
||||
self.subpixel_rendering.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+52
-15
@@ -12,12 +12,12 @@ use crate::{
|
||||
PlatformInputHandler, PlatformWindow, Point, PolychromeSprite, Priority, PromptButton,
|
||||
PromptLevel, Quad, Render, RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams,
|
||||
Replay, ResizeEdge, SMOOTH_SVG_SCALE_FACTOR, SUBPIXEL_VARIANTS_X, SUBPIXEL_VARIANTS_Y,
|
||||
ScaledPixels, Scene, Shadow, SharedString, Size, StrikethroughStyle, Style, SubscriberSet,
|
||||
Subscription, SystemWindowTab, SystemWindowTabController, TabStopMap, TaffyLayoutEngine, Task,
|
||||
TextStyle, TextStyleRefinement, TransformationMatrix, Underline, UnderlineStyle,
|
||||
WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowControls, WindowDecorations,
|
||||
WindowOptions, WindowParams, WindowTextSystem, point, prelude::*, px, rems, size,
|
||||
transparent_black,
|
||||
ScaledPixels, Scene, Shadow, SharedString, Size, StrikethroughStyle, Style, SubpixelSprite,
|
||||
SubscriberSet, Subscription, SystemWindowTab, SystemWindowTabController, TabStopMap,
|
||||
TaffyLayoutEngine, Task, TextRenderingMode, TextStyle, TextStyleRefinement,
|
||||
TransformationMatrix, Underline, UnderlineStyle, WindowAppearance, WindowBackgroundAppearance,
|
||||
WindowBounds, WindowControls, WindowDecorations, WindowOptions, WindowParams, WindowTextSystem,
|
||||
point, prelude::*, px, rems, size, transparent_black,
|
||||
};
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use collections::{FxHashMap, FxHashSet};
|
||||
@@ -838,6 +838,7 @@ pub struct Window {
|
||||
display_id: Option<DisplayId>,
|
||||
sprite_atlas: Arc<dyn PlatformAtlas>,
|
||||
text_system: Arc<WindowTextSystem>,
|
||||
text_rendering_mode: Rc<Cell<TextRenderingMode>>,
|
||||
rem_size: Pixels,
|
||||
/// The stack of override values for the window's rem size.
|
||||
///
|
||||
@@ -1312,6 +1313,7 @@ impl Window {
|
||||
display_id,
|
||||
sprite_atlas,
|
||||
text_system,
|
||||
text_rendering_mode: cx.text_rendering_mode.clone(),
|
||||
rem_size: px(16.),
|
||||
rem_size_override_stack: SmallVec::new(),
|
||||
viewport_size: content_size,
|
||||
@@ -3130,6 +3132,7 @@ impl Window {
|
||||
x: (glyph_origin.x.0.fract() * SUBPIXEL_VARIANTS_X as f32).floor() as u8,
|
||||
y: (glyph_origin.y.0.fract() * SUBPIXEL_VARIANTS_Y as f32).floor() as u8,
|
||||
};
|
||||
let subpixel_rendering = self.should_use_subpixel_rendering(font_id, font_size);
|
||||
let params = RenderGlyphParams {
|
||||
font_id,
|
||||
glyph_id,
|
||||
@@ -3137,6 +3140,7 @@ impl Window {
|
||||
subpixel_variant,
|
||||
scale_factor,
|
||||
is_emoji: false,
|
||||
subpixel_rendering,
|
||||
};
|
||||
|
||||
let raster_bounds = self.text_system().raster_bounds(¶ms)?;
|
||||
@@ -3153,19 +3157,51 @@ impl Window {
|
||||
size: tile.bounds.size.map(Into::into),
|
||||
};
|
||||
let content_mask = self.content_mask().scale(scale_factor);
|
||||
self.next_frame.scene.insert_primitive(MonochromeSprite {
|
||||
order: 0,
|
||||
pad: 0,
|
||||
bounds,
|
||||
content_mask,
|
||||
color: color.opacity(element_opacity),
|
||||
tile,
|
||||
transformation: TransformationMatrix::unit(),
|
||||
});
|
||||
|
||||
if subpixel_rendering {
|
||||
self.next_frame.scene.insert_primitive(SubpixelSprite {
|
||||
order: 0,
|
||||
pad: 0,
|
||||
bounds,
|
||||
content_mask,
|
||||
color: color.opacity(element_opacity),
|
||||
tile,
|
||||
transformation: TransformationMatrix::unit(),
|
||||
});
|
||||
} else {
|
||||
self.next_frame.scene.insert_primitive(MonochromeSprite {
|
||||
order: 0,
|
||||
pad: 0,
|
||||
bounds,
|
||||
content_mask,
|
||||
color: color.opacity(element_opacity),
|
||||
tile,
|
||||
transformation: TransformationMatrix::unit(),
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn should_use_subpixel_rendering(&self, font_id: FontId, font_size: Pixels) -> bool {
|
||||
if self.platform_window.background_appearance() != WindowBackgroundAppearance::Opaque {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !self.platform_window.is_subpixel_rendering_supported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mode = match self.text_rendering_mode.get() {
|
||||
TextRenderingMode::PlatformDefault => self
|
||||
.text_system()
|
||||
.recommended_rendering_mode(font_id, font_size),
|
||||
mode => mode,
|
||||
};
|
||||
|
||||
mode == TextRenderingMode::Subpixel
|
||||
}
|
||||
|
||||
/// Paints an emoji glyph into the scene for the next frame at the current z-index.
|
||||
///
|
||||
/// The y component of the origin is the baseline of the glyph.
|
||||
@@ -3193,6 +3229,7 @@ impl Window {
|
||||
subpixel_variant: Default::default(),
|
||||
scale_factor,
|
||||
is_emoji: true,
|
||||
subpixel_rendering: false,
|
||||
};
|
||||
|
||||
let raster_bounds = self.text_system().raster_bounds(¶ms)?;
|
||||
|
||||
@@ -15,6 +15,10 @@ use crate::{
|
||||
pub struct WorkspaceSettingsContent {
|
||||
/// Active pane styling settings.
|
||||
pub active_pane_modifiers: Option<ActivePaneModifiers>,
|
||||
/// The text rendering mode to use.
|
||||
///
|
||||
/// Default: platform_default
|
||||
pub text_rendering_mode: Option<TextRenderingMode>,
|
||||
/// Layout mode for the bottom dock
|
||||
///
|
||||
/// Default: contained
|
||||
@@ -545,6 +549,31 @@ pub enum OnLastWindowClosed {
|
||||
QuitApp,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Copy,
|
||||
Clone,
|
||||
Default,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
JsonSchema,
|
||||
MergeFrom,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Debug,
|
||||
strum::VariantArray,
|
||||
strum::VariantNames,
|
||||
)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum TextRenderingMode {
|
||||
/// Use platform default behavior.
|
||||
#[default]
|
||||
PlatformDefault,
|
||||
/// Use subpixel (ClearType-style) text rendering.
|
||||
Subpixel,
|
||||
/// Use grayscale text rendering.
|
||||
Grayscale,
|
||||
}
|
||||
|
||||
impl OnLastWindowClosed {
|
||||
pub fn is_quit_app(&self) -> bool {
|
||||
match self {
|
||||
|
||||
@@ -817,6 +817,7 @@ impl VsCodeSettings {
|
||||
fn workspace_settings_content(&self) -> WorkspaceSettingsContent {
|
||||
WorkspaceSettingsContent {
|
||||
active_pane_modifiers: self.active_pane_modifiers(),
|
||||
text_rendering_mode: None,
|
||||
autosave: self.read_enum("files.autoSave", |s| match s {
|
||||
"off" => Some(AutosaveSetting::Off),
|
||||
"afterDelay" => Some(AutosaveSetting::AfterDelay {
|
||||
|
||||
@@ -890,6 +890,22 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SectionHeader("Text Rendering"),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Text Rendering Mode",
|
||||
description: "The text rendering mode to use.",
|
||||
field: Box::new(SettingField {
|
||||
json_path: Some("text_rendering_mode"),
|
||||
pick: |settings_content| {
|
||||
settings_content.workspace.text_rendering_mode.as_ref()
|
||||
},
|
||||
write: |settings_content, value| {
|
||||
settings_content.workspace.text_rendering_mode = value;
|
||||
},
|
||||
}),
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SectionHeader("Cursor"),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Multi Cursor Modifier",
|
||||
|
||||
@@ -436,6 +436,7 @@ fn init_renderers(cx: &mut App) {
|
||||
.add_basic_renderer::<settings::BottomDockLayout>(render_dropdown)
|
||||
.add_basic_renderer::<settings::OnLastWindowClosed>(render_dropdown)
|
||||
.add_basic_renderer::<settings::CloseWindowWhenNoItems>(render_dropdown)
|
||||
.add_basic_renderer::<settings::TextRenderingMode>(render_dropdown)
|
||||
.add_basic_renderer::<settings::FontFamilyName>(render_font_picker)
|
||||
.add_basic_renderer::<settings::BaseKeymapContent>(render_dropdown)
|
||||
.add_basic_renderer::<settings::MultiCursorModifier>(render_dropdown)
|
||||
|
||||
@@ -27,6 +27,7 @@ pub struct WorkspaceSettings {
|
||||
pub max_tabs: Option<NonZeroUsize>,
|
||||
pub when_closing_with_no_tabs: settings::CloseWindowWhenNoItems,
|
||||
pub on_last_window_closed: settings::OnLastWindowClosed,
|
||||
pub text_rendering_mode: settings::TextRenderingMode,
|
||||
pub resize_all_panels_in_dock: Vec<DockPosition>,
|
||||
pub close_on_file_delete: bool,
|
||||
pub use_system_window_tabs: bool,
|
||||
@@ -96,6 +97,7 @@ impl Settings for WorkspaceSettings {
|
||||
max_tabs: workspace.max_tabs,
|
||||
when_closing_with_no_tabs: workspace.when_closing_with_no_tabs.unwrap(),
|
||||
on_last_window_closed: workspace.on_last_window_closed.unwrap(),
|
||||
text_rendering_mode: workspace.text_rendering_mode.unwrap(),
|
||||
resize_all_panels_in_dock: workspace
|
||||
.resize_all_panels_in_dock
|
||||
.clone()
|
||||
|
||||
@@ -678,6 +678,18 @@ fn main() {
|
||||
.ok();
|
||||
}
|
||||
|
||||
cx.set_text_rendering_mode(
|
||||
match WorkspaceSettings::get_global(cx).text_rendering_mode {
|
||||
settings::TextRenderingMode::PlatformDefault => {
|
||||
gpui::TextRenderingMode::PlatformDefault
|
||||
}
|
||||
settings::TextRenderingMode::Subpixel => gpui::TextRenderingMode::Subpixel,
|
||||
settings::TextRenderingMode::Grayscale => {
|
||||
gpui::TextRenderingMode::Grayscale
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let new_host = &client::ClientSettings::get_global(cx).server_url;
|
||||
if &http.base_url() != new_host {
|
||||
http.set_base_url(new_host);
|
||||
|
||||
Reference in new issue
Block a user