Switch to a single GPU context in Blade (#20853)
Some checks are pending
CI / Check Postgres and Protobuf migrations, mergability (push) Waiting to run
CI / Check formatting and spelling (push) Waiting to run
CI / (macOS) Run Clippy and tests (push) Waiting to run
CI / (Linux) Run Clippy and tests (push) Waiting to run
CI / (Linux) Build Remote Server (push) Waiting to run
CI / (Windows) Run Clippy and tests (push) Waiting to run
CI / Create a macOS bundle (push) Blocked by required conditions
CI / Create a Linux bundle (push) Blocked by required conditions
CI / Create arm64 Linux bundle (push) Blocked by required conditions
CI / Auto release preview (push) Blocked by required conditions
Deploy Docs / Deploy Docs (push) Waiting to run
Docs / Check formatting (push) Waiting to run
Script / ShellCheck Scripts (push) Waiting to run

Closes #17005

Release Notes:

- Improved GPU context management: share a single context with multiple
surfaces.

### High Level

Blade got a proper support for Surface objects in
https://github.com/kvark/blade/pull/203.
That was mainly motivated by Zed needing to draw multiple windows. With
the Surface API, Zed is now able to have the GPU context tied to the
"Platform" instead of "Window". Practically speaking, this means:
  - architecture more sound
  - faster to open/close windows
  - less surprises, more robust

### Concerns

1. Zed has been using a temporary workaround for the platform bug on
some Intel+Nvidia machines that makes us unable to present -
https://github.com/kvark/blade/pull/144 . This workaround is no longer
available with the new architecture. I'm looking for ideas on how to
approach this better.
- we are now picking up the change in
https://github.com/kvark/blade/pull/210, which allows forcing a specific
Device ID. This should allow Zed users to work around the issue. We
could help them to automate it, too.
2. ~~Metal-rs dependency is switched to
https://github.com/kvark/metal-rs/tree/blade, since upstream isn't
responsive in merging changes that are required for Blade. Hopefully,
temporary.~~
- ~~we can also hack around it by just transmuting the texture
references, since we know those are unchanged in the branch. That would
allow Blade to use it's own version of Metal, temporarily, if switching
metal-rs in the workspace is a concern.~~
- merged my metal-rs changes and updated Zed to use the upstream github
reference

---------

Co-authored-by: Mikayla Maki <mikayla@zed.dev>
Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
Dzmitry Malyshau 2024-12-18 13:47:09 -08:00 committed by GitHub
parent 56d20fc0a3
commit 298b9df589
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 380 additions and 341 deletions

33
Cargo.lock generated
View file

@ -1750,15 +1750,6 @@ dependencies = [
"bit-vec 0.6.3",
]
[[package]]
name = "bit-set"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0481a0e032742109b1133a095184ee93d88f3dc9e0d28a5d033dc77a073f44f"
dependencies = [
"bit-vec 0.7.0",
]
[[package]]
name = "bit-set"
version = "0.8.0"
@ -1774,12 +1765,6 @@ version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
[[package]]
name = "bit-vec"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22"
[[package]]
name = "bit-vec"
version = "0.8.0"
@ -1828,7 +1813,7 @@ dependencies = [
[[package]]
name = "blade-graphics"
version = "0.5.0"
source = "git+https://github.com/kvark/blade?rev=e142a3a5e678eb6a13e642ad8401b1f3aa38e969#e142a3a5e678eb6a13e642ad8401b1f3aa38e969"
source = "git+https://github.com/kvark/blade?rev=099555282605c7c4cca9e66a8f40148298347f80#099555282605c7c4cca9e66a8f40148298347f80"
dependencies = [
"ash",
"ash-window",
@ -1858,7 +1843,7 @@ dependencies = [
[[package]]
name = "blade-macros"
version = "0.3.0"
source = "git+https://github.com/kvark/blade?rev=e142a3a5e678eb6a13e642ad8401b1f3aa38e969#e142a3a5e678eb6a13e642ad8401b1f3aa38e969"
source = "git+https://github.com/kvark/blade?rev=099555282605c7c4cca9e66a8f40148298347f80#099555282605c7c4cca9e66a8f40148298347f80"
dependencies = [
"proc-macro2",
"quote",
@ -1868,7 +1853,7 @@ dependencies = [
[[package]]
name = "blade-util"
version = "0.1.0"
source = "git+https://github.com/kvark/blade?rev=e142a3a5e678eb6a13e642ad8401b1f3aa38e969#e142a3a5e678eb6a13e642ad8401b1f3aa38e969"
source = "git+https://github.com/kvark/blade?rev=099555282605c7c4cca9e66a8f40148298347f80#099555282605c7c4cca9e66a8f40148298347f80"
dependencies = [
"blade-graphics",
"bytemuck",
@ -7594,9 +7579,8 @@ dependencies = [
[[package]]
name = "metal"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21"
version = "0.30.0"
source = "git+https://github.com/gfx-rs/metal-rs?rev=ef768ff9d742ae6a0f4e83ddc8031264e7d460c4#ef768ff9d742ae6a0f4e83ddc8031264e7d460c4"
dependencies = [
"bitflags 2.6.0",
"block",
@ -7735,12 +7719,11 @@ checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03"
[[package]]
name = "naga"
version = "22.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bd5a652b6faf21496f2cfd88fc49989c8db0825d1f6746b1a71a6ede24a63ad"
version = "23.0.0"
source = "git+https://github.com/gfx-rs/wgpu?rev=1a643291c2e8854ba7e4f5445a4388202731bfa1#1a643291c2e8854ba7e4f5445a4388202731bfa1"
dependencies = [
"arrayvec",
"bit-set 0.6.0",
"bit-set 0.8.0",
"bitflags 2.6.0",
"cfg_aliases 0.1.1",
"codespan-reporting",

View file

@ -355,9 +355,9 @@ async-watch = "0.3.1"
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
base64 = "0.22"
bitflags = "2.6.0"
blade-graphics = { git = "https://github.com/kvark/blade", rev = "e142a3a5e678eb6a13e642ad8401b1f3aa38e969" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "e142a3a5e678eb6a13e642ad8401b1f3aa38e969" }
blade-util = { git = "https://github.com/kvark/blade", rev = "e142a3a5e678eb6a13e642ad8401b1f3aa38e969" }
blade-graphics = { git = "https://github.com/kvark/blade", rev = "099555282605c7c4cca9e66a8f40148298347f80" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "099555282605c7c4cca9e66a8f40148298347f80" }
blade-util = { git = "https://github.com/kvark/blade", rev = "099555282605c7c4cca9e66a8f40148298347f80" }
blake3 = "1.5.3"
bytes = "1.0"
cargo_metadata = "0.19"
@ -525,6 +525,12 @@ wasmtime-wasi = "24"
which = "6.0.0"
wit-component = "0.201"
zstd = "0.11"
# Custom metal-rs is only needed for "macos-blade" feature of GPUI
#TODO: switch to crates once these are published:
# - https://github.com/gfx-rs/metal-rs/pull/335
# - https://github.com/gfx-rs/metal-rs/pull/336
# - https://github.com/gfx-rs/metal-rs/pull/337
metal = { git = "https://github.com/gfx-rs/metal-rs", rev = "ef768ff9d742ae6a0f4e83ddc8031264e7d460c4" }
[workspace.dependencies.async-stripe]
git = "https://github.com/zed-industries/async-stripe"

View file

@ -136,8 +136,8 @@ font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "40391b7"
foreign-types = "0.5"
log.workspace = true
media.workspace = true
metal = "0.29"
objc = "0.2"
metal.workspace = true
[target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))'.dependencies]
pathfinder_geometry = "0.5"

View file

@ -1,5 +1,11 @@
#[cfg(target_os = "macos")]
mod apple_compat;
mod blade_atlas;
mod blade_context;
mod blade_renderer;
#[cfg(target_os = "macos")]
pub(crate) use apple_compat::*;
pub(crate) use blade_atlas::*;
pub(crate) use blade_context::*;
pub(crate) use blade_renderer::*;

View file

@ -0,0 +1,60 @@
use super::{BladeContext, BladeRenderer, BladeSurfaceConfig};
use blade_graphics as gpu;
use std::{ffi::c_void, ptr::NonNull};
#[derive(Clone)]
pub struct Context {
inner: BladeContext,
}
impl Default for Context {
fn default() -> Self {
Self {
inner: BladeContext::new().unwrap(),
}
}
}
pub type Renderer = BladeRenderer;
pub unsafe fn new_renderer(
context: Context,
_native_window: *mut c_void,
native_view: *mut c_void,
bounds: crate::Size<f32>,
transparent: bool,
) -> Renderer {
use raw_window_handle as rwh;
struct RawWindow {
view: *mut c_void,
}
impl rwh::HasWindowHandle for RawWindow {
fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
let view = NonNull::new(self.view).unwrap();
let handle = rwh::AppKitWindowHandle::new(view);
Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) })
}
}
impl rwh::HasDisplayHandle for RawWindow {
fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
let handle = rwh::AppKitDisplayHandle::new();
Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
}
}
BladeRenderer::new(
&context.inner,
&RawWindow {
view: native_view as *mut _,
},
BladeSurfaceConfig {
size: gpu::Extent {
width: bounds.width as u32,
height: bounds.height as u32,
depth: 1,
},
transparent,
},
)
.unwrap()
}

View file

@ -268,7 +268,7 @@ impl BladeAtlasState {
fn flush(&mut self, encoder: &mut gpu::CommandEncoder) {
self.flush_initializations(encoder);
let mut transfers = encoder.transfer();
let mut transfers = encoder.transfer("atlas");
for upload in self.uploads.drain(..) {
let texture = &self.storage[upload.id];
transfers.copy_buffer_to_texture(

View file

@ -0,0 +1,24 @@
use blade_graphics as gpu;
use std::sync::Arc;
#[cfg_attr(target_os = "macos", derive(Clone))]
pub struct BladeContext {
pub(super) gpu: Arc<gpu::Context>,
}
impl BladeContext {
pub fn new() -> anyhow::Result<Self> {
let gpu = Arc::new(
unsafe {
gpu::Context::init(gpu::ContextDesc {
presentation: true,
validation: false,
device_id: 0, //TODO: hook up to user settings
..Default::default()
})
}
.map_err(|e| anyhow::anyhow!("{:?}", e))?,
);
Ok(Self { gpu })
}
}

View file

@ -1,7 +1,7 @@
// Doing `if let` gives you nice scoping with passes/encoders
#![allow(irrefutable_let_patterns)]
use super::{BladeAtlas, PATH_TEXTURE_FORMAT};
use super::{BladeAtlas, BladeContext, PATH_TEXTURE_FORMAT};
use crate::{
AtlasTextureKind, AtlasTile, Background, Bounds, ContentMask, DevicePixels, GpuSpecs,
MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch, Quad,
@ -11,8 +11,6 @@ use bytemuck::{Pod, Zeroable};
use collections::HashMap;
#[cfg(target_os = "macos")]
use media::core_video::CVMetalTextureCache;
#[cfg(target_os = "macos")]
use std::{ffi::c_void, ptr::NonNull};
use blade_graphics as gpu;
use blade_util::{BufferBelt, BufferBeltDescriptor};
@ -20,66 +18,6 @@ use std::{mem, sync::Arc};
const MAX_FRAME_TIME_MS: u32 = 10000;
#[cfg(target_os = "macos")]
#[derive(Clone, Default)]
pub struct Context {}
#[cfg(target_os = "macos")]
pub type Renderer = BladeRenderer;
#[cfg(target_os = "macos")]
pub unsafe fn new_renderer(
_context: self::Context,
_native_window: *mut c_void,
native_view: *mut c_void,
bounds: crate::Size<f32>,
transparent: bool,
) -> Renderer {
use raw_window_handle as rwh;
struct RawWindow {
view: *mut c_void,
}
impl rwh::HasWindowHandle for RawWindow {
fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
let view = NonNull::new(self.view).unwrap();
let handle = rwh::AppKitWindowHandle::new(view);
Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) })
}
}
impl rwh::HasDisplayHandle for RawWindow {
fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
let handle = rwh::AppKitDisplayHandle::new();
Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
}
}
let gpu = Arc::new(
gpu::Context::init_windowed(
&RawWindow {
view: native_view as *mut _,
},
gpu::ContextDesc {
validation: cfg!(debug_assertions),
capture: false,
overlay: false,
},
)
.unwrap(),
);
BladeRenderer::new(
gpu,
BladeSurfaceConfig {
size: gpu::Extent {
width: bounds.width as u32,
height: bounds.height as u32,
depth: 1,
},
transparent,
},
)
}
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
struct GlobalParams {
@ -354,10 +292,14 @@ pub struct BladeSurfaceConfig {
pub transparent: bool,
}
//Note: we could see some of these fields moved into `BladeContext`
// so that they are shared between windows. E.g. `pipelines`.
// But that is complicated by the fact that pipelines depend on
// the format and alpha mode.
pub struct BladeRenderer {
gpu: Arc<gpu::Context>,
surface: gpu::Surface,
surface_config: gpu::SurfaceConfig,
alpha_mode: gpu::AlphaMode,
command_encoder: gpu::CommandEncoder,
last_sync_point: Option<gpu::SyncPoint>,
pipelines: BladePipelines,
@ -370,7 +312,11 @@ pub struct BladeRenderer {
}
impl BladeRenderer {
pub fn new(gpu: Arc<gpu::Context>, config: BladeSurfaceConfig) -> Self {
pub fn new<I: raw_window_handle::HasWindowHandle + raw_window_handle::HasDisplayHandle>(
context: &BladeContext,
window: &I,
config: BladeSurfaceConfig,
) -> anyhow::Result<Self> {
let surface_config = gpu::SurfaceConfig {
size: config.size,
usage: gpu::TextureUsage::TARGET,
@ -379,20 +325,23 @@ impl BladeRenderer {
allow_exclusive_full_screen: false,
transparent: config.transparent,
};
let surface_info = gpu.resize(surface_config);
let surface = context
.gpu
.create_surface_configured(window, surface_config)
.unwrap();
let command_encoder = gpu.create_command_encoder(gpu::CommandEncoderDesc {
let command_encoder = context.gpu.create_command_encoder(gpu::CommandEncoderDesc {
name: "main",
buffer_count: 2,
});
let pipelines = BladePipelines::new(&gpu, surface_info);
let pipelines = BladePipelines::new(&context.gpu, surface.info());
let instance_belt = BufferBelt::new(BufferBeltDescriptor {
memory: gpu::Memory::Shared,
min_chunk_size: 0x1000,
alignment: 0x40, // Vulkan `minStorageBufferOffsetAlignment` on Intel Xe
});
let atlas = Arc::new(BladeAtlas::new(&gpu));
let atlas_sampler = gpu.create_sampler(gpu::SamplerDesc {
let atlas = Arc::new(BladeAtlas::new(&context.gpu));
let atlas_sampler = context.gpu.create_sampler(gpu::SamplerDesc {
name: "atlas",
mag_filter: gpu::FilterMode::Linear,
min_filter: gpu::FilterMode::Linear,
@ -402,13 +351,13 @@ impl BladeRenderer {
#[cfg(target_os = "macos")]
let core_video_texture_cache = unsafe {
use foreign_types::ForeignType as _;
CVMetalTextureCache::new(gpu.metal_device().as_ptr()).unwrap()
CVMetalTextureCache::new(context.gpu.metal_device().as_ptr()).unwrap()
};
Self {
gpu,
Ok(Self {
gpu: Arc::clone(&context.gpu),
surface,
surface_config,
alpha_mode: surface_info.alpha,
command_encoder,
last_sync_point: None,
pipelines,
@ -418,7 +367,7 @@ impl BladeRenderer {
atlas_sampler,
#[cfg(target_os = "macos")]
core_video_texture_cache,
}
})
}
fn wait_for_gpu(&mut self) {
@ -452,7 +401,8 @@ impl BladeRenderer {
if always_resize || gpu_size != self.surface_config.size {
self.wait_for_gpu();
self.surface_config.size = gpu_size;
self.gpu.resize(self.surface_config);
self.gpu
.reconfigure_surface(&mut self.surface, self.surface_config);
}
}
@ -460,10 +410,10 @@ impl BladeRenderer {
if transparent != self.surface_config.transparent {
self.wait_for_gpu();
self.surface_config.transparent = transparent;
let surface_info = self.gpu.resize(self.surface_config);
self.gpu
.reconfigure_surface(&mut self.surface, self.surface_config);
self.pipelines.destroy(&self.gpu);
self.pipelines = BladePipelines::new(&self.gpu, surface_info);
self.alpha_mode = surface_info.alpha;
self.pipelines = BladePipelines::new(&self.gpu, self.surface.info());
}
}
@ -490,13 +440,13 @@ impl BladeRenderer {
#[cfg(target_os = "macos")]
pub fn layer(&self) -> metal::MetalLayer {
self.gpu.metal_layer().unwrap()
self.surface.metal_layer()
}
#[cfg(target_os = "macos")]
pub fn layer_ptr(&self) -> *mut metal::CAMetalLayer {
use metal::foreign_types::ForeignType as _;
self.gpu.metal_layer().unwrap().as_ptr()
self.surface.metal_layer().as_ptr()
}
#[profiling::function]
@ -538,14 +488,17 @@ impl BladeRenderer {
};
let vertex_buf = unsafe { self.instance_belt.alloc_typed(&vertices, &self.gpu) };
let mut pass = self.command_encoder.render(gpu::RenderTargetSet {
colors: &[gpu::RenderTarget {
view: tex_info.raw_view,
init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
finish_op: gpu::FinishOp::Store,
}],
depth_stencil: None,
});
let mut pass = self.command_encoder.render(
"paths",
gpu::RenderTargetSet {
colors: &[gpu::RenderTarget {
view: tex_info.raw_view,
init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
finish_op: gpu::FinishOp::Store,
}],
depth_stencil: None,
},
);
let mut encoder = pass.with(&self.pipelines.path_rasterization);
encoder.bind(
@ -566,6 +519,7 @@ impl BladeRenderer {
self.instance_belt.destroy(&self.gpu);
self.gpu.destroy_command_encoder(&mut self.command_encoder);
self.pipelines.destroy(&self.gpu);
self.gpu.destroy_surface(&mut self.surface);
}
pub fn draw(&mut self, scene: &Scene) {
@ -575,7 +529,7 @@ impl BladeRenderer {
let frame = {
profiling::scope!("acquire frame");
self.gpu.acquire_frame()
self.surface.acquire_frame()
};
self.command_encoder.init_texture(frame.texture());
@ -584,21 +538,24 @@ impl BladeRenderer {
self.surface_config.size.width as f32,
self.surface_config.size.height as f32,
],
premultiplied_alpha: match self.alpha_mode {
premultiplied_alpha: match self.surface.info().alpha {
gpu::AlphaMode::Ignored | gpu::AlphaMode::PostMultiplied => 0,
gpu::AlphaMode::PreMultiplied => 1,
},
pad: 0,
};
if let mut pass = self.command_encoder.render(gpu::RenderTargetSet {
colors: &[gpu::RenderTarget {
view: frame.texture_view(),
init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
finish_op: gpu::FinishOp::Store,
}],
depth_stencil: None,
}) {
if let mut pass = self.command_encoder.render(
"main",
gpu::RenderTargetSet {
colors: &[gpu::RenderTarget {
view: frame.texture_view(),
init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
finish_op: gpu::FinishOp::Store,
}],
depth_stencil: None,
},
) {
profiling::scope!("render pass");
for batch in scene.batches() {
match batch {

View file

@ -1,52 +1,45 @@
#![allow(unused)]
use std::any::{type_name, Any};
use std::cell::{self, RefCell};
use std::env;
use std::ffi::OsString;
use std::fs::File;
use std::io::Read;
use std::ops::{Deref, DerefMut};
use std::os::fd::{AsFd, AsRawFd, FromRawFd};
use std::panic::{AssertUnwindSafe, Location};
use std::rc::Weak;
use std::{
env,
panic::AssertUnwindSafe,
path::{Path, PathBuf},
process::Command,
rc::Rc,
sync::Arc,
};
#[cfg(any(feature = "wayland", feature = "x11"))]
use std::{
ffi::OsString,
fs::File,
io::Read as _,
os::fd::{AsFd, AsRawFd, FromRawFd},
time::Duration,
};
use anyhow::{anyhow, Context as _};
use async_task::Runnable;
use calloop::channel::Channel;
use calloop::{EventLoop, LoopHandle, LoopSignal};
use flume::{Receiver, Sender};
use calloop::{channel::Channel, LoopSignal};
use futures::{channel::oneshot, future::FutureExt};
use parking_lot::Mutex;
use util::ResultExt;
use util::ResultExt as _;
#[cfg(any(feature = "wayland", feature = "x11"))]
use xkbcommon::xkb::{self, Keycode, Keysym, State};
use crate::platform::NoopTextSystem;
use crate::{
px, Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
ForegroundExecutor, Keymap, Keystroke, LinuxDispatcher, Menu, MenuItem, Modifiers, OwnedMenu,
PathPromptOptions, Pixels, Platform, PlatformDisplay, PlatformInputHandler, PlatformTextSystem,
PlatformWindow, Point, PromptLevel, Result, ScreenCaptureSource, SemanticVersion, SharedString,
Size, Task, WindowAppearance, WindowOptions, WindowParams,
ForegroundExecutor, Keymap, LinuxDispatcher, Menu, MenuItem, OwnedMenu, PathPromptOptions,
Pixels, Platform, PlatformDisplay, PlatformTextSystem, PlatformWindow, Point, Result,
ScreenCaptureSource, Task, WindowAppearance, WindowParams,
};
#[cfg(any(feature = "wayland", feature = "x11"))]
pub(crate) const SCROLL_LINES: f32 = 3.0;
// Values match the defaults on GTK.
// Taken from https://github.com/GNOME/gtk/blob/main/gtk/gtksettings.c#L320
#[cfg(any(feature = "wayland", feature = "x11"))]
pub(crate) const DOUBLE_CLICK_INTERVAL: Duration = Duration::from_millis(400);
pub(crate) const DOUBLE_CLICK_DISTANCE: Pixels = px(5.0);
pub(crate) const KEYRING_LABEL: &str = "zed-github-account";
#[cfg(any(feature = "wayland", feature = "x11"))]
const FILE_PICKER_PORTAL_MISSING: &str =
"Couldn't open file picker due to missing xdg-desktop-portal implementation.";
@ -54,8 +47,9 @@ pub trait LinuxClient {
fn compositor_name(&self) -> &'static str;
fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R;
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
#[allow(unused)]
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
fn open_window(
&self,
@ -98,9 +92,9 @@ pub(crate) struct LinuxCommon {
impl LinuxCommon {
pub fn new(signal: LoopSignal) -> (Self, Channel<Runnable>) {
let (main_sender, main_receiver) = calloop::channel::channel::<Runnable>();
#[cfg(any(feature = "wayland", feature = "x11"))]
let text_system = Arc::new(crate::CosmicTextSystem::new());
#[cfg(not(any(feature = "wayland", feature = "x11")))]
let text_system = Arc::new(crate::NoopTextSystem::new());
@ -218,7 +212,7 @@ impl<P: LinuxClient + 'static> Platform for P {
}
}
fn activate(&self, ignoring_other_apps: bool) {
fn activate(&self, _ignoring_other_apps: bool) {
log::info!("activate is not implemented on Linux, ignoring the call")
}
@ -281,7 +275,7 @@ impl<P: LinuxClient + 'static> Platform for P {
let (done_tx, done_rx) = oneshot::channel();
#[cfg(not(any(feature = "wayland", feature = "x11")))]
done_tx.send(Ok(None));
let _ = (done_tx.send(Ok(None)), options);
#[cfg(any(feature = "wayland", feature = "x11"))]
self.foreground_executor()
@ -306,7 +300,7 @@ impl<P: LinuxClient + 'static> Platform for P {
ashpd::Error::PortalNotFound(_) => anyhow!(FILE_PICKER_PORTAL_MISSING),
err => err.into(),
};
done_tx.send(Err(result));
let _ = done_tx.send(Err(result));
return;
}
};
@ -322,7 +316,7 @@ impl<P: LinuxClient + 'static> Platform for P {
Err(ashpd::Error::Response(_)) => Ok(None),
Err(e) => Err(e.into()),
};
done_tx.send(result);
let _ = done_tx.send(result);
})
.detach();
done_rx
@ -332,7 +326,7 @@ impl<P: LinuxClient + 'static> Platform for P {
let (done_tx, done_rx) = oneshot::channel();
#[cfg(not(any(feature = "wayland", feature = "x11")))]
done_tx.send(Ok(None));
let _ = (done_tx.send(Ok(None)), directory);
#[cfg(any(feature = "wayland", feature = "x11"))]
self.foreground_executor()
@ -356,7 +350,7 @@ impl<P: LinuxClient + 'static> Platform for P {
}
err => err.into(),
};
done_tx.send(Err(result));
let _ = done_tx.send(Err(result));
return;
}
};
@ -369,7 +363,7 @@ impl<P: LinuxClient + 'static> Platform for P {
Err(ashpd::Error::Response(_)) => Ok(None),
Err(e) => Err(e.into()),
};
done_tx.send(result);
let _ = done_tx.send(result);
}
})
.detach();
@ -426,7 +420,7 @@ impl<P: LinuxClient + 'static> Platform for P {
fn app_path(&self) -> Result<PathBuf> {
// get the path of the executable of the current process
let exe_path = std::env::current_exe()?;
let exe_path = env::current_exe()?;
Ok(exe_path)
}
@ -440,9 +434,9 @@ impl<P: LinuxClient + 'static> Platform for P {
self.with_common(|common| Some(common.menus.clone()))
}
fn set_dock_menu(&self, menu: Vec<MenuItem>, keymap: &Keymap) {}
fn set_dock_menu(&self, _menu: Vec<MenuItem>, _keymap: &Keymap) {}
fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
fn path_for_auxiliary_executable(&self, _name: &str) -> Result<PathBuf> {
Err(anyhow::Error::msg(
"Platform<LinuxPlatform>::path_for_auxiliary_executable is not implemented yet",
))
@ -614,6 +608,7 @@ pub(super) fn reveal_path_internal(
.detach();
}
#[allow(unused)]
pub(super) fn is_within_click_distance(a: Point<Pixels>, b: Point<Pixels>) -> bool {
let diff = a - b;
diff.x.abs() <= DOUBLE_CLICK_DISTANCE && diff.y.abs() <= DOUBLE_CLICK_DISTANCE
@ -622,7 +617,7 @@ pub(super) fn is_within_click_distance(a: Point<Pixels>, b: Point<Pixels>) -> bo
#[cfg(any(feature = "wayland", feature = "x11"))]
pub(super) fn get_xkb_compose_state(cx: &xkb::Context) -> Option<xkb::compose::State> {
let mut locales = Vec::default();
if let Some(locale) = std::env::var_os("LC_CTYPE") {
if let Some(locale) = env::var_os("LC_CTYPE") {
locales.push(locale);
}
locales.push(OsString::from("C"));
@ -650,6 +645,7 @@ pub(super) unsafe fn read_fd(mut fd: filedescriptor::FileDescriptor) -> Result<V
}
impl CursorStyle {
#[allow(unused)]
pub(super) fn to_icon_name(&self) -> String {
// Based on cursor names from https://gitlab.gnome.org/GNOME/adwaita-icon-theme (GNOME)
// and https://github.com/KDE/breeze (KDE). Both of them seem to be also derived from
@ -682,10 +678,12 @@ impl CursorStyle {
}
#[cfg(any(feature = "wayland", feature = "x11"))]
impl Keystroke {
pub(super) fn from_xkb(state: &State, modifiers: Modifiers, keycode: Keycode) -> Self {
let mut modifiers = modifiers;
impl crate::Keystroke {
pub(super) fn from_xkb(
state: &State,
mut modifiers: crate::Modifiers,
keycode: Keycode,
) -> Self {
let key_utf32 = state.key_get_utf32(keycode);
let key_utf8 = state.key_get_utf8(keycode);
let key_sym = state.key_get_one_sym(keycode);
@ -759,7 +757,7 @@ impl Keystroke {
let key_char =
(key_utf32 >= 32 && key_utf32 != 127 && !key_utf8.is_empty()).then_some(key_utf8);
Keystroke {
Self {
modifiers,
key,
key_char,
@ -776,7 +774,6 @@ impl Keystroke {
Keysym::dead_acute => Some("´".to_owned()),
Keysym::dead_circumflex => Some("^".to_owned()),
Keysym::dead_tilde => Some("~".to_owned()),
Keysym::dead_perispomeni => Some("͂".to_owned()),
Keysym::dead_macron => Some("¯".to_owned()),
Keysym::dead_breve => Some("˘".to_owned()),
Keysym::dead_abovedot => Some("˙".to_owned()),
@ -794,9 +791,7 @@ impl Keystroke {
Keysym::dead_horn => Some("̛".to_owned()),
Keysym::dead_stroke => Some("̶̶".to_owned()),
Keysym::dead_abovecomma => Some("̓̓".to_owned()),
Keysym::dead_psili => Some("᾿".to_owned()),
Keysym::dead_abovereversedcomma => Some("ʽ".to_owned()),
Keysym::dead_dasia => Some("".to_owned()),
Keysym::dead_doublegrave => Some("̏".to_owned()),
Keysym::dead_belowring => Some("˳".to_owned()),
Keysym::dead_belowmacron => Some("̱".to_owned()),
@ -830,7 +825,7 @@ impl Keystroke {
}
#[cfg(any(feature = "wayland", feature = "x11"))]
impl Modifiers {
impl crate::Modifiers {
pub(super) fn from_xkb(keymap_state: &State) -> Self {
let shift = keymap_state.mod_name_is_active(xkb::MOD_NAME_SHIFT, xkb::STATE_MODS_EFFECTIVE);
let alt = keymap_state.mod_name_is_active(xkb::MOD_NAME_ALT, xkb::STATE_MODS_EFFECTIVE);
@ -838,7 +833,7 @@ impl Modifiers {
keymap_state.mod_name_is_active(xkb::MOD_NAME_CTRL, xkb::STATE_MODS_EFFECTIVE);
let platform =
keymap_state.mod_name_is_active(xkb::MOD_NAME_LOGO, xkb::STATE_MODS_EFFECTIVE);
Modifiers {
Self {
shift,
alt,
control,

View file

@ -1,12 +1,16 @@
use std::cell::{RefCell, RefMut};
use std::hash::Hash;
use std::os::fd::{AsRawFd, BorrowedFd};
use std::path::PathBuf;
use std::rc::{Rc, Weak};
use std::time::{Duration, Instant};
use std::{
cell::{RefCell, RefMut},
hash::Hash,
os::fd::{AsRawFd, BorrowedFd},
path::PathBuf,
rc::{Rc, Weak},
time::{Duration, Instant},
};
use calloop::timer::{TimeoutAction, Timer};
use calloop::{EventLoop, LoopHandle};
use calloop::{
timer::{TimeoutAction, Timer},
EventLoop, LoopHandle,
};
use calloop_wayland_source::WaylandSource;
use collections::HashMap;
use filedescriptor::Pipe;
@ -64,30 +68,28 @@ use xkbcommon::xkb::{self, Keycode, KEYMAP_COMPILE_NO_FLAGS};
use super::display::WaylandDisplay;
use super::window::{ImeInput, WaylandWindowStatePtr};
use crate::platform::linux::wayland::clipboard::{
Clipboard, DataOffer, FILE_LIST_MIME_TYPE, TEXT_MIME_TYPE,
};
use crate::platform::linux::wayland::cursor::Cursor;
use crate::platform::linux::wayland::serial::{SerialKind, SerialTracker};
use crate::platform::linux::wayland::window::WaylandWindow;
use crate::platform::linux::xdg_desktop_portal::{Event as XDPEvent, XDPEventSource};
use crate::platform::linux::LinuxClient;
use crate::platform::linux::{
get_xkb_compose_state, is_within_click_distance, open_uri_internal, read_fd,
reveal_path_internal,
wayland::{
clipboard::{Clipboard, DataOffer, FILE_LIST_MIME_TYPE, TEXT_MIME_TYPE},
cursor::Cursor,
serial::{SerialKind, SerialTracker},
window::WaylandWindow,
},
xdg_desktop_portal::{Event as XDPEvent, XDPEventSource},
LinuxClient,
};
use crate::platform::PlatformWindow;
use crate::platform::{blade::BladeContext, PlatformWindow};
use crate::{
point, px, size, Bounds, DevicePixels, FileDropEvent, ForegroundExecutor, MouseExitEvent, Size,
DOUBLE_CLICK_INTERVAL, SCROLL_LINES,
point, px, size, AnyWindowHandle, Bounds, CursorStyle, DevicePixels, DisplayId, FileDropEvent,
ForegroundExecutor, KeyDownEvent, KeyUpEvent, Keystroke, LinuxCommon, Modifiers,
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseExitEvent, MouseMoveEvent,
MouseUpEvent, NavigationDirection, Pixels, PlatformDisplay, PlatformInput, Point, ScaledPixels,
ScrollDelta, ScrollWheelEvent, Size, TouchPhase, WindowParams, DOUBLE_CLICK_INTERVAL,
SCROLL_LINES,
};
use crate::{
AnyWindowHandle, CursorStyle, DisplayId, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers,
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
NavigationDirection, Pixels, PlatformDisplay, PlatformInput, Point, ScaledPixels, ScrollDelta,
ScrollWheelEvent, TouchPhase,
};
use crate::{LinuxCommon, WindowParams};
/// Used to convert evdev scancode to xkb scancode
const MIN_KEYCODE: u32 = 8;
@ -186,6 +188,7 @@ pub struct Output {
pub(crate) struct WaylandClientState {
serial_tracker: SerialTracker,
globals: Globals,
gpu_context: BladeContext,
wl_seat: wl_seat::WlSeat, // TODO: Multi seat support
wl_pointer: Option<wl_pointer::WlPointer>,
wl_keyboard: Option<wl_keyboard::WlKeyboard>,
@ -459,6 +462,8 @@ impl WaylandClient {
})
.unwrap();
let gpu_context = BladeContext::new().expect("Unable to init GPU context");
let seat = seat.unwrap();
let globals = Globals::new(
globals,
@ -512,6 +517,7 @@ impl WaylandClient {
let mut state = Rc::new(RefCell::new(WaylandClientState {
serial_tracker: SerialTracker::new(),
globals,
gpu_context,
wl_seat: seat,
wl_pointer: None,
wl_keyboard: None,
@ -627,6 +633,7 @@ impl LinuxClient for WaylandClient {
let (window, surface_id) = WaylandWindow::new(
handle,
state.globals.clone(),
&state.gpu_context,
WaylandClientStatePtr(Rc::downgrade(&self.0)),
params,
state.common.appearance,

View file

@ -1,8 +1,10 @@
use std::cell::{Ref, RefCell, RefMut};
use std::ffi::c_void;
use std::ptr::NonNull;
use std::rc::Rc;
use std::sync::Arc;
use std::{
cell::{Ref, RefCell, RefMut},
ffi::c_void,
ptr::NonNull,
rc::Rc,
sync::Arc,
};
use blade_graphics as gpu;
use collections::HashMap;
@ -19,10 +21,11 @@ use wayland_protocols::xdg::shell::client::xdg_surface;
use wayland_protocols::xdg::shell::client::xdg_toplevel::{self};
use wayland_protocols_plasma::blur::client::org_kde_kwin_blur;
use crate::platform::blade::{BladeRenderer, BladeSurfaceConfig};
use crate::platform::linux::wayland::display::WaylandDisplay;
use crate::platform::linux::wayland::serial::SerialKind;
use crate::platform::{PlatformAtlas, PlatformInputHandler, PlatformWindow};
use crate::platform::{
blade::{BladeContext, BladeRenderer, BladeSurfaceConfig},
linux::wayland::{display::WaylandDisplay, serial::SerialKind},
PlatformAtlas, PlatformInputHandler, PlatformWindow,
};
use crate::scene::Scene;
use crate::{
px, size, AnyWindowHandle, Bounds, Decorations, Globals, GpuSpecs, Modifiers, Output, Pixels,
@ -123,37 +126,28 @@ impl WaylandWindowState {
viewport: Option<wp_viewport::WpViewport>,
client: WaylandClientStatePtr,
globals: Globals,
gpu_context: &BladeContext,
options: WindowParams,
) -> anyhow::Result<Self> {
let raw = RawWindow {
window: surface.id().as_ptr().cast::<c_void>(),
display: surface
.backend()
.upgrade()
.unwrap()
.display_ptr()
.cast::<c_void>(),
};
let gpu = Arc::new(
unsafe {
gpu::Context::init_windowed(
&raw,
gpu::ContextDesc {
validation: false,
capture: false,
overlay: false,
},
)
}
.map_err(|e| anyhow::anyhow!("{:?}", e))?,
);
let config = BladeSurfaceConfig {
size: gpu::Extent {
width: options.bounds.size.width.0 as u32,
height: options.bounds.size.height.0 as u32,
depth: 1,
},
transparent: true,
let renderer = {
let raw_window = RawWindow {
window: surface.id().as_ptr().cast::<c_void>(),
display: surface
.backend()
.upgrade()
.unwrap()
.display_ptr()
.cast::<c_void>(),
};
let config = BladeSurfaceConfig {
size: gpu::Extent {
width: options.bounds.size.width.0 as u32,
height: options.bounds.size.height.0 as u32,
depth: 1,
},
transparent: true,
};
BladeRenderer::new(gpu_context, &raw_window, config)?
};
Ok(Self {
@ -168,7 +162,7 @@ impl WaylandWindowState {
globals,
outputs: HashMap::default(),
display: None,
renderer: BladeRenderer::new(gpu, config),
renderer,
bounds: options.bounds,
scale: 1.0,
input_handler: None,
@ -266,6 +260,7 @@ impl WaylandWindow {
pub fn new(
handle: AnyWindowHandle,
globals: Globals,
gpu_context: &BladeContext,
client: WaylandClientStatePtr,
params: WindowParams,
appearance: WindowAppearance,
@ -308,6 +303,7 @@ impl WaylandWindow {
viewport,
client,
globals,
gpu_context,
params,
)?)),
callbacks: Rc::new(RefCell::new(Callbacks::default())),

View file

@ -1,13 +1,17 @@
use core::str;
use std::cell::RefCell;
use std::collections::{BTreeMap, HashSet};
use std::ops::Deref;
use std::path::PathBuf;
use std::rc::{Rc, Weak};
use std::time::{Duration, Instant};
use std::{
cell::RefCell,
collections::{BTreeMap, HashSet},
ops::Deref,
path::PathBuf,
rc::{Rc, Weak},
time::{Duration, Instant},
};
use calloop::generic::{FdWrapper, Generic};
use calloop::{EventLoop, LoopHandle, RegistrationToken};
use calloop::{
generic::{FdWrapper, Generic},
EventLoop, LoopHandle, RegistrationToken,
};
use anyhow::Context as _;
use collections::HashMap;
@ -15,44 +19,49 @@ use http_client::Url;
use smallvec::SmallVec;
use util::ResultExt;
use x11rb::connection::{Connection, RequestConnection};
use x11rb::cursor;
use x11rb::errors::ConnectionError;
use x11rb::protocol::randr::ConnectionExt as _;
use x11rb::protocol::xinput::ConnectionExt;
use x11rb::protocol::xkb::ConnectionExt as _;
use x11rb::protocol::xproto::{
AtomEnum, ChangeWindowAttributesAux, ClientMessageData, ClientMessageEvent, ConnectionExt as _,
EventMask, KeyPressEvent,
use x11rb::{
connection::{Connection, RequestConnection},
cursor,
errors::ConnectionError,
protocol::randr::ConnectionExt as _,
protocol::xinput::ConnectionExt,
protocol::xkb::ConnectionExt as _,
protocol::xproto::{
AtomEnum, ChangeWindowAttributesAux, ClientMessageData, ClientMessageEvent,
ConnectionExt as _, EventMask, KeyPressEvent,
},
protocol::{randr, render, xinput, xkb, xproto, Event},
resource_manager::Database,
wrapper::ConnectionExt as _,
xcb_ffi::XCBConnection,
};
use x11rb::protocol::{randr, render, xinput, xkb, xproto, Event};
use x11rb::resource_manager::Database;
use x11rb::wrapper::ConnectionExt as _;
use x11rb::xcb_ffi::XCBConnection;
use xim::{x11rb::X11rbClient, Client};
use xim::{AttributeName, InputStyle};
use xim::{x11rb::X11rbClient, AttributeName, Client, InputStyle};
use xkbc::x11::ffi::{XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION};
use xkbcommon::xkb::{self as xkbc, LayoutIndex, ModMask};
use crate::platform::linux::LinuxClient;
use crate::platform::{LinuxCommon, PlatformWindow};
use crate::{
modifiers_from_xinput_info, point, px, AnyWindowHandle, Bounds, ClipboardItem, CursorStyle,
DisplayId, FileDropEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, Pixels,
Platform, PlatformDisplay, PlatformInput, Point, RequestFrameOptions, ScaledPixels,
ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
};
use super::{
button_or_scroll_from_event_detail, get_valuator_axis_index, modifiers_from_state,
pressed_button_from_mask, ButtonOrScroll, ScrollDirection,
};
use super::{X11Display, X11WindowStatePtr, XcbAtoms};
use super::{XimCallbackEvent, XimHandler};
use crate::platform::linux::platform::{DOUBLE_CLICK_INTERVAL, SCROLL_LINES};
use crate::platform::linux::xdg_desktop_portal::{Event as XDPEvent, XDPEventSource};
use crate::platform::linux::{
get_xkb_compose_state, is_within_click_distance, open_uri_internal, reveal_path_internal,
use crate::platform::{
blade::BladeContext,
linux::{
get_xkb_compose_state, is_within_click_distance, open_uri_internal,
platform::{DOUBLE_CLICK_INTERVAL, SCROLL_LINES},
reveal_path_internal,
xdg_desktop_portal::{Event as XDPEvent, XDPEventSource},
LinuxClient,
},
LinuxCommon, PlatformWindow,
};
use crate::{
modifiers_from_xinput_info, point, px, AnyWindowHandle, Bounds, ClipboardItem, CursorStyle,
DisplayId, FileDropEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, Pixels,
Platform, PlatformDisplay, PlatformInput, Point, RequestFrameOptions, ScaledPixels,
ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
};
/// Value for DeviceId parameters which selects all devices.
@ -158,6 +167,8 @@ pub struct X11ClientState {
pub(crate) last_location: Point<Pixels>,
pub(crate) current_count: usize,
gpu_context: BladeContext,
pub(crate) scale_factor: f32,
xkb_context: xkbc::Context,
@ -360,6 +371,8 @@ impl X11Client {
let compose_state = get_xkb_compose_state(&xkb_context);
let resource_database = x11rb::resource_manager::new_from_default(&xcb_connection).unwrap();
let gpu_context = BladeContext::new().expect("Unable to init GPU context");
let scale_factor = resource_database
.get_value("Xft.dpi", "Xft.dpi")
.ok()
@ -428,6 +441,7 @@ impl X11Client {
last_mouse_button: None,
last_location: Point::new(px(0.0), px(0.0)),
current_count: 0,
gpu_context,
scale_factor,
xkb_context,
@ -1299,6 +1313,7 @@ impl LinuxClient for X11Client {
handle,
X11ClientStatePtr(Rc::downgrade(&self.0)),
state.common.foreground_executor.clone(),
&state.gpu_context,
params,
&state.xcb_connection,
state.client_side_decorations_supported,

View file

@ -1,7 +1,7 @@
use anyhow::{anyhow, Context};
use crate::platform::blade::{BladeContext, BladeRenderer, BladeSurfaceConfig};
use crate::{
platform::blade::{BladeRenderer, BladeSurfaceConfig},
px, size, AnyWindowHandle, Bounds, Decorations, DevicePixels, ForegroundExecutor, GpuSpecs,
Modifiers, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler,
PlatformWindow, Point, PromptLevel, RequestFrameOptions, ResizeEdge, ScaledPixels, Scene, Size,
@ -247,7 +247,6 @@ pub struct X11WindowState {
x_root_window: xproto::Window,
pub(crate) counter_id: sync::Counter,
pub(crate) last_sync_counter: Option<sync::Int64>,
_raw: RawWindow,
bounds: Bounds<Pixels>,
scale_factor: f32,
renderer: BladeRenderer,
@ -358,6 +357,7 @@ impl X11WindowState {
handle: AnyWindowHandle,
client: X11ClientStatePtr,
executor: ForegroundExecutor,
gpu_context: &BladeContext,
params: WindowParams,
xcb: &Rc<XCBConnection>,
client_side_decorations_supported: bool,
@ -555,50 +555,39 @@ impl X11WindowState {
xcb.flush().with_context(|| "X11 Flush failed.")?;
let raw = RawWindow {
connection: as_raw_xcb_connection::AsRawXcbConnection::as_raw_xcb_connection(xcb)
as *mut _,
screen_id: x_screen_index,
window_id: x_window,
visual_id: visual.id,
let renderer = {
let raw_window = RawWindow {
connection: as_raw_xcb_connection::AsRawXcbConnection::as_raw_xcb_connection(
xcb,
) as *mut _,
screen_id: x_screen_index,
window_id: x_window,
visual_id: visual.id,
};
let config = BladeSurfaceConfig {
// Note: this has to be done after the GPU init, or otherwise
// the sizes are immediately invalidated.
size: query_render_extent(xcb, x_window)?,
// We set it to transparent by default, even if we have client-side
// decorations, since those seem to work on X11 even without `true` here.
// If the window appearance changes, then the renderer will get updated
// too
transparent: false,
};
BladeRenderer::new(gpu_context, &raw_window, config)?
};
let gpu = Arc::new(
unsafe {
gpu::Context::init_windowed(
&raw,
gpu::ContextDesc {
validation: false,
capture: false,
overlay: false,
},
)
}
.map_err(|e| anyhow!("{:?}", e))?,
);
let config = BladeSurfaceConfig {
// Note: this has to be done after the GPU init, or otherwise
// the sizes are immediately invalidated.
size: query_render_extent(xcb, x_window)?,
// We set it to transparent by default, even if we have client-side
// decorations, since those seem to work on X11 even without `true` here.
// If the window appearance changes, then the renderer will get updated
// too
transparent: false,
};
check_reply(|| "X11 MapWindow failed.", xcb.map_window(x_window))?;
let display = Rc::new(X11Display::new(xcb, scale_factor, x_screen_index)?);
Ok(Self {
client,
executor,
display,
_raw: raw,
x_root_window: visual_set.root,
bounds: bounds.to_pixels(scale_factor),
scale_factor,
renderer: BladeRenderer::new(gpu, config),
renderer,
atoms: *atoms,
input_handler: None,
active: false,
@ -716,6 +705,7 @@ impl X11Window {
handle: AnyWindowHandle,
client: X11ClientStatePtr,
executor: ForegroundExecutor,
gpu_context: &BladeContext,
params: WindowParams,
xcb: &Rc<XCBConnection>,
client_side_decorations_supported: bool,
@ -730,6 +720,7 @@ impl X11Window {
handle,
client,
executor,
gpu_context,
params,
xcb,
client_side_decorations_supported,

View file

@ -28,11 +28,12 @@ use windows::{
UI::ViewManagement::UISettings,
};
use crate::*;
use crate::{platform::blade::BladeContext, *};
pub(crate) struct WindowsPlatform {
state: RefCell<WindowsPlatformState>,
raw_window_handles: RwLock<SmallVec<[HWND; 4]>>,
gpu_context: BladeContext,
// The below members will never change throughout the entire lifecycle of the app.
icon: HICON,
main_receiver: flume::Receiver<Runnable>,
@ -94,12 +95,14 @@ impl WindowsPlatform {
let icon = load_icon().unwrap_or_default();
let state = RefCell::new(WindowsPlatformState::new());
let raw_window_handles = RwLock::new(SmallVec::new());
let gpu_context = BladeContext::new().expect("Unable to init GPU context");
let windows_version = WindowsVersion::new().expect("Error retrieve windows version");
let validation_number = rand::random::<usize>();
Self {
state,
raw_window_handles,
gpu_context,
icon,
main_receiver,
dispatch_event,
@ -344,7 +347,12 @@ impl Platform for WindowsPlatform {
handle: AnyWindowHandle,
options: WindowParams,
) -> Result<Box<dyn PlatformWindow>> {
let window = WindowsWindow::new(handle, options, self.generate_creation_info())?;
let window = WindowsWindow::new(
handle,
options,
self.generate_creation_info(),
&self.gpu_context,
)?;
let handle = window.get_raw_handle();
self.raw_window_handles.write().push(handle);

View file

@ -27,7 +27,7 @@ use windows::{
},
};
use crate::platform::blade::BladeRenderer;
use crate::platform::blade::{BladeContext, BladeRenderer};
use crate::*;
pub(crate) struct WindowsWindow(pub Rc<WindowsWindowStatePtr>);
@ -78,6 +78,7 @@ impl WindowsWindowState {
cs: &CREATESTRUCTW,
current_cursor: HCURSOR,
display: WindowsDisplay,
gpu_context: &BladeContext,
) -> Result<Self> {
let scale_factor = {
let monitor_dpi = unsafe { GetDpiForWindow(hwnd) } as f32;
@ -94,7 +95,7 @@ impl WindowsWindowState {
};
let border_offset = WindowBorderOffset::default();
let is_minimized = None;
let renderer = windows_renderer::windows_renderer(hwnd, transparent)?;
let renderer = windows_renderer::init(gpu_context, hwnd, transparent)?;
let callbacks = Callbacks::default();
let input_handler = None;
let system_key_handled = false;
@ -227,6 +228,7 @@ impl WindowsWindowStatePtr {
cs,
context.current_cursor,
context.display,
context.gpu_context,
)?);
Ok(Rc::new_cyclic(|this| Self {
@ -340,7 +342,7 @@ pub(crate) struct Callbacks {
pub(crate) appearance_changed: Option<Box<dyn FnMut()>>,
}
struct WindowCreateContext {
struct WindowCreateContext<'a> {
inner: Option<Result<Rc<WindowsWindowStatePtr>>>,
handle: AnyWindowHandle,
hide_title_bar: bool,
@ -352,6 +354,7 @@ struct WindowCreateContext {
windows_version: WindowsVersion,
validation_number: usize,
main_receiver: flume::Receiver<Runnable>,
gpu_context: &'a BladeContext,
}
impl WindowsWindow {
@ -359,6 +362,7 @@ impl WindowsWindow {
handle: AnyWindowHandle,
params: WindowParams,
creation_info: WindowCreationInfo,
gpu_context: &BladeContext,
) -> Result<Self> {
let WindowCreationInfo {
icon,
@ -410,6 +414,7 @@ impl WindowsWindow {
windows_version,
validation_number,
main_receiver,
gpu_context,
};
let lpparam = Some(&context as *const _ as *const _);
let creation_result = unsafe {
@ -1236,38 +1241,24 @@ fn set_window_composition_attribute(hwnd: HWND, color: Option<Color>, state: u32
}
mod windows_renderer {
use std::{num::NonZeroIsize, sync::Arc};
use blade_graphics as gpu;
use crate::platform::blade::{BladeContext, BladeRenderer, BladeSurfaceConfig};
use raw_window_handle as rwh;
use std::num::NonZeroIsize;
use windows::Win32::{Foundation::HWND, UI::WindowsAndMessaging::GWLP_HINSTANCE};
use crate::{
get_window_long,
platform::blade::{BladeRenderer, BladeSurfaceConfig},
};
use crate::get_window_long;
pub(super) fn windows_renderer(hwnd: HWND, transparent: bool) -> anyhow::Result<BladeRenderer> {
pub(super) fn init(
context: &BladeContext,
hwnd: HWND,
transparent: bool,
) -> anyhow::Result<BladeRenderer> {
let raw = RawWindow { hwnd };
let gpu: Arc<gpu::Context> = Arc::new(
unsafe {
gpu::Context::init_windowed(
&raw,
gpu::ContextDesc {
validation: false,
capture: false,
overlay: false,
},
)
}
.map_err(|e| anyhow::anyhow!("{:?}", e))?,
);
let config = BladeSurfaceConfig {
size: gpu::Extent::default(),
size: Default::default(),
transparent,
};
Ok(BladeRenderer::new(gpu, config))
BladeRenderer::new(context, &raw, config)
}
struct RawWindow {

View file

@ -19,7 +19,7 @@ anyhow.workspace = true
core-foundation.workspace = true
ctor.workspace = true
foreign-types = "0.5"
metal = "0.29"
metal.workspace = true
objc = "0.2"
[build-dependencies]