Rebrand to Galaxy, major improvements to Bedrock support, still needs some TLC though
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
//! Module containing types to report GPU information that can be useful debug purposes.
|
||||
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
/// Function called when a GPU device is first selected upon constructing a window.
|
||||
pub type OnGPUDeviceSelected = dyn Fn(GPUDeviceInfo) + 'static + Send + Sync;
|
||||
|
||||
/// Physical GPU device types.
|
||||
/// This is a direct fork of wgpu's `DeviceType` struct. However, we redefine it to avoid a direct
|
||||
/// dependency on wgpu in cases where we don't rely on the wgpu rendering backend.
|
||||
///
|
||||
/// See <https://docs.rs/wgpu/latest/wgpu/enum.DeviceType.html> for more details.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum GPUDeviceType {
|
||||
/// Other or Unknown.
|
||||
Other,
|
||||
/// Integrated GPU with shared CPU/GPU memory.
|
||||
IntegratedGpu,
|
||||
/// Discrete GPU with separate CPU/GPU memory.
|
||||
DiscreteGpu,
|
||||
/// Virtual / Hosted.
|
||||
VirtualGpu,
|
||||
/// Cpu / Software Rendering.
|
||||
Cpu,
|
||||
}
|
||||
|
||||
/// The GPU backend that is being renderer to.
|
||||
/// This is a direct fork of wgpu's `Backend` struct. However, we redefine it to avoid a direct
|
||||
/// dependency on wgpu in cases where we don't rely on the wgpu rendering backend.
|
||||
///
|
||||
/// See <https://docs.rs/wgpu/latest/wgpu/enum.Backend.html> for more details.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum GPUBackend {
|
||||
/// Dummy backend, used for testing.
|
||||
Empty,
|
||||
/// Vulkan API
|
||||
Vulkan,
|
||||
/// Metal API (Apple platforms)
|
||||
Metal,
|
||||
/// Direct3D-12 (Windows)
|
||||
Dx12,
|
||||
/// OpenGL ES-3 (Linux, Android)
|
||||
Gl,
|
||||
/// WebGPU in the browser
|
||||
BrowserWebGpu,
|
||||
}
|
||||
|
||||
impl Display for GPUBackend {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
GPUBackend::Empty => write!(f, "Empty"),
|
||||
GPUBackend::Vulkan => write!(f, "Vulkan"),
|
||||
GPUBackend::Metal => write!(f, "Metal"),
|
||||
GPUBackend::Dx12 => write!(f, "Dx12"),
|
||||
GPUBackend::Gl => write!(f, "Gl"),
|
||||
GPUBackend::BrowserWebGpu => write!(f, "BrowserWebGpu"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Information about the GPU device a given window is rendering to.
|
||||
#[derive(Debug)]
|
||||
pub struct GPUDeviceInfo {
|
||||
/// The type of the device we are rendering to (e.g. integrated vs discrete).
|
||||
pub device_type: GPUDeviceType,
|
||||
/// The name of the GPU _device_ we are rendering to.
|
||||
pub device_name: String,
|
||||
/// The name of the GPU _driver_ that the OS is using to connect to the given GPU device.
|
||||
pub driver_name: String,
|
||||
/// Any additional information about the driver that the OS is using to connect to the given
|
||||
/// GPU device.
|
||||
pub driver_info: String,
|
||||
/// The backend (e.g. Metal vs Vulkan vs OpenGL) we using when rendering.
|
||||
pub backend: GPUBackend,
|
||||
}
|
||||
|
||||
impl Display for GPUDeviceType {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
GPUDeviceType::Other => write!(f, "Other"),
|
||||
GPUDeviceType::IntegratedGpu => write!(f, "Integrated"),
|
||||
GPUDeviceType::DiscreteGpu => write!(f, "Discrete"),
|
||||
GPUDeviceType::VirtualGpu => write!(f, "Virtual"),
|
||||
GPUDeviceType::Cpu => write!(f, "Cpu"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
mod gpu_info;
|
||||
pub mod texture_cache;
|
||||
pub use gpu_info::{GPUBackend, GPUDeviceInfo, GPUDeviceType, OnGPUDeviceSelected};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::platform::GraphicsBackend;
|
||||
|
||||
/// Circumstances under which glyphs should be rasterized with thin strokes.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schema_gen", derive(schemars::JsonSchema))]
|
||||
#[cfg_attr(
|
||||
feature = "schema_gen",
|
||||
schemars(
|
||||
description = "When to render text with thinner strokes for a lighter appearance.",
|
||||
rename_all = "snake_case"
|
||||
)
|
||||
)]
|
||||
#[cfg_attr(feature = "settings_value", derive(settings_value::SettingsValue))]
|
||||
pub enum ThinStrokes {
|
||||
/// Never render glyphs using thin strokes.
|
||||
Never,
|
||||
/// Render glyphs using thin strokes when rendering on a low-DPI display.
|
||||
OnLowDpiDisplays,
|
||||
/// Render glyphs using thin strokes when rendering on a high-DPI display.
|
||||
#[default]
|
||||
OnHighDpiDisplays,
|
||||
/// Always render glyphs using thin strokes.
|
||||
Always,
|
||||
}
|
||||
|
||||
impl ThinStrokes {
|
||||
/// The minimum scale factor for which we'll consider a display to be high-DPI.
|
||||
const HIGH_DPI_SCALE_FACTOR: f32 = 1.5;
|
||||
|
||||
pub fn enabled_for_scale_factor(&self, scale_factor: f32) -> bool {
|
||||
match self {
|
||||
Self::Never => false,
|
||||
Self::OnLowDpiDisplays => scale_factor < Self::HIGH_DPI_SCALE_FACTOR,
|
||||
Self::OnHighDpiDisplays => scale_factor >= Self::HIGH_DPI_SCALE_FACTOR,
|
||||
Self::Always => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Options for configuring rendering of glyphs.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub struct GlyphConfig {
|
||||
/// Whether to render glyphs using thin strokes.
|
||||
pub use_thin_strokes: ThinStrokes,
|
||||
}
|
||||
|
||||
/// Power preference for GPU for rendering.
|
||||
///
|
||||
/// Relevant for machines with multiple GPUs (typically a discrete high-performance GPU and an
|
||||
/// integrated low-power-usage GPU).
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
pub enum GPUPowerPreference {
|
||||
LowPower,
|
||||
#[default]
|
||||
HighPerformance,
|
||||
}
|
||||
|
||||
/// Options for configuring rendering at the application level. These options
|
||||
/// will apply for the entirety of a frame, but may change between frames.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub struct Config {
|
||||
/// Configuration options relating to glyph rendering.
|
||||
pub glyphs: GlyphConfig,
|
||||
|
||||
/// Power preference for GPU used for rendering; this is applicable on dual GPU machines where
|
||||
/// there's a choice between a discrete high-performance GPU and a more power-efficient
|
||||
/// integrated GPU.
|
||||
pub gpu_power_preference: GPUPowerPreference,
|
||||
|
||||
pub backend_preference: Option<GraphicsBackend>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct CornerRadius {
|
||||
pub top_left: f32,
|
||||
pub top_right: f32,
|
||||
pub bottom_left: f32,
|
||||
pub bottom_right: f32,
|
||||
}
|
||||
|
||||
impl CornerRadius {
|
||||
pub fn from_ui_corner_radius(
|
||||
corner_radius: crate::scene::CornerRadius,
|
||||
scale_factor: f32,
|
||||
min_dimension: f32,
|
||||
) -> Self {
|
||||
let top_left = match corner_radius.get_top_left() {
|
||||
crate::scene::Radius::Pixels(px) => px * scale_factor,
|
||||
crate::scene::Radius::Percentage(percent) => percent / 100. * min_dimension,
|
||||
};
|
||||
let top_right = match corner_radius.get_top_right() {
|
||||
crate::scene::Radius::Pixels(px) => px * scale_factor,
|
||||
crate::scene::Radius::Percentage(percent) => percent / 100. * min_dimension,
|
||||
};
|
||||
let bottom_left = match corner_radius.get_bottom_left() {
|
||||
crate::scene::Radius::Pixels(px) => px * scale_factor,
|
||||
crate::scene::Radius::Percentage(percent) => percent / 100. * min_dimension,
|
||||
};
|
||||
let bottom_right = match corner_radius.get_bottom_right() {
|
||||
crate::scene::Radius::Pixels(px) => px * scale_factor,
|
||||
crate::scene::Radius::Percentage(percent) => percent / 100. * min_dimension,
|
||||
};
|
||||
Self {
|
||||
top_left,
|
||||
top_right,
|
||||
bottom_left,
|
||||
bottom_right,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
use crate::image_cache::StaticImage;
|
||||
|
||||
/// An opaque identifier for a texture from which we can render an image.
|
||||
///
|
||||
/// This *MUST* only be used within a single frame, and is not safe to use
|
||||
/// across frames.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct TextureCacheIndex(usize);
|
||||
|
||||
pub struct TextureInfo<T> {
|
||||
/// The actual information about the texture.
|
||||
inner: T,
|
||||
|
||||
/// The backing asset for the texture.
|
||||
asset: Weak<StaticImage>,
|
||||
|
||||
/// The index of the last frame on which this texture was accessed. This
|
||||
/// is used to know when a texture has gone "stale" and can be dropped from
|
||||
/// the cache.
|
||||
last_accessed_frame: usize,
|
||||
}
|
||||
|
||||
/// A simple cache for textures from which we can render images.
|
||||
pub struct TextureCache<T> {
|
||||
textures: Vec<TextureInfo<T>>,
|
||||
|
||||
/// The index of the last frame that was rendered.
|
||||
frame_index: usize,
|
||||
}
|
||||
|
||||
impl<T> TextureCache<T> {
|
||||
/// The maximum number of frames that a texture can go unused before it
|
||||
/// gets dropped from the cache.
|
||||
const MAX_UNUSED_FRAMES: usize = 10;
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
textures: Default::default(),
|
||||
frame_index: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, texture_id: TextureCacheIndex) -> Option<&T> {
|
||||
self.textures.get(texture_id.0).map(|info| &info.inner)
|
||||
}
|
||||
|
||||
pub fn get_or_insert_by_asset(
|
||||
&mut self,
|
||||
asset: &Arc<StaticImage>,
|
||||
texinfo_func: impl FnOnce(&Arc<StaticImage>) -> T,
|
||||
) -> (TextureCacheIndex, &T) {
|
||||
let mut found = None;
|
||||
let weak_asset = Arc::downgrade(asset);
|
||||
for (index, texture) in self.textures.iter().enumerate() {
|
||||
if texture.asset.ptr_eq(&weak_asset) {
|
||||
found = Some(index);
|
||||
}
|
||||
}
|
||||
let index = match found {
|
||||
Some(index) => index,
|
||||
None => {
|
||||
self.textures.push(TextureInfo {
|
||||
inner: texinfo_func(asset),
|
||||
asset: weak_asset.clone(),
|
||||
last_accessed_frame: self.frame_index,
|
||||
});
|
||||
self.textures.len() - 1
|
||||
}
|
||||
};
|
||||
|
||||
// This array lookup is safe, as we either found the texture in the
|
||||
// cache or we inserted a new one and returned its index.
|
||||
self.textures[index].last_accessed_frame = self.frame_index;
|
||||
|
||||
(TextureCacheIndex(index), &self.textures[index].inner)
|
||||
}
|
||||
|
||||
/// Updates the texture cache at the end of a frame.
|
||||
///
|
||||
/// This should be called at the end of every frame to ensure that stale
|
||||
/// texture resources get cleaned up.
|
||||
pub fn end_frame(&mut self) {
|
||||
// Drop any textures which are no longer referenced by the asset cache
|
||||
// or have not been rendered in the last MAX_UNUSED_FRAMES frames.
|
||||
self.textures.retain(|texture| {
|
||||
texture.asset.strong_count() > 0
|
||||
&& self.frame_index - texture.last_accessed_frame < Self::MAX_UNUSED_FRAMES
|
||||
});
|
||||
|
||||
self.frame_index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for TextureCache<T> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "texture_cache_tests.rs"]
|
||||
mod tests;
|
||||
@@ -0,0 +1,92 @@
|
||||
use super::*;
|
||||
use crate::image_cache::test_utils::make_static_image;
|
||||
|
||||
#[test]
|
||||
fn test_end_frame_evicts_when_asset_dropped() {
|
||||
let mut cache = TextureCache::<()>::new();
|
||||
let asset = make_static_image(4, 4);
|
||||
let weak = Arc::downgrade(&asset);
|
||||
|
||||
cache.get_or_insert_by_asset(&asset, |_| ());
|
||||
|
||||
// Dropping the only strong reference makes strong_count == 0.
|
||||
drop(asset);
|
||||
assert_eq!(weak.strong_count(), 0);
|
||||
|
||||
// end_frame should detect strong_count == 0 and evict the entry.
|
||||
cache.end_frame();
|
||||
assert_eq!(
|
||||
cache.textures.len(),
|
||||
0,
|
||||
"TextureCache should evict entries whose backing asset has been dropped (cascade invariant)"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_end_frame_retains_asset_in_use() {
|
||||
let mut cache = TextureCache::<()>::new();
|
||||
let asset = make_static_image(4, 4);
|
||||
|
||||
cache.get_or_insert_by_asset(&asset, |_| ());
|
||||
|
||||
// The Arc is still alive; the texture should be retained.
|
||||
cache.end_frame();
|
||||
assert_eq!(
|
||||
cache.textures.len(),
|
||||
1,
|
||||
"TextureCache should retain entries whose backing asset is still alive"
|
||||
);
|
||||
|
||||
drop(asset);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_end_frame_evicts_after_max_unused_frames() {
|
||||
let mut cache = TextureCache::<()>::new();
|
||||
let asset = make_static_image(4, 4);
|
||||
|
||||
cache.get_or_insert_by_asset(&asset, |_| ());
|
||||
|
||||
// Advance past MAX_UNUSED_FRAMES without re-accessing the texture.
|
||||
// The asset is still alive (strong_count > 0), but the texture goes stale.
|
||||
for _ in 0..TextureCache::<()>::MAX_UNUSED_FRAMES {
|
||||
cache.end_frame();
|
||||
assert_eq!(
|
||||
cache.textures.len(),
|
||||
1,
|
||||
"Texture should still be present before MAX_UNUSED_FRAMES is exceeded"
|
||||
);
|
||||
}
|
||||
|
||||
// One more end_frame tips it over the threshold.
|
||||
cache.end_frame();
|
||||
assert_eq!(
|
||||
cache.textures.len(),
|
||||
0,
|
||||
"TextureCache should evict stale entries after MAX_UNUSED_FRAMES unused frames"
|
||||
);
|
||||
|
||||
drop(asset);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_end_frame_retains_recently_used_entry() {
|
||||
let mut cache = TextureCache::<()>::new();
|
||||
let asset = make_static_image(4, 4);
|
||||
|
||||
cache.get_or_insert_by_asset(&asset, |_| ());
|
||||
|
||||
// Re-access the texture each frame to keep it fresh.
|
||||
for _ in 0..=TextureCache::<()>::MAX_UNUSED_FRAMES {
|
||||
cache.get_or_insert_by_asset(&asset, |_| ());
|
||||
cache.end_frame();
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
cache.textures.len(),
|
||||
1,
|
||||
"Texture accessed every frame should never be evicted by the frame-count check"
|
||||
);
|
||||
|
||||
drop(asset);
|
||||
}
|
||||
Reference in New Issue
Block a user