diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ddb386a8..da90dcae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [macOS-latest, windows-2019, ubuntu-latest] + os: [macOS-latest, windows-2019] name: cargo clippy+test steps: - uses: actions/checkout@v2 @@ -56,7 +56,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: clippy - args: --manifest-path=Cargo.toml --all-targets --features=x11,raw-win-handle --no-default-features -- -D warnings + args: --manifest-path=Cargo.toml --all-targets --features=x11 --no-default-features -- -D warnings # Test packages in deeper-to-higher dependency order - name: cargo test glazier @@ -65,113 +65,113 @@ jobs: command: test args: --manifest-path=Cargo.toml --no-default-features --features=x11 - # we test the wayland backend as a separate job - test-stable-wayland: - runs-on: ubuntu-latest - name: cargo clippy+test (wayland) - steps: - - uses: actions/checkout@v2 - - - name: install wayland - run: | - sudo apt update - sudo apt install libwayland-dev libpango1.0-dev libxkbcommon-dev - - - name: install stable toolchain - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - components: clippy - profile: minimal - override: true - - - name: restore cache - uses: Swatinem/rust-cache@v1 - - - name: cargo clippy glazier - uses: actions-rs/cargo@v1 - with: - command: clippy - args: --manifest-path=Cargo.toml --all-targets --features=wayland,raw-win-handle --no-default-features -- -D warnings - - - name: cargo test glazier - uses: actions-rs/cargo@v1 - with: - command: test - args: --manifest-path=Cargo.toml --features wayland --no-default-features - - # we test the gtk backend as a separate job because gtk install takes - # a long time. - test-stable-gtk: - runs-on: ubuntu-latest - name: cargo clippy+test (gtk) - steps: - - uses: actions/checkout@v2 - - - name: install libgtk-3-dev - run: | - sudo apt update - sudo apt install libgtk-3-dev - - - name: install stable toolchain - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - components: clippy - profile: minimal - override: true - - - name: restore cache - uses: Swatinem/rust-cache@v1 - - - name: cargo clippy glazier - uses: actions-rs/cargo@v1 - with: - command: clippy - args: --manifest-path=Cargo.toml --all-targets --features=raw-win-handle -- -D warnings - - - name: cargo test glazier - uses: actions-rs/cargo@v1 - with: - command: test - args: --manifest-path=Cargo.toml - - test-stable-wasm: - runs-on: macOS-latest - name: cargo test (wasm32) - steps: - - uses: actions/checkout@v2 - - - name: install wasm-pack - uses: jetli/wasm-pack-action@v0.3.0 - with: - version: latest - - - name: install stable toolchain - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: wasm32-unknown-unknown - components: clippy - profile: minimal - override: true - - - name: restore cache - uses: Swatinem/rust-cache@v1 - - - name: cargo clippy glazier (wasm) - uses: actions-rs/cargo@v1 - with: - command: clippy - args: --manifest-path=Cargo.toml --all-targets --target wasm32-unknown-unknown -- -D warnings - - # Test wasm32 relevant packages in deeper-to-higher dependency order - # TODO: Find a way to make tests work. Until then the tests are merely compiled. - - name: cargo test compile glazier - uses: actions-rs/cargo@v1 - with: - command: test - args: --manifest-path=Cargo.toml --no-run --target wasm32-unknown-unknown +# # we test the wayland backend as a separate job +# test-stable-wayland: +# runs-on: ubuntu-latest +# name: cargo clippy+test (wayland) +# steps: +# - uses: actions/checkout@v2 +# +# - name: install wayland +# run: | +# sudo apt update +# sudo apt install libwayland-dev libpango1.0-dev libxkbcommon-dev +# +# - name: install stable toolchain +# uses: actions-rs/toolchain@v1 +# with: +# toolchain: stable +# components: clippy +# profile: minimal +# override: true +# +# - name: restore cache +# uses: Swatinem/rust-cache@v1 +# +# - name: cargo clippy glazier +# uses: actions-rs/cargo@v1 +# with: +# command: clippy +# args: --manifest-path=Cargo.toml --all-targets --features=wayland --no-default-features -- -D warnings +# +# - name: cargo test glazier +# uses: actions-rs/cargo@v1 +# with: +# command: test +# args: --manifest-path=Cargo.toml --features wayland --no-default-features +# +# # we test the gtk backend as a separate job because gtk install takes +# # a long time. +# test-stable-gtk: +# runs-on: ubuntu-latest +# name: cargo clippy+test (gtk) +# steps: +# - uses: actions/checkout@v2 +# +# - name: install libgtk-3-dev +# run: | +# sudo apt update +# sudo apt install libgtk-3-dev +# +# - name: install stable toolchain +# uses: actions-rs/toolchain@v1 +# with: +# toolchain: stable +# components: clippy +# profile: minimal +# override: true +# +# - name: restore cache +# uses: Swatinem/rust-cache@v1 +# +# - name: cargo clippy glazier +# uses: actions-rs/cargo@v1 +# with: +# command: clippy +# args: --manifest-path=Cargo.toml --all-targets -- -D warnings +# +# - name: cargo test glazier +# uses: actions-rs/cargo@v1 +# with: +# command: test +# args: --manifest-path=Cargo.toml + +# test-stable-wasm: +# runs-on: macOS-latest +# name: cargo test (wasm32) +# steps: +# - uses: actions/checkout@v2 +# +# - name: install wasm-pack +# uses: jetli/wasm-pack-action@v0.3.0 +# with: +# version: latest +# +# - name: install stable toolchain +# uses: actions-rs/toolchain@v1 +# with: +# toolchain: stable +# target: wasm32-unknown-unknown +# components: clippy +# profile: minimal +# override: true +# +# - name: restore cache +# uses: Swatinem/rust-cache@v1 +# +# - name: cargo clippy glazier (wasm) +# uses: actions-rs/cargo@v1 +# with: +# command: clippy +# args: --manifest-path=Cargo.toml --all-targets --target wasm32-unknown-unknown -- -D warnings +# +# # Test wasm32 relevant packages in deeper-to-higher dependency order +# # TODO: Find a way to make tests work. Until then the tests are merely compiled. +# - name: cargo test compile glazier +# uses: actions-rs/cargo@v1 +# with: +# command: test +# args: --manifest-path=Cargo.toml --no-run --target wasm32-unknown-unknown doctest-stable: runs-on: macOS-latest @@ -222,7 +222,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: clippy - args: --manifest-path=Cargo.toml --all-targets --features=raw-win-handle -- -D warnings + args: --manifest-path=Cargo.toml --all-targets -- -D warnings continue-on-error: true # Test packages in deeper-to-higher dependency order @@ -260,7 +260,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [macOS-latest, windows-2019, ubuntu-latest] + os: [macOS-latest, windows-2019] steps: - uses: actions/checkout@v2 diff --git a/Cargo.toml b/Cargo.toml index eb7d67fb..99f569a9 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,12 +14,11 @@ rustdoc-args = ["--cfg", "docsrs"] default-target = "x86_64-pc-windows-msvc" [features] -default = ["gtk"] -gtk = ["gdk-sys", "glib-sys", "gtk-sys", "gtk-rs"] +default = ["x11"] +gtk = ["cairo-rs", "gdk-sys", "glib-sys", "gtk-sys", "gtk-rs"] x11 = [ "ashpd", "bindgen", - "cairo-sys-rs", "futures", "nix", "pkg-config", @@ -39,31 +38,29 @@ wayland = [ "bindgen", "pkg-config", ] -# Implement HasRawWindowHandle for WindowHandle -raw-win-handle = ["raw-window-handle"] # passing on all the image features. AVIF is not supported because it does not # support decoding, and that's all we use `Image` for. -image_png = ["piet-common/image_png"] -jpeg = ["piet-common/jpeg"] -jpeg_rayon = ["piet-common/jpeg_rayon"] -gif = ["piet-common/gif"] -bmp = ["piet-common/bmp"] -ico = ["piet-common/ico"] -tiff = ["piet-common/tiff"] -webp = ["piet-common/webp"] -pnm = ["piet-common/pnm"] -dds = ["piet-common/dds"] -tga = ["piet-common/tga"] -farbfeld = ["piet-common/farbfeld"] -dxt = ["piet-common/dxt"] -hdr = ["piet-common/hdr"] +bmp = [] +dds = [] +dxt = [] +farbfeld = [] +gif = [] +jpeg = [] +png = [] +ico = [] +tiff = [] +webp = [] +tga = [] +hdr = [] +image_png = [] +jpeg_rayon = [] +pnm = [] serde = ["kurbo/serde"] [dependencies] # NOTE: When changing the piet or kurbo versions, ensure that # the kurbo version included in piet is compatible with the kurbo version specified here. -piet-common = "=0.5.0" kurbo = "0.8.2" tracing = "0.1.22" @@ -75,8 +72,8 @@ anyhow = "1.0.32" keyboard-types = { version = "0.6.2", default_features = false } # Optional dependencies -image = { version = "0.23.12", optional = true, default_features = false } -raw-window-handle = { version = "0.5.0", optional = true, default_features = false } +image = { version = "0.24.4", optional = true, default_features = false } +raw-window-handle = { version = "0.5.0", default_features = false } [target.'cfg(target_os="windows")'.dependencies] scopeguard = "1.1.0" @@ -100,7 +97,7 @@ bitflags = "1.2.1" [target.'cfg(any(target_os = "freebsd", target_os="linux", target_os="openbsd"))'.dependencies] ashpd = { version = "0.3.0", optional = true } # TODO(x11/dependencies): only use feature "xcb" if using X11 -cairo-rs = { version = "0.14.0", default_features = false, features = ["xcb"] } +cairo-rs = { version = "0.14.0", default_features = false, features = ["xcb"], optional = true } cairo-sys-rs = { version = "0.14.0", default_features = false, optional = true } futures = { version = "0.3.21", optional = true, features = ["executor"]} gdk-sys = { version = "0.14.0", optional = true } @@ -127,12 +124,12 @@ version = "0.3.44" features = ["Window", "MouseEvent", "CssStyleDeclaration", "WheelEvent", "KeyEvent", "KeyboardEvent", "Navigator"] [dev-dependencies] -piet-common = { version = "=0.5.0", features = ["png"] } static_assertions = "1.1.0" test-log = { version = "0.2.5", features = ["trace"], default-features = false } tracing-subscriber = { version = "0.3.2", features = ["env-filter"] } unicode-segmentation = "1.7.0" +piet-gpu-hal = { git = "https://github.com/linebender/piet-gpu", branch = "dev" } [build-dependencies] -bindgen = {version = "0.58", optional = true} +bindgen = { version = "0.61", optional = true } pkg-config = { version = "0.3", optional = true } diff --git a/examples/edit_text.rs b/examples/edit_text.rs deleted file mode 100644 index 33e26e1c..00000000 --- a/examples/edit_text.rs +++ /dev/null @@ -1,368 +0,0 @@ -// Copyright 2020 The Druid Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! This example shows a how a single-line text field might be implemented for glazier. -//! Beyond the omission of multiple lines and text wrapping, it also is missing many motions -//! (like "move to previous word") and bidirectional text support. - -use std::any::Any; -use std::borrow::Cow; -use std::cell::RefCell; -use std::ops::Range; -use std::rc::Rc; - -use unicode_segmentation::GraphemeCursor; - -use glazier::kurbo::Size; -use glazier::piet::{ - Color, FontFamily, HitTestPoint, PietText, PietTextLayout, RenderContext, Text, TextLayout, - TextLayoutBuilder, -}; - -use glazier::{ - keyboard_types::Key, text, text::Action, text::Event, text::InputHandler, text::Selection, - text::VerticalMovement, Application, KeyEvent, Region, TextFieldToken, WinHandler, - WindowBuilder, WindowHandle, -}; - -use glazier::kurbo::{Point, Rect}; - -const BG_COLOR: Color = Color::rgb8(0xff, 0xff, 0xff); -const COMPOSITION_BG_COLOR: Color = Color::rgb8(0xff, 0xd8, 0x6e); -const SELECTION_BG_COLOR: Color = Color::rgb8(0x87, 0xc5, 0xff); -const CARET_COLOR: Color = Color::rgb8(0x00, 0x82, 0xfc); -const FONT: FontFamily = FontFamily::SANS_SERIF; -const FONT_SIZE: f64 = 16.0; - -#[derive(Default)] -struct AppState { - size: Size, - handle: WindowHandle, - document: Rc>, - text_input_token: Option, -} - -#[derive(Default)] -struct DocumentState { - text: String, - selection: Selection, - composition: Option>, - text_engine: Option, - layout: Option, -} - -impl DocumentState { - fn refresh_layout(&mut self) { - let text_engine = self.text_engine.as_mut().unwrap(); - self.layout = Some( - text_engine - .new_text_layout(self.text.clone()) - .font(FONT, FONT_SIZE) - .build() - .unwrap(), - ); - } -} - -impl WinHandler for AppState { - fn connect(&mut self, handle: &WindowHandle) { - self.handle = handle.clone(); - let token = self.handle.add_text_field(); - self.handle.set_focused_text_field(Some(token)); - self.text_input_token = Some(token); - let mut doc = self.document.borrow_mut(); - doc.text_engine = Some(handle.text()); - doc.refresh_layout(); - } - - fn prepare_paint(&mut self) { - self.handle.invalidate(); - } - - fn paint(&mut self, piet: &mut piet_common::Piet, _: &Region) { - let rect = self.size.to_rect(); - piet.fill(rect, &BG_COLOR); - let doc = self.document.borrow(); - let layout = doc.layout.as_ref().unwrap(); - // TODO(lord): rects for range on layout - if let Some(composition_range) = doc.composition.as_ref() { - for rect in layout.rects_for_range(composition_range.clone()) { - piet.fill(rect, &COMPOSITION_BG_COLOR); - } - } - if !doc.selection.is_caret() { - for rect in layout.rects_for_range(doc.selection.range()) { - piet.fill(rect, &SELECTION_BG_COLOR); - } - } - piet.draw_text(layout, (0.0, 0.0)); - - // draw caret - let caret_x = layout.hit_test_text_position(doc.selection.active).point.x; - piet.fill( - Rect::new(caret_x - 1.0, 0.0, caret_x + 1.0, FONT_SIZE), - &CARET_COLOR, - ); - } - - fn command(&mut self, id: u32) { - match id { - 0x100 => { - self.handle.close(); - Application::global().quit() - } - _ => println!("unexpected id {}", id), - } - } - - fn key_down(&mut self, event: KeyEvent) -> bool { - if event.key == Key::Character("c".to_string()) { - // custom hotkey for pressing "c" - println!("user pressed c! wow! setting selection to 0"); - - // update internal selection state - self.document.borrow_mut().selection = Selection::caret(0); - - // notify the OS that we've updated the selection - self.handle - .update_text_field(self.text_input_token.unwrap(), Event::SelectionChanged); - - // repaint window - self.handle.request_anim_frame(); - - // return true prevents the keypress event from being handled as text input - return true; - } - false - } - - fn acquire_input_lock( - &mut self, - _token: TextFieldToken, - _mutable: bool, - ) -> Box { - Box::new(AppInputHandler { - state: self.document.clone(), - window_size: self.size, - window_handle: self.handle.clone(), - }) - } - - fn release_input_lock(&mut self, _token: TextFieldToken) { - // no action required; this example is simple enough that this - // state is not actually shared. - } - - fn size(&mut self, size: Size) { - self.size = size; - } - - fn request_close(&mut self) { - self.handle.close(); - } - - fn destroy(&mut self) { - Application::global().quit() - } - - fn as_any(&mut self) -> &mut dyn Any { - self - } -} - -struct AppInputHandler { - state: Rc>, - window_size: Size, - window_handle: WindowHandle, -} - -impl InputHandler for AppInputHandler { - fn selection(&self) -> Selection { - self.state.borrow().selection - } - fn composition_range(&self) -> Option> { - self.state.borrow().composition.clone() - } - fn set_selection(&mut self, range: Selection) { - self.state.borrow_mut().selection = range; - self.window_handle.request_anim_frame(); - } - fn set_composition_range(&mut self, range: Option>) { - self.state.borrow_mut().composition = range; - self.window_handle.request_anim_frame(); - } - fn replace_range(&mut self, range: Range, text: &str) { - let mut doc = self.state.borrow_mut(); - doc.text.replace_range(range.clone(), text); - if doc.selection.anchor < range.start && doc.selection.active < range.start { - // no need to update selection - } else if doc.selection.anchor > range.end && doc.selection.active > range.end { - doc.selection.anchor -= range.len(); - doc.selection.active -= range.len(); - doc.selection.anchor += text.len(); - doc.selection.active += text.len(); - } else { - doc.selection.anchor = range.start + text.len(); - doc.selection.active = range.start + text.len(); - } - doc.refresh_layout(); - doc.composition = None; - self.window_handle.request_anim_frame(); - } - fn slice(&self, range: Range) -> Cow { - self.state.borrow().text[range].to_string().into() - } - fn is_char_boundary(&self, i: usize) -> bool { - self.state.borrow().text.is_char_boundary(i) - } - fn len(&self) -> usize { - self.state.borrow().text.len() - } - fn hit_test_point(&self, point: Point) -> HitTestPoint { - self.state - .borrow() - .layout - .as_ref() - .unwrap() - .hit_test_point(point) - } - fn bounding_box(&self) -> Option { - Some(Rect::new( - 0.0, - 0.0, - self.window_size.width, - self.window_size.height, - )) - } - fn slice_bounding_box(&self, range: Range) -> Option { - let doc = self.state.borrow(); - let layout = doc.layout.as_ref().unwrap(); - let range_start_x = layout.hit_test_text_position(range.start).point.x; - let range_end_x = layout.hit_test_text_position(range.end).point.x; - Some(Rect::new(range_start_x, 0.0, range_end_x, FONT_SIZE)) - } - fn line_range(&self, _char_index: usize, _affinity: text::Affinity) -> Range { - // we don't have multiple lines, so no matter the input, output is the whole document - 0..self.state.borrow().text.len() - } - - fn handle_action(&mut self, action: Action) { - let handled = apply_default_behavior(self, action); - println!("action: {:?} handled: {:?}", action, handled); - } -} - -fn apply_default_behavior(handler: &mut AppInputHandler, action: Action) -> bool { - let is_caret = handler.selection().is_caret(); - match action { - Action::Move(movement) => { - let selection = handler.selection(); - let index = if movement_goes_downstream(movement) { - selection.max() - } else { - selection.min() - }; - let updated_index = if let (false, text::Movement::Grapheme(_)) = (is_caret, movement) { - // handle special cases of pressing left/right when the selection is not a caret - index - } else { - match apply_movement(handler, movement, index) { - Some(v) => v, - None => return false, - } - }; - handler.set_selection(Selection::caret(updated_index)); - } - Action::MoveSelecting(movement) => { - let mut selection = handler.selection(); - selection.active = match apply_movement(handler, movement, selection.active) { - Some(v) => v, - None => return false, - }; - handler.set_selection(selection); - } - Action::SelectAll => { - let len = handler.len(); - let selection = Selection::new(0, len); - handler.set_selection(selection); - } - Action::Delete(_) if !is_caret => { - // movement is ignored for non-caret selections - let selection = handler.selection(); - handler.replace_range(selection.range(), ""); - } - Action::Delete(movement) => { - let mut selection = handler.selection(); - selection.active = match apply_movement(handler, movement, selection.active) { - Some(v) => v, - None => return false, - }; - handler.replace_range(selection.range(), ""); - } - _ => return false, - } - true -} - -fn movement_goes_downstream(movement: text::Movement) -> bool { - match movement { - text::Movement::Grapheme(dir) => direction_goes_downstream(dir), - text::Movement::Word(dir) => direction_goes_downstream(dir), - text::Movement::Line(dir) => direction_goes_downstream(dir), - text::Movement::ParagraphEnd => true, - text::Movement::Vertical(VerticalMovement::LineDown) => true, - text::Movement::Vertical(VerticalMovement::PageDown) => true, - text::Movement::Vertical(VerticalMovement::DocumentEnd) => true, - _ => false, - } -} - -fn direction_goes_downstream(direction: text::Direction) -> bool { - match direction { - text::Direction::Left => false, - text::Direction::Right => true, - text::Direction::Upstream => false, - text::Direction::Downstream => true, - } -} - -fn apply_movement( - edit_lock: &mut AppInputHandler, - movement: text::Movement, - index: usize, -) -> Option { - match movement { - text::Movement::Grapheme(dir) => { - let doc_len = edit_lock.len(); - let mut cursor = GraphemeCursor::new(index, doc_len, true); - let doc = edit_lock.slice(0..doc_len); - if direction_goes_downstream(dir) { - cursor.next_boundary(&doc, 0).unwrap() - } else { - cursor.prev_boundary(&doc, 0).unwrap() - } - } - _ => None, - } -} - -fn main() { - let app = Application::new().unwrap(); - let mut builder = WindowBuilder::new(app.clone()); - builder.set_handler(Box::new(AppState::default())); - builder.set_title("Text editing example"); - let window = builder.build().unwrap(); - window.show(); - app.run(None); -} diff --git a/examples/gpu.rs b/examples/gpu.rs new file mode 100644 index 00000000..70931179 --- /dev/null +++ b/examples/gpu.rs @@ -0,0 +1,372 @@ +// Copyright 2018 The Druid Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::any::Any; +use std::sync::{Arc, Mutex}; + +use glazier::kurbo::Size; + +use glazier::{ + Application, Cursor, FileDialogOptions, FileDialogToken, FileInfo, FileSpec, HotKey, KeyEvent, + Menu, MouseEvent, Region, SysMods, TimerToken, WinHandler, WindowBuilder, WindowHandle, +}; +use piet_gpu_hal::{ + include_shader, BindType, Buffer, BufferUsage, ComputePassDescriptor, DescriptorSet, Image, + ImageFormat, ImageLayout, Instance, InstanceFlags, Pipeline, Semaphore, Session, Swapchain, +}; +use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; +use tracing::info; + +#[derive(Default)] +struct HelloState { + size: Size, + handle: WindowHandle, + gpu_state: Arc>>, +} + +impl WinHandler for HelloState { + fn connect(&mut self, handle: &WindowHandle) { + self.handle = handle.clone(); + } + + fn size(&mut self, size: Size) { + info!("size: {:?}", size); + self.size = size; + } + + fn prepare_paint(&mut self) { + self.handle.invalidate(); + } + + fn paint(&mut self, _: &Region) { + unsafe { + // TODO: wire up size + let width = 1000; + let height = 800; + let mut state_guard = self.gpu_state.lock().unwrap(); + let state = state_guard.as_mut().unwrap(); + let frame_idx = state.current_frame % 2; + let (image_idx, acquisition_semaphore) = state.swapchain.next().unwrap(); + let swap_image = state.swapchain.image(image_idx); + + // TODO: wire up time for animation purposes + let i_time: f32 = 0.0; + let config_data = [width, height, i_time.to_bits()]; + state.config_host.write(&config_data).unwrap(); + + let mut cmd_buf = state.session.cmd_buf().unwrap(); + cmd_buf.begin(); + cmd_buf.image_barrier(&swap_image, ImageLayout::Undefined, ImageLayout::BlitDst); + cmd_buf.copy_buffer(&state.config_host, &state.config_dev); + cmd_buf.memory_barrier(); + + cmd_buf.image_barrier( + &state.staging_img, + ImageLayout::Undefined, + ImageLayout::General, + ); + let wg_x = width / 16; + let wg_y = height / 16; + let mut pass = cmd_buf.begin_compute_pass(&ComputePassDescriptor::default()); + pass.dispatch( + &state.pipeline, + &state.descriptor_set, + (wg_x, wg_y, 1), + (16, 16, 1), + ); + pass.end(); + cmd_buf.image_barrier( + &state.staging_img, + ImageLayout::General, + ImageLayout::BlitSrc, + ); + cmd_buf.blit_image(&state.staging_img, &swap_image); + cmd_buf.image_barrier(&swap_image, ImageLayout::BlitDst, ImageLayout::Present); + cmd_buf.finish(); + let submitted = state + .session + .run_cmd_buf( + cmd_buf, + &[&acquisition_semaphore], + &[&state.present_semaphores[frame_idx]], + ) + .unwrap(); + state + .swapchain + .present(image_idx, &[&state.present_semaphores[frame_idx]]) + .unwrap(); + let start = std::time::Instant::now(); + submitted.wait().unwrap(); + info!("wait elapsed: {:?}", start.elapsed()); + state.current_frame += 1; + } + } + + fn command(&mut self, id: u32) { + match id { + 0x100 => { + self.handle.close(); + Application::global().quit() + } + 0x101 => { + let options = FileDialogOptions::new().show_hidden().allowed_types(vec![ + FileSpec::new("Rust Files", &["rs", "toml"]), + FileSpec::TEXT, + FileSpec::JPG, + ]); + self.handle.open_file(options); + } + 0x102 => { + let options = FileDialogOptions::new().show_hidden().allowed_types(vec![ + FileSpec::new("Rust Files", &["rs", "toml"]), + FileSpec::TEXT, + FileSpec::JPG, + ]); + self.handle.save_as(options); + } + _ => info!("unexpected id {}", id), + } + } + + fn save_as(&mut self, _token: FileDialogToken, file: Option) { + info!("save file result: {:?}", file); + } + + fn open_file(&mut self, _token: FileDialogToken, file_info: Option) { + info!("open file result: {:?}", file_info); + } + + fn key_down(&mut self, event: KeyEvent) -> bool { + info!("keydown: {:?}", event); + false + } + + fn key_up(&mut self, event: KeyEvent) { + info!("keyup: {:?}", event); + } + + fn wheel(&mut self, event: &MouseEvent) { + info!("mouse_wheel {:?}", event); + } + + fn mouse_move(&mut self, event: &MouseEvent) { + self.handle.set_cursor(&Cursor::Arrow); + info!("mouse_move {:?}", event); + } + + fn mouse_down(&mut self, event: &MouseEvent) { + info!("mouse_down {:?}", event); + self.render(); + } + + fn mouse_up(&mut self, event: &MouseEvent) { + info!("mouse_up {:?}", event); + } + + fn timer(&mut self, id: TimerToken) { + info!("timer fired: {:?}", id); + } + + fn got_focus(&mut self) { + info!("Got focus"); + } + + fn lost_focus(&mut self) { + info!("Lost focus"); + } + + fn request_close(&mut self) { + self.handle.close(); + } + + fn destroy(&mut self) { + Application::global().quit() + } + + fn as_any(&mut self) -> &mut dyn Any { + self + } +} + +impl HelloState { + fn render(&self) { + unsafe { + // TODO: wire up size + let width = 1000; + let height = 800; + let mut state_guard = self.gpu_state.lock().unwrap(); + let state = state_guard.as_mut().unwrap(); + let frame_idx = state.current_frame % 2; + let (image_idx, acquisition_semaphore) = state.swapchain.next().unwrap(); + let swap_image = state.swapchain.image(image_idx); + + // TODO: wire up time for animation purposes + let i_time: f32 = 0.0; + let config_data = [width, height, i_time.to_bits()]; + state.config_host.write(&config_data).unwrap(); + + let mut cmd_buf = state.session.cmd_buf().unwrap(); + cmd_buf.begin(); + cmd_buf.image_barrier(&swap_image, ImageLayout::Undefined, ImageLayout::BlitDst); + cmd_buf.copy_buffer(&state.config_host, &state.config_dev); + cmd_buf.memory_barrier(); + + cmd_buf.image_barrier( + &state.staging_img, + ImageLayout::Undefined, + ImageLayout::General, + ); + let wg_x = width / 16; + let wg_y = height / 16; + let mut pass = cmd_buf.begin_compute_pass(&ComputePassDescriptor::default()); + pass.dispatch( + &state.pipeline, + &state.descriptor_set, + (wg_x, wg_y, 1), + (16, 16, 1), + ); + pass.end(); + cmd_buf.image_barrier( + &state.staging_img, + ImageLayout::General, + ImageLayout::BlitSrc, + ); + cmd_buf.blit_image(&state.staging_img, &swap_image); + cmd_buf.image_barrier(&swap_image, ImageLayout::BlitDst, ImageLayout::Present); + cmd_buf.finish(); + let submitted = state + .session + .run_cmd_buf( + cmd_buf, + &[&acquisition_semaphore], + &[&state.present_semaphores[frame_idx]], + ) + .unwrap(); + state + .swapchain + .present(image_idx, &[&state.present_semaphores[frame_idx]]) + .unwrap(); + let start = std::time::Instant::now(); + submitted.wait().unwrap(); + info!("wait elapsed: {:?}", start.elapsed()); + state.current_frame += 1; + } + } +} + +fn main() { + tracing_subscriber::fmt().init(); + let mut file_menu = Menu::new(); + file_menu.add_item( + 0x100, + "E&xit", + Some(&HotKey::new(SysMods::Cmd, "q")), + Some(true), + false, + ); + file_menu.add_item( + 0x101, + "O&pen", + Some(&HotKey::new(SysMods::Cmd, "o")), + Some(true), + false, + ); + file_menu.add_item( + 0x102, + "S&ave", + Some(&HotKey::new(SysMods::Cmd, "s")), + Some(true), + false, + ); + let mut menubar = Menu::new(); + menubar.add_dropdown(Menu::new(), "Application", true); + menubar.add_dropdown(file_menu, "&File", true); + + let app = Application::new().unwrap(); + let mut builder = WindowBuilder::new(app.clone()); + let win_state = HelloState::default(); + let gpu_state = win_state.gpu_state.clone(); + builder.set_handler(Box::new(win_state)); + builder.set_title("Hello example"); + builder.set_menu(menubar); + + let window = builder.build().unwrap(); + unsafe { + let width = 1000; + let height = 800; + let state = GpuState::new(&window, width, height).unwrap(); + *gpu_state.lock().unwrap() = Some(state); + } + window.show(); + + app.run(None); +} + +struct GpuState { + _instance: Instance, + current_frame: usize, + session: Session, + swapchain: Swapchain, + present_semaphores: Vec, + pipeline: Pipeline, + descriptor_set: DescriptorSet, + config_host: Buffer, + config_dev: Buffer, + staging_img: Image, +} + +impl GpuState { + unsafe fn new( + window: &WindowHandle, + width: usize, + height: usize, + ) -> Result> { + let instance = Instance::new(InstanceFlags::empty())?; + let surface = instance.surface(window.raw_display_handle(), window.raw_window_handle())?; + let device = instance.device()?; + let swapchain = instance.swapchain(width, height, &device, &surface)?; + let session = Session::new(device); + let present_semaphores = (0..2) + .map(|_| session.create_semaphore()) + .collect::, _>>()?; + let shader_code = include_shader!(&session, "./shader/gen/shader"); + let pipeline = + session.create_compute_pipeline(shader_code, &[BindType::Buffer, BindType::Image])?; + let config_size = 12; + let config_host = + session.create_buffer(config_size, BufferUsage::COPY_SRC | BufferUsage::MAP_WRITE)?; + let config_dev = + session.create_buffer(config_size, BufferUsage::COPY_DST | BufferUsage::STORAGE)?; + let staging_img = + session.create_image2d(width as u32, height as u32, ImageFormat::Rgba8)?; + let descriptor_set = session + .descriptor_set_builder() + .add_buffers(&[&config_dev]) + .add_images(&[&staging_img]) + .build(&session, &pipeline)?; + let current_frame = 0; + Ok(GpuState { + _instance: instance, + current_frame, + session, + swapchain, + present_semaphores, + pipeline, + descriptor_set, + config_host, + config_dev, + staging_img, + }) + } +} diff --git a/examples/invalidate.rs b/examples/invalidate.rs deleted file mode 100644 index d67679ca..00000000 --- a/examples/invalidate.rs +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright 2020 The Druid Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use std::any::Any; - -use std::time::Instant; - -use glazier::kurbo::{Point, Rect, Size}; -use glazier::piet::{Color, Piet, RenderContext}; - -use glazier::{Application, Region, WinHandler, WindowBuilder, WindowHandle}; - -struct InvalidateTest { - handle: WindowHandle, - size: Size, - start_time: Instant, - color: Color, - rect: Rect, - cursor: Rect, -} - -impl InvalidateTest { - fn update_color_and_rect(&mut self) { - let time_since_start = (Instant::now() - self.start_time).as_millis(); - let (r, g, b, _) = self.color.as_rgba8(); - self.color = match (time_since_start % 2, time_since_start % 3) { - (0, _) => Color::rgb8(r.wrapping_add(10), g, b), - (_, 0) => Color::rgb8(r, g.wrapping_add(10), b), - (_, _) => Color::rgb8(r, g, b.wrapping_add(10)), - }; - - self.rect.x0 = (self.rect.x0 + 5.0) % self.size.width; - self.rect.x1 = (self.rect.x1 + 5.5) % self.size.width; - self.rect.y0 = (self.rect.y0 + 3.0) % self.size.height; - self.rect.y1 = (self.rect.y1 + 3.5) % self.size.height; - - // Invalidate the old and new cursor positions. - self.handle.invalidate_rect(self.cursor); - self.cursor.x0 += 4.0; - self.cursor.x1 += 4.0; - if self.cursor.x0 > self.size.width { - self.cursor.x1 = self.cursor.width(); - self.cursor.x0 = 0.0; - } - self.handle.invalidate_rect(self.cursor); - } -} - -impl WinHandler for InvalidateTest { - fn connect(&mut self, handle: &WindowHandle) { - self.handle = handle.clone(); - } - - fn prepare_paint(&mut self) { - self.update_color_and_rect(); - self.handle.invalidate_rect(self.rect); - } - - fn paint(&mut self, piet: &mut Piet, region: &Region) { - // We can ask to draw something bigger than our rect, but things outside - // the invalidation region won't draw. (So they'll draw if and only if - // they intersect the cursor's invalidated region or the rect that we - // invalidated.) - piet.fill(region.bounding_box(), &self.color); - piet.fill(self.cursor, &Color::WHITE); - self.handle.request_anim_frame(); - } - - fn size(&mut self, size: Size) { - self.size = size; - } - - fn command(&mut self, id: u32) { - match id { - 0x100 => self.handle.close(), - _ => println!("unexpected id {}", id), - } - } - - fn request_close(&mut self) { - self.handle.close(); - } - - fn destroy(&mut self) { - Application::global().quit() - } - - fn as_any(&mut self) -> &mut dyn Any { - self - } -} - -fn main() { - tracing_subscriber::fmt().init(); - let app = Application::new().unwrap(); - let mut builder = WindowBuilder::new(app.clone()); - let inv_test = InvalidateTest { - size: Size::ZERO, - handle: Default::default(), - start_time: Instant::now(), - rect: Rect::from_origin_size(Point::ZERO, (10.0, 20.0)), - cursor: Rect::from_origin_size(Point::ZERO, (2.0, 100.0)), - color: Color::WHITE, - }; - builder.set_handler(Box::new(inv_test)); - builder.set_title("Invalidate tester"); - - let window = builder.build().unwrap(); - window.show(); - app.run(None); -} diff --git a/examples/perftest.rs b/examples/perftest.rs deleted file mode 100644 index 0b48086f..00000000 --- a/examples/perftest.rs +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright 2018 The Druid Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use std::any::Any; - -use time::Instant; - -use piet_common::kurbo::{Line, Size}; -use piet_common::{Color, FontFamily, Piet, RenderContext, Text, TextLayoutBuilder}; - -use glazier::{Application, KeyEvent, Region, WinHandler, WindowBuilder, WindowHandle}; - -const BG_COLOR: Color = Color::rgb8(0x27, 0x28, 0x22); -const FG_COLOR: Color = Color::rgb8(0xf0, 0xf0, 0xea); -const RED: Color = Color::rgb8(0xff, 0x80, 0x80); -const CYAN: Color = Color::rgb8(0x80, 0xff, 0xff); - -struct PerfTest { - handle: WindowHandle, - size: Size, - start_time: Instant, - last_time: Instant, - red: bool, -} - -impl WinHandler for PerfTest { - fn connect(&mut self, handle: &WindowHandle) { - self.handle = handle.clone(); - } - - fn prepare_paint(&mut self) { - self.handle.invalidate(); - } - - fn paint(&mut self, piet: &mut Piet, _: &Region) { - let rect = self.size.to_rect(); - piet.fill(rect, &BG_COLOR); - - piet.stroke( - Line::new((0.0, self.size.height), (self.size.width, 0.0)), - &FG_COLOR, - 1.0, - ); - - let current_ns = (Instant::now() - self.start_time).whole_nanoseconds(); - let th = ::std::f64::consts::PI * (current_ns as f64) * 2e-9; - let dx = 100.0 * th.sin(); - let dy = 100.0 * th.cos(); - piet.stroke( - Line::new((100.0, 100.0), (100.0 + dx, 100.0 - dy)), - &FG_COLOR, - 1.0, - ); - - let now = Instant::now(); - let msg = format!("{}ms", (now - self.last_time).whole_milliseconds()); - self.last_time = now; - let layout = piet - .text() - .new_text_layout(msg) - .font(FontFamily::MONOSPACE, 15.0) - .text_color(FG_COLOR) - .build() - .unwrap(); - piet.draw_text(&layout, (10.0, 210.0)); - - let msg = "VSYNC"; - let color = if self.red { RED } else { CYAN }; - - let layout = piet - .text() - .new_text_layout(msg) - .text_color(color) - .font(FontFamily::MONOSPACE, 48.0) - .build() - .unwrap(); - piet.draw_text(&layout, (10.0, 280.0)); - self.red = !self.red; - - let msg = "Hello DWrite! This is a somewhat longer string of text intended to provoke slightly longer draw times."; - let layout = piet - .text() - .new_text_layout(msg) - .font(FontFamily::MONOSPACE, 15.0) - .text_color(FG_COLOR) - .build() - .unwrap(); - let dy = 15.0; - let x0 = 210.0; - let y0 = 10.0; - for i in 0..60 { - let y = y0 + (i as f64) * dy; - piet.draw_text(&layout, (x0, y)); - } - self.handle.request_anim_frame(); - } - - fn command(&mut self, id: u32) { - match id { - 0x100 => self.handle.close(), - _ => println!("unexpected id {}", id), - } - } - - fn key_down(&mut self, event: KeyEvent) -> bool { - println!("keydown: {:?}", event); - false - } - - fn size(&mut self, size: Size) { - self.size = size; - } - - fn request_close(&mut self) { - self.handle.close(); - } - - fn destroy(&mut self) { - Application::global().quit() - } - - fn as_any(&mut self) -> &mut dyn Any { - self - } -} - -fn main() { - tracing_subscriber::fmt().init(); - let app = Application::new().unwrap(); - let mut builder = WindowBuilder::new(app.clone()); - let perf_test = PerfTest { - size: Size::ZERO, - handle: Default::default(), - start_time: time::Instant::now(), - last_time: time::Instant::now(), - red: true, - }; - builder.set_handler(Box::new(perf_test)); - builder.set_title("Performance tester"); - - let window = builder.build().unwrap(); - window.show(); - - app.run(None); -} diff --git a/examples/quit.rs b/examples/quit.rs deleted file mode 100644 index cd584bf9..00000000 --- a/examples/quit.rs +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2020 The Druid Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use std::any::Any; - -use glazier::kurbo::{Line, Size}; -use glazier::piet::{Color, RenderContext}; - -use glazier::{ - Application, HotKey, Menu, Region, SysMods, WinHandler, WindowBuilder, WindowHandle, -}; - -const BG_COLOR: Color = Color::rgb8(0x27, 0x28, 0x22); -const FG_COLOR: Color = Color::rgb8(0xf0, 0xf0, 0xea); - -#[derive(Default)] -struct QuitState { - quit_count: u32, - size: Size, - handle: WindowHandle, -} - -impl WinHandler for QuitState { - fn connect(&mut self, handle: &WindowHandle) { - self.handle = handle.clone(); - } - - fn prepare_paint(&mut self) {} - - fn paint(&mut self, piet: &mut piet_common::Piet, _: &Region) { - let rect = self.size.to_rect(); - piet.fill(rect, &BG_COLOR); - piet.stroke(Line::new((10.0, 50.0), (90.0, 90.0)), &FG_COLOR, 1.0); - } - - fn size(&mut self, size: Size) { - self.size = size; - } - - fn request_close(&mut self) { - self.quit_count += 1; - if self.quit_count >= 5 { - self.handle.close(); - } else { - tracing::info!("Don't wanna quit"); - } - } - - fn destroy(&mut self) { - Application::global().quit() - } - - fn as_any(&mut self) -> &mut dyn Any { - self - } -} - -fn main() { - tracing_subscriber::fmt().init(); - let app = Application::new().unwrap(); - - let mut file_menu = Menu::new(); - file_menu.add_item( - 0x100, - "E&xit", - Some(&HotKey::new(SysMods::Cmd, "q")), - None, - true, - ); - - let mut menubar = Menu::new(); - menubar.add_dropdown(file_menu, "Application", true); - - let mut builder = WindowBuilder::new(app.clone()); - builder.set_handler(Box::new(QuitState::default())); - builder.set_title("Quit example"); - builder.set_menu(menubar); - - let window = builder.build().unwrap(); - window.show(); - - app.run(None); -} diff --git a/examples/shader/build.ninja b/examples/shader/build.ninja new file mode 100644 index 00000000..d22b6da2 --- /dev/null +++ b/examples/shader/build.ninja @@ -0,0 +1,19 @@ +# Build file for shaders. + +# You must have Vulkan tools in your path, or patch here. + +glslang_validator = glslangValidator +spirv_cross = spirv-cross + +rule glsl + command = $glslang_validator -V -o $out $in + +rule hlsl + command = $spirv_cross --hlsl --shader-model 50 $in --output $out + +rule msl + command = $spirv_cross --msl $in --output $out + +build gen/shader.spv: glsl shader.comp +build gen/shader.hlsl: hlsl gen/shader.spv +build gen/shader.msl: msl gen/shader.spv diff --git a/examples/shader/gen/shader.dxil b/examples/shader/gen/shader.dxil new file mode 100644 index 00000000..e69de29b diff --git a/examples/shader/gen/shader.hlsl b/examples/shader/gen/shader.hlsl new file mode 100644 index 00000000..14df7fe5 --- /dev/null +++ b/examples/shader/gen/shader.hlsl @@ -0,0 +1,25 @@ +static const uint3 gl_WorkGroupSize = uint3(16u, 16u, 1u); + +RWByteAddressBuffer _24 : register(u0); +RWTexture2D image : register(u1); + +static uint3 gl_GlobalInvocationID; +struct SPIRV_Cross_Input +{ + uint3 gl_GlobalInvocationID : SV_DispatchThreadID; +}; + +void comp_main() +{ + uint2 xy = gl_GlobalInvocationID.xy; + float2 fragCoord = (float2(gl_GlobalInvocationID.xy) / float2(float(_24.Load(0)), float(_24.Load(4)))) - 0.5f.xx; + float4 fragColor = float4(fragCoord.x + 0.5f, fragCoord.y + 0.5f, 0.5f + (0.5f * sin(asfloat(_24.Load(8)))), 1.0f); + image[int2(xy)] = fragColor; +} + +[numthreads(16, 16, 1)] +void main(SPIRV_Cross_Input stage_input) +{ + gl_GlobalInvocationID = stage_input.gl_GlobalInvocationID; + comp_main(); +} diff --git a/examples/shader/gen/shader.msl b/examples/shader/gen/shader.msl new file mode 100644 index 00000000..0a38d7e7 --- /dev/null +++ b/examples/shader/gen/shader.msl @@ -0,0 +1,22 @@ +#include +#include + +using namespace metal; + +struct Params +{ + uint width; + uint height; + float iTime; +}; + +constant uint3 gl_WorkGroupSize [[maybe_unused]] = uint3(16u, 16u, 1u); + +kernel void main0(device Params& _24 [[buffer(0)]], texture2d image [[texture(1)]], uint3 gl_GlobalInvocationID [[thread_position_in_grid]]) +{ + uint2 xy = gl_GlobalInvocationID.xy; + float2 fragCoord = (float2(gl_GlobalInvocationID.xy) / float2(float(_24.width), float(_24.height))) - float2(0.5); + float4 fragColor = float4(fragCoord.x + 0.5, fragCoord.y + 0.5, 0.5 + (0.5 * sin(_24.iTime)), 1.0); + image.write(fragColor, uint2(int2(xy))); +} + diff --git a/examples/shader/gen/shader.spv b/examples/shader/gen/shader.spv new file mode 100644 index 00000000..3e02018a Binary files /dev/null and b/examples/shader/gen/shader.spv differ diff --git a/examples/shader/shader.comp b/examples/shader/shader.comp new file mode 100644 index 00000000..349dbd61 --- /dev/null +++ b/examples/shader/shader.comp @@ -0,0 +1,39 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Also licensed under MIT license, at your choice. + +// Simple compute shader for generating an image +// When updating, remember to recompile using ninja. + +#version 450 +layout(local_size_x = 16, local_size_y = 16) in; + +layout(set = 0, binding = 0) restrict buffer Params { + uint width; + uint height; + float iTime; +}; + +layout(rgba8, set = 0, binding = 1) uniform restrict writeonly image2D image; + +void main() { + uvec2 xy = gl_GlobalInvocationID.xy; + vec2 fragCoord = vec2(gl_GlobalInvocationID.xy) / vec2(float(width), float(height)) - 0.5; + + // Shadertoy-like code can go here. + vec4 fragColor = vec4(fragCoord.x + 0.5, fragCoord.y + 0.5, 0.5 + 0.5 * sin(iTime), 1.0); + + imageStore(image, ivec2(xy), fragColor); +} diff --git a/examples/shello.rs b/examples/shello.rs deleted file mode 100644 index 9f3d53e0..00000000 --- a/examples/shello.rs +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright 2018 The Druid Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use std::any::Any; - -use glazier::kurbo::{Line, Size}; -use glazier::piet::{Color, RenderContext}; - -use glazier::{ - Application, Cursor, FileDialogOptions, FileDialogToken, FileInfo, FileSpec, HotKey, KeyEvent, - Menu, MouseEvent, Region, SysMods, TimerToken, WinHandler, WindowBuilder, WindowHandle, -}; - -const BG_COLOR: Color = Color::rgb8(0x27, 0x28, 0x22); -const FG_COLOR: Color = Color::rgb8(0xf0, 0xf0, 0xea); - -#[derive(Default)] -struct HelloState { - size: Size, - handle: WindowHandle, -} - -impl WinHandler for HelloState { - fn connect(&mut self, handle: &WindowHandle) { - self.handle = handle.clone(); - } - - fn prepare_paint(&mut self) {} - - fn paint(&mut self, piet: &mut piet_common::Piet, _: &Region) { - let rect = self.size.to_rect(); - piet.fill(rect, &BG_COLOR); - piet.stroke(Line::new((10.0, 50.0), (90.0, 90.0)), &FG_COLOR, 1.0); - } - - fn command(&mut self, id: u32) { - match id { - 0x100 => { - self.handle.close(); - Application::global().quit() - } - 0x101 => { - let options = FileDialogOptions::new().show_hidden().allowed_types(vec![ - FileSpec::new("Rust Files", &["rs", "toml"]), - FileSpec::TEXT, - FileSpec::JPG, - ]); - self.handle.open_file(options); - } - 0x102 => { - let options = FileDialogOptions::new().show_hidden().allowed_types(vec![ - FileSpec::new("Rust Files", &["rs", "toml"]), - FileSpec::TEXT, - FileSpec::JPG, - ]); - self.handle.save_as(options); - } - _ => println!("unexpected id {}", id), - } - } - - fn open_file(&mut self, _token: FileDialogToken, file_info: Option) { - println!("open file result: {:?}", file_info); - } - - fn save_as(&mut self, _token: FileDialogToken, file: Option) { - println!("save file result: {:?}", file); - } - - fn key_down(&mut self, event: KeyEvent) -> bool { - println!("keydown: {:?}", event); - false - } - - fn key_up(&mut self, event: KeyEvent) { - println!("keyup: {:?}", event); - } - - fn wheel(&mut self, event: &MouseEvent) { - println!("mouse_wheel {:?}", event); - } - - fn mouse_move(&mut self, event: &MouseEvent) { - self.handle.set_cursor(&Cursor::Arrow); - println!("mouse_move {:?}", event); - } - - fn mouse_down(&mut self, event: &MouseEvent) { - println!("mouse_down {:?}", event); - } - - fn mouse_up(&mut self, event: &MouseEvent) { - println!("mouse_up {:?}", event); - } - - fn timer(&mut self, id: TimerToken) { - println!("timer fired: {:?}", id); - } - - fn size(&mut self, size: Size) { - self.size = size; - } - - fn got_focus(&mut self) { - println!("Got focus"); - } - - fn lost_focus(&mut self) { - println!("Lost focus"); - } - - fn request_close(&mut self) { - self.handle.close(); - } - - fn destroy(&mut self) { - Application::global().quit() - } - - fn as_any(&mut self) -> &mut dyn Any { - self - } -} - -fn main() { - tracing_subscriber::fmt().init(); - let mut file_menu = Menu::new(); - file_menu.add_item( - 0x100, - "E&xit", - Some(&HotKey::new(SysMods::Cmd, "q")), - None, - true, - ); - file_menu.add_item( - 0x101, - "O&pen", - Some(&HotKey::new(SysMods::Cmd, "o")), - None, - true, - ); - file_menu.add_item( - 0x102, - "S&ave", - Some(&HotKey::new(SysMods::Cmd, "s")), - None, - true, - ); - let mut menubar = Menu::new(); - menubar.add_dropdown(Menu::new(), "Application", true); - menubar.add_dropdown(file_menu, "&File", true); - - let app = Application::new().unwrap(); - let mut builder = WindowBuilder::new(app.clone()); - builder.set_handler(Box::new(HelloState::default())); - builder.set_title("Hello example"); - builder.set_menu(menubar); - - let window = builder.build().unwrap(); - window.show(); - - app.run(None); -} diff --git a/src/backend/gtk/window.rs b/src/backend/gtk/window.rs index d8f7ad53..d4a7155a 100644 --- a/src/backend/gtk/window.rs +++ b/src/backend/gtk/window.rs @@ -43,7 +43,6 @@ use gtk::gdk::{ use instant::Duration; use tracing::{error, warn}; -#[cfg(feature = "raw-win-handle")] use raw_window_handle::{HasRawWindowHandle, RawWindowHandle, XcbWindowHandle}; use crate::kurbo::{Insets, Point, Rect, Size, Vec2}; @@ -121,7 +120,6 @@ impl PartialEq for WindowHandle { } impl Eq for WindowHandle {} -#[cfg(feature = "raw-win-handle")] unsafe impl HasRawWindowHandle for WindowHandle { fn raw_window_handle(&self) -> RawWindowHandle { error!("HasRawWindowHandle trait not implemented for gtk."); diff --git a/src/backend/mac/application.rs b/src/backend/mac/application.rs index 9f983c89..cd09bdba 100644 --- a/src/backend/mac/application.rs +++ b/src/backend/mac/application.rs @@ -75,7 +75,7 @@ impl Application { // Clean up the delegate let () = msg_send![self.ns_app, setDelegate: nil]; - Box::from_raw(state_ptr); // Causes it to drop & dealloc automatically + drop(Box::from_raw(state_ptr)); // Causes it to drop & dealloc automatically } } diff --git a/src/backend/mac/text_input.rs b/src/backend/mac/text_input.rs index f34f0227..bb0031a8 100644 --- a/src/backend/mac/text_input.rs +++ b/src/backend/mac/text_input.rs @@ -26,7 +26,6 @@ use std::ops::Range; use std::os::raw::c_uchar; use super::window::with_edit_lock_from_window; -use crate::kurbo::Point; use crate::text::{ Action, Affinity, Direction, InputHandler, Movement, Selection, VerticalMovement, WritingDirection, @@ -213,15 +212,17 @@ pub extern "C" fn insert_text(this: &mut Object, _: Sel, text: id, replacement_r } pub extern "C" fn character_index_for_point( - this: &mut Object, + _this: &mut Object, _: Sel, - point: NSPoint, + _point: NSPoint, ) -> NSUInteger { - with_edit_lock_from_window(this, true, |edit_lock| { - let hit_test = edit_lock.hit_test_point(Point::new(point.x, point.y)); - hit_test.idx as NSUInteger - }) - .unwrap_or(0) + todo!() + // TODO: figure out how to do text hit testing without piet + // with_edit_lock_from_window(this, true, |edit_lock| { + // let hit_test = edit_lock.hit_test_point(Point::new(point.x, point.y)); + // hit_test.idx as NSUInteger + // }) + // .unwrap_or(0) } pub extern "C" fn first_rect_for_character_range( diff --git a/src/backend/mac/window.rs b/src/backend/mac/window.rs index a3b18cf1..bc50a7cc 100644 --- a/src/backend/mac/window.rs +++ b/src/backend/mac/window.rs @@ -30,20 +30,19 @@ use cocoa::base::{id, nil, BOOL, NO, YES}; use cocoa::foundation::{ NSArray, NSAutoreleasePool, NSInteger, NSPoint, NSRect, NSSize, NSString, NSUInteger, }; -use core_graphics::context::CGContextRef; -use foreign_types::ForeignTypeRef; use lazy_static::lazy_static; use objc::declare::ClassDecl; use objc::rc::WeakPtr; use objc::runtime::{Class, Object, Protocol, Sel}; use objc::{class, msg_send, sel, sel_impl}; -use tracing::{debug, error, info}; +use tracing::{debug, info}; -#[cfg(feature = "raw-win-handle")] -use raw_window_handle::{AppKitWindowHandle, HasRawWindowHandle, RawWindowHandle}; +use raw_window_handle::{ + AppKitDisplayHandle, AppKitWindowHandle, HasRawDisplayHandle, HasRawWindowHandle, + RawDisplayHandle, RawWindowHandle, +}; use crate::kurbo::{Insets, Point, Rect, Size, Vec2}; -use crate::piet::{Piet, PietText, RenderContext}; use super::appkit::{ NSRunLoopCommonModes, NSTrackingArea, NSTrackingAreaOptions, NSView as NSViewExt, @@ -103,15 +102,17 @@ pub(crate) struct WindowHandle { nsview: WeakPtr, idle_queue: Weak>>, } + impl PartialEq for WindowHandle { fn eq(&self, other: &Self) -> bool { match (self.idle_queue.upgrade(), other.idle_queue.upgrade()) { (None, None) => true, - (Some(s), Some(o)) => std::sync::Arc::ptr_eq(&s, &o), + (Some(s), Some(o)) => Arc::ptr_eq(&s, &o), (_, _) => false, } } } + impl Eq for WindowHandle {} impl std::fmt::Debug for WindowHandle { @@ -175,7 +176,6 @@ struct ViewState { // Tracks whether we have already received the mouseExited event mouse_left: bool, keyboard_state: KeyboardState, - text: PietText, active_text_input: Option, parent: Option, } @@ -374,7 +374,7 @@ lazy_static! { info!("view is dealloc'ed"); unsafe { let view_state: *mut c_void = *this.get_ivar("viewState"); - Box::from_raw(view_state as *mut ViewState); + drop(Box::from_raw(view_state as *mut ViewState)); } } @@ -557,7 +557,7 @@ fn make_view(handler: Box) -> (id, Weak>>) { focus_click: false, mouse_left: true, keyboard_state, - text: PietText::new_with_unique_state(), + //text: PietText::new_with_unique_state(), active_text_input: None, parent: None, }; @@ -850,13 +850,6 @@ extern "C" fn view_will_draw(this: &mut Object, _: Sel) { extern "C" fn draw_rect(this: &mut Object, _: Sel, dirtyRect: NSRect) { unsafe { - let context: id = msg_send![class![NSGraphicsContext], currentContext]; - //FIXME: when core_graphics is at 0.20, we should be able to use - //core_graphics::sys::CGContextRef as our pointer type. - let cgcontext_ptr: *mut ::CType = - msg_send![context, CGContext]; - let cgcontext_ref = CGContextRef::from_ptr_mut(cgcontext_ptr); - // FIXME: use the actual invalid region instead of just this bounding box. // https://developer.apple.com/documentation/appkit/nsview/1483772-getrectsbeingdrawn?language=objc let rect = Rect::from_origin_size( @@ -867,12 +860,8 @@ extern "C" fn draw_rect(this: &mut Object, _: Sel, dirtyRect: NSRect) { let view_state: *mut c_void = *this.get_ivar("viewState"); let view_state = &mut *(view_state as *mut ViewState); - let mut piet_ctx = Piet::new_y_down(cgcontext_ref, Some(view_state.text.clone())); - (*view_state).handler.paint(&mut piet_ctx, &invalid); - if let Err(e) = piet_ctx.finish() { - error!("{}", e) - } + (*view_state).handler.paint(&invalid); let superclass = msg_send![this, superclass]; let () = msg_send![super(this, superclass), drawRect: dirtyRect]; @@ -1092,7 +1081,7 @@ impl WindowHandle { None } - pub fn request_timer(&self, deadline: std::time::Instant) -> TimerToken { + pub fn request_timer(&self, deadline: Instant) -> TimerToken { let ti = time_interval_from_deadline(deadline); let token = TimerToken::next(); unsafe { @@ -1108,19 +1097,6 @@ impl WindowHandle { token } - pub fn text(&self) -> PietText { - let view = self.nsview.load(); - unsafe { - if let Some(view) = (*view).as_ref() { - let state: *mut c_void = *view.get_ivar("viewState"); - (*(state as *mut ViewState)).text.clone() - } else { - // this codepath should only happen during tests in druid, when view is nil - PietText::new_with_unique_state() - } - } - } - pub fn add_text_field(&self) -> TextFieldToken { TextFieldToken::next() } @@ -1413,7 +1389,6 @@ impl WindowHandle { } } -#[cfg(feature = "raw-win-handle")] unsafe impl HasRawWindowHandle for WindowHandle { fn raw_window_handle(&self) -> RawWindowHandle { let nsv = self.nsview.load(); @@ -1423,6 +1398,12 @@ unsafe impl HasRawWindowHandle for WindowHandle { } } +unsafe impl HasRawDisplayHandle for WindowHandle { + fn raw_display_handle(&self) -> RawDisplayHandle { + RawDisplayHandle::AppKit(AppKitDisplayHandle::empty()) + } +} + unsafe impl Send for IdleHandle {} unsafe impl Sync for IdleHandle {} @@ -1464,7 +1445,7 @@ impl IdleHandle { /// of seconds from now. /// /// This may lose some precision for multi-month durations. -fn time_interval_from_deadline(deadline: std::time::Instant) -> f64 { +fn time_interval_from_deadline(deadline: Instant) -> f64 { let now = Instant::now(); if now >= deadline { 0.0 diff --git a/src/backend/mod.rs b/src/backend/mod.rs index eeb92084..a0c8552e 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -38,11 +38,6 @@ mod x11; any(target_os = "freebsd", target_os = "linux", target_os = "openbsd") ))] pub use x11::*; -#[cfg(all( - feature = "x11", - any(target_os = "freebsd", target_os = "linux", target_os = "openbsd") -))] -pub(crate) mod shared; #[cfg(all( feature = "wayland", @@ -54,8 +49,9 @@ mod wayland; any(target_os = "freebsd", target_os = "linux", target_os = "openbsd") ))] pub use wayland::*; + #[cfg(all( - feature = "wayland", + any(feature = "wayland", feature = "x11"), any(target_os = "freebsd", target_os = "linux", target_os = "openbsd") ))] pub(crate) mod shared; diff --git a/src/backend/wayland/surfaces/mod.rs b/src/backend/wayland/surfaces/mod.rs index eb3a9deb..6416b2bf 100644 --- a/src/backend/wayland/surfaces/mod.rs +++ b/src/backend/wayland/surfaces/mod.rs @@ -141,17 +141,17 @@ impl Compositor for CompositorHandle { } } - fn get_xdg_positioner(&self) -> wlc::Main { + fn get_xdg_surface(&self, s: &wlc::Main) -> wlc::Main { match self.inner.upgrade() { - None => panic!("unable to acquire underlying compositor to create an xdg positioner"), - Some(c) => c.get_xdg_positioner(), + None => panic!("unable to acquire underlying compositor to create an xdg surface"), + Some(c) => c.get_xdg_surface(s), } } - fn get_xdg_surface(&self, s: &wlc::Main) -> wlc::Main { + fn get_xdg_positioner(&self) -> wlc::Main { match self.inner.upgrade() { - None => panic!("unable to acquire underlying compositor to create an xdg surface"), - Some(c) => c.get_xdg_surface(s), + None => panic!("unable to acquire underlying compositor to create an xdg positioner"), + Some(c) => c.get_xdg_positioner(), } } diff --git a/src/backend/wayland/surfaces/surface.rs b/src/backend/wayland/surfaces/surface.rs index 2700db7b..f664aa87 100644 --- a/src/backend/wayland/surfaces/surface.rs +++ b/src/backend/wayland/surfaces/surface.rs @@ -8,7 +8,7 @@ use wayland_protocols::xdg_shell::client::xdg_surface; use crate::kurbo; use crate::window; -use crate::{piet::Piet, region::Region, scale::Scale, TextFieldToken}; +use crate::{region::Region, scale::Scale, TextFieldToken}; use super::super::Changed; @@ -400,50 +400,6 @@ impl Data { } } - // create cairo context (safety: we must drop the buffer before we commit the frame) - // TODO: Cairo is native-endian while wayland is little-endian, which is a pain. Currently - // will give incorrect results on big-endian architectures. - // TODO cairo might use a different stride than the width of the format. Since we always - // use argb32 which is 32-bit aligned we should be ok, but strictly speaking cairo might - // choose a wider stride and read past the end of our buffer (UB). Fixing this would - // require a fair bit of effort. - unsafe { - // We're going to lie about the lifetime of our buffer here. This is (I think) ok, - // because the Rust wrapper for cairo is overly pessimistic: the buffer only has to - // last as long as the `ImageSurface` (which we know this buffer will). - let buf: &'static mut [u8] = &mut *(buf as *mut _); - let cairo_surface = match cairo::ImageSurface::create_for_data( - buf, - cairo::Format::ARgb32, - physical_size.width, - physical_size.height, - physical_size.width * buffers::PIXEL_WIDTH, - ) { - Ok(s) => s, - Err(cause) => { - tracing::error!("unable to create cairo surface: {:?}", cause); - return; - } - }; - let ctx = match cairo::Context::new(&cairo_surface) { - Ok(ctx) => ctx, - Err(cause) => { - tracing::error!("unable to create cairo context: {:?}", cause); - return; - } - }; - // Apply scaling - let scale = self.scale.get() as f64; - ctx.scale(scale, scale); - - let mut piet = Piet::new(&ctx); - // Actually paint the new frame - let region = self.damaged_region.borrow(); - - // The handler must not be already borrowed. This may mean deferring this call. - self.handler.borrow_mut().paint(&mut piet, &*region); - } - // reset damage ready for next frame. self.damaged_region.borrow_mut().clear(); self.buffers.attach(self); diff --git a/src/backend/wayland/window.rs b/src/backend/wayland/window.rs index 7cf928d6..a5a60bb7 100644 --- a/src/backend/wayland/window.rs +++ b/src/backend/wayland/window.rs @@ -18,8 +18,7 @@ use wayland_protocols::xdg_shell::client::xdg_popup; use wayland_protocols::xdg_shell::client::xdg_positioner; use wayland_protocols::xdg_shell::client::xdg_surface; -#[cfg(feature = "raw-win-handle")] -use raw_window_handle::{HasRawWindowHandle, RawWindowHandle, WaylandWindowHandle}; +use raw_window_handle::{HasRawWindowHandle, RawWindowHandle, WaylandHandle}; use super::application::{self, Timer}; use super::{error::Error, menu::Menu, outputs, surfaces}; @@ -29,7 +28,6 @@ use crate::{ error::Error as ShellError, kurbo::{Insets, Point, Rect, Size}, mouse::{Cursor, CursorDesc}, - piet::PietText, scale::Scale, text::Event, window::{self, FileDialogToken, TimerToken, WinHandler, WindowLevel}, @@ -181,10 +179,6 @@ impl WindowHandle { self.inner.surface.invalidate_rect(rect); } - pub fn text(&self) -> PietText { - PietText::new() - } - pub fn add_text_field(&self) -> TextFieldToken { TextFieldToken::next() } @@ -287,7 +281,7 @@ impl WindowHandle { } } -impl std::cmp::PartialEq for WindowHandle { +impl PartialEq for WindowHandle { fn eq(&self, rhs: &Self) -> bool { self.id() == rhs.id() } @@ -295,7 +289,7 @@ impl std::cmp::PartialEq for WindowHandle { impl Eq for WindowHandle {} -impl std::default::Default for WindowHandle { +impl Default for WindowHandle { fn default() -> WindowHandle { WindowHandle { inner: std::sync::Arc::new(Inner { @@ -310,11 +304,10 @@ impl std::default::Default for WindowHandle { } } -#[cfg(feature = "raw-win-handle")] unsafe impl HasRawWindowHandle for WindowHandle { fn raw_window_handle(&self) -> RawWindowHandle { tracing::error!("HasRawWindowHandle trait not implemented for wasm."); - RawWindowHandle::Wayland(WaylandWindowHandle::empty()) + RawWindowHandle::Wayland(WaylandHandle::empty()) } } diff --git a/src/backend/web/window.rs b/src/backend/web/window.rs index aac18117..5b7e3733 100644 --- a/src/backend/web/window.rs +++ b/src/backend/web/window.rs @@ -24,7 +24,6 @@ use tracing::{error, warn}; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; -#[cfg(feature = "raw-win-handle")] use raw_window_handle::{HasRawWindowHandle, RawWindowHandle, WebWindowHandle}; use crate::kurbo::{Insets, Point, Rect, Size, Vec2}; @@ -90,7 +89,6 @@ impl PartialEq for WindowHandle { } impl Eq for WindowHandle {} -#[cfg(feature = "raw-win-handle")] unsafe impl HasRawWindowHandle for WindowHandle { fn raw_window_handle(&self) -> RawWindowHandle { error!("HasRawWindowHandle trait not implemented for wasm."); diff --git a/src/backend/windows/application.rs b/src/backend/windows/application.rs index 084757b5..5ca9e4b0 100644 --- a/src/backend/windows/application.rs +++ b/src/backend/windows/application.rs @@ -35,8 +35,6 @@ use winapi::um::winuser::{ IDI_APPLICATION, MSG, PM_NOREMOVE, WM_TIMER, WNDCLASSW, }; -use piet_common::D2DLoadedFonts; - use crate::application::AppHandler; use super::accels; @@ -48,7 +46,6 @@ use super::window::{self, DS_REQUEST_DESTROY}; #[derive(Clone)] pub(crate) struct Application { state: Rc>, - pub(crate) fonts: D2DLoadedFonts, } struct State { @@ -66,8 +63,7 @@ impl Application { quitting: false, windows: HashSet::new(), })); - let fonts = D2DLoadedFonts::default(); - Ok(Application { state, fonts }) + Ok(Application { state }) } /// Initialize the app. At the moment, this is mostly needed for hi-dpi. diff --git a/src/backend/windows/dcomp.rs b/src/backend/windows/dcomp.rs deleted file mode 100644 index 9a57de32..00000000 --- a/src/backend/windows/dcomp.rs +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2018 The Druid Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Safe-ish wrappers for DirectComposition and related interfaces. - -// This module could become a general wrapper for DirectComposition, but -// for now we're just using what we need to get a swapchain up. - -use std::ptr::{null, null_mut}; - -use tracing::error; - -use winapi::shared::winerror::SUCCEEDED; -use winapi::um::d3d11::*; -use winapi::um::d3dcommon::{D3D_DRIVER_TYPE_HARDWARE, D3D_DRIVER_TYPE_WARP}; -use winapi::um::winnt::HRESULT; -use winapi::Interface; -use wio::com::ComPtr; - -unsafe fn wrap(hr: HRESULT, ptr: *mut T, f: F) -> Result -where - F: Fn(ComPtr) -> U, - T: Interface, -{ - if SUCCEEDED(hr) { - Ok(f(ComPtr::from_raw(ptr))) - } else { - Err(hr) - } -} - -pub struct D3D11Device(ComPtr); - -impl D3D11Device { - /// Creates a new device with basic defaults. - pub(crate) fn new_simple() -> Result { - let mut hr = 0; - unsafe { - let mut d3d11_device: *mut ID3D11Device = null_mut(); - // Note: could probably set single threaded in flags for small performance boost. - let flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; - // Prefer hardware but use warp if it's the only driver available. - for driver_type in &[D3D_DRIVER_TYPE_HARDWARE, D3D_DRIVER_TYPE_WARP] { - hr = D3D11CreateDevice( - null_mut(), - *driver_type, - null_mut(), - flags, - null(), - 0, - D3D11_SDK_VERSION, - &mut d3d11_device, - null_mut(), - null_mut(), - ); - if SUCCEEDED(hr) { - break; - } - } - if !SUCCEEDED(hr) { - error!("D3D11CreateDevice: 0x{:x}", hr); - } - wrap(hr, d3d11_device, D3D11Device) - } - } - - pub(crate) fn raw_ptr(&mut self) -> *mut ID3D11Device { - self.0.as_raw() - } -} diff --git a/src/backend/windows/dialog.rs b/src/backend/windows/dialog.rs index caf83784..2dd76168 100644 --- a/src/backend/windows/dialog.rs +++ b/src/backend/windows/dialog.rs @@ -60,7 +60,7 @@ unsafe fn make_wstrs(spec: &FileSpec) -> (Vec, Vec) { let exts = spec .extensions .iter() - .map(|s| normalize_extension(*s)) + .map(|s| normalize_extension(s)) .collect::>(); let name = format!("{} ({})", spec.name, exts.as_slice().join("; ")).to_wide(); let extensions = exts.as_slice().join(";").to_wide(); diff --git a/src/backend/windows/mod.rs b/src/backend/windows/mod.rs index dbacbe98..12241e8e 100644 --- a/src/backend/windows/mod.rs +++ b/src/backend/windows/mod.rs @@ -17,12 +17,12 @@ mod accels; pub mod application; pub mod clipboard; -pub mod dcomp; +// pub mod dcomp; pub mod dialog; pub mod error; mod keyboard; pub mod menu; -pub mod paint; +//pub mod paint; pub mod screen; mod timers; pub mod util; @@ -46,39 +46,8 @@ pub mod window; // Basically, go from HwndRenderTarget or DxgiSurfaceRenderTarget (2d or 3d) to a Device Context. // Go back up for particular needs. -use piet_common::d2d::DeviceContext; use std::fmt::{Debug, Display, Formatter}; use winapi::shared::winerror::HRESULT; -use winapi::um::d2d1::ID2D1RenderTarget; -use wio::com::ComPtr; - -#[derive(Clone)] -pub struct DxgiSurfaceRenderTarget { - ptr: ComPtr, -} - -impl DxgiSurfaceRenderTarget { - /// construct from raw ptr - /// - /// # Safety - /// TODO - pub unsafe fn from_raw(raw: *mut ID2D1RenderTarget) -> Self { - DxgiSurfaceRenderTarget { - ptr: ComPtr::from_raw(raw), - } - } - - /// cast to DeviceContext - /// - /// # Safety - /// TODO - pub unsafe fn as_device_context(&self) -> Option { - self.ptr - .cast() - .ok() - .map(|com_ptr| DeviceContext::new(com_ptr)) - } -} // error handling pub enum Error { diff --git a/src/backend/windows/util.rs b/src/backend/windows/util.rs index 8cd86538..af2ef1e1 100644 --- a/src/backend/windows/util.rs +++ b/src/backend/windows/util.rs @@ -40,11 +40,8 @@ use winapi::um::winbase::{FILE_TYPE_UNKNOWN, STD_ERROR_HANDLE, STD_OUTPUT_HANDLE use winapi::um::wincon::{AttachConsole, ATTACH_PARENT_PROCESS}; use winapi::um::winnt::{FILE_SHARE_WRITE, GENERIC_READ, GENERIC_WRITE}; -use crate::kurbo::Rect; -use crate::region::Region; -use crate::scale::{Scalable, Scale}; - use super::error::Error; +use crate::kurbo::Rect; pub fn as_result(hr: HRESULT) -> Result<(), Error> { if SUCCEEDED(hr) { @@ -107,17 +104,6 @@ impl FromWide for [u16] { } } -/// Converts a `Rect` to a winapi `RECT`. -#[inline] -pub(crate) fn rect_to_recti(rect: Rect) -> RECT { - RECT { - left: rect.x0 as i32, - top: rect.y0 as i32, - right: rect.x1 as i32, - bottom: rect.y1 as i32, - } -} - /// Converts a winapi `RECT` to a `Rect`. #[inline] pub(crate) fn recti_to_rect(rect: RECT) -> Rect { @@ -129,16 +115,6 @@ pub(crate) fn recti_to_rect(rect: RECT) -> Rect { ) } -/// Converts a `Region` into a vec of winapi `RECT`, with a scaling applied. -/// If necessary, the rectangles are rounded to the nearest pixel border. -pub(crate) fn region_to_rectis(region: &Region, scale: Scale) -> Vec { - region - .rects() - .iter() - .map(|r| rect_to_recti(r.to_px(scale).round())) - .collect() -} - // Types for functions we want to load, which are only supported on newer windows versions // from user32.dll type GetDpiForSystem = unsafe extern "system" fn() -> UINT; diff --git a/src/backend/windows/window.rs b/src/backend/windows/window.rs index 10e63c4e..019d50b5 100644 --- a/src/backend/windows/window.rs +++ b/src/backend/windows/window.rs @@ -25,47 +25,35 @@ use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; use scopeguard::defer; -use tracing::{debug, error, warn}; +use tracing::{error, warn}; use winapi::ctypes::{c_int, c_void}; -use winapi::shared::dxgi::*; -use winapi::shared::dxgi1_2::*; -use winapi::shared::dxgiformat::*; -use winapi::shared::dxgitype::*; use winapi::shared::minwindef::*; use winapi::shared::windef::*; use winapi::shared::winerror::*; -use winapi::um::dcomp::{IDCompositionDevice, IDCompositionTarget, IDCompositionVisual}; use winapi::um::dwmapi::{DwmExtendFrameIntoClientArea, DwmSetWindowAttribute}; use winapi::um::errhandlingapi::GetLastError; use winapi::um::shellscalingapi::MDT_EFFECTIVE_DPI; -use winapi::um::unknwnbase::*; use winapi::um::uxtheme::*; use winapi::um::wingdi::*; use winapi::um::winnt::*; -use winapi::um::winreg::{RegGetValueW, HKEY_CURRENT_USER, RRF_RT_REG_DWORD}; use winapi::um::winuser::*; -use winapi::Interface; -use wio::com::ComPtr; -#[cfg(feature = "raw-win-handle")] -use raw_window_handle::{HasRawWindowHandle, RawWindowHandle, Win32WindowHandle}; - -use piet_common::d2d::{D2DFactory, DeviceContext}; -use piet_common::dwrite::DwriteFactory; +use raw_window_handle::{ + HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle, Win32WindowHandle, + WindowsDisplayHandle, +}; use crate::kurbo::{Insets, Point, Rect, Size, Vec2}; -use crate::piet::{Piet, PietText, RenderContext}; use super::accels::register_accel; use super::application::Application; -use super::dcomp::D3D11Device; use super::dialog::get_file_dialog_path; use super::error::Error; use super::keyboard::{self, KeyboardState}; use super::menu::Menu; -use super::paint; +// use super::paint; use super::timers::TimerSlots; -use super::util::{self, as_result, FromWide, ToWide, OPTIONAL_FUNCTIONS}; +use super::util::{self, ToWide, OPTIONAL_FUNCTIONS}; use crate::common_util::IdleCallback; use crate::dialog::{FileDialogOptions, FileDialogType, FileInfo}; @@ -164,9 +152,8 @@ enum DeferredOp { ReleaseMouseCapture, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct WindowHandle { - text: PietText, state: Weak, } @@ -174,28 +161,32 @@ impl PartialEq for WindowHandle { fn eq(&self, other: &Self) -> bool { match (self.state.upgrade(), other.state.upgrade()) { (None, None) => true, - (Some(s), Some(o)) => std::rc::Rc::ptr_eq(&s, &o), + (Some(s), Some(o)) => Rc::ptr_eq(&s, &o), (_, _) => false, } } } impl Eq for WindowHandle {} -#[cfg(feature = "raw-win-handle")] unsafe impl HasRawWindowHandle for WindowHandle { fn raw_window_handle(&self) -> RawWindowHandle { + let mut handle = Win32WindowHandle::empty(); if let Some(hwnd) = self.get_hwnd() { - let mut handle = Win32WindowHandle::empty(); handle.hwnd = hwnd as *mut core::ffi::c_void; handle.hinstance = unsafe { - winapi::um::libloaderapi::GetModuleHandleW(0 as winapi::um::winnt::LPCWSTR) - as *mut core::ffi::c_void + winapi::um::libloaderapi::GetModuleHandleW(0 as LPCWSTR) as *mut core::ffi::c_void }; - RawWindowHandle::Win32(handle) - } else { - error!("Cannot retrieved HWND for window."); - RawWindowHandle::Win32(Win32WindowHandle::empty()) } + RawWindowHandle::Win32(handle) + } +} + +unsafe impl HasRawDisplayHandle for WindowHandle { + /// See: + /// * https://github.com/rust-windowing/raw-window-handle/issues/92 + /// * https://github.com/rust-windowing/winit/blob/92fdf5ba85f920262a61cee4590f4a11ad5738d1/src/platform_impl/windows/window.rs#L285 + fn raw_display_handle(&self) -> RawDisplayHandle { + RawDisplayHandle::Windows(WindowsDisplayHandle::empty()) } } @@ -263,24 +254,18 @@ trait WndProc { struct MyWndProc { app: Application, handle: RefCell, - d2d_factory: D2DFactory, - text: PietText, state: RefCell>, - present_strategy: PresentStrategy, } /// The mutable state of the window. struct WndState { handler: Box, - render_target: Option, - dxgi_state: Option, min_size: Option, keyboard_state: KeyboardState, // Stores a set of all mouse buttons that are currently holding mouse // capture. When the first mouse button is down on our window we enter // capture, and we hold it until the last mouse button is up. captured_mouse_buttons: MouseButtons, - transparent: bool, // Is this window the topmost window under the mouse cursor has_mouse_focus: bool, //TODO: track surrogate orphan @@ -289,19 +274,6 @@ struct WndState { click_count: u8, } -/// State for DXGI swapchains. -struct DxgiState { - swap_chain: *mut IDXGISwapChain1, - - // These ComPtrs must live as long as the window - #[allow(dead_code)] - composition_device: Option>, - #[allow(dead_code)] - composition_target: Option>, - #[allow(dead_code)] - composition_visual: Option>, -} - #[derive(Clone, PartialEq, Eq)] pub struct CustomCursor(Arc); @@ -426,41 +398,9 @@ fn set_style(hwnd: HWND, resizable: bool, titlebar: bool) { } impl WndState { - fn rebuild_render_target(&mut self, d2d: &D2DFactory, scale: Scale) -> Result<(), Error> { - unsafe { - let swap_chain = self.dxgi_state.as_ref().unwrap().swap_chain; - match paint::create_render_target_dxgi(d2d, swap_chain, scale, self.transparent) { - Ok(rt) => { - self.render_target = - Some(rt.as_device_context().expect("TODO remove this expect")); - Ok(()) - } - Err(e) => Err(e), - } - } - } - // Renders but does not present. - fn render(&mut self, d2d: &D2DFactory, text: &PietText, invalid: &Region) { - let rt = self.render_target.as_mut().unwrap(); - - rt.begin_draw(); - { - let mut piet_ctx = Piet::new(d2d, text.clone(), rt); - - // The documentation on DXGI_PRESENT_PARAMETERS says we "must not update any - // pixel outside of the dirty rectangles." - piet_ctx.clip(invalid.to_bez_path()); - self.handler.paint(&mut piet_ctx, invalid); - if let Err(e) = piet_ctx.finish() { - error!("piet error on render: {:?}", e); - } - } - // Maybe should deal with lost device here... - let res = rt.end_draw(); - if let Err(e) = res { - error!("EndDraw error: {:?}", e); - } + fn render(&mut self, invalid: &Region) { + self.handler.paint(invalid); } fn enter_mouse_capture(&mut self, hwnd: HWND, button: MouseButton) { @@ -521,7 +461,7 @@ impl MyWndProc { /// Takes the invalid region and returns it, replacing it with the empty region. fn take_invalid(&self) -> Region { self.with_window_state(|state| { - std::mem::replace(&mut *state.invalid.borrow_mut(), Region::EMPTY) + mem::replace(&mut *state.invalid.borrow_mut(), Region::EMPTY) }) } @@ -738,21 +678,8 @@ impl WndProc for MyWndProc { state.hwnd.set(hwnd); } if let Some(state) = self.state.borrow_mut().as_mut() { - let dxgi_state = unsafe { - create_dxgi_state(self.present_strategy, hwnd, self.is_transparent()) - .unwrap_or_else(|e| { - error!("Creating swapchain failed: {:?}", e); - None - }) - }; - state.dxgi_state = dxgi_state; - let handle = self.handle.borrow().to_owned(); state.handler.connect(&handle.into()); - - if let Err(e) = state.rebuild_render_target(&self.d2d_factory, scale) { - error!("error building render target: {}", e); - } } Some(0) } @@ -760,7 +687,7 @@ impl WndProc for MyWndProc { if LOWORD(wparam as u32) as u32 != 0 { unsafe { if !self.has_titlebar() && !self.is_transparent() { - // This makes windows paint the dropshadow around the window + // This makes windows paint the drop-shadow around the window // since we give it a "1 pixel frame" that we paint over anyway. // From my testing top seems to be the best option when it comes to avoiding resize artifacts. let margins = MARGINS { @@ -822,17 +749,7 @@ impl WndProc for MyWndProc { let invalid = self.take_invalid(); if !invalid.rects().is_empty() { s.handler.rebuild_resources(); - s.render(&self.d2d_factory, &self.text, &invalid); - if let Some(ref mut ds) = s.dxgi_state { - let mut dirty_rects = util::region_to_rectis(&invalid, self.scale()); - let params = DXGI_PRESENT_PARAMETERS { - DirtyRectsCount: dirty_rects.len() as u32, - pDirtyRects: dirty_rects.as_mut_ptr(), - pScrollRect: null_mut(), - pScrollOffset: null_mut(), - }; - (*ds.swap_chain).Present1(1, 0, ¶ms); - } + s.render(&invalid); } }); Some(0) @@ -934,7 +851,7 @@ impl WndProc for MyWndProc { } Some(hit) }, - WM_SIZE => unsafe { + WM_SIZE => { let width = LOWORD(lparam as u32) as u32; let height = HIWORD(lparam as u32) as u32; if width == 0 || height == 0 { @@ -946,36 +863,10 @@ impl WndProc for MyWndProc { let size_dp = area.size_dp(); self.set_area(area); s.handler.size(size_dp); - let res; - { - s.render_target = None; - res = (*s.dxgi_state.as_mut().unwrap().swap_chain).ResizeBuffers( - 0, - width, - height, - DXGI_FORMAT_UNKNOWN, - 0, - ); - } - if SUCCEEDED(res) { - if let Err(e) = s.rebuild_render_target(&self.d2d_factory, scale) { - error!("error building render target: {}", e); - } - s.render(&self.d2d_factory, &self.text, &size_dp.to_rect().into()); - let present_after = match self.present_strategy { - PresentStrategy::Sequential => 1, - _ => 0, - }; - if let Some(ref mut dxgi_state) = s.dxgi_state { - (*dxgi_state.swap_chain).Present(present_after, 0); - } - ValidateRect(hwnd, null_mut()); - } else { - error!("ResizeBuffers failed: 0x{:x}", res); - } + s.render(&size_dp.to_rect().into()); }) .map(|_| 0) - }, + } WM_COMMAND => { self.with_wnd_state(|s| s.handler.command(LOWORD(wparam as u32) as u32)); Some(0) @@ -1319,7 +1210,7 @@ impl WindowBuilder { self.present_strategy = PresentStrategy::Flip; self.transparent = true; } else { - tracing::warn!("Transparency requires Windows 8 or newer"); + warn!("Transparency requires Windows 8 or newer"); } } } @@ -1346,17 +1237,11 @@ impl WindowBuilder { pub fn build(self) -> Result { unsafe { - let class_name = super::util::CLASS_NAME.to_wide(); - let dwrite_factory = DwriteFactory::new().unwrap(); - let fonts = self.app.fonts.clone(); - let text = PietText::new_with_shared_fonts(dwrite_factory, Some(fonts)); + let class_name = util::CLASS_NAME.to_wide(); let wndproc = MyWndProc { app: self.app.clone(), handle: Default::default(), - d2d_factory: D2DFactory::new().unwrap(), - text: text.clone(), state: RefCell::new(None), - present_strategy: self.present_strategy, }; // TODO: pos_x and pos_y are only scaled for windows with parents. But they need to be @@ -1440,19 +1325,15 @@ impl WindowBuilder { }; let win = Rc::new(window); let handle = WindowHandle { - text, state: Rc::downgrade(&win), }; let state = WndState { handler: self.handler.unwrap(), - render_target: None, - dxgi_state: None, min_size: self.min_size, keyboard_state: KeyboardState::new(), captured_mouse_buttons: MouseButtons::new(), has_mouse_focus: false, - transparent: self.transparent, last_click_time: Instant::now(), last_click_pos: (0, 0), click_count: 0, @@ -1518,13 +1399,13 @@ impl WindowBuilder { // Dark mode support // https://docs.microsoft.com/en-us/windows/apps/desktop/modernize/apply-windows-themes const DWMWA_USE_IMMERSIVE_DARK_MODE: u32 = 20; - let value: BOOL = if should_use_light_theme() { 0 } else { 1 }; + let value: BOOL = 1; let value_ptr = &value as *const _ as *const c_void; DwmSetWindowAttribute( hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, value_ptr, - std::mem::size_of::() as u32, + mem::size_of::() as u32, ); self.app.add_window(hwnd); @@ -1552,184 +1433,6 @@ impl WindowBuilder { } } -/// Attempt to read the registry and see if the system is set to a dark or -/// light theme. -pub fn should_use_light_theme() -> bool { - let mut data: [u8; 4] = [0; 4]; - let mut cb_data: u32 = data.len() as u32; - let res = unsafe { - RegGetValueW( - HKEY_CURRENT_USER, - r#"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize"# - .to_wide() - .as_ptr(), - "AppsUseLightTheme".to_wide().as_ptr(), - RRF_RT_REG_DWORD, - std::ptr::null_mut(), - data.as_mut_ptr() as _, - &mut cb_data as *mut _, - ) - }; - - // ERROR_SUCCESS - if res == 0 { - i32::from_le_bytes(data) == 1 - } else { - true // Default to light theme. - } -} - -/// Choose an adapter. Here the heuristic is to choose the adapter with the -/// largest video memory, which will generally be the discrete adapter. It's -/// possible that on some systems the integrated adapter might be a better -/// choice, but that probably depends on usage. -unsafe fn choose_adapter(factory: *mut IDXGIFactory2) -> *mut IDXGIAdapter { - let mut i = 0; - let mut best_adapter = null_mut(); - let mut best_vram = 0; - loop { - let mut adapter: *mut IDXGIAdapter = null_mut(); - if !SUCCEEDED((*factory).EnumAdapters(i, &mut adapter)) { - break; - } - let mut desc = mem::MaybeUninit::uninit(); - let hr = (*adapter).GetDesc(desc.as_mut_ptr()); - if !SUCCEEDED(hr) { - error!("Failed to get adapter description: {:?}", Error::Hr(hr)); - break; - } - let mut desc: DXGI_ADAPTER_DESC = desc.assume_init(); - let vram = desc.DedicatedVideoMemory; - if i == 0 || vram > best_vram { - best_vram = vram; - best_adapter = adapter; - } - debug!( - "{:?}: desc = {:?}, vram = {}", - adapter, - (&mut desc.Description[0] as LPWSTR).to_string(), - desc.DedicatedVideoMemory - ); - i += 1; - } - best_adapter -} - -unsafe fn create_dxgi_state( - present_strategy: PresentStrategy, - hwnd: HWND, - transparent: bool, -) -> Result, Error> { - let mut factory: *mut IDXGIFactory2 = null_mut(); - as_result(CreateDXGIFactory1( - &IID_IDXGIFactory2, - &mut factory as *mut *mut IDXGIFactory2 as *mut *mut c_void, - ))?; - debug!("dxgi factory pointer = {:?}", factory); - let adapter = choose_adapter(factory); - debug!("adapter = {:?}", adapter); - - let mut d3d11_device = D3D11Device::new_simple()?; - - let (swap_effect, bufs) = match present_strategy { - PresentStrategy::Sequential => (DXGI_SWAP_EFFECT_SEQUENTIAL, 1), - PresentStrategy::Flip | PresentStrategy::FlipRedirect => { - (DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL, 2) - } - }; - - let desc = DXGI_SWAP_CHAIN_DESC1 { - Width: 1024, - Height: 768, - Format: DXGI_FORMAT_B8G8R8A8_UNORM, - Stereo: FALSE, - SampleDesc: DXGI_SAMPLE_DESC { - Count: 1, - Quality: 0, - }, - BufferUsage: DXGI_USAGE_RENDER_TARGET_OUTPUT, - BufferCount: bufs, - Scaling: DXGI_SCALING_STRETCH, - SwapEffect: swap_effect, - AlphaMode: if transparent { - DXGI_ALPHA_MODE_PREMULTIPLIED - } else { - DXGI_ALPHA_MODE_IGNORE - }, - Flags: 0, - }; - let mut swap_chain: *mut IDXGISwapChain1 = null_mut(); - let swap_chain_res = if transparent { - (*factory).CreateSwapChainForComposition( - d3d11_device.raw_ptr() as *mut IUnknown, - &desc, - null_mut(), - &mut swap_chain, - ) - } else { - (*factory).CreateSwapChainForHwnd( - d3d11_device.raw_ptr() as *mut IUnknown, - hwnd, - &desc, - null_mut(), - null_mut(), - &mut swap_chain, - ) - }; - debug!( - "swap chain res = 0x{:x}, pointer = {:?}", - swap_chain_res, swap_chain - ); - - let (composition_device, composition_target, composition_visual) = if transparent { - // This behavior is only supported on windows 8 and newer where - // composition is available - - // Following resources are created according to this tutorial: - // https://docs.microsoft.com/en-us/archive/msdn-magazine/2014/june/windows-with-c-high-performance-window-layering-using-the-windows-composition-engine - let DCompositionCreateDevice = OPTIONAL_FUNCTIONS.DCompositionCreateDevice.unwrap(); - - // Create IDCompositionDevice - let mut ptr: *mut c_void = null_mut(); - DCompositionCreateDevice( - d3d11_device.raw_ptr() as *mut IDXGIDevice, - &IDCompositionDevice::uuidof(), - &mut ptr, - ); - let composition_device = ComPtr::::from_raw(ptr as _); - - // Create IDCompositionTarget for the window - let mut ptr: *mut IDCompositionTarget = null_mut(); - composition_device.CreateTargetForHwnd(hwnd as _, 1, &mut ptr); - let composition_target = ComPtr::from_raw(ptr); - - // Create IDCompositionVisual and assign to swap chain - let mut ptr: *mut IDCompositionVisual = null_mut(); - composition_device.CreateVisual(&mut ptr); - let composition_visual = ComPtr::from_raw(ptr); - composition_visual.SetContent(swap_chain as *mut IUnknown); - - // Set the root as composition target and commit - composition_target.SetRoot(composition_visual.as_raw()); - composition_device.Commit(); - - ( - Some(composition_device), - Some(composition_target), - Some(composition_visual), - ) - } else { - (None, None, None) - }; - - Ok(Some(DxgiState { - swap_chain, - composition_device, - composition_target, - composition_visual, - })) -} - #[cfg(target_arch = "x86_64")] type WindowLongPtr = winapi::shared::basetsd::LONG_PTR; #[cfg(target_arch = "x86")] @@ -1758,7 +1461,7 @@ pub(crate) unsafe extern "system" fn win_proc_dispatch( if msg == WM_NCDESTROY && !window_ptr.is_null() { (*window_ptr).wndproc.cleanup(hwnd); SetWindowLongPtrW(hwnd, GWLP_USERDATA, 0); - mem::drop(Rc::from_raw(window_ptr)); + drop(Rc::from_raw(window_ptr)); } match result { @@ -2086,10 +1789,6 @@ impl WindowHandle { self.defer(DeferredOp::ContextMenu(menu, pos)); } - pub fn text(&self) -> PietText { - self.text.clone() - } - pub fn add_text_field(&self) -> TextFieldToken { TextFieldToken::next() } @@ -2115,7 +1814,7 @@ impl WindowHandle { /// Request a timer event. /// /// The return value is an identifier. - pub fn request_timer(&self, deadline: std::time::Instant) -> TimerToken { + pub fn request_timer(&self, deadline: Instant) -> TimerToken { let (id, elapse) = self.get_timer_slot(deadline); let id = self .get_hwnd() @@ -2153,8 +1852,8 @@ impl WindowHandle { } defer!(DeleteDC(bmp_dc);); - let width = cursor_desc.image.width(); - let height = cursor_desc.image.height(); + let width = 1; //cursor_desc.image.width(); + let height = 1; //cursor_desc.image.height(); let mask = CreateCompatibleBitmap(hdc, width as c_int, height as c_int); if mask.is_null() { return None; @@ -2170,16 +1869,16 @@ impl WindowHandle { let old_mask = SelectObject(mask_dc, mask as *mut c_void); let old_bmp = SelectObject(bmp_dc, bmp as *mut c_void); - for (row_idx, row) in cursor_desc.image.pixel_colors().enumerate() { - for (col_idx, p) in row.enumerate() { - let (r, g, b, a) = p.as_rgba8(); - // TODO: what's the story on partial transparency? I couldn't find documentation. - let mask_px = RGB(255 - a, 255 - a, 255 - a); - let bmp_px = RGB(r, g, b); - SetPixel(mask_dc, col_idx as i32, row_idx as i32, mask_px); - SetPixel(bmp_dc, col_idx as i32, row_idx as i32, bmp_px); - } - } + // for (row_idx, row) in cursor_desc.image.pixel_colors().enumerate() { + // for (col_idx, p) in row.enumerate() { + // let (r, g, b, a) = p.as_rgba8(); + // // TODO: what's the story on partial transparency? I couldn't find documentation. + // let mask_px = RGB(255 - a, 255 - a, 255 - a); + // let bmp_px = RGB(r, g, b); + // SetPixel(mask_dc, col_idx as i32, row_idx as i32, mask_px); + // SetPixel(bmp_dc, col_idx as i32, row_idx as i32, bmp_px); + // } + // } SelectObject(mask_dc, old_mask); SelectObject(bmp_dc, old_bmp); @@ -2254,7 +1953,7 @@ impl WindowHandle { /// Allocate a timer slot. /// /// Returns an id and an elapsed time in ms - fn get_timer_slot(&self, deadline: std::time::Instant) -> (TimerToken, u32) { + fn get_timer_slot(&self, deadline: Instant) -> (TimerToken, u32) { if let Some(w) = self.state.upgrade() { let mut timers = w.timers.lock().unwrap(); let id = timers.alloc(); @@ -2303,12 +2002,3 @@ impl IdleHandle { queue.push(IdleKind::Token(token)); } } - -impl Default for WindowHandle { - fn default() -> Self { - WindowHandle { - state: Default::default(), - text: PietText::new_with_shared_fonts(DwriteFactory::new().unwrap(), None), - } - } -} diff --git a/src/backend/x11/application.rs b/src/backend/x11/application.rs index e1594080..b3c8a845 100644 --- a/src/backend/x11/application.rs +++ b/src/backend/x11/application.rs @@ -23,9 +23,7 @@ use std::time::{Duration, Instant}; use anyhow::{anyhow, Context, Error}; use x11rb::connection::{Connection, RequestConnection}; -use x11rb::protocol::present::ConnectionExt as _; use x11rb::protocol::render::{self, ConnectionExt as _, Pictformat}; -use x11rb::protocol::xfixes::ConnectionExt as _; use x11rb::protocol::xproto::{ self, ConnectionExt, CreateWindowAux, EventMask, Timestamp, Visualtype, WindowClass, }; @@ -173,8 +171,6 @@ pub(crate) struct Application { /// The write end of the "idle pipe", a pipe that allows the event loop to be woken up from /// other threads. idle_write: RawFd, - /// The major opcode of the Present extension, if it is supported. - present_opcode: Option, /// Support for the render extension in at least version 0.5? render_argb32_pictformat_cursor: Option, /// Newest timestamp that we received @@ -238,19 +234,6 @@ impl Application { let (idle_read, idle_write) = nix::unistd::pipe2(nix::fcntl::OFlag::O_NONBLOCK)?; - let present_opcode = if std::env::var_os("glazier_DISABLE_X11_PRESENT").is_some() { - // Allow disabling Present with an environment variable. - None - } else { - match Application::query_present_opcode(&connection) { - Ok(p) => p, - Err(e) => { - tracing::info!("failed to find Present extension: {}", e); - None - } - } - }; - let pictformats = connection.render_query_pict_formats()?; let render_create_cursor_supported = matches!(connection .extension_information(render::X11_EXTENSION_NAME)? @@ -312,7 +295,7 @@ impl Application { .ok_or_else(|| anyhow!("Invalid screen num: {}", screen_num))?; let root_visual_type = util::get_visual_from_screen(screen) .ok_or_else(|| anyhow!("Couldn't get visual from screen"))?; - let argb_visual_type = util::get_argb_visual_type(&*connection, screen)?; + let argb_visual_type = util::get_argb_visual_type(&connection, screen)?; let timestamp = Rc::new(Cell::new(x11rb::CURRENT_TIME)); let pending_events = Default::default(); @@ -344,7 +327,6 @@ impl Application { clipboard, primary, idle_write, - present_opcode, root_visual_type, argb_visual_type, atoms, @@ -355,53 +337,6 @@ impl Application { }) } - // Check if the Present extension is supported, returning its opcode if it is. - fn query_present_opcode(conn: &Rc) -> Result, Error> { - let query = conn - .query_extension(b"Present")? - .reply() - .context("query Present extension")?; - - if !query.present { - return Ok(None); - } - - let opcode = Some(query.major_opcode); - - // If Present is there at all, version 1.0 should be supported. This code - // shouldn't have a real effect; it's just a sanity check. - let version = conn - .present_query_version(1, 0)? - .reply() - .context("query Present version")?; - tracing::info!( - "X server supports Present version {}.{}", - version.major_version, - version.minor_version, - ); - - // We need the XFIXES extension to use regions. This code looks like it's just doing a - // sanity check but it is *necessary*: XFIXES doesn't work until we've done version - // negotiation - // (https://www.x.org/releases/X11R7.7/doc/fixesproto/fixesproto.txt) - let version = conn - .xfixes_query_version(5, 0)? - .reply() - .context("query XFIXES version")?; - tracing::info!( - "X server supports XFIXES version {}.{}", - version.major_version, - version.minor_version, - ); - - Ok(opcode) - } - - #[inline] - pub(crate) fn present_opcode(&self) -> Option { - self.present_opcode - } - /// Return the ARGB32 pictformat of the server, but only if RENDER's CreateCursor is supported #[inline] pub(crate) fn render_argb32_pictformat_cursor(&self) -> Option { @@ -506,7 +441,7 @@ impl Application { #[inline] pub(crate) fn atoms(&self) -> &AppAtoms { - &*self.atoms + &self.atoms } /// Returns `Ok(true)` if we want to exit the main loop. @@ -634,20 +569,6 @@ impl Application { .context("CONFIGURE_NOTIFY - failed to handle")?; } } - Event::PresentCompleteNotify(ev) => { - let w = self - .window(ev.window) - .context("COMPLETE_NOTIFY - failed to get window")?; - w.handle_complete_notify(ev) - .context("COMPLETE_NOTIFY - failed to handle")?; - } - Event::PresentIdleNotify(ev) => { - let w = self - .window(ev.window) - .context("IDLE_NOTIFY - failed to get window")?; - w.handle_idle_notify(ev) - .context("IDLE_NOTIFY - failed to handle")?; - } Event::SelectionClear(ev) => { self.clipboard .handle_clear(*ev) @@ -915,7 +836,7 @@ fn poll_with_timeout( break; } else { let millis = c_int::try_from(deadline.duration_since(now).as_millis()) - .unwrap_or(c_int::max_value() - 1); + .unwrap_or(c_int::MAX - 1); // The above .as_millis() rounds down. This means we would wake up before the // deadline is reached. Add one to 'simulate' rounding up instead. millis + 1 diff --git a/src/backend/x11/clipboard.rs b/src/backend/x11/clipboard.rs index ca11cc15..ef6135a7 100644 --- a/src/backend/x11/clipboard.rs +++ b/src/backend/x11/clipboard.rs @@ -365,7 +365,7 @@ impl ClipboardState { if Some(event.owner) == window { // We lost ownership of the selection, clean up if let Some(mut contents) = self.contents.take() { - contents.destroy(&*self.connection)?; + contents.destroy(&self.connection)?; } } Ok(()) @@ -453,7 +453,7 @@ impl ClipboardState { property: event.property, time: event.time, }; - conn.send_event(false, event.requestor, EventMask::NO_EVENT, &event)?; + conn.send_event(false, event.requestor, EventMask::NO_EVENT, event)?; Ok(()) } @@ -473,7 +473,7 @@ impl ClipboardState { .iter_mut() .find(|transfer| matches(transfer, event)) { - let done = transfer.continue_incremental(&*self.connection)?; + let done = transfer.continue_incremental(&self.connection)?; if done { debug!("INCR transfer finished"); // Remove the transfer @@ -650,7 +650,7 @@ fn reject_transfer( property: x11rb::NONE, time: event.time, }; - conn.send_event(false, event.requestor, EventMask::NO_EVENT, &event)?; + conn.send_event(false, event.requestor, EventMask::NO_EVENT, event)?; Ok(()) } @@ -682,7 +682,7 @@ fn wait_for_event_with_deadline( // Use poll() to wait for the socket to become readable. let mut poll_fds = [PollFd::new(conn.as_raw_fd(), PollFlags::POLLIN)]; let poll_timeout = c_int::try_from(deadline.duration_since(now).as_millis()) - .unwrap_or(c_int::max_value() - 1) + .unwrap_or(c_int::MAX - 1) // The above rounds down, but we don't want to wake up to early, so add one .saturating_add(1); diff --git a/src/backend/x11/window.rs b/src/backend/x11/window.rs index 2d4c0f87..0d5d2551 100644 --- a/src/backend/x11/window.rs +++ b/src/backend/x11/window.rs @@ -16,7 +16,7 @@ use std::cell::{Cell, RefCell}; use std::collections::BinaryHeap; -use std::convert::{TryFrom, TryInto}; +use std::convert::TryFrom; use std::os::unix::io::RawFd; use std::panic::Location; use std::rc::{Rc, Weak}; @@ -25,24 +25,20 @@ use std::time::Instant; use crate::scale::Scalable; use anyhow::{anyhow, Context, Error}; -use cairo::{XCBConnection as CairoXCBConnection, XCBDrawable, XCBSurface, XCBVisualType}; -use tracing::{error, info, warn}; +use tracing::{error, warn}; use x11rb::connection::Connection; use x11rb::errors::ReplyOrIdError; use x11rb::properties::{WmHints, WmHintsState, WmSizeHints}; -use x11rb::protocol::present::{CompleteNotifyEvent, ConnectionExt as _, IdleNotifyEvent}; -use x11rb::protocol::render::{ConnectionExt as _, Pictformat}; -use x11rb::protocol::xfixes::{ConnectionExt as _, Region as XRegion}; +use x11rb::protocol::render::Pictformat; use x11rb::protocol::xproto::{ self, AtomEnum, ChangeWindowAttributesAux, ColormapAlloc, ConfigureNotifyEvent, - ConfigureWindowAux, ConnectionExt, CreateGCAux, EventMask, Gcontext, ImageFormat, - ImageOrder as X11ImageOrder, Pixmap, PropMode, Rectangle, Visualtype, WindowClass, + ConfigureWindowAux, ConnectionExt, EventMask, ImageOrder as X11ImageOrder, PropMode, + Visualtype, WindowClass, }; use x11rb::wrapper::ConnectionExt as _; use x11rb::xcb_ffi::XCBConnection; -#[cfg(feature = "raw-win-handle")] -use raw_window_handle::{HasRawWindowHandle, RawWindowHandle, XcbWindowHandle}; +use raw_window_handle::{HasRawWindowHandle, RawWindowHandle, XcbHandle}; use crate::backend::shared::Timer; use crate::common_util::IdleCallback; @@ -51,7 +47,6 @@ use crate::error::Error as ShellError; use crate::keyboard::{KeyState, Modifiers}; use crate::kurbo::{Insets, Point, Rect, Size, Vec2}; use crate::mouse::{Cursor, CursorDesc, MouseButton, MouseButtons, MouseEvent}; -use crate::piet::{Piet, PietText, RenderContext}; use crate::region::Region; use crate::scale::Scale; use crate::text::{simulate_input, Event}; @@ -173,7 +168,7 @@ impl WindowBuilder { self.position = Some(position); } - pub fn set_level(&mut self, level: window::WindowLevel) { + pub fn set_level(&mut self, level: WindowLevel) { self.level = level; } @@ -189,35 +184,6 @@ impl WindowBuilder { // TODO(x11/menus): implement WindowBuilder::set_menu (currently a no-op) } - fn create_cairo_surface( - &self, - window_id: u32, - visual_type: &Visualtype, - ) -> Result { - let conn = self.app.connection(); - let cairo_xcb_connection = unsafe { - CairoXCBConnection::from_raw_none( - conn.get_raw_xcb_connection() as *mut cairo_sys::xcb_connection_t - ) - }; - let cairo_drawable = XCBDrawable(window_id); - let mut xcb_visual = xcb_visualtype_t::from(*visual_type); - let cairo_visual_type = unsafe { - XCBVisualType::from_raw_none( - &mut xcb_visual as *mut xcb_visualtype_t as *mut cairo_sys::xcb_visualtype_t, - ) - }; - let cairo_surface = XCBSurface::create( - &cairo_xcb_connection, - &cairo_drawable, - &cairo_visual_type, - self.size.width as i32, - self.size.height as i32, - ) - .map_err(|status| anyhow!("Failed to create cairo surface: {}", status))?; - Ok(cairo_surface) - } - // TODO(x11/menus): make menus if requested pub fn build(self) -> Result { let conn = self.app.connection(); @@ -337,37 +303,13 @@ impl WindowBuilder { conn.free_colormap(colormap)?; } - // Allocate a graphics context (currently used only for copying pixels when present is - // unavailable). - let gc = conn.generate_id()?; - conn.create_gc(gc, id, &CreateGCAux::new())? - .check() - .context("create graphics context")?; - - // TODO(x11/errors): Should do proper cleanup (window destruction etc) in case of error - let cairo_surface = RefCell::new(self.create_cairo_surface(id, &visual_type)?); - let present_data = match self.initialize_present_data(id) { - Ok(p) => Some(p), - Err(e) => { - info!("Failed to initialize present extension: {}", e); - None - } - }; let handler = RefCell::new(self.handler.unwrap()); - // When using present, we generally need two buffers (because after we present, we aren't - // allowed to use that buffer for a little while, and so we might want to render to the - // other one). Otherwise, we only need one. - let buf_count = if present_data.is_some() { 2 } else { 1 }; - let buffers = RefCell::new(Buffers::new( - conn, id, buf_count, width_px, height_px, depth, - )?); - // Initialize some properties let atoms = self.app.atoms(); let pid = nix::unistd::Pid::this().as_raw(); if let Ok(pid) = u32::try_from(pid) { conn.change_property32( - xproto::PropMode::REPLACE, + PropMode::REPLACE, id, atoms._NET_WM_PID, AtomEnum::CARDINAL, @@ -471,10 +413,8 @@ impl WindowBuilder { let window = Rc::new(Window { id, - gc, app: self.app.clone(), handler, - cairo_surface, area: Cell::new(ScaledArea::from_px(size_px, scale)), scale: Cell::new(scale), min_size, @@ -483,8 +423,6 @@ impl WindowBuilder { timer_queue: Mutex::new(BinaryHeap::new()), idle_queue: Arc::new(Mutex::new(Vec::new())), idle_pipe: self.app.idle_pipe(), - present_data: RefCell::new(present_data), - buffers, active_text_field: Cell::new(None), parent, }); @@ -501,39 +439,6 @@ impl WindowBuilder { Ok(handle) } - - fn initialize_present_data(&self, window_id: u32) -> Result { - if self.app.present_opcode().is_some() { - let conn = self.app.connection(); - - // We use the CompleteNotify events to schedule the next frame, and the IdleNotify - // events to manage our buffers. - let id = conn.generate_id()?; - use x11rb::protocol::present::EventMask; - conn.present_select_input( - id, - window_id, - EventMask::COMPLETE_NOTIFY | EventMask::IDLE_NOTIFY, - )? - .check() - .context("set present event mask")?; - - let region_id = conn.generate_id()?; - conn.xfixes_create_region(region_id, &[]) - .context("create region")?; - - Ok(PresentData { - serial: 0, - region: region_id, - waiting_on: None, - needs_present: false, - last_msc: None, - last_ust: None, - }) - } else { - Err(anyhow!("no present opcode")) - } - } } /// An X11 window. @@ -555,10 +460,8 @@ impl WindowBuilder { // case 2 smaller than the data accessible in case 1). pub(crate) struct Window { id: u32, - gc: Gcontext, app: Application, handler: RefCell>, - cairo_surface: RefCell, area: Cell, scale: Cell, // min size in px @@ -572,107 +475,17 @@ pub(crate) struct Window { idle_queue: Arc>>, // Writing to this wakes up the event loop, so that it can run idle handlers. idle_pipe: RawFd, - - /// When this is `Some(_)`, we use the X11 Present extension to present windows. This syncs all - /// presentation to vblank and it appears to prevent tearing (subject to various caveats - /// regarding broken video drivers). - /// - /// The Present extension works roughly like this: we submit a pixmap for presentation. It will - /// get drawn at the next vblank, and some time shortly after that we'll get a notification - /// that the drawing was completed. - /// - /// There are three ways that rendering can get triggered: - /// 1) We render a frame, and it signals to us that an animation is requested. In this case, we - /// will render the next frame as soon as we get a notification that the just-presented - /// frame completed. In other words, we use `CompleteNotifyEvent` to schedule rendering. - /// 2) We get an expose event telling us that a region got invalidated. In - /// this case, we will render the next frame immediately unless we're already waiting for a - /// completion notification. (If we are waiting for a completion notification, we just make - /// a note to schedule a new frame once we get it.) - /// 3) Someone calls `invalidate` or `invalidate_rect` on us. We schedule ourselves to repaint - /// in the idle loop. This is better than rendering straight away, because for example they - /// might have called `invalidate` from their paint callback, and then we'd end up painting - /// re-entrantively. - /// - /// This is probably not the best (or at least, not the lowest-latency) scheme we can come up - /// with, because invalidations that happen shortly after a vblank might need to wait 2 frames - /// before they appear. If we're getting lots of invalidations, it might be better to render more - /// than once per frame. Note that if we do, it will require some changes to part 1) above, - /// because if we render twice in a frame then we will get two completion notifications in a - /// row, so we don't want to present on both of them. The `msc` field of the completion - /// notification might be useful here, because it allows us to check how many frames have - /// actually been presented. - present_data: RefCell>, - buffers: RefCell, active_text_field: Cell>, parent: Weak, } -/// A collection of pixmaps for rendering to. This gets used in two different ways: if the present -/// extension is enabled, we render to a pixmap and then present it. If the present extension is -/// disabled, we render to a pixmap and then call `copy_area` on it (this probably isn't the best -/// way to imitate double buffering, but it's the fallback anyway). -struct Buffers { - /// A list of idle pixmaps. We take a pixmap from here for rendering to. - /// - /// When we're not using the present extension, all pixmaps belong in here; as soon as we copy - /// from one, we can use it again. - /// - /// When we submit a pixmap to present, we're not allowed to touch it again until we get a - /// corresponding IDLE_NOTIFY event. In my limited experiments this happens shortly after - /// vsync, meaning that we may want to start rendering the next pixmap before we get the old - /// one back. Therefore, we keep a list of pixmaps. We pop one each time we render, and push - /// one when we get IDLE_NOTIFY. - /// - /// Since the current code only renders at most once per vsync, two pixmaps seems to always be - /// enough. Nevertheless, we will allocate more on the fly if we need them. Note that rendering - /// more than once per vsync can only improve latency, because only the most recently-presented - /// pixmap will get rendered. - idle_pixmaps: Vec, - /// A list of all the allocated pixmaps (including the idle ones). - all_pixmaps: Vec, - /// The sizes of the pixmaps (they all have the same size). In order to avoid repeatedly - /// reallocating as the window size changes, we allow these to be bigger than the window. - width: u16, - height: u16, - /// The depth of the currently allocated pixmaps. - depth: u8, -} - -/// The state involved in using X's [Present] extension. -/// -/// [Present]: https://cgit.freedesktop.org/xorg/proto/presentproto/tree/presentproto.txt -#[derive(Debug)] -struct PresentData { - /// A monotonically increasing present request counter. - serial: u32, - /// The region that we use for telling X what to present. - region: XRegion, - /// Did we submit a present that hasn't completed yet? If so, this is its serial number. - waiting_on: Option, - /// We need to render another frame as soon as the current one is done presenting. - needs_present: bool, - /// The last MSC (media stream counter) that was completed. This can be used to diagnose - /// latency problems, because MSC is a frame counter: it increments once per frame. We should - /// be presenting on every frame, and storing the last completed MSC lets us know if we missed - /// one. - last_msc: Option, - /// The time at which the last frame was completed. The present protocol documentation doesn't - /// define the units, but it appears to be in microseconds. - last_ust: Option, -} - #[derive(Clone, PartialEq, Eq)] pub struct CustomCursor(xproto::Cursor); impl Window { #[track_caller] fn with_handler T>(&self, f: F) -> Option { - if self.cairo_surface.try_borrow_mut().is_err() - || self.invalid.try_borrow_mut().is_err() - || self.present_data.try_borrow_mut().is_err() - || self.buffers.try_borrow_mut().is_err() - { + if self.invalid.try_borrow_mut().is_err() { error!("other RefCells were borrowed when calling into the handler"); return None; } @@ -733,21 +546,6 @@ impl Window { } }; if new_size { - borrow_mut!(self.buffers)?.set_size( - self.app.connection(), - self.id, - size.width as u16, - size.height as u16, - ); - borrow_mut!(self.cairo_surface)? - .set_size(size.width as i32, size.height as i32) - .map_err(|status| { - anyhow!( - "Failed to update cairo surface size to {:?}: {}", - size, - status - ) - })?; self.add_invalid_rect(size.to_dp(scale).to_rect())?; self.with_handler(|h| h.size(size.to_dp(scale))); self.with_handler(|h| h.scale(scale)); @@ -755,23 +553,6 @@ impl Window { Ok(()) } - // Ensure that our cairo context is targeting the right drawable, allocating one if necessary. - fn update_cairo_surface(&self) -> Result<(), Error> { - let mut buffers = borrow_mut!(self.buffers)?; - let pixmap = if let Some(p) = buffers.idle_pixmaps.last() { - *p - } else { - info!("ran out of idle pixmaps, creating a new one"); - buffers.create_pixmap(self.app.connection(), self.id)? - }; - - let drawable = XCBDrawable(pixmap); - borrow_mut!(self.cairo_surface)? - .set_drawable(&drawable, buffers.width as i32, buffers.height as i32) - .map_err(|e| anyhow!("Failed to update cairo drawable: {}", e))?; - Ok(()) - } - fn render(&self) -> Result<(), Error> { self.with_handler(|h| h.prepare_paint()); @@ -779,71 +560,11 @@ impl Window { return Ok(()); } - self.update_cairo_surface()?; let invalid = std::mem::replace(&mut *borrow_mut!(self.invalid)?, Region::EMPTY); - { - let surface = borrow!(self.cairo_surface)?; - let cairo_ctx = cairo::Context::new(&surface).unwrap(); - let scale = self.scale.get(); - for rect in invalid.rects() { - let rect = rect.to_px(scale).round(); - cairo_ctx.rectangle(rect.x0, rect.y0, rect.width(), rect.height()); - } - cairo_ctx.clip(); - cairo_ctx.scale(scale.x(), scale.y()); - let mut piet_ctx = Piet::new(&cairo_ctx); - - // We need to be careful with earlier returns here, because piet_ctx - // can panic if it isn't finish()ed. Also, we want to reset cairo's clip - // even on error. - // - // Note that we're borrowing the surface while calling the handler. This is ok, because - // we don't return control to the system or re-borrow the surface from any code that - // the client can call. - let result = self.with_handler_and_dont_check_the_other_borrows(|handler| { - handler.paint(&mut piet_ctx, &invalid); - piet_ctx - .finish() - .map_err(|e| anyhow!("Window::render - piet finish failed: {}", e)) - }); - let err = match result { - None => { - // The handler borrow failed, so finish didn't get called. - piet_ctx - .finish() - .map_err(|e| anyhow!("Window::render - piet finish failed: {}", e)) - } - Some(e) => { - // Finish might have errored, in which case we want to propagate it. - e - } - }; - cairo_ctx.reset_clip(); - - err?; - } - - self.set_needs_present(false)?; + self.with_handler_and_dont_check_the_other_borrows(|handler| { + handler.paint(&invalid); + }); - let mut buffers = borrow_mut!(self.buffers)?; - let pixmap = *buffers - .idle_pixmaps - .last() - .ok_or_else(|| anyhow!("after rendering, no pixmap to present"))?; - let scale = self.scale.get(); - if let Some(present) = borrow_mut!(self.present_data)?.as_mut() { - present.present(self.app.connection(), pixmap, self.id, &invalid, scale)?; - buffers.idle_pixmaps.pop(); - } else { - for rect in invalid.rects() { - let rect = rect.to_px(scale).round(); - let (x, y) = (rect.x0 as i16, rect.y0 as i16); - let (w, h) = (rect.width() as u16, rect.height() as u16); - self.app - .connection() - .copy_area(pixmap, self.id, self.gc, x, y, x, y, w, h)?; - } - } Ok(()) } @@ -946,31 +667,18 @@ impl Window { /// "More-or-less" because if we're already waiting on a present, we defer the drawing until it /// completes. fn redraw_now(&self) -> Result<(), Error> { - if self.waiting_on_present()? { - self.set_needs_present(true)?; - } else { - self.render()?; - } + self.render()?; Ok(()) } /// Schedule a redraw on the idle loop, or if we are waiting on present then schedule it for /// when the current present finishes. fn request_anim_frame(&self) { - if let Ok(true) = self.waiting_on_present() { - if let Err(e) = self.set_needs_present(true) { - error!( - "Window::request_anim_frame - failed to schedule present: {}", - e - ); - } - } else { - let idle = IdleHandle { - queue: Arc::clone(&self.idle_queue), - pipe: self.idle_pipe, - }; - idle.schedule_redraw(); - } + let idle = IdleHandle { + queue: Arc::clone(&self.idle_queue), + pipe: self.idle_pipe, + }; + idle.schedule_redraw(); } fn invalidate(&self) { @@ -1059,9 +767,7 @@ impl Window { .to_dp(self.scale.get()); self.add_invalid_rect(rect)?; - if self.waiting_on_present()? { - self.set_needs_present(true)?; - } else if expose.count == 0 { + if expose.count == 0 { self.request_anim_frame(); } Ok(()) @@ -1198,86 +904,6 @@ impl Window { self.size_changed(Size::new(event.width as f64, event.height as f64)) } - pub fn handle_complete_notify(&self, event: &CompleteNotifyEvent) -> Result<(), Error> { - if let Some(present) = borrow_mut!(self.present_data)?.as_mut() { - // A little sanity check (which isn't worth an early return): we should only have - // one present request in flight, so we should only get notified about the request - // that we're waiting for. - if present.waiting_on != Some(event.serial) { - warn!( - "Got a notify for serial {}, but waiting on {:?}", - event.serial, present.waiting_on - ); - } - - // Check whether we missed presenting on any frames. - if let Some(last_msc) = present.last_msc { - if last_msc.wrapping_add(1) != event.msc { - tracing::debug!( - "missed a present: msc went from {} to {}", - last_msc, - event.msc - ); - if let Some(last_ust) = present.last_ust { - tracing::debug!("ust went from {} to {}", last_ust, event.ust); - } - } - } - - // Only store the last MSC if we're animating (if we aren't animating, missed MSCs - // aren't interesting). - present.last_msc = if present.needs_present { - Some(event.msc) - } else { - None - }; - present.last_ust = Some(event.ust); - present.waiting_on = None; - } - - if self.needs_present()? { - self.render()?; - } - Ok(()) - } - - pub fn handle_idle_notify(&self, event: &IdleNotifyEvent) -> Result<(), Error> { - if self.destroyed() { - return Ok(()); - } - - let mut buffers = borrow_mut!(self.buffers)?; - if buffers.all_pixmaps.contains(&event.pixmap) { - buffers.idle_pixmaps.push(event.pixmap); - } else { - // We must have reallocated the buffers while this pixmap was busy, so free it now. - // Regular freeing happens in `Buffers::free_pixmaps`. - self.app.connection().free_pixmap(event.pixmap)?; - } - Ok(()) - } - - fn waiting_on_present(&self) -> Result { - Ok(borrow!(self.present_data)? - .as_ref() - .map(|p| p.waiting_on.is_some()) - .unwrap_or(false)) - } - - fn set_needs_present(&self, val: bool) -> Result<(), Error> { - if let Some(present) = borrow_mut!(self.present_data)?.as_mut() { - present.needs_present = val; - } - Ok(()) - } - - fn needs_present(&self) -> Result { - Ok(borrow!(self.present_data)? - .as_ref() - .map(|p| p.needs_present) - .unwrap_or(false)) - } - pub(crate) fn run_idle(&self) { let mut queue = Vec::new(); std::mem::swap(&mut *self.idle_queue.lock().unwrap(), &mut queue); @@ -1326,147 +952,6 @@ impl Window { } } -impl Buffers { - fn new( - conn: &Rc, - window_id: u32, - buf_count: usize, - width: u16, - height: u16, - depth: u8, - ) -> Result { - let mut ret = Buffers { - width, - height, - depth, - idle_pixmaps: Vec::new(), - all_pixmaps: Vec::new(), - }; - ret.create_pixmaps(conn, window_id, buf_count)?; - Ok(ret) - } - - /// Frees all the X pixmaps that we hold. - fn free_pixmaps(&mut self, conn: &Rc) { - // We can't touch pixmaps if the present extension is waiting on them, so only free the - // idle ones. We'll free the busy ones when we get notified that they're idle in `Window::handle_idle_notify`. - for &p in &self.idle_pixmaps { - log_x11!(conn.free_pixmap(p)); - } - self.all_pixmaps.clear(); - self.idle_pixmaps.clear(); - } - - fn set_size(&mut self, conn: &Rc, window_id: u32, width: u16, height: u16) { - // How big should the buffer be if we want at least x pixels? Rounding up to the next power - // of 2 has the potential to waste 75% of our memory (factor 2 in both directions), so - // instead we round up to the nearest number of the form 2^k or 3 * 2^k. - fn next_size(x: u16) -> u16 { - // We round up to the nearest multiple of `accuracy`, which is between x/2 and x/4. - // Don't bother rounding to anything smaller than 32 = 2^(7-1). - let accuracy = 1 << ((16 - x.leading_zeros()).max(7) - 2); - let mask = accuracy - 1; - (x + mask) & !mask - } - - let width = next_size(width); - let height = next_size(height); - if (width, height) != (self.width, self.height) { - let count = self.all_pixmaps.len(); - self.free_pixmaps(conn); - self.width = width; - self.height = height; - log_x11!(self.create_pixmaps(conn, window_id, count)); - } - } - - /// Creates a new pixmap for rendering to. The new pixmap will be first in line for rendering. - fn create_pixmap(&mut self, conn: &Rc, window_id: u32) -> Result { - let pixmap_id = conn.generate_id()?; - conn.create_pixmap(self.depth, pixmap_id, window_id, self.width, self.height)?; - self.all_pixmaps.push(pixmap_id); - self.idle_pixmaps.push(pixmap_id); - Ok(pixmap_id) - } - - fn create_pixmaps( - &mut self, - conn: &Rc, - window_id: u32, - count: usize, - ) -> Result<(), Error> { - if !self.all_pixmaps.is_empty() { - self.free_pixmaps(conn); - } - - for _ in 0..count { - self.create_pixmap(conn, window_id)?; - } - Ok(()) - } -} - -impl PresentData { - // We have already rendered into the active pixmap buffer. Present it to the - // X server, and then rotate the buffers. - fn present( - &mut self, - conn: &Rc, - pixmap: Pixmap, - window_id: u32, - region: &Region, - scale: Scale, - ) -> Result<(), Error> { - let x_rects: Vec = region - .rects() - .iter() - .map(|r| { - let r = r.to_px(scale).round(); - Rectangle { - x: r.x0 as i16, - y: r.y0 as i16, - width: r.width() as u16, - height: r.height() as u16, - } - }) - .collect(); - - conn.xfixes_set_region(self.region, &x_rects[..])?; - conn.present_pixmap( - window_id, - pixmap, - self.serial, - // valid region of the pixmap - self.region, - // region of the window that must get updated - self.region, - // window-relative x-offset of the pixmap - 0, - // window-relative y-offset of the pixmap - 0, - // target CRTC - x11rb::NONE, - // wait fence - x11rb::NONE, - // idle fence - x11rb::NONE, - // present options - x11rb::protocol::present::Option::NONE.into(), - // target msc (0 means present at the next time that msc % divisor == remainder) - 0, - // divisor - 1, - // remainder - 0, - // notifies - &[], - )?; - self.waiting_on = Some(self.serial); - self.serial += 1; - Ok(()) - } -} - // Converts from, e.g., the `details` field of `xcb::xproto::ButtonPressEvent` fn mouse_button(button: u8) -> MouseButton { match button { @@ -1733,10 +1218,6 @@ impl WindowHandle { } } - pub fn text(&self) -> PietText { - PietText::new() - } - pub fn add_text_field(&self) -> TextFieldToken { TextFieldToken::next() } @@ -1786,7 +1267,7 @@ impl WindowHandle { let conn = w.app.connection(); let setup = &conn.setup(); let screen = &setup.roots[w.app.screen_num()]; - match make_cursor(&**conn, setup.image_byte_order, screen.root, format, desc) { + match make_cursor(conn, setup.image_byte_order, screen.root, format, desc) { // TODO: We 'leak' the cursor - nothing ever calls render_free_cursor Ok(cursor) => Some(cursor), Err(err) => { @@ -1849,89 +1330,30 @@ impl WindowHandle { } } -#[cfg(feature = "raw-win-handle")] unsafe impl HasRawWindowHandle for WindowHandle { fn raw_window_handle(&self) -> RawWindowHandle { - let mut handle = XcbWindowHandle::empty(); + let mut handle = XcbHandle::empty(); handle.window = self.id; handle.visual_id = self.visual_id; + + if let Some(window) = self.window.upgrade() { + handle.connection = window.app.connection().get_raw_xcb_connection(); + } else { + // Documentation for HasRawWindowHandle encourages filling in all fields possible, + // leaving those empty that cannot be derived. + error!("Failed to get XCBConnection, returning incomplete handle"); + } + RawWindowHandle::Xcb(handle) } } fn make_cursor( - conn: &XCBConnection, - byte_order: X11ImageOrder, - root_window: u32, - argb32_format: Pictformat, - desc: &CursorDesc, + _conn: &XCBConnection, + _byte_order: X11ImageOrder, + _root_window: u32, + _argb32_format: Pictformat, + _desc: &CursorDesc, ) -> Result { - // BEGIN: Lots of code just to get the image into a RENDER Picture - - fn multiply_alpha(color: u8, alpha: u8) -> u8 { - let (color, alpha) = (u16::from(color), u16::from(alpha)); - let temp = color * alpha + 0x80u16; - ((temp + (temp >> 8)) >> 8) as u8 - } - - // No idea how to sanely get the pixel values, so I'll go with 'insane': - // Iterate over all pixels and build an array - let pixels = desc - .image - .pixel_colors() - .flat_map(|row| { - row.flat_map(|color| { - let (r, g, b, a) = color.as_rgba8(); - // RENDER wants premultiplied alpha - let (r, g, b) = ( - multiply_alpha(r, a), - multiply_alpha(g, a), - multiply_alpha(b, a), - ); - // piet gives us rgba in this order, the server expects an u32 with argb. - let (b0, b1, b2, b3) = match byte_order { - X11ImageOrder::LSB_FIRST => (b, g, r, a), - _ => (a, r, g, b), - }; - // TODO Ownership and flat_map don't go well together :-( - vec![b0, b1, b2, b3] - }) - }) - .collect::>(); - let width = desc.image.width().try_into().expect("Invalid cursor width"); - let height = desc - .image - .height() - .try_into() - .expect("Invalid cursor height"); - - let pixmap = conn.generate_id()?; - let gc = conn.generate_id()?; - let picture = conn.generate_id()?; - conn.create_pixmap(32, pixmap, root_window, width, height)?; - conn.create_gc(gc, pixmap, &Default::default())?; - - conn.put_image( - ImageFormat::Z_PIXMAP, - pixmap, - gc, - width, - height, - 0, - 0, - 0, - 32, - &pixels, - )?; - conn.render_create_picture(picture, pixmap, argb32_format, &Default::default())?; - - conn.free_gc(gc)?; - conn.free_pixmap(pixmap)?; - // End: Lots of code just to get the image into a RENDER Picture - - let cursor = conn.generate_id()?; - conn.render_create_cursor(cursor, picture, desc.hot.x as u16, desc.hot.y as u16)?; - conn.render_free_picture(picture)?; - - Ok(Cursor::Custom(CustomCursor(cursor))) + Ok(Cursor::Arrow) } diff --git a/src/lib.rs b/src/lib.rs index ed6dd5bd..b861f156 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,10 +49,8 @@ extern crate gtk_rs as gtk; pub use image; pub use kurbo; -pub use piet_common as piet; // Reexport the version of `raw_window_handle` we are using. -#[cfg(feature = "raw-win-handle")] pub use raw_window_handle; #[macro_use] diff --git a/src/mouse.rs b/src/mouse.rs index 039f74c7..c2a1cc90 100644 --- a/src/mouse.rs +++ b/src/mouse.rs @@ -16,7 +16,7 @@ use crate::backend; use crate::kurbo::{Point, Vec2}; -use crate::piet::ImageBuf; +// use crate::piet::ImageBuf; use crate::Modifiers; /// Information about the mouse event. @@ -270,8 +270,8 @@ pub enum Cursor { /// A platform-independent description of a custom cursor. #[derive(Clone)] pub struct CursorDesc { - #[allow(dead_code)] // Not yet used on all platforms. - pub(crate) image: ImageBuf, + // #[allow(dead_code)] // Not yet used on all platforms. + // pub(crate) image: ImageBuf, #[allow(dead_code)] // Not yet used on all platforms. pub(crate) hot: Point, } @@ -283,9 +283,12 @@ impl CursorDesc { /// `(0, 0)` at the top left. The hot spot is the logical position of the mouse cursor within /// the image. For example, if the image is a picture of a arrow, the hot spot might be the /// coordinates of the arrow's tip. - pub fn new(image: ImageBuf, hot: impl Into) -> CursorDesc { + pub fn new( + //image: ImageBuf, + hot: impl Into, + ) -> CursorDesc { CursorDesc { - image, + //image, hot: hot.into(), } } diff --git a/src/text.rs b/src/text.rs index 9b07bbc9..3d3fc4f0 100644 --- a/src/text.rs +++ b/src/text.rs @@ -102,8 +102,7 @@ //! doesn't allow for IME input, dead keys, etc. use crate::keyboard::{KbKey, KeyEvent}; -use crate::kurbo::{Point, Rect}; -use crate::piet::HitTestPoint; +use crate::kurbo::Rect; use crate::window::{TextFieldToken, WinHandler}; use std::borrow::Cow; use std::ops::Range; @@ -418,8 +417,8 @@ pub trait InputHandler { /// boundary. fn replace_range(&mut self, range: Range, text: &str); - /// Given a `Point`, determine the corresponding text position. - fn hit_test_point(&self, point: Point) -> HitTestPoint; + // /// Given a `Point`, determine the corresponding text position. + // fn hit_test_point(&self, point: Point) -> HitTestPoint; /// Returns the range, in UTF-8 code units, of the line (soft- or hard-wrapped) /// containing the byte specified by `index`. diff --git a/src/window.rs b/src/window.rs index 72de7be6..ff83f224 100644 --- a/src/window.rs +++ b/src/window.rs @@ -29,9 +29,10 @@ use crate::mouse::{Cursor, CursorDesc, MouseEvent}; use crate::region::Region; use crate::scale::Scale; use crate::text::{Event, InputHandler}; -use piet_common::PietText; -#[cfg(feature = "raw-win-handle")] -use raw_window_handle::{HasRawWindowHandle, RawWindowHandle}; + +use raw_window_handle::{ + HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle, +}; /// A token that uniquely identifies a running timer. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Hash)] @@ -311,11 +312,6 @@ impl WindowHandle { self.0.set_menu(menu.into_inner()) } - /// Get access to a type that can perform text layout. - pub fn text(&self) -> PietText { - self.0.text() - } - /// Register a new text input receiver for this window. /// /// This method should be called any time a new editable text field is @@ -418,13 +414,18 @@ impl WindowHandle { } } -#[cfg(feature = "raw-win-handle")] unsafe impl HasRawWindowHandle for WindowHandle { fn raw_window_handle(&self) -> RawWindowHandle { self.0.raw_window_handle() } } +unsafe impl HasRawDisplayHandle for WindowHandle { + fn raw_display_handle(&self) -> RawDisplayHandle { + self.0.raw_display_handle() + } +} + /// A builder type for creating new windows. pub struct WindowBuilder(backend::WindowBuilder); @@ -557,7 +558,7 @@ pub trait WinHandler { /// Request the handler to paint the window contents. `invalid` is the region in [display /// points](crate::Scale) that needs to be repainted; painting outside the invalid region will /// have no effect. - fn paint(&mut self, piet: &mut piet_common::Piet, invalid: &Region); + fn paint(&mut self, invalid: &Region); /// Called when the resources need to be rebuilt. ///