diff --git a/assets/i18n/de.yaml b/assets/i18n/de.yaml index 351019ce9..35a43540b 100644 --- a/assets/i18n/de.yaml +++ b/assets/i18n/de.yaml @@ -285,6 +285,10 @@ "preferences.backend": "Renderer-Backend" "preferences.hwdecode.enable": "Hardwarebeschleunigte Videodekodierung (VideoToolbox / VA-API / NVDEC / D3D11VA)" "preferences.backend.placeholder": "Backend auswählen…" +"preferences.bit_depth.label": "Anzeige-Bittiefe" +"preferences.bit_depth.10bit": "10 Bit" +"preferences.bit_depth.8bit": "8 Bit" +"preferences.bit_depth.restart_hint": "Eine Änderung wird nach einem Neustart wirksam." "preferences.language": "Sprache" "preferences.language.placeholder": "Sprache auswählen…" "preferences.theme": "Design" diff --git a/assets/i18n/en-US.yaml b/assets/i18n/en-US.yaml index b7cdd33fd..9f385ed0a 100644 --- a/assets/i18n/en-US.yaml +++ b/assets/i18n/en-US.yaml @@ -285,6 +285,10 @@ "preferences.backend": "Renderer backend" "preferences.hwdecode.enable": "Hardware-accelerated video decoding (VideoToolbox / VA-API / NVDEC / D3D11VA)" "preferences.backend.placeholder": "Select a backend…" +"preferences.bit_depth.label": "Display bit depth" +"preferences.bit_depth.10bit": "10-bit" +"preferences.bit_depth.8bit": "8-bit" +"preferences.bit_depth.restart_hint": "A change takes effect after a restart." "preferences.language": "Language" "preferences.language.placeholder": "Select a language…" "preferences.theme": "Theme" diff --git a/assets/i18n/es.yaml b/assets/i18n/es.yaml index f70790ad3..15afe1002 100644 --- a/assets/i18n/es.yaml +++ b/assets/i18n/es.yaml @@ -285,6 +285,10 @@ "preferences.backend": "Motor de renderizado" "preferences.hwdecode.enable": "Decodificación de vídeo acelerada por hardware (VideoToolbox / VA-API / NVDEC / D3D11VA)" "preferences.backend.placeholder": "Seleccionar un motor…" +"preferences.bit_depth.label": "Profundidad de color de pantalla" +"preferences.bit_depth.10bit": "10 bits" +"preferences.bit_depth.8bit": "8 bits" +"preferences.bit_depth.restart_hint": "El cambio surte efecto tras reiniciar." "preferences.language": "Idioma" "preferences.language.placeholder": "Seleccionar un idioma…" "preferences.theme": "Tema" diff --git a/assets/i18n/fr.yaml b/assets/i18n/fr.yaml index 4523fd59b..b2767a1f5 100644 --- a/assets/i18n/fr.yaml +++ b/assets/i18n/fr.yaml @@ -285,6 +285,10 @@ "preferences.backend": "Moteur de rendu" "preferences.hwdecode.enable": "Décodage vidéo accéléré par le matériel (VideoToolbox / VA-API / NVDEC / D3D11VA)" "preferences.backend.placeholder": "Sélectionner un moteur…" +"preferences.bit_depth.label": "Profondeur de couleur de l'affichage" +"preferences.bit_depth.10bit": "10 bits" +"preferences.bit_depth.8bit": "8 bits" +"preferences.bit_depth.restart_hint": "Le changement prend effet après un redémarrage." "preferences.language": "Langue" "preferences.language.placeholder": "Sélectionner une langue…" "preferences.theme": "Thème" diff --git a/assets/i18n/ja.yaml b/assets/i18n/ja.yaml index cf6eb0eac..07b1ee757 100644 --- a/assets/i18n/ja.yaml +++ b/assets/i18n/ja.yaml @@ -285,6 +285,10 @@ "preferences.backend": "レンダラーバックエンド" "preferences.hwdecode.enable": "ハードウェアアクセラレーションによるビデオデコード(VideoToolbox / VA-API / NVDEC / D3D11VA)" "preferences.backend.placeholder": "バックエンドを選択…" +"preferences.bit_depth.label": "表示ビット深度" +"preferences.bit_depth.10bit": "10 ビット" +"preferences.bit_depth.8bit": "8 ビット" +"preferences.bit_depth.restart_hint": "変更は再起動後に反映されます。" "preferences.language": "言語" "preferences.language.placeholder": "言語を選択…" "preferences.theme": "テーマ" diff --git a/assets/i18n/pt.yaml b/assets/i18n/pt.yaml index 683927f5a..6a1f5d55e 100644 --- a/assets/i18n/pt.yaml +++ b/assets/i18n/pt.yaml @@ -285,6 +285,10 @@ "preferences.backend": "Backend do renderizador" "preferences.hwdecode.enable": "Decodificação de vídeo acelerada por hardware (VideoToolbox / VA-API / NVDEC / D3D11VA)" "preferences.backend.placeholder": "Selecionar um backend…" +"preferences.bit_depth.label": "Profundidade de bits da tela" +"preferences.bit_depth.10bit": "10 bits" +"preferences.bit_depth.8bit": "8 bits" +"preferences.bit_depth.restart_hint": "A alteração entra em vigor após reiniciar." "preferences.language": "Idioma" "preferences.language.placeholder": "Selecionar um idioma…" "preferences.theme": "Tema" diff --git a/assets/i18n/ru.yaml b/assets/i18n/ru.yaml index 185c182f1..961453bc3 100644 --- a/assets/i18n/ru.yaml +++ b/assets/i18n/ru.yaml @@ -285,6 +285,10 @@ "preferences.backend": "Бэкенд рендеринга" "preferences.hwdecode.enable": "Аппаратное декодирование видео (VideoToolbox / VA-API / NVDEC / D3D11VA)" "preferences.backend.placeholder": "Выберите бэкенд…" +"preferences.bit_depth.label": "Глубина цвета экрана" +"preferences.bit_depth.10bit": "10 бит" +"preferences.bit_depth.8bit": "8 бит" +"preferences.bit_depth.restart_hint": "Изменение вступит в силу после перезапуска." "preferences.language": "Язык" "preferences.language.placeholder": "Выберите язык…" "preferences.theme": "Тема" diff --git a/assets/i18n/zh-CN.yaml b/assets/i18n/zh-CN.yaml index 043199a49..c0f9da481 100644 --- a/assets/i18n/zh-CN.yaml +++ b/assets/i18n/zh-CN.yaml @@ -285,6 +285,10 @@ "preferences.backend": "渲染后端" "preferences.hwdecode.enable": "硬件加速视频解码(VideoToolbox / VA-API / NVDEC / D3D11VA)" "preferences.backend.placeholder": "选择一个后端…" +"preferences.bit_depth.label": "渲染色深" +"preferences.bit_depth.10bit": "10 位" +"preferences.bit_depth.8bit": "8 位" +"preferences.bit_depth.restart_hint": "更改将在重启后生效。" "preferences.language": "语言" "preferences.language.placeholder": "选择语言…" "preferences.theme": "主题" diff --git a/crates/oak-app/src/app.rs b/crates/oak-app/src/app.rs index 3ee9be338..a32b85cf0 100644 --- a/crates/oak-app/src/app.rs +++ b/crates/oak-app/src/app.rs @@ -3010,6 +3010,18 @@ fn run_with(args: AppArgs) { // SAFETY: single-threaded startup, before any window exists. unsafe { std::env::set_var("OAK_MACOS_LAYER_COLORSPACE", "display") }; } + // Display bit depth: the wgpu window layer must pick the swapchain + // format before the surface is created, so surface the persisted + // choice the same way the colorspace tag is passed to the platform + // layer above. Read by gpui_wgpu as OAK_DISPLAY_BIT_DEPTH ("8" opts + // into the 8-bit pair; anything else requests 10-bit). + let bit_depth = oak_render::backend::DisplayBitDepth::from_config_string( + &crate::oakui::real::config_get_string( + oak_render::backend::CONFIG_KEY_DISPLAY_BIT_DEPTH, + ), + ); + // SAFETY: single-threaded startup, before any window exists. + unsafe { std::env::set_var("OAK_DISPLAY_BIT_DEPTH", bit_depth.to_config_string()) }; cx.init_colors(); let bounds = Bounds::centered(None, size(px(1600.0), px(900.0)), cx); let initial = initial.clone(); diff --git a/crates/oak-app/src/dialogs.rs b/crates/oak-app/src/dialogs.rs index 417cee1ac..bd9e1d18c 100644 --- a/crates/oak-app/src/dialogs.rs +++ b/crates/oak-app/src/dialogs.rs @@ -54,6 +54,10 @@ use crate::oakui::real::{ DEFAULT_PREVIEW_WINDOW_FORWARD, DEFAULT_SNAPSHOT_INTERVAL_SEC, DEFAULT_TRANSITION_SEC, EXPORT_FORMAT_MP4, }; +// The `DisplayBitDepth` config key lives with the format mapping it +// drives (oak-render's backend); the preferences dropdown and the +// window-layer consumer share the same key. +use oak_render::backend::CONFIG_KEY_DISPLAY_BIT_DEPTH; // --------------------------------------------------------------------------- // Preferences @@ -100,6 +104,10 @@ impl gpui::EventEmitter for PreferencesContent {} /// exit). pub struct PreferencesContent { backend: Entity, + /// The on-screen display bit depth (10-bit default, 8-bit fallback). + /// The swapchain format is fixed at surface creation, so a change + /// takes effect after a restart. + display_bit_depth: Entity, language: Entity, theme: Entity, cache_dir: Entity, @@ -162,6 +170,37 @@ impl PreferencesContent { combo.set_selected(Some(backend_selected), cx) }); + // --- 渲染 Rendering: on-screen display bit depth --------------------- + // 10-bit is the default; 8-bit is the compatibility fallback. The + // swapchain format is chosen once at window/surface creation, so a + // change takes effect after a restart (see oak-render's + // `DisplayBitDepth::present_formats`). + let display_bit_depth = cx.new(|cx| { + let options = vec![ + ComboBoxOption::new(0, i18n::tr("preferences.bit_depth.10bit")), + ComboBoxOption::new(1, i18n::tr("preferences.bit_depth.8bit")), + ]; + ComboBox::new(12, options, window, cx) + }); + cx.subscribe(&display_bit_depth, |_this, _combo, event: &ComboBoxEvent, cx| { + if let ComboBoxEvent::Selected { value, .. } = event { + let depth = if *value == 1 { "8" } else { "10" }; + config_set_string(CONFIG_KEY_DISPLAY_BIT_DEPTH, depth); + println!("[preferences] display bit depth → {depth}"); + } + let _ = cx; + }) + .detach(); + let bit_depth_selected = + if config_get_string(CONFIG_KEY_DISPLAY_BIT_DEPTH) == "8" { + 1 + } else { + 0 + }; + display_bit_depth.update(cx, |combo, cx| { + combo.set_selected(Some(bit_depth_selected), cx) + }); + // --- 常规 General: language + theme -------------------------------- // Data-driven: one option per discovered language pack, labelled // with the pack's own `language.name` — a newly dropped-in pack @@ -494,6 +533,7 @@ impl PreferencesContent { Self { backend, + display_bit_depth, language, theme, cache_dir, @@ -855,6 +895,19 @@ impl Render for PreferencesContent { i18n::tr("preferences.audio.input").into(), self.audio_input.clone(), )) + // 渲染 Rendering: display bit depth (kept at the bottom of the list + // so the rows above stay inside the card's scroll viewport). + .child(form_row( + &colors, + i18n::tr("preferences.bit_depth.label").into(), + self.display_bit_depth.clone(), + )) + .child( + div() + .text_color(colors.disabled) + .text_xs() + .child(i18n::tr("preferences.bit_depth.restart_hint")), + ) .child( div() .text_color(colors.disabled) diff --git a/crates/oak-render/src/backend.rs b/crates/oak-render/src/backend.rs index c312f7215..d80009c7d 100644 --- a/crates/oak-render/src/backend.rs +++ b/crates/oak-render/src/backend.rs @@ -144,6 +144,74 @@ impl BackendKind { } } +/// The config-store key for the on-screen display bit depth ("10" or +/// "8"). The window swapchain format is chosen once at surface creation, +/// so a change takes effect after a restart. +pub const CONFIG_KEY_DISPLAY_BIT_DEPTH: &str = "DisplayBitDepth"; + +/// On-screen presentation bit depth, persisted under the +/// `DisplayBitDepth` config key. The choice is user-visible: the +/// preferences dialog exposes a 10-bit/8-bit dropdown, and it decides +/// the formats the window layer presents with (see +/// [`DisplayBitDepth::present_formats`]). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum DisplayBitDepth { + /// 8-bit per channel (Bgra8/Rgba8 swapchain). + Bit8, + /// 10-bit per channel (RGB10A2 — the default; RGBA16F for HDR). + Bit10, +} + +impl DisplayBitDepth { + /// Parse the settings string; unknown values yield the default 10-bit. + pub fn from_config_string(s: &str) -> DisplayBitDepth { + match s.trim() { + "8" => DisplayBitDepth::Bit8, + _ => DisplayBitDepth::Bit10, + } + } + + /// The config-store value persisted for this depth. + pub fn to_config_string(self) -> &'static str { + match self { + DisplayBitDepth::Bit8 => "8", + DisplayBitDepth::Bit10 => "10", + } + } + + /// Read the user's persisted choice through the oakcommon config C ABI + /// ("DisplayBitDepth"). + pub fn from_user_config() -> DisplayBitDepth { + let configured = + crate::commonutil::config_get_string(None, CONFIG_KEY_DISPLAY_BIT_DEPTH) + .unwrap_or_default(); + DisplayBitDepth::from_config_string(&configured) + } + + /// The wgpu surface formats the window layer should present with, in + /// preference order (the platform picks the first the surface + /// supports). 10-bit prefers RGB10A2 with an RGBA16F (HDR) fallback; + /// 8-bit mirrors the engine's current default preference list. + /// + /// The actual swapchain is configured by the window layer + /// (gpui_wgpu's surface setup intersects these with the surface + /// capabilities); this crate is headless and never creates a surface + /// itself, so the mapping is the format-selection contract the window + /// layer consumes at surface creation. + pub fn present_formats(self) -> &'static [wgpu::TextureFormat] { + match self { + DisplayBitDepth::Bit10 => &[ + wgpu::TextureFormat::Rgb10a2Unorm, + wgpu::TextureFormat::Rgba16Float, + ], + DisplayBitDepth::Bit8 => &[ + wgpu::TextureFormat::Bgra8Unorm, + wgpu::TextureFormat::Rgba8Unorm, + ], + } + } +} + /// A GPU-resident texture in the context registry. #[derive(Clone)] struct GpuTexture { @@ -1312,6 +1380,43 @@ mod tests { std::env::remove_var("OAK_RENDER_BACKEND"); } + #[test] + fn display_bit_depth_string_roundtrip() { + assert_eq!(DisplayBitDepth::from_config_string("8"), DisplayBitDepth::Bit8); + assert_eq!( + DisplayBitDepth::from_config_string("10"), + DisplayBitDepth::Bit10 + ); + // Unknown / missing → the 10-bit default. + assert_eq!(DisplayBitDepth::from_config_string(""), DisplayBitDepth::Bit10); + assert_eq!( + DisplayBitDepth::from_config_string("bogus"), + DisplayBitDepth::Bit10 + ); + assert_eq!( + DisplayBitDepth::from_config_string(DisplayBitDepth::Bit8.to_config_string()), + DisplayBitDepth::Bit8 + ); + assert_eq!( + DisplayBitDepth::from_config_string(DisplayBitDepth::Bit10.to_config_string()), + DisplayBitDepth::Bit10 + ); + } + + #[test] + fn display_bit_depth_present_formats() { + // 10-bit → the 10-bit (RGB10A2) swapchain format, HDR float fallback. + assert_eq!( + DisplayBitDepth::Bit10.present_formats(), + &[wgpu::TextureFormat::Rgb10a2Unorm, wgpu::TextureFormat::Rgba16Float] + ); + // 8-bit → the engine's current default preference list. + assert_eq!( + DisplayBitDepth::Bit8.present_formats(), + &[wgpu::TextureFormat::Bgra8Unorm, wgpu::TextureFormat::Rgba8Unorm] + ); + } + #[test] fn cpu_backend_has_no_adapter() { assert!(GpuContext::create(BackendKind::Cpu).is_none()); diff --git a/gpui b/gpui index 0b4329e50..4022d1db5 160000 --- a/gpui +++ b/gpui @@ -1 +1 @@ -Subproject commit 0b4329e50e6a107d470f447b262b13ef4f59a094 +Subproject commit 4022d1db5e90765b2a46fb6604d773602321e5a4