render/app: display bit depth option (10-bit default, 8-bit optional)

Preferences gains a display-bit-depth combo stored in the config store;
at startup the app forwards it to gpui_wgpu via OAK_DISPLAY_BIT_DEPTH,
which prefers Rgb10a2Unorm for 10-bit presentation. Takes effect after
restart (noted in the dialog); i18n in all eight packs.
This commit is contained in:
2026-08-27 19:08:55 +08:00
parent 7422ccd7e9
commit 5d21f83e1e
12 changed files with 203 additions and 1 deletions
+4
View File
@@ -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"
+4
View File
@@ -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"
+4
View File
@@ -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"
+4
View File
@@ -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"
+4
View File
@@ -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": "テーマ"
+4
View File
@@ -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"
+4
View File
@@ -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": "Тема"
+4
View File
@@ -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": "主题"
+12
View File
@@ -3010,6 +3010,18 @@ fn run_with<E: AppEngine>(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();
+53
View File
@@ -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<PreferencesEvent> for PreferencesContent {}
/// exit).
pub struct PreferencesContent {
backend: Entity<ComboBox>,
/// 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<ComboBox>,
language: Entity<ComboBox>,
theme: Entity<ComboBox>,
cache_dir: Entity<PathField>,
@@ -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)
+105
View File
@@ -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());
+1 -1
Submodule gpui updated: 0b4329e50e...4022d1db5e