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
+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());