├── .cargo └── config.toml ├── .github ├── CODEOWNERS ├── dependabot.yml └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── benches └── buffer_mut.rs ├── build.rs ├── examples ├── animation.rs ├── drm.rs ├── fruit.jpg ├── fruit.jpg.license ├── fruit.rs ├── libxcb.rs ├── rectangle.rs ├── utils │ └── winit_app.rs ├── winit.rs ├── winit_android.rs ├── winit_multithread.rs ├── winit_multithread_android.rs └── winit_wrong_sized_buffer.rs ├── run-wasm ├── Cargo.toml └── src │ └── main.rs └── src ├── backend_dispatch.rs ├── backend_interface.rs ├── backends ├── android.rs ├── cg.rs ├── kms.rs ├── mod.rs ├── orbital.rs ├── wayland │ ├── buffer.rs │ └── mod.rs ├── web.rs ├── win32.rs └── x11.rs ├── error.rs ├── lib.rs └── util.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | run-wasm = ["run", "--release", "--package", "run-wasm", "--"] 3 | 4 | [target.wasm32-unknown-unknown] 5 | runner = "wasm-bindgen-test-runner" 6 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Android 2 | /src/android.rs @MarijnS95 3 | 4 | # Apple platforms 5 | /src/cg.rs @madsmtm 6 | 7 | # DRM/KMS (no maintainer) 8 | /src/kms.rs 9 | 10 | # Redox 11 | /src/orbital.rs @jackpot51 12 | 13 | # Wayland 14 | /src/wayland @ids1024 15 | 16 | # Web 17 | /src/web.rs @daxpedda 18 | 19 | # Windows 20 | /src/win32.rs @notgull 21 | 22 | # X11 23 | /src/x11.rs @notgull 24 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: / 5 | schedule: 6 | interval: weekly 7 | - package-ecosystem: github-actions 8 | directory: / 9 | schedule: 10 | interval: weekly 11 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: [master] 7 | 8 | jobs: 9 | fmt: 10 | name: Tidy Code 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: hecrj/setup-rust-action@v2 15 | with: 16 | rust-version: stable 17 | components: rustfmt 18 | - name: Check Formatting 19 | run: cargo +stable fmt --all -- --check 20 | - uses: taiki-e/install-action@v2 21 | with: 22 | tool: typos-cli 23 | - name: Check Formatting 24 | run: cargo fmt --all -- --check 25 | - name: Run Typos 26 | run: typos 27 | 28 | tests: 29 | name: Tests 30 | needs: fmt 31 | strategy: 32 | fail-fast: false 33 | matrix: 34 | rust_version: ['1.71.0', stable, nightly] 35 | platform: 36 | - { target: x86_64-pc-windows-msvc, os: windows-latest, } 37 | - { target: i686-pc-windows-msvc, os: windows-latest, } 38 | # TODO: wait for https://github.com/microsoft/windows-rs/issues/2614#issuecomment-1684152597 39 | # to be resolved before re-enabling these 40 | # - { target: x86_64-pc-windows-gnu, os: windows-latest, host: -x86_64-pc-windows-gnu } 41 | # - { target: i686-pc-windows-gnu, os: windows-latest, host: -i686-pc-windows-gnu } 42 | - { target: i686-unknown-linux-gnu, os: ubuntu-latest, } 43 | - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, } 44 | - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: "x11,x11-dlopen" } 45 | - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: "wayland,wayland-dlopen" } 46 | - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: "kms" } 47 | - { target: x86_64-unknown-redox, os: ubuntu-latest, } 48 | - { target: x86_64-unknown-freebsd, os: ubuntu-latest, } 49 | - { target: x86_64-unknown-netbsd, os: ubuntu-latest, options: --no-default-features, features: "x11,x11-dlopen,wayland,wayland-dlopen" } 50 | - { target: aarch64-apple-darwin, os: macos-latest, } 51 | - { target: wasm32-unknown-unknown, os: ubuntu-latest, } 52 | exclude: 53 | # Orbital doesn't follow MSRV 54 | - rust_version: '1.71.0' 55 | platform: { target: x86_64-unknown-redox, os: ubuntu-latest } 56 | include: 57 | - rust_version: nightly 58 | platform: { target: wasm32-unknown-unknown, os: ubuntu-latest, options: "-Zbuild-std=panic_abort,std", rustflags: "-Ctarget-feature=+atomics,+bulk-memory" } 59 | # Mac Catalyst is only Tier 2 since Rust 1.81 60 | - rust_version: 'nightly' 61 | platform: { target: aarch64-apple-ios-macabi, os: macos-latest } 62 | 63 | env: 64 | RUST_BACKTRACE: 1 65 | CARGO_INCREMENTAL: 0 66 | RUSTFLAGS: "-C debuginfo=0 --deny warnings ${{ matrix.platform.rustflags }}" 67 | # Disable doc tests on Rust 1.83, since some path handling regressed there. 68 | # This has been fixed in Rust 1.84 beta. 69 | # https://github.com/rust-lang/rust/issues/132203 70 | OPTIONS: ${{ matrix.platform.options }} ${{ matrix.rust_version == 'stable' && '--lib' || '' }} 71 | FEATURES: ${{ format(',{0}', matrix.platform.features ) }} 72 | CMD: ${{ matrix.platform.cmd }} 73 | RUSTDOCFLAGS: -Dwarnings 74 | 75 | runs-on: ${{ matrix.platform.os }} 76 | steps: 77 | - uses: actions/checkout@v4 78 | 79 | - uses: taiki-e/install-action@v2 80 | if: matrix.platform.target == 'wasm32-unknown-unknown' 81 | with: 82 | tool: wasm-bindgen-cli 83 | 84 | - uses: hecrj/setup-rust-action@v2 85 | with: 86 | rust-version: ${{ matrix.rust_version }}${{ matrix.platform.host }} 87 | targets: ${{ matrix.platform.target }} 88 | components: clippy, rust-src 89 | 90 | - name: Install libwayland 91 | if: (matrix.platform.os == 'ubuntu-latest') 92 | run: sudo apt-get update && sudo apt-get install libwayland-dev 93 | 94 | - name: Install GCC Multilib 95 | if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686') 96 | run: sudo apt-get install gcc-multilib 97 | 98 | - name: Pin deps that break MSRV 99 | if: matrix.rust_version == '1.71.0' 100 | run: | 101 | cargo update -p bumpalo --precise 3.14.0 102 | 103 | - name: Build crate 104 | shell: bash 105 | run: cargo $CMD build --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES 106 | 107 | - name: Build tests 108 | shell: bash 109 | if: > 110 | !((matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')) && 111 | !contains(matrix.platform.target, 'redox') && 112 | !contains(matrix.platform.target, 'freebsd') && 113 | !contains(matrix.platform.target, 'netbsd') 114 | run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES 115 | 116 | - name: Run tests 117 | shell: bash 118 | if: > 119 | !((matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')) && 120 | !contains(matrix.platform.target, 'redox') && 121 | !contains(matrix.platform.target, 'freebsd') && 122 | !contains(matrix.platform.target, 'netbsd') && 123 | !contains(matrix.platform.target, 'linux') 124 | run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES 125 | 126 | # TODO: We should also be using Wayland for testing here. 127 | - name: Run tests using Xvfb 128 | shell: bash 129 | if: > 130 | !((matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')) && 131 | !contains(matrix.platform.target, 'redox') && 132 | !contains(matrix.platform.target, 'freebsd') && 133 | !contains(matrix.platform.target, 'netbsd') && 134 | contains(matrix.platform.target, 'linux') && 135 | !contains(matrix.platform.options, '--no-default-features') && 136 | !contains(matrix.platform.features, 'wayland') 137 | run: xvfb-run cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES 138 | 139 | - name: Lint with clippy 140 | shell: bash 141 | if: > 142 | (matrix.rust_version == 'stable') && 143 | !contains(matrix.platform.options, '--no-default-features') && 144 | !((matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')) && 145 | !contains(matrix.platform.target, 'redox') && 146 | !contains(matrix.platform.target, 'freebsd') && 147 | !contains(matrix.platform.target, 'netbsd') 148 | run: cargo clippy --all-targets --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES -- -Dwarnings 149 | 150 | - name: Lint with rustdoc 151 | shell: bash 152 | if: > 153 | (matrix.rust_version == 'stable') && 154 | !contains(matrix.platform.options, '--no-default-features') && 155 | !((matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')) && 156 | !contains(matrix.platform.target, 'redox') && 157 | !contains(matrix.platform.target, 'freebsd') && 158 | !contains(matrix.platform.target, 'netbsd') 159 | run: cargo doc --no-deps --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES --document-private-items 160 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: 7 | push: 8 | tags: 9 | - v[0-9]+.* 10 | 11 | jobs: 12 | create-release: 13 | if: github.repository_owner == 'rust-windowing' 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: taiki-e/create-gh-release-action@v1 18 | with: 19 | changelog: CHANGELOG.md 20 | branch: master 21 | env: 22 | GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} 23 | - name: Publish to crates.io 24 | run: cargo publish --token ${{ secrets.CRATES_IO_API_TOKEN }} 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | /.idea 4 | /.vscode 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Unreleased 2 | 3 | - Update to `objc2` 0.6.0. 4 | - Bump MSRV to Rust 1.71. 5 | 6 | # 0.4.6 7 | 8 | - Added support for iOS, tvOS, watchOS and visionOS (UIKit). 9 | - Redo the way surfaces work on macOS to work directly with layers, which will 10 | allow initializing directly from a `CALayer` in the future. 11 | - Update to `windows-sys` 0.59.0 and `core-graphics` v0.24.0. 12 | - Add an MSRV policy. 13 | 14 | # 0.4.5 15 | 16 | - Make the `wayland-sys` dependency optional. (#223) 17 | - Allow for transparent visuals on X11. This technically doesn't work, but 18 | otherwise `winit` users might get crashes. (#226) 19 | 20 | # 0.4.4 21 | 22 | - Make `Context` `Send`+`Sync` and `Surface` `Send`. (#217) 23 | 24 | # 0.4.3 25 | 26 | - Use objc2 as the backend for the CoreGraphics implementation. (#210) 27 | - Update drm-rs to version v0.12.0. (#209) 28 | - Bump MSRV to 1.70.0 to be in line with Winit. 29 | 30 | # 0.4.2 31 | 32 | - Add the ability to get the underlying window handle. (#193) 33 | - Rework the backend to use a trait-based interface. (#196) 34 | - On Orbital, fix window resize. (#200) 35 | - Fix `bytes()` for KMS/DRM implementation. (#203) 36 | 37 | # 0.4.1 38 | 39 | - On MacOS, Fix double-free of `NSWindow`. (#180) 40 | - Update `drm` to 0.11 (#178) 41 | * Fixes build on architectures where drm-rs did not have generated bindings. 42 | - Update x11rb to v0.13 (#183) 43 | - On Web, add support for more `RawWindowHandle` variants. (#188) 44 | - On Wayland, fix buffer age. (#191) 45 | 46 | # 0.4.0 47 | 48 | - **Breaking:** Port to use `raw-window-handle` v0.6.(#132) 49 | - Enable creating X11 displays without an existing connection. (#171) 50 | 51 | # 0.3.3 52 | 53 | - Fix a bug in the new shared memory model in X11. (#170) 54 | 55 | # 0.3.2 56 | 57 | * Document that `present_with_damage` is supported on web platforms. (#152) 58 | * Replace our usage of `nix` with `rustix`. This enables this crate to run without `libc`. (#164) 59 | * Use POSIX shared memory instead of Sys-V shared memory for the X11 backend. (#165) 60 | * Bump version for the following dependencies: 61 | * `memmap2` (#156) 62 | * `redox_syscall` (#161) 63 | * `drm` (#163) 64 | 65 | # 0.3.1 66 | 67 | * On X11, fix the length of the returned buffer when using the wire-transferred buffer. 68 | * On Web, fix incorrect starting coordinates when handling buffer damage. 69 | * On Redox, use `MAP_SHARED`; fixing behavior with latest Orbital. 70 | * Error instead of segfault on macOS if size isn't set. 71 | * Add `OffscreenCanvas` support in web backend. 72 | * Add DRM/KMS backend, for running on tty without X/Wayland. 73 | * Make `fetch` error on Windows, where it wasn't working correctly. 74 | * Implement `Error` trait for `SoftBufferError`. 75 | * Dependency updates. 76 | 77 | # 0.3.0 78 | 79 | - On MacOS, the contents scale is updated when set_buffer() is called, to adapt when the window is on a new screen (#68). 80 | - **Breaking:** Split the `GraphicsContext` type into `Context` and `Surface` (#64). 81 | - On Web, cache the document in the `Context` type (#66). 82 | - **Breaking:** Introduce a new "owned buffer" for no-copy presentation (#65). 83 | - Enable support for multi-threaded WASM (#77). 84 | - Fix buffer resizing on X11 (#69). 85 | - Add a set of functions for handling buffer damage (#99). 86 | - Add a `fetch()` function for getting the window contents (#104). 87 | - Bump MSRV to 1.64 (#81). 88 | 89 | # 0.2.1 90 | 91 | - Bump `windows-sys` to 0.48 92 | 93 | # 0.2.0 94 | 95 | - Add support for Redox/Orbital. 96 | - Add support for BSD distributions. 97 | - Ported Windows backend from `winapi` to `windows-sys`. 98 | - **Breaking:** Take a reference to a window instead of owning the window. 99 | - Add a `from_raw` function for directly using raw handles. 100 | - Improvements for Wayland support. 101 | - Support for HiDPI on macOS. 102 | - **Breaking:** Add feature flags for `x11` and `wayland` backends. 103 | - Use static dispatch instead of dynamic dispatch for the backends. 104 | - Add `libxcb` support to the X11 backend. 105 | - Use X11 MIT-SHM extension, if available. 106 | 107 | # 0.1.1 108 | 109 | - Added WASM support (Thanks to [Liamolucko](https://github.com/Liamolucko)!) 110 | - CALayer is now used for Mac OS backend, which is more flexible about what happens in the windowing library (Thanks to [lunixbochs](https://github.com/lunixbochs)!) 111 | 112 | # 0.1.0 113 | 114 | Initial published version with support for Linux (X11 and Wayland), Mac OS (but buggy), and Windows. 115 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "softbuffer" 3 | version = "0.4.6" 4 | edition = "2021" 5 | license = "MIT OR Apache-2.0" 6 | description = "Cross-platform software buffer" 7 | documentation = "https://docs.rs/softbuffer" 8 | readme = "README.md" 9 | repository = "https://github.com/rust-windowing/softbuffer" 10 | keywords = ["framebuffer", "windowing"] 11 | categories = ["game-development", "graphics", "gui", "multimedia", "rendering"] 12 | exclude = ["examples"] 13 | rust-version = "1.71.0" 14 | 15 | [[bench]] 16 | name = "buffer_mut" 17 | harness = false 18 | 19 | [features] 20 | default = ["kms", "x11", "x11-dlopen", "wayland", "wayland-dlopen"] 21 | kms = ["bytemuck", "drm", "rustix"] 22 | wayland = [ 23 | "wayland-backend", 24 | "wayland-client", 25 | "wayland-sys", 26 | "memmap2", 27 | "rustix", 28 | "fastrand", 29 | ] 30 | wayland-dlopen = ["wayland-sys/dlopen"] 31 | x11 = [ 32 | "as-raw-xcb-connection", 33 | "bytemuck", 34 | "fastrand", 35 | "rustix", 36 | "tiny-xlib", 37 | "x11rb", 38 | ] 39 | x11-dlopen = ["tiny-xlib/dlopen", "x11rb/dl-libxcb"] 40 | 41 | [dependencies] 42 | raw_window_handle = { package = "raw-window-handle", version = "0.6", features = [ 43 | "std", 44 | ] } 45 | tracing = { version = "0.1.41", default-features = false } 46 | 47 | [target.'cfg(target_os = "android")'.dependencies] 48 | bytemuck = "1.12.3" 49 | ndk = "0.9.0" 50 | 51 | [target.'cfg(all(unix, not(any(target_vendor = "apple", target_os = "android", target_os = "redox"))))'.dependencies] 52 | as-raw-xcb-connection = { version = "1.0.0", optional = true } 53 | bytemuck = { version = "1.12.3", optional = true } 54 | drm = { version = "0.14.1", default-features = false, optional = true } 55 | fastrand = { version = "2.0.0", optional = true } 56 | memmap2 = { version = "0.9.0", optional = true } 57 | rustix = { version = "1.0.1", features = [ 58 | "fs", 59 | "mm", 60 | "shm", 61 | "std", 62 | ], default-features = false, optional = true } 63 | tiny-xlib = { version = "0.2.1", optional = true } 64 | wayland-backend = { version = "0.3.0", features = [ 65 | "client_system", 66 | ], optional = true } 67 | wayland-client = { version = "0.31.0", optional = true } 68 | wayland-sys = { version = "0.31.0", optional = true } 69 | x11rb = { version = "0.13.0", features = [ 70 | "allow-unsafe-code", 71 | "shm", 72 | ], optional = true } 73 | 74 | [target.'cfg(target_os = "windows")'.dependencies.windows-sys] 75 | version = "0.59.0" 76 | features = [ 77 | "Win32_Graphics_Gdi", 78 | "Win32_UI_Shell", 79 | "Win32_UI_WindowsAndMessaging", 80 | "Win32_Foundation", 81 | ] 82 | 83 | [target.'cfg(target_vendor = "apple")'.dependencies] 84 | objc2-core-graphics = { version = "0.3.0", default-features = false, features = [ 85 | "std", 86 | "objc2", 87 | "CGColorSpace", 88 | "CGDataProvider", 89 | "CGImage", 90 | ] } 91 | objc2 = "0.6.0" 92 | objc2-core-foundation = { version = "0.3.0", default-features = false, features = [ 93 | "std", 94 | "CFCGTypes", 95 | ] } 96 | objc2-foundation = { version = "0.3.0", default-features = false, features = [ 97 | "std", 98 | "objc2-core-foundation", 99 | "NSDictionary", 100 | "NSGeometry", 101 | "NSKeyValueObserving", 102 | "NSString", 103 | "NSThread", 104 | "NSValue", 105 | ] } 106 | objc2-quartz-core = { version = "0.3.0", default-features = false, features = [ 107 | "std", 108 | "objc2-core-foundation", 109 | "CALayer", 110 | "CATransaction", 111 | ] } 112 | 113 | [target.'cfg(target_arch = "wasm32")'.dependencies] 114 | js-sys = "0.3.63" 115 | wasm-bindgen = "0.2.86" 116 | 117 | [target.'cfg(target_arch = "wasm32")'.dependencies.web-sys] 118 | version = "0.3.55" 119 | features = [ 120 | "CanvasRenderingContext2d", 121 | "Document", 122 | "Element", 123 | "HtmlCanvasElement", 124 | "ImageData", 125 | "OffscreenCanvas", 126 | "OffscreenCanvasRenderingContext2d", 127 | "Window", 128 | ] 129 | 130 | [target.'cfg(target_os = "redox")'.dependencies] 131 | redox_syscall = "0.5" 132 | 133 | [build-dependencies] 134 | cfg_aliases = "0.2.0" 135 | 136 | [dev-dependencies] 137 | colorous = "1.0.12" 138 | criterion = { version = "0.4.0", default-features = false, features = [ 139 | "cargo_bench_support", 140 | ] } 141 | web-time = "1.0.0" 142 | winit = "0.30.0" 143 | 144 | [target.'cfg(target_os = "android")'.dev-dependencies] 145 | winit = { version = "0.30.0", features = ["android-native-activity"] } 146 | android-activity = "0.6" 147 | 148 | [dev-dependencies.image] 149 | version = "0.25.0" 150 | # Disable rayon on web 151 | default-features = false 152 | features = ["jpeg"] 153 | 154 | [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] 155 | # Turn rayon back on everywhere else; creating the separate entry resets the features to default. 156 | rayon = "1.5.1" 157 | 158 | [target.'cfg(target_arch = "wasm32")'.dev-dependencies] 159 | wasm-bindgen-test = "0.3" 160 | 161 | [target.'cfg(all(unix, not(any(target_vendor = "apple", target_os = "android", target_os = "redox"))))'.dev-dependencies] 162 | rustix = { version = "1.0.1", features = ["event"] } 163 | 164 | [workspace] 165 | members = ["run-wasm"] 166 | 167 | [[example]] 168 | # Run with `cargo apk r --example winit_android` 169 | name = "winit_android" 170 | crate-type = ["cdylib"] 171 | 172 | [[example]] 173 | # Run with `cargo apk r --example winit_multithread_android` 174 | name = "winit_multithread_android" 175 | crate-type = ["cdylib"] 176 | 177 | [package.metadata.docs.rs] 178 | all-features = true 179 | rustdoc-args = ["--cfg", "docsrs"] 180 | default-target = "x86_64-unknown-linux-gnu" 181 | targets = [ 182 | "x86_64-pc-windows-msvc", 183 | "x86_64-apple-darwin", 184 | "x86_64-unknown-linux-gnu", 185 | "wasm32-unknown-unknown", 186 | ] 187 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2022 Kirill Chibisov 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright 2022 Kirill Chibisov 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 16 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Softbuffer 2 | 3 | Enables software rendering via drawing an image straight to a window. 4 | 5 | Softbuffer integrates with the [`raw-window-handle`](https://crates.io/crates/raw-window-handle) crate 6 | to allow writing pixels to a window in a cross-platform way while using the very high quality dedicated window management 7 | libraries that are available in the Rust ecosystem. 8 | 9 | ## Alternatives 10 | 11 | [minifb](https://crates.io/crates/minifb) also allows putting a 2D buffer/image on a window in a platform-independent way. 12 | Minifb's approach to doing window management itself, however, is problematic code duplication. We already have very high quality 13 | libraries for this in the Rust ecosystem (such as [winit](https://crates.io/crates/winit)), and minifb's implementation 14 | of window management is not ideal. For example, it occasionally segfaults and is missing key features such as setting 15 | a window icon on some platforms. While adding these features to minifb would be possible, it makes more sense to use 16 | the standard window handling systems instead. 17 | 18 | What about [pixels](https://crates.io/crates/pixels)? Pixels accomplishes a very similar goal to Softbuffer, 19 | however there are two key differences. Pixels provides some capacity for GPU-accelerated post-processing of what is 20 | displayed, while Softbuffer does not. Due to not having this post-processing, Softbuffer does not rely on the GPU or 21 | hardware accelerated graphics stack in any way, and is thus more portable to installations that do not have access to 22 | hardware acceleration (e.g. VMs, older computers, computers with misconfigured drivers). Softbuffer should be used over 23 | pixels when its GPU-accelerated post-processing effects are not needed. 24 | 25 | ## License & Credits 26 | 27 | This library is dual-licensed under MIT or Apache-2.0, just like minifb and rust. Significant portions of code were taken 28 | from the minifb library to do platform-specific work. 29 | 30 | ## Platform support: 31 | 32 | Some, but not all, platforms supported in [raw-window-handle](https://crates.io/crates/raw-window-handle) are supported 33 | by Softbuffer. Pull requests are welcome to add new platforms! **Nonetheless, all major desktop platforms that winit uses 34 | on desktop are supported.** 35 | 36 | For now, the priority for new platforms is: 37 | 38 | 1. to have at least one platform on each OS working (e.g. one of Win32 or WinRT, or one of Xlib, Xcb, and Wayland) and 39 | 2. for that one platform on each OS to be the one that winit uses. 40 | 41 | (PRs will be accepted for any platform, even if it does not follow the above priority.) 42 | 43 | | Platform || 44 | |-----------|--| 45 | |Android NDK|✅| 46 | | AppKit |✅| 47 | | Orbital |✅| 48 | | UIKit |✅| 49 | | Wayland |✅| 50 | | Web |✅| 51 | | Win32 |✅| 52 | | WinRT |❌| 53 | | XCB |✅| 54 | | Xlib |✅| 55 | 56 | ✅: Present\ 57 | ❔: Immature\ 58 | ❌: Absent 59 | 60 | ## WebAssembly 61 | 62 | To run an example with the web backend: `cargo run-wasm --example winit` 63 | 64 | ## Android 65 | 66 | To run the Android-specific example on an Android phone: `cargo apk r --example winit_android` or `cargo apk r --example winit_multithread_android`. 67 | 68 | ## Example 69 | 70 | ```rust,no_run 71 | use std::num::NonZeroU32; 72 | use std::rc::Rc; 73 | use winit::event::{Event, WindowEvent}; 74 | use winit::event_loop::{ControlFlow, EventLoop}; 75 | use winit::window::Window; 76 | 77 | #[path = "../examples/utils/winit_app.rs"] 78 | mod winit_app; 79 | 80 | fn main() { 81 | let event_loop = EventLoop::new().unwrap(); 82 | 83 | let mut app = winit_app::WinitAppBuilder::with_init( 84 | |elwt| { 85 | let window = { 86 | let window = elwt.create_window(Window::default_attributes()); 87 | Rc::new(window.unwrap()) 88 | }; 89 | let context = softbuffer::Context::new(window.clone()).unwrap(); 90 | 91 | (window, context) 92 | }, 93 | |_elwt, (window, context)| softbuffer::Surface::new(context, window.clone()).unwrap(), 94 | ) 95 | .with_event_handler(|(window, _context), surface, event, elwt| { 96 | elwt.set_control_flow(ControlFlow::Wait); 97 | 98 | match event { 99 | Event::WindowEvent { window_id, event: WindowEvent::RedrawRequested } if window_id == window.id() => { 100 | let Some(surface) = surface else { 101 | eprintln!("RedrawRequested fired before Resumed or after Suspended"); 102 | return; 103 | }; 104 | let (width, height) = { 105 | let size = window.inner_size(); 106 | (size.width, size.height) 107 | }; 108 | surface 109 | .resize( 110 | NonZeroU32::new(width).unwrap(), 111 | NonZeroU32::new(height).unwrap(), 112 | ) 113 | .unwrap(); 114 | 115 | let mut buffer = surface.buffer_mut().unwrap(); 116 | for index in 0..(width * height) { 117 | let y = index / width; 118 | let x = index % width; 119 | let red = x % 255; 120 | let green = y % 255; 121 | let blue = (x * y) % 255; 122 | 123 | buffer[index as usize] = blue | (green << 8) | (red << 16); 124 | } 125 | 126 | buffer.present().unwrap(); 127 | } 128 | Event::WindowEvent { 129 | event: WindowEvent::CloseRequested, 130 | window_id, 131 | } if window_id == window.id() => { 132 | elwt.exit(); 133 | } 134 | _ => {} 135 | } 136 | }); 137 | 138 | event_loop.run_app(&mut app).unwrap(); 139 | } 140 | ``` 141 | 142 | ## MSRV Policy 143 | 144 | This crate's Minimum Supported Rust Version (MSRV) is **1.71**. Changes to 145 | the MSRV will be accompanied by a minor version bump. 146 | 147 | As a **tentative** policy, the upper bound of the MSRV is given by the following 148 | formula: 149 | 150 | ```text 151 | min(sid, stable - 3) 152 | ``` 153 | 154 | Where `sid` is the current version of `rustc` provided by [Debian Sid], and 155 | `stable` is the latest stable version of Rust. This bound may be broken in case of a major ecosystem shift or a security vulnerability. 156 | 157 | [Debian Sid]: https://packages.debian.org/sid/rustc 158 | 159 | Orbital is not covered by this MSRV policy, as it requires a Rust nightly 160 | toolchain to compile. 161 | 162 | All crates in the [`rust-windowing`] organizations have the 163 | same MSRV policy. 164 | 165 | [`rust-windowing`]: https://github.com/rust-windowing 166 | 167 | ## Changelog 168 | 169 | See the [changelog](CHANGELOG.md) for a list of this package's versions and the changes made in each version. 170 | -------------------------------------------------------------------------------- /benches/buffer_mut.rs: -------------------------------------------------------------------------------- 1 | #![allow(deprecated)] // TODO 2 | 3 | use criterion::{criterion_group, criterion_main, Criterion}; 4 | 5 | fn buffer_mut(c: &mut Criterion) { 6 | #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] 7 | { 8 | // Do nothing. 9 | let _ = c; 10 | } 11 | 12 | #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] 13 | { 14 | use criterion::black_box; 15 | use softbuffer::{Context, Surface}; 16 | use std::num::NonZeroU32; 17 | use winit::event_loop::ControlFlow; 18 | use winit::platform::run_on_demand::EventLoopExtRunOnDemand; 19 | 20 | let mut evl = winit::event_loop::EventLoop::new().unwrap(); 21 | let window = evl 22 | .create_window(winit::window::Window::default_attributes().with_visible(false)) 23 | .unwrap(); 24 | 25 | evl.run_on_demand(move |ev, elwt| { 26 | elwt.set_control_flow(ControlFlow::Poll); 27 | 28 | if let winit::event::Event::AboutToWait = ev { 29 | elwt.exit(); 30 | 31 | let mut surface = { 32 | let context = Context::new(elwt).unwrap(); 33 | Surface::new(&context, &window).unwrap() 34 | }; 35 | 36 | let size = window.inner_size(); 37 | surface 38 | .resize( 39 | NonZeroU32::new(size.width).unwrap(), 40 | NonZeroU32::new(size.height).unwrap(), 41 | ) 42 | .unwrap(); 43 | 44 | c.bench_function("buffer_mut()", |b| { 45 | b.iter(|| { 46 | for _ in 0..500 { 47 | black_box(surface.buffer_mut().unwrap()); 48 | } 49 | }); 50 | }); 51 | 52 | c.bench_function("pixels_mut()", |b| { 53 | let mut buffer = surface.buffer_mut().unwrap(); 54 | b.iter(|| { 55 | for _ in 0..500 { 56 | let x: &mut [u32] = &mut buffer; 57 | black_box(x); 58 | } 59 | }); 60 | }); 61 | } 62 | }) 63 | .unwrap(); 64 | } 65 | } 66 | 67 | criterion_group!(benches, buffer_mut); 68 | criterion_main!(benches); 69 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("cargo:rustc-check-cfg=cfg(free_unix)"); 3 | println!("cargo:rustc-check-cfg=cfg(kms_platform)"); 4 | println!("cargo:rustc-check-cfg=cfg(x11_platform)"); 5 | println!("cargo:rustc-check-cfg=cfg(wayland_platform)"); 6 | 7 | cfg_aliases::cfg_aliases! { 8 | free_unix: { all(unix, not(any(target_vendor = "apple", target_os = "android", target_os = "redox"))) }, 9 | kms_platform: { all(feature = "kms", free_unix, not(target_arch = "wasm32")) }, 10 | x11_platform: { all(feature = "x11", free_unix, not(target_arch = "wasm32")) }, 11 | wayland_platform: { all(feature = "wayland", free_unix, not(target_arch = "wasm32")) }, 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/animation.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(target_arch = "wasm32"))] 2 | use rayon::prelude::*; 3 | use std::f64::consts::PI; 4 | use std::num::NonZeroU32; 5 | use web_time::Instant; 6 | use winit::event::{Event, KeyEvent, WindowEvent}; 7 | use winit::event_loop::{ControlFlow, EventLoop}; 8 | use winit::keyboard::{Key, NamedKey}; 9 | 10 | #[path = "utils/winit_app.rs"] 11 | mod winit_app; 12 | 13 | fn main() { 14 | let event_loop = EventLoop::new().unwrap(); 15 | let start = Instant::now(); 16 | 17 | let app = winit_app::WinitAppBuilder::with_init( 18 | |event_loop| { 19 | let window = winit_app::make_window(event_loop, |w| w); 20 | 21 | let context = softbuffer::Context::new(window.clone()).unwrap(); 22 | 23 | let old_size = (0, 0); 24 | let frames = pre_render_frames(0, 0); 25 | 26 | (window, context, old_size, frames) 27 | }, 28 | |_elwft, (window, context, _old_size, _frames)| { 29 | softbuffer::Surface::new(context, window.clone()).unwrap() 30 | }, 31 | ) 32 | .with_event_handler(move |state, surface, event, elwt| { 33 | let (window, _context, old_size, frames) = state; 34 | 35 | elwt.set_control_flow(ControlFlow::Poll); 36 | 37 | match event { 38 | Event::WindowEvent { 39 | window_id, 40 | event: WindowEvent::Resized(size), 41 | } if window_id == window.id() => { 42 | let Some(surface) = surface else { 43 | eprintln!("Resized fired before Resumed or after Suspended"); 44 | return; 45 | }; 46 | 47 | if let (Some(width), Some(height)) = 48 | (NonZeroU32::new(size.width), NonZeroU32::new(size.height)) 49 | { 50 | surface.resize(width, height).unwrap(); 51 | } 52 | } 53 | Event::WindowEvent { 54 | window_id, 55 | event: WindowEvent::RedrawRequested, 56 | } if window_id == window.id() => { 57 | let Some(surface) = surface else { 58 | eprintln!("RedrawRequested fired before Resumed or after Suspended"); 59 | return; 60 | }; 61 | 62 | let size = window.inner_size(); 63 | if let (Some(width), Some(height)) = 64 | (NonZeroU32::new(size.width), NonZeroU32::new(size.height)) 65 | { 66 | let elapsed = start.elapsed().as_secs_f64() % 1.0; 67 | 68 | if (width.get(), height.get()) != *old_size { 69 | *old_size = (width.get(), height.get()); 70 | *frames = pre_render_frames(width.get() as usize, height.get() as usize); 71 | }; 72 | 73 | let frame = &frames[((elapsed * 60.0).round() as usize).clamp(0, 59)]; 74 | 75 | let mut buffer = surface.buffer_mut().unwrap(); 76 | buffer.copy_from_slice(frame); 77 | buffer.present().unwrap(); 78 | } 79 | } 80 | Event::AboutToWait => { 81 | window.request_redraw(); 82 | } 83 | Event::WindowEvent { 84 | event: 85 | WindowEvent::CloseRequested 86 | | WindowEvent::KeyboardInput { 87 | event: 88 | KeyEvent { 89 | logical_key: Key::Named(NamedKey::Escape), 90 | .. 91 | }, 92 | .. 93 | }, 94 | window_id, 95 | } if window_id == window.id() => { 96 | elwt.exit(); 97 | } 98 | _ => {} 99 | } 100 | }); 101 | 102 | winit_app::run_app(event_loop, app); 103 | } 104 | 105 | fn pre_render_frames(width: usize, height: usize) -> Vec> { 106 | let render = |frame_id| { 107 | let elapsed = ((frame_id as f64) / (60.0)) * 2.0 * PI; 108 | 109 | let coords = (0..height).flat_map(|x| (0..width).map(move |y| (x, y))); 110 | coords 111 | .map(|(x, y)| { 112 | let y = (y as f64) / (height as f64); 113 | let x = (x as f64) / (width as f64); 114 | let red = 115 | ((((y + elapsed).sin() * 0.5 + 0.5) * 255.0).round() as u32).clamp(0, 255); 116 | let green = 117 | ((((x + elapsed).sin() * 0.5 + 0.5) * 255.0).round() as u32).clamp(0, 255); 118 | let blue = 119 | ((((y - elapsed).cos() * 0.5 + 0.5) * 255.0).round() as u32).clamp(0, 255); 120 | 121 | blue | (green << 8) | (red << 16) 122 | }) 123 | .collect::>() 124 | }; 125 | 126 | #[cfg(target_arch = "wasm32")] 127 | return (0..60).map(render).collect(); 128 | 129 | #[cfg(not(target_arch = "wasm32"))] 130 | (0..60).into_par_iter().map(render).collect() 131 | } 132 | -------------------------------------------------------------------------------- /examples/drm.rs: -------------------------------------------------------------------------------- 1 | //! Example of using softbuffer with drm-rs. 2 | 3 | #[cfg(kms_platform)] 4 | mod imple { 5 | use drm::control::{connector, Device as CtrlDevice, Event, ModeTypeFlags, PlaneType}; 6 | use drm::Device; 7 | 8 | use raw_window_handle::{DisplayHandle, DrmDisplayHandle, DrmWindowHandle, WindowHandle}; 9 | use softbuffer::{Context, Surface}; 10 | 11 | use std::num::NonZeroU32; 12 | use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd}; 13 | use std::path::Path; 14 | use std::time::{Duration, Instant}; 15 | 16 | pub(super) fn entry() -> Result<(), Box> { 17 | // Open a new device. 18 | let device = Card::find()?; 19 | 20 | // Create the softbuffer context. 21 | let context = unsafe { 22 | Context::new(DisplayHandle::borrow_raw({ 23 | let handle = DrmDisplayHandle::new(device.as_fd().as_raw_fd()); 24 | handle.into() 25 | })) 26 | }?; 27 | 28 | // Get the DRM handles. 29 | let handles = device.resource_handles()?; 30 | 31 | // Get the list of connectors and CRTCs. 32 | let connectors = handles 33 | .connectors() 34 | .iter() 35 | .map(|&con| device.get_connector(con, true)) 36 | .collect::, _>>()?; 37 | let crtcs = handles 38 | .crtcs() 39 | .iter() 40 | .map(|&crtc| device.get_crtc(crtc)) 41 | .collect::, _>>()?; 42 | 43 | // Find a connected crtc. 44 | let con = connectors 45 | .iter() 46 | .find(|con| con.state() == connector::State::Connected) 47 | .ok_or("No connected connectors")?; 48 | 49 | // Get the first CRTC. 50 | let crtc = crtcs.first().ok_or("No CRTCs")?; 51 | 52 | // Find a mode to use. 53 | let mode = con 54 | .modes() 55 | .iter() 56 | .find(|mode| mode.mode_type().contains(ModeTypeFlags::PREFERRED)) 57 | .or_else(|| con.modes().first()) 58 | .ok_or("No modes")?; 59 | 60 | // Look for a primary plane compatible with our CRTC. 61 | let planes = device.plane_handles()?; 62 | let planes = planes 63 | .iter() 64 | .filter(|&&plane| { 65 | device.get_plane(plane).is_ok_and(|plane| { 66 | let crtcs = handles.filter_crtcs(plane.possible_crtcs()); 67 | crtcs.contains(&crtc.handle()) 68 | }) 69 | }) 70 | .collect::>(); 71 | 72 | // Find the first primary plane or take the first one period. 73 | let plane = planes 74 | .iter() 75 | .find(|&&&plane| { 76 | if let Ok(props) = device.get_properties(plane) { 77 | let (ids, vals) = props.as_props_and_values(); 78 | for (&id, &val) in ids.iter().zip(vals.iter()) { 79 | if let Ok(info) = device.get_property(id) { 80 | if info.name().to_str() == Ok("type") { 81 | return val == PlaneType::Primary as u32 as u64; 82 | } 83 | } 84 | } 85 | } 86 | 87 | false 88 | }) 89 | .or(planes.first()) 90 | .ok_or("No planes")?; 91 | 92 | // Create the surface on top of this plane. 93 | // Note: This requires root on DRM/KMS. 94 | let mut surface = unsafe { 95 | Surface::new( 96 | &context, 97 | WindowHandle::borrow_raw({ 98 | let handle = DrmWindowHandle::new((**plane).into()); 99 | handle.into() 100 | }), 101 | ) 102 | }?; 103 | 104 | // Resize the surface. 105 | let (width, height) = mode.size(); 106 | surface.resize( 107 | NonZeroU32::new(width as u32).unwrap(), 108 | NonZeroU32::new(height as u32).unwrap(), 109 | )?; 110 | 111 | // Start drawing to it. 112 | let start = Instant::now(); 113 | let mut tick = 0; 114 | while Instant::now().duration_since(start) < Duration::from_secs(2) { 115 | tick += 1; 116 | println!("Drawing tick {tick}"); 117 | 118 | // Start drawing. 119 | let mut buffer = surface.buffer_mut()?; 120 | draw_to_buffer(&mut buffer, tick); 121 | buffer.present()?; 122 | 123 | // Wait for the page flip to happen. 124 | rustix::event::poll( 125 | &mut [rustix::event::PollFd::new( 126 | &device, 127 | rustix::event::PollFlags::IN, 128 | )], 129 | None, 130 | )?; 131 | 132 | // Receive the events. 133 | let events = device.receive_events()?; 134 | println!("Got some events..."); 135 | for event in events { 136 | match event { 137 | Event::PageFlip(_) => { 138 | println!("Page flip event."); 139 | } 140 | Event::Vblank(_) => { 141 | println!("Vblank event."); 142 | } 143 | _ => { 144 | println!("Unknown event."); 145 | } 146 | } 147 | } 148 | } 149 | 150 | Ok(()) 151 | } 152 | 153 | fn draw_to_buffer(buf: &mut [u32], tick: usize) { 154 | let scale = colorous::SINEBOW; 155 | let mut i = (tick as f64) / 20.0; 156 | while i > 1.0 { 157 | i -= 1.0; 158 | } 159 | 160 | let color = scale.eval_continuous(i); 161 | let pixel = ((color.r as u32) << 16) | ((color.g as u32) << 8) | (color.b as u32); 162 | buf.fill(pixel); 163 | } 164 | 165 | struct Card(std::fs::File); 166 | 167 | impl Card { 168 | fn find() -> Result> { 169 | for i in 0..10 { 170 | let path = format!("/dev/dri/card{i}"); 171 | // Card enumeration may not start at zero, allow failures while opening 172 | let Ok(device) = Card::open(path) else { 173 | continue; 174 | }; 175 | 176 | // Only use it if it has connectors. 177 | let Ok(handles) = device.resource_handles() else { 178 | continue; 179 | }; 180 | 181 | if handles 182 | .connectors 183 | .iter() 184 | .filter_map(|c| device.get_connector(*c, false).ok()) 185 | .any(|c| c.state() == connector::State::Connected) 186 | { 187 | return Ok(device); 188 | } 189 | } 190 | 191 | Err("No DRM device found".into()) 192 | } 193 | 194 | fn open(path: impl AsRef) -> Result> { 195 | let file = std::fs::OpenOptions::new() 196 | .read(true) 197 | .write(true) 198 | .open(path)?; 199 | Ok(Card(file)) 200 | } 201 | } 202 | 203 | impl AsFd for Card { 204 | fn as_fd(&self) -> BorrowedFd<'_> { 205 | self.0.as_fd() 206 | } 207 | } 208 | 209 | impl Device for Card {} 210 | impl CtrlDevice for Card {} 211 | } 212 | 213 | #[cfg(not(kms_platform))] 214 | mod imple { 215 | pub(super) fn entry() -> Result<(), Box> { 216 | eprintln!("This example requires the `kms` feature."); 217 | Ok(()) 218 | } 219 | } 220 | 221 | fn main() -> Result<(), Box> { 222 | imple::entry() 223 | } 224 | -------------------------------------------------------------------------------- /examples/fruit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-windowing/softbuffer/ba60228f4b7bea6986c67288b29c6320f6c02050/examples/fruit.jpg -------------------------------------------------------------------------------- /examples/fruit.jpg.license: -------------------------------------------------------------------------------- 1 | fruit.jpg is licensed under the Creative Commons Attribution 3.0 Unported license and was put on Wikimedia by user Atomcibre 2 | 3 | Source: https://commons.wikimedia.org/wiki/File:Culinary_fruits_front_view.jpg 4 | 5 | License: https://creativecommons.org/licenses/by/3.0/deed.en -------------------------------------------------------------------------------- /examples/fruit.rs: -------------------------------------------------------------------------------- 1 | use image::GenericImageView; 2 | use std::num::NonZeroU32; 3 | use winit::event::{Event, KeyEvent, WindowEvent}; 4 | use winit::event_loop::{ControlFlow, EventLoop}; 5 | use winit::keyboard::{Key, NamedKey}; 6 | 7 | #[path = "utils/winit_app.rs"] 8 | mod winit_app; 9 | 10 | fn main() { 11 | //see fruit.jpg.license for the license of fruit.jpg 12 | let fruit = image::load_from_memory(include_bytes!("fruit.jpg")).unwrap(); 13 | let (width, height) = (fruit.width(), fruit.height()); 14 | 15 | let event_loop = EventLoop::new().unwrap(); 16 | 17 | let app = winit_app::WinitAppBuilder::with_init( 18 | move |elwt| { 19 | let window = winit_app::make_window(elwt, |w| { 20 | w.with_inner_size(winit::dpi::PhysicalSize::new(width, height)) 21 | }); 22 | 23 | let context = softbuffer::Context::new(window.clone()).unwrap(); 24 | 25 | (window, context) 26 | }, 27 | move |_elwt, (window, context)| { 28 | let mut surface = softbuffer::Surface::new(context, window.clone()).unwrap(); 29 | // Intentionally only set the size of the surface once, at creation. 30 | // This is needed if the window chooses to ignore the size we passed in above, and for the 31 | // platforms softbuffer supports that don't yet extract the size from the window. 32 | surface 33 | .resize( 34 | NonZeroU32::new(width).unwrap(), 35 | NonZeroU32::new(height).unwrap(), 36 | ) 37 | .unwrap(); 38 | surface 39 | }, 40 | ) 41 | .with_event_handler(move |state, surface, event, elwt| { 42 | let (window, _context) = state; 43 | elwt.set_control_flow(ControlFlow::Wait); 44 | 45 | match event { 46 | Event::WindowEvent { 47 | window_id, 48 | event: WindowEvent::RedrawRequested, 49 | } if window_id == window.id() => { 50 | let Some(surface) = surface else { 51 | eprintln!("RedrawRequested fired before Resumed or after Suspended"); 52 | return; 53 | }; 54 | 55 | let mut buffer = surface.buffer_mut().unwrap(); 56 | let width = fruit.width() as usize; 57 | for (x, y, pixel) in fruit.pixels() { 58 | let red = pixel.0[0] as u32; 59 | let green = pixel.0[1] as u32; 60 | let blue = pixel.0[2] as u32; 61 | 62 | let color = blue | (green << 8) | (red << 16); 63 | buffer[y as usize * width + x as usize] = color; 64 | } 65 | 66 | buffer.present().unwrap(); 67 | } 68 | Event::WindowEvent { 69 | event: 70 | WindowEvent::CloseRequested 71 | | WindowEvent::KeyboardInput { 72 | event: 73 | KeyEvent { 74 | logical_key: Key::Named(NamedKey::Escape), 75 | .. 76 | }, 77 | .. 78 | }, 79 | window_id, 80 | } if window_id == window.id() => { 81 | elwt.exit(); 82 | } 83 | _ => {} 84 | } 85 | }); 86 | 87 | winit_app::run_app(event_loop, app); 88 | } 89 | -------------------------------------------------------------------------------- /examples/libxcb.rs: -------------------------------------------------------------------------------- 1 | //! Example of using `softbuffer` with `libxcb`. 2 | 3 | #[cfg(all(feature = "x11", any(target_os = "linux", target_os = "freebsd")))] 4 | mod example { 5 | use raw_window_handle::{ 6 | DisplayHandle, RawDisplayHandle, RawWindowHandle, WindowHandle, XcbDisplayHandle, 7 | XcbWindowHandle, 8 | }; 9 | use std::{env, num::NonZeroU32, ptr::NonNull}; 10 | use x11rb::{ 11 | connection::Connection, 12 | protocol::{ 13 | xproto::{self, ConnectionExt as _}, 14 | Event, 15 | }, 16 | xcb_ffi::XCBConnection, 17 | }; 18 | 19 | const RED: u32 = 255 << 16; 20 | 21 | pub(crate) fn run() { 22 | // Create a new XCB connection 23 | let (conn, screen) = XCBConnection::connect(None).expect("Failed to connect to X server"); 24 | 25 | // x11rb doesn't use raw-window-handle yet, so just create our own. 26 | let display_handle = XcbDisplayHandle::new( 27 | if env::var_os("SOFTBUFFER_NO_DISPLAY").is_some() { 28 | None 29 | } else { 30 | NonNull::new(conn.get_raw_xcb_connection() as *mut _) 31 | }, 32 | screen as _, 33 | ); 34 | 35 | // Create a new window. 36 | let mut width = 640u16; 37 | let mut height = 480u16; 38 | 39 | let window = conn.generate_id().unwrap(); 40 | let screen = &conn.setup().roots[screen]; 41 | let (root_visual, root_parent) = (screen.root_visual, screen.root); 42 | conn.create_window( 43 | x11rb::COPY_FROM_PARENT as _, 44 | window, 45 | root_parent, 46 | 0, 47 | 0, 48 | width, 49 | height, 50 | 0, 51 | xproto::WindowClass::COPY_FROM_PARENT, 52 | root_visual, 53 | &xproto::CreateWindowAux::new() 54 | .background_pixel(screen.white_pixel) 55 | .event_mask(xproto::EventMask::EXPOSURE | xproto::EventMask::STRUCTURE_NOTIFY), 56 | ) 57 | .unwrap() 58 | .check() 59 | .unwrap(); 60 | 61 | let mut window_handle = XcbWindowHandle::new(NonZeroU32::new(window).unwrap()); 62 | window_handle.visual_id = NonZeroU32::new(root_visual); 63 | 64 | // Create a new softbuffer context. 65 | // SAFETY: The display and window handles outlive the context. 66 | let display_handle = 67 | unsafe { DisplayHandle::borrow_raw(RawDisplayHandle::Xcb(display_handle)) }; 68 | let window_handle = 69 | unsafe { WindowHandle::borrow_raw(RawWindowHandle::Xcb(window_handle)) }; 70 | let context = softbuffer::Context::new(display_handle).unwrap(); 71 | let mut surface = softbuffer::Surface::new(&context, window_handle).unwrap(); 72 | 73 | // Register an atom for closing the window. 74 | let wm_protocols_atom = conn 75 | .intern_atom(false, "WM_PROTOCOLS".as_bytes()) 76 | .unwrap() 77 | .reply() 78 | .unwrap() 79 | .atom; 80 | let delete_window_atom = conn 81 | .intern_atom(false, "WM_DELETE_WINDOW".as_bytes()) 82 | .unwrap() 83 | .reply() 84 | .unwrap() 85 | .atom; 86 | conn.change_property( 87 | xproto::PropMode::REPLACE as _, 88 | window, 89 | wm_protocols_atom, 90 | xproto::AtomEnum::ATOM, 91 | 32, 92 | 1, 93 | &delete_window_atom.to_ne_bytes(), 94 | ) 95 | .unwrap() 96 | .check() 97 | .unwrap(); 98 | 99 | // Map the window to the screen. 100 | conn.map_window(window).unwrap().check().unwrap(); 101 | 102 | // Pump events. 103 | loop { 104 | let event = conn.wait_for_event().unwrap(); 105 | 106 | match event { 107 | Event::Expose(_) => { 108 | // Draw a width x height red rectangle. 109 | surface 110 | .resize( 111 | NonZeroU32::new(width.into()).unwrap(), 112 | NonZeroU32::new(height.into()).unwrap(), 113 | ) 114 | .unwrap(); 115 | let mut buffer = surface.buffer_mut().unwrap(); 116 | buffer.fill(RED); 117 | buffer.present().unwrap(); 118 | } 119 | Event::ConfigureNotify(configure_notify) => { 120 | width = configure_notify.width; 121 | height = configure_notify.height; 122 | } 123 | Event::ClientMessage(cm) => { 124 | if cm.data.as_data32()[0] == delete_window_atom { 125 | break; 126 | } 127 | } 128 | _ => {} 129 | } 130 | } 131 | 132 | // Delete the context and drop the window. 133 | drop(context); 134 | conn.destroy_window(window).unwrap().check().unwrap(); 135 | } 136 | } 137 | 138 | #[cfg(all(feature = "x11", any(target_os = "linux", target_os = "freebsd")))] 139 | fn main() { 140 | example::run(); 141 | } 142 | 143 | #[cfg(not(all(feature = "x11", any(target_os = "linux", target_os = "freebsd"))))] 144 | fn main() { 145 | eprintln!("This example requires the `x11` feature to be enabled on a supported platform."); 146 | } 147 | -------------------------------------------------------------------------------- /examples/rectangle.rs: -------------------------------------------------------------------------------- 1 | use std::num::NonZeroU32; 2 | use winit::event::{ElementState, Event, KeyEvent, WindowEvent}; 3 | use winit::event_loop::{ControlFlow, EventLoop}; 4 | use winit::keyboard::{Key, NamedKey}; 5 | 6 | #[path = "utils/winit_app.rs"] 7 | mod winit_app; 8 | 9 | fn redraw(buffer: &mut [u32], width: usize, height: usize, flag: bool) { 10 | for y in 0..height { 11 | for x in 0..width { 12 | let value = if flag && x >= 100 && x < width - 100 && y >= 100 && y < height - 100 { 13 | 0x00ffffff 14 | } else { 15 | let red = (x & 0xff) ^ (y & 0xff); 16 | let green = (x & 0x7f) ^ (y & 0x7f); 17 | let blue = (x & 0x3f) ^ (y & 0x3f); 18 | (blue | (green << 8) | (red << 16)) as u32 19 | }; 20 | buffer[y * width + x] = value; 21 | } 22 | } 23 | } 24 | 25 | fn main() { 26 | let event_loop = EventLoop::new().unwrap(); 27 | 28 | let app = winit_app::WinitAppBuilder::with_init( 29 | |elwt| { 30 | let window = winit_app::make_window(elwt, |w| { 31 | w.with_title("Press space to show/hide a rectangle") 32 | }); 33 | 34 | let context = softbuffer::Context::new(window.clone()).unwrap(); 35 | 36 | let flag = false; 37 | 38 | (window, context, flag) 39 | }, 40 | |_elwt, (window, context, _flag)| { 41 | softbuffer::Surface::new(context, window.clone()).unwrap() 42 | }, 43 | ) 44 | .with_event_handler(|state, surface, event, elwt| { 45 | let (window, _context, flag) = state; 46 | 47 | elwt.set_control_flow(ControlFlow::Wait); 48 | 49 | match event { 50 | Event::WindowEvent { 51 | window_id, 52 | event: WindowEvent::Resized(size), 53 | } if window_id == window.id() => { 54 | let Some(surface) = surface else { 55 | eprintln!("Resized fired before Resumed or after Suspended"); 56 | return; 57 | }; 58 | 59 | if let (Some(width), Some(height)) = 60 | (NonZeroU32::new(size.width), NonZeroU32::new(size.height)) 61 | { 62 | // Resize surface 63 | surface.resize(width, height).unwrap(); 64 | } 65 | } 66 | Event::WindowEvent { 67 | window_id, 68 | event: WindowEvent::RedrawRequested, 69 | } if window_id == window.id() => { 70 | let Some(surface) = surface else { 71 | eprintln!("RedrawRequested fired before Resumed or after Suspended"); 72 | return; 73 | }; 74 | // Grab the window's client area dimensions, and ensure they're valid 75 | let size = window.inner_size(); 76 | if let (Some(width), Some(height)) = 77 | (NonZeroU32::new(size.width), NonZeroU32::new(size.height)) 78 | { 79 | // Draw something in the window 80 | let mut buffer = surface.buffer_mut().unwrap(); 81 | redraw( 82 | &mut buffer, 83 | width.get() as usize, 84 | height.get() as usize, 85 | *flag, 86 | ); 87 | buffer.present().unwrap(); 88 | } 89 | } 90 | 91 | Event::WindowEvent { 92 | event: 93 | WindowEvent::CloseRequested 94 | | WindowEvent::KeyboardInput { 95 | event: 96 | KeyEvent { 97 | logical_key: Key::Named(NamedKey::Escape), 98 | .. 99 | }, 100 | .. 101 | }, 102 | window_id, 103 | } if window_id == window.id() => { 104 | elwt.exit(); 105 | } 106 | 107 | Event::WindowEvent { 108 | event: 109 | WindowEvent::KeyboardInput { 110 | event: 111 | KeyEvent { 112 | state: ElementState::Pressed, 113 | logical_key: Key::Named(NamedKey::Space), 114 | .. 115 | }, 116 | .. 117 | }, 118 | window_id, 119 | } if window_id == window.id() => { 120 | // Flip the rectangle flag and request a redraw to show the changed image 121 | *flag = !*flag; 122 | window.request_redraw(); 123 | } 124 | 125 | _ => {} 126 | } 127 | }); 128 | 129 | winit_app::run_app(event_loop, app); 130 | } 131 | -------------------------------------------------------------------------------- /examples/utils/winit_app.rs: -------------------------------------------------------------------------------- 1 | /// Common boilerplate for setting up a winit application. 2 | use std::marker::PhantomData; 3 | use std::rc::Rc; 4 | 5 | use winit::application::ApplicationHandler; 6 | use winit::event::{Event, WindowEvent}; 7 | use winit::event_loop::{ActiveEventLoop, EventLoop}; 8 | use winit::window::{Window, WindowAttributes, WindowId}; 9 | 10 | /// Run a Winit application. 11 | #[allow(unused_mut)] 12 | pub(crate) fn run_app(event_loop: EventLoop<()>, mut app: impl ApplicationHandler<()> + 'static) { 13 | #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] 14 | event_loop.run_app(&mut app).unwrap(); 15 | 16 | #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] 17 | winit::platform::web::EventLoopExtWebSys::spawn_app(event_loop, app); 18 | } 19 | 20 | /// Create a window from a set of window attributes. 21 | #[allow(dead_code)] 22 | pub(crate) fn make_window( 23 | elwt: &ActiveEventLoop, 24 | f: impl FnOnce(WindowAttributes) -> WindowAttributes, 25 | ) -> Rc { 26 | let attributes = f(WindowAttributes::default()); 27 | #[cfg(target_arch = "wasm32")] 28 | let attributes = winit::platform::web::WindowAttributesExtWebSys::with_append(attributes, true); 29 | let window = elwt.create_window(attributes); 30 | Rc::new(window.unwrap()) 31 | } 32 | 33 | /// Easily constructable winit application. 34 | pub(crate) struct WinitApp { 35 | /// Closure to initialize `state`. 36 | init: Init, 37 | 38 | /// Closure to initialize `surface_state`. 39 | init_surface: InitSurface, 40 | 41 | /// Closure to run on window events. 42 | event: Handler, 43 | 44 | /// Contained state. 45 | state: Option, 46 | 47 | /// Contained surface state. 48 | surface_state: Option, 49 | } 50 | 51 | /// Builder that makes it so we don't have to name `T`. 52 | pub(crate) struct WinitAppBuilder { 53 | /// Closure to initialize `state`. 54 | init: Init, 55 | 56 | /// Closure to initialize `surface_state`. 57 | init_surface: InitSurface, 58 | 59 | /// Eat the type parameter. 60 | _marker: PhantomData<(Option, Option)>, 61 | } 62 | 63 | impl WinitAppBuilder 64 | where 65 | Init: FnMut(&ActiveEventLoop) -> T, 66 | InitSurface: FnMut(&ActiveEventLoop, &mut T) -> S, 67 | { 68 | /// Create with an "init" closure. 69 | pub(crate) fn with_init(init: Init, init_surface: InitSurface) -> Self { 70 | Self { 71 | init, 72 | init_surface, 73 | _marker: PhantomData, 74 | } 75 | } 76 | 77 | /// Build a new application. 78 | pub(crate) fn with_event_handler(self, handler: F) -> WinitApp 79 | where 80 | F: FnMut(&mut T, Option<&mut S>, Event<()>, &ActiveEventLoop), 81 | { 82 | WinitApp::new(self.init, self.init_surface, handler) 83 | } 84 | } 85 | 86 | impl WinitApp 87 | where 88 | Init: FnMut(&ActiveEventLoop) -> T, 89 | InitSurface: FnMut(&ActiveEventLoop, &mut T) -> S, 90 | Handler: FnMut(&mut T, Option<&mut S>, Event<()>, &ActiveEventLoop), 91 | { 92 | /// Create a new application. 93 | pub(crate) fn new(init: Init, init_surface: InitSurface, event: Handler) -> Self { 94 | Self { 95 | init, 96 | init_surface, 97 | event, 98 | state: None, 99 | surface_state: None, 100 | } 101 | } 102 | } 103 | 104 | impl ApplicationHandler 105 | for WinitApp 106 | where 107 | Init: FnMut(&ActiveEventLoop) -> T, 108 | InitSurface: FnMut(&ActiveEventLoop, &mut T) -> S, 109 | Handler: FnMut(&mut T, Option<&mut S>, Event<()>, &ActiveEventLoop), 110 | { 111 | fn resumed(&mut self, el: &ActiveEventLoop) { 112 | debug_assert!(self.state.is_none()); 113 | let mut state = (self.init)(el); 114 | self.surface_state = Some((self.init_surface)(el, &mut state)); 115 | self.state = Some(state); 116 | } 117 | 118 | fn suspended(&mut self, _event_loop: &ActiveEventLoop) { 119 | let surface_state = self.surface_state.take(); 120 | debug_assert!(surface_state.is_some()); 121 | drop(surface_state); 122 | } 123 | 124 | fn window_event( 125 | &mut self, 126 | event_loop: &ActiveEventLoop, 127 | window_id: WindowId, 128 | event: WindowEvent, 129 | ) { 130 | let state = self.state.as_mut().unwrap(); 131 | let surface_state = self.surface_state.as_mut(); 132 | (self.event)( 133 | state, 134 | surface_state, 135 | Event::WindowEvent { window_id, event }, 136 | event_loop, 137 | ); 138 | } 139 | 140 | fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { 141 | if let Some(state) = self.state.as_mut() { 142 | (self.event)( 143 | state, 144 | self.surface_state.as_mut(), 145 | Event::AboutToWait, 146 | event_loop, 147 | ); 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /examples/winit.rs: -------------------------------------------------------------------------------- 1 | use std::num::NonZeroU32; 2 | use winit::event::{Event, KeyEvent, WindowEvent}; 3 | use winit::event_loop::{ControlFlow, EventLoop}; 4 | use winit::keyboard::{Key, NamedKey}; 5 | 6 | #[path = "utils/winit_app.rs"] 7 | mod winit_app; 8 | 9 | #[cfg(not(target_os = "android"))] 10 | fn main() { 11 | entry(EventLoop::new().unwrap()) 12 | } 13 | 14 | pub(crate) fn entry(event_loop: EventLoop<()>) { 15 | let app = winit_app::WinitAppBuilder::with_init( 16 | |elwt| { 17 | let window = winit_app::make_window(elwt, |w| w); 18 | 19 | let context = softbuffer::Context::new(window.clone()).unwrap(); 20 | 21 | (window, context) 22 | }, 23 | |_elwt, (window, context)| softbuffer::Surface::new(context, window.clone()).unwrap(), 24 | ) 25 | .with_event_handler(|(window, _context), surface, event, elwt| { 26 | elwt.set_control_flow(ControlFlow::Wait); 27 | 28 | match event { 29 | Event::WindowEvent { 30 | window_id, 31 | event: WindowEvent::Resized(size), 32 | } if window_id == window.id() => { 33 | let Some(surface) = surface else { 34 | eprintln!("Resized fired before Resumed or after Suspended"); 35 | return; 36 | }; 37 | 38 | if let (Some(width), Some(height)) = 39 | (NonZeroU32::new(size.width), NonZeroU32::new(size.height)) 40 | { 41 | surface.resize(width, height).unwrap(); 42 | } 43 | } 44 | Event::WindowEvent { 45 | window_id, 46 | event: WindowEvent::RedrawRequested, 47 | } if window_id == window.id() => { 48 | let Some(surface) = surface else { 49 | eprintln!("RedrawRequested fired before Resumed or after Suspended"); 50 | return; 51 | }; 52 | let size = window.inner_size(); 53 | if let (Some(width), Some(height)) = 54 | (NonZeroU32::new(size.width), NonZeroU32::new(size.height)) 55 | { 56 | let mut buffer = surface.buffer_mut().unwrap(); 57 | for y in 0..height.get() { 58 | for x in 0..width.get() { 59 | let red = x % 255; 60 | let green = y % 255; 61 | let blue = (x * y) % 255; 62 | let index = y as usize * width.get() as usize + x as usize; 63 | buffer[index] = blue | (green << 8) | (red << 16); 64 | } 65 | } 66 | 67 | buffer.present().unwrap(); 68 | } 69 | } 70 | Event::WindowEvent { 71 | event: 72 | WindowEvent::CloseRequested 73 | | WindowEvent::KeyboardInput { 74 | event: 75 | KeyEvent { 76 | logical_key: Key::Named(NamedKey::Escape), 77 | .. 78 | }, 79 | .. 80 | }, 81 | window_id, 82 | } if window_id == window.id() => { 83 | elwt.exit(); 84 | } 85 | _ => {} 86 | } 87 | }); 88 | 89 | winit_app::run_app(event_loop, app); 90 | } 91 | -------------------------------------------------------------------------------- /examples/winit_android.rs: -------------------------------------------------------------------------------- 1 | #![cfg(target_os = "android")] 2 | 3 | use winit::event_loop::EventLoop; 4 | pub use winit::platform::android::{activity::AndroidApp, EventLoopBuilderExtAndroid}; 5 | 6 | #[path = "winit.rs"] 7 | mod desktop_example; 8 | 9 | /// Run with `cargo apk r --example winit_android` 10 | #[no_mangle] 11 | fn android_main(app: AndroidApp) { 12 | let mut builder = EventLoop::builder(); 13 | 14 | // Install the Android event loop extension if necessary. 15 | builder.with_android_app(app); 16 | 17 | desktop_example::entry(builder.build().unwrap()) 18 | } 19 | -------------------------------------------------------------------------------- /examples/winit_multithread.rs: -------------------------------------------------------------------------------- 1 | //! `Surface` implements `Send`. This makes sure that multithreading can work here. 2 | 3 | #[cfg(not(target_family = "wasm"))] 4 | #[path = "utils/winit_app.rs"] 5 | mod winit_app; 6 | 7 | #[cfg(not(target_family = "wasm"))] 8 | pub mod ex { 9 | use std::num::NonZeroU32; 10 | use std::sync::{mpsc, Arc, Mutex}; 11 | use winit::event::{Event, KeyEvent, WindowEvent}; 12 | use winit::event_loop::{ControlFlow, EventLoop}; 13 | use winit::keyboard::{Key, NamedKey}; 14 | use winit::window::Window; 15 | 16 | use super::winit_app; 17 | 18 | type Surface = softbuffer::Surface, Arc>; 19 | 20 | fn render_thread( 21 | window: Arc, 22 | do_render: mpsc::Receiver>>, 23 | done: mpsc::Sender<()>, 24 | ) { 25 | loop { 26 | println!("waiting for render..."); 27 | let Ok(surface) = do_render.recv() else { 28 | println!("main thread destroyed"); 29 | break; 30 | }; 31 | 32 | // Perform the rendering. 33 | let mut surface = surface.lock().unwrap(); 34 | if let (Some(width), Some(height)) = { 35 | let size = window.inner_size(); 36 | println!("got size: {size:?}"); 37 | (NonZeroU32::new(size.width), NonZeroU32::new(size.height)) 38 | } { 39 | println!("resizing..."); 40 | surface.resize(width, height).unwrap(); 41 | 42 | let mut buffer = surface.buffer_mut().unwrap(); 43 | for y in 0..height.get() { 44 | for x in 0..width.get() { 45 | let red = x % 255; 46 | let green = y % 255; 47 | let blue = (x * y) % 255; 48 | let index = y as usize * width.get() as usize + x as usize; 49 | buffer[index] = blue | (green << 8) | (red << 16); 50 | } 51 | } 52 | 53 | println!("presenting..."); 54 | buffer.present().unwrap(); 55 | } 56 | 57 | // We're done, tell the main thread to keep going. 58 | done.send(()).ok(); 59 | } 60 | } 61 | 62 | pub fn entry(event_loop: EventLoop<()>) { 63 | let app = winit_app::WinitAppBuilder::with_init( 64 | |elwt| { 65 | let attributes = Window::default_attributes(); 66 | #[cfg(target_arch = "wasm32")] 67 | let attributes = 68 | winit::platform::web::WindowAttributesExtWebSys::with_append(attributes, true); 69 | let window = Arc::new(elwt.create_window(attributes).unwrap()); 70 | 71 | let context = softbuffer::Context::new(window.clone()).unwrap(); 72 | 73 | // Spawn a thread to handle rendering for this specific surface. The channels will 74 | // be closed and the thread will be stopped whenever this surface (the returned 75 | // context below) is dropped, so that it can all be recreated again (on Android) 76 | // when a new surface is created. 77 | let (start_render, do_render) = mpsc::channel(); 78 | let (render_done, finish_render) = mpsc::channel(); 79 | println!("starting thread..."); 80 | std::thread::spawn({ 81 | let window = window.clone(); 82 | move || render_thread(window, do_render, render_done) 83 | }); 84 | 85 | (window, context, start_render, finish_render) 86 | }, 87 | |_elwt, (window, context, _start_render, _finish_render)| { 88 | println!("making surface..."); 89 | Arc::new(Mutex::new( 90 | softbuffer::Surface::new(context, window.clone()).unwrap(), 91 | )) 92 | }, 93 | ) 94 | .with_event_handler(|state, surface, event, elwt| { 95 | let (window, _context, start_render, finish_render) = state; 96 | elwt.set_control_flow(ControlFlow::Wait); 97 | 98 | match event { 99 | Event::WindowEvent { 100 | window_id, 101 | event: WindowEvent::RedrawRequested, 102 | } if window_id == window.id() => { 103 | let Some(surface) = surface else { 104 | eprintln!("RedrawRequested fired before Resumed or after Suspended"); 105 | return; 106 | }; 107 | // Start the render and then finish it. 108 | start_render.send(surface.clone()).unwrap(); 109 | finish_render.recv().unwrap(); 110 | } 111 | Event::WindowEvent { 112 | event: 113 | WindowEvent::CloseRequested 114 | | WindowEvent::KeyboardInput { 115 | event: 116 | KeyEvent { 117 | logical_key: Key::Named(NamedKey::Escape), 118 | .. 119 | }, 120 | .. 121 | }, 122 | window_id, 123 | } if window_id == window.id() => { 124 | elwt.exit(); 125 | } 126 | _ => {} 127 | } 128 | }); 129 | 130 | winit_app::run_app(event_loop, app); 131 | } 132 | } 133 | 134 | #[cfg(target_family = "wasm")] 135 | mod ex { 136 | use winit::event_loop::EventLoop; 137 | pub(crate) fn entry(_event_loop: EventLoop<()>) { 138 | eprintln!("winit_multithreaded doesn't work on WASM"); 139 | } 140 | } 141 | 142 | #[cfg(not(target_os = "android"))] 143 | fn main() { 144 | use winit::event_loop::EventLoop; 145 | ex::entry(EventLoop::new().unwrap()) 146 | } 147 | -------------------------------------------------------------------------------- /examples/winit_multithread_android.rs: -------------------------------------------------------------------------------- 1 | #![cfg(target_os = "android")] 2 | 3 | use winit::event_loop::EventLoop; 4 | pub use winit::platform::android::{activity::AndroidApp, EventLoopBuilderExtAndroid}; 5 | 6 | #[path = "winit_multithread.rs"] 7 | mod desktop_example; 8 | 9 | /// Run with `cargo apk r --example winit_android` 10 | #[no_mangle] 11 | fn android_main(app: AndroidApp) { 12 | let mut builder = EventLoop::builder(); 13 | 14 | // Install the Android event loop extension if necessary. 15 | builder.with_android_app(app); 16 | 17 | desktop_example::ex::entry(builder.build().unwrap()) 18 | } 19 | -------------------------------------------------------------------------------- /examples/winit_wrong_sized_buffer.rs: -------------------------------------------------------------------------------- 1 | use std::num::NonZeroU32; 2 | use winit::event::{Event, KeyEvent, WindowEvent}; 3 | use winit::event_loop::{ControlFlow, EventLoop}; 4 | use winit::keyboard::{Key, NamedKey}; 5 | 6 | #[path = "utils/winit_app.rs"] 7 | mod winit_app; 8 | 9 | const BUFFER_WIDTH: usize = 256; 10 | const BUFFER_HEIGHT: usize = 128; 11 | 12 | fn main() { 13 | let event_loop = EventLoop::new().unwrap(); 14 | 15 | let app = winit_app::WinitAppBuilder::with_init( 16 | |elwt| { 17 | let window = winit_app::make_window(elwt, |w| w); 18 | 19 | let context = softbuffer::Context::new(window.clone()).unwrap(); 20 | 21 | (window, context) 22 | }, 23 | |_elwt, (window, context)| { 24 | let mut surface = softbuffer::Surface::new(context, window.clone()).unwrap(); 25 | // Intentionally set the size of the surface to something different than the size of the window. 26 | surface 27 | .resize( 28 | NonZeroU32::new(BUFFER_WIDTH as u32).unwrap(), 29 | NonZeroU32::new(BUFFER_HEIGHT as u32).unwrap(), 30 | ) 31 | .unwrap(); 32 | surface 33 | }, 34 | ) 35 | .with_event_handler(|state, surface, event, elwt| { 36 | let (window, _context) = state; 37 | elwt.set_control_flow(ControlFlow::Wait); 38 | 39 | match event { 40 | Event::WindowEvent { 41 | window_id, 42 | event: WindowEvent::RedrawRequested, 43 | } if window_id == window.id() => { 44 | let Some(surface) = surface else { 45 | eprintln!("RedrawRequested fired before Resumed or after Suspended"); 46 | return; 47 | }; 48 | 49 | let mut buffer = surface.buffer_mut().unwrap(); 50 | for y in 0..BUFFER_HEIGHT { 51 | for x in 0..BUFFER_WIDTH { 52 | let red = x as u32 % 255; 53 | let green = y as u32 % 255; 54 | let blue = (x as u32 * y as u32) % 255; 55 | 56 | let color = blue | (green << 8) | (red << 16); 57 | buffer[y * BUFFER_WIDTH + x] = color; 58 | } 59 | } 60 | buffer.present().unwrap(); 61 | } 62 | Event::WindowEvent { 63 | event: 64 | WindowEvent::CloseRequested 65 | | WindowEvent::KeyboardInput { 66 | event: 67 | KeyEvent { 68 | logical_key: Key::Named(NamedKey::Escape), 69 | .. 70 | }, 71 | .. 72 | }, 73 | window_id, 74 | } if window_id == window.id() => { 75 | elwt.exit(); 76 | } 77 | _ => {} 78 | } 79 | }); 80 | 81 | winit_app::run_app(event_loop, app); 82 | } 83 | -------------------------------------------------------------------------------- /run-wasm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "run-wasm" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | cargo-run-wasm = "0.4.0" 8 | -------------------------------------------------------------------------------- /run-wasm/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | cargo_run_wasm::run_wasm_with_css("body { margin: 0px; }"); 3 | } 4 | -------------------------------------------------------------------------------- /src/backend_dispatch.rs: -------------------------------------------------------------------------------- 1 | //! Implements `buffer_interface::*` traits for enums dispatching to backends 2 | 3 | use crate::{backend_interface::*, backends, InitError, Rect, SoftBufferError}; 4 | 5 | use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; 6 | use std::num::NonZeroU32; 7 | #[cfg(any(wayland_platform, x11_platform, kms_platform))] 8 | use std::sync::Arc; 9 | 10 | /// A macro for creating the enum used to statically dispatch to the platform-specific implementation. 11 | macro_rules! make_dispatch { 12 | ( 13 | <$dgen: ident, $wgen: ident> => 14 | $( 15 | $(#[$attr:meta])* 16 | $name: ident 17 | ($context_inner: ty, $surface_inner: ty, $buffer_inner: ty), 18 | )* 19 | ) => { 20 | pub(crate) enum ContextDispatch<$dgen> { 21 | $( 22 | $(#[$attr])* 23 | $name($context_inner), 24 | )* 25 | } 26 | 27 | impl ContextDispatch { 28 | pub fn variant_name(&self) -> &'static str { 29 | match self { 30 | $( 31 | $(#[$attr])* 32 | Self::$name(_) => stringify!($name), 33 | )* 34 | } 35 | } 36 | } 37 | 38 | impl ContextInterface for ContextDispatch { 39 | fn new(mut display: D) -> Result> 40 | where 41 | D: Sized, 42 | { 43 | $( 44 | $(#[$attr])* 45 | match <$context_inner as ContextInterface>::new(display) { 46 | Ok(x) => { 47 | return Ok(Self::$name(x)); 48 | } 49 | Err(InitError::Unsupported(d)) => display = d, 50 | Err(InitError::Failure(f)) => return Err(InitError::Failure(f)), 51 | } 52 | )* 53 | 54 | Err(InitError::Unsupported(display)) 55 | } 56 | } 57 | 58 | #[allow(clippy::large_enum_variant)] // it's boxed anyways 59 | pub(crate) enum SurfaceDispatch<$dgen, $wgen> { 60 | $( 61 | $(#[$attr])* 62 | $name($surface_inner), 63 | )* 64 | } 65 | 66 | impl SurfaceInterface for SurfaceDispatch { 67 | type Context = ContextDispatch; 68 | type Buffer<'a> = BufferDispatch<'a, D, W> where Self: 'a; 69 | 70 | fn new(window: W, display: &Self::Context) -> Result> 71 | where 72 | W: Sized, 73 | Self: Sized { 74 | match display { 75 | $( 76 | $(#[$attr])* 77 | ContextDispatch::$name(inner) => Ok(Self::$name(<$surface_inner>::new(window, inner)?)), 78 | )* 79 | } 80 | } 81 | 82 | fn window(&self) -> &W { 83 | match self { 84 | $( 85 | $(#[$attr])* 86 | Self::$name(inner) => inner.window(), 87 | )* 88 | } 89 | } 90 | 91 | fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { 92 | match self { 93 | $( 94 | $(#[$attr])* 95 | Self::$name(inner) => inner.resize(width, height), 96 | )* 97 | } 98 | } 99 | 100 | fn buffer_mut(&mut self) -> Result, SoftBufferError> { 101 | match self { 102 | $( 103 | $(#[$attr])* 104 | Self::$name(inner) => Ok(BufferDispatch::$name(inner.buffer_mut()?)), 105 | )* 106 | } 107 | } 108 | 109 | fn fetch(&mut self) -> Result, SoftBufferError> { 110 | match self { 111 | $( 112 | $(#[$attr])* 113 | Self::$name(inner) => inner.fetch(), 114 | )* 115 | } 116 | } 117 | } 118 | 119 | pub(crate) enum BufferDispatch<'a, $dgen, $wgen> { 120 | $( 121 | $(#[$attr])* 122 | $name($buffer_inner), 123 | )* 124 | } 125 | 126 | impl<'a, D: HasDisplayHandle, W: HasWindowHandle> BufferInterface for BufferDispatch<'a, D, W> { 127 | #[inline] 128 | fn pixels(&self) -> &[u32] { 129 | match self { 130 | $( 131 | $(#[$attr])* 132 | Self::$name(inner) => inner.pixels(), 133 | )* 134 | } 135 | } 136 | 137 | #[inline] 138 | fn pixels_mut(&mut self) -> &mut [u32] { 139 | match self { 140 | $( 141 | $(#[$attr])* 142 | Self::$name(inner) => inner.pixels_mut(), 143 | )* 144 | } 145 | } 146 | 147 | fn age(&self) -> u8 { 148 | match self { 149 | $( 150 | $(#[$attr])* 151 | Self::$name(inner) => inner.age(), 152 | )* 153 | } 154 | } 155 | 156 | fn present(self) -> Result<(), SoftBufferError> { 157 | match self { 158 | $( 159 | $(#[$attr])* 160 | Self::$name(inner) => inner.present(), 161 | )* 162 | } 163 | } 164 | 165 | fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError> { 166 | match self { 167 | $( 168 | $(#[$attr])* 169 | Self::$name(inner) => inner.present_with_damage(damage), 170 | )* 171 | } 172 | } 173 | } 174 | }; 175 | } 176 | 177 | // XXX empty enum with generic bound is invalid? 178 | 179 | make_dispatch! { 180 | => 181 | #[cfg(target_os = "android")] 182 | Android(D, backends::android::AndroidImpl, backends::android::BufferImpl<'a, D, W>), 183 | #[cfg(x11_platform)] 184 | X11(Arc>, backends::x11::X11Impl, backends::x11::BufferImpl<'a, D, W>), 185 | #[cfg(wayland_platform)] 186 | Wayland(Arc>, backends::wayland::WaylandImpl, backends::wayland::BufferImpl<'a, D, W>), 187 | #[cfg(kms_platform)] 188 | Kms(Arc>, backends::kms::KmsImpl, backends::kms::BufferImpl<'a, D, W>), 189 | #[cfg(target_os = "windows")] 190 | Win32(D, backends::win32::Win32Impl, backends::win32::BufferImpl<'a, D, W>), 191 | #[cfg(target_vendor = "apple")] 192 | CoreGraphics(D, backends::cg::CGImpl, backends::cg::BufferImpl<'a, D, W>), 193 | #[cfg(target_arch = "wasm32")] 194 | Web(backends::web::WebDisplayImpl, backends::web::WebImpl, backends::web::BufferImpl<'a, D, W>), 195 | #[cfg(target_os = "redox")] 196 | Orbital(D, backends::orbital::OrbitalImpl, backends::orbital::BufferImpl<'a, D, W>), 197 | } 198 | -------------------------------------------------------------------------------- /src/backend_interface.rs: -------------------------------------------------------------------------------- 1 | //! Interface implemented by backends 2 | 3 | use crate::{InitError, Rect, SoftBufferError}; 4 | 5 | use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; 6 | use std::num::NonZeroU32; 7 | 8 | pub(crate) trait ContextInterface { 9 | fn new(display: D) -> Result> 10 | where 11 | D: Sized, 12 | Self: Sized; 13 | } 14 | 15 | pub(crate) trait SurfaceInterface { 16 | type Context: ContextInterface; 17 | type Buffer<'a>: BufferInterface 18 | where 19 | Self: 'a; 20 | 21 | fn new(window: W, context: &Self::Context) -> Result> 22 | where 23 | W: Sized, 24 | Self: Sized; 25 | /// Get the inner window handle. 26 | fn window(&self) -> &W; 27 | /// Resize the internal buffer to the given width and height. 28 | fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError>; 29 | /// Get a mutable reference to the buffer. 30 | fn buffer_mut(&mut self) -> Result, SoftBufferError>; 31 | /// Fetch the buffer from the window. 32 | fn fetch(&mut self) -> Result, SoftBufferError> { 33 | Err(SoftBufferError::Unimplemented) 34 | } 35 | } 36 | 37 | pub(crate) trait BufferInterface { 38 | fn pixels(&self) -> &[u32]; 39 | fn pixels_mut(&mut self) -> &mut [u32]; 40 | fn age(&self) -> u8; 41 | fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError>; 42 | fn present(self) -> Result<(), SoftBufferError>; 43 | } 44 | -------------------------------------------------------------------------------- /src/backends/android.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of software buffering for Android. 2 | 3 | use std::marker::PhantomData; 4 | use std::num::{NonZeroI32, NonZeroU32}; 5 | 6 | use ndk::{ 7 | hardware_buffer_format::HardwareBufferFormat, 8 | native_window::{NativeWindow, NativeWindowBufferLockGuard}, 9 | }; 10 | #[cfg(doc)] 11 | use raw_window_handle::AndroidNdkWindowHandle; 12 | use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawWindowHandle}; 13 | 14 | use crate::error::InitError; 15 | use crate::{BufferInterface, Rect, SoftBufferError, SurfaceInterface}; 16 | 17 | /// The handle to a window for software buffering. 18 | pub struct AndroidImpl { 19 | native_window: NativeWindow, 20 | window: W, 21 | _display: PhantomData, 22 | } 23 | 24 | impl SurfaceInterface for AndroidImpl { 25 | type Context = D; 26 | type Buffer<'a> 27 | = BufferImpl<'a, D, W> 28 | where 29 | Self: 'a; 30 | 31 | /// Create a new [`AndroidImpl`] from an [`AndroidNdkWindowHandle`]. 32 | fn new(window: W, _display: &Self::Context) -> Result> { 33 | let raw = window.window_handle()?.as_raw(); 34 | let RawWindowHandle::AndroidNdk(a) = raw else { 35 | return Err(InitError::Unsupported(window)); 36 | }; 37 | 38 | // Acquire a new owned reference to the window, that will be freed on drop. 39 | // SAFETY: We have confirmed that the window handle is valid. 40 | let native_window = unsafe { NativeWindow::clone_from_ptr(a.a_native_window.cast()) }; 41 | 42 | Ok(Self { 43 | native_window, 44 | _display: PhantomData, 45 | window, 46 | }) 47 | } 48 | 49 | #[inline] 50 | fn window(&self) -> &W { 51 | &self.window 52 | } 53 | 54 | /// Also changes the pixel format to [`HardwareBufferFormat::R8G8B8A8_UNORM`]. 55 | fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { 56 | let (width, height) = (|| { 57 | let width = NonZeroI32::try_from(width).ok()?; 58 | let height = NonZeroI32::try_from(height).ok()?; 59 | Some((width, height)) 60 | })() 61 | .ok_or(SoftBufferError::SizeOutOfRange { width, height })?; 62 | 63 | self.native_window 64 | .set_buffers_geometry( 65 | width.into(), 66 | height.into(), 67 | // Default is typically R5G6B5 16bpp, switch to 32bpp 68 | Some(HardwareBufferFormat::R8G8B8X8_UNORM), 69 | ) 70 | .map_err(|err| { 71 | SoftBufferError::PlatformError( 72 | Some("Failed to set buffer geometry on ANativeWindow".to_owned()), 73 | Some(Box::new(err)), 74 | ) 75 | }) 76 | } 77 | 78 | fn buffer_mut(&mut self) -> Result, SoftBufferError> { 79 | let native_window_buffer = self.native_window.lock(None).map_err(|err| { 80 | SoftBufferError::PlatformError( 81 | Some("Failed to lock ANativeWindow".to_owned()), 82 | Some(Box::new(err)), 83 | ) 84 | })?; 85 | 86 | if !matches!( 87 | native_window_buffer.format(), 88 | // These are the only formats we support 89 | HardwareBufferFormat::R8G8B8A8_UNORM | HardwareBufferFormat::R8G8B8X8_UNORM 90 | ) { 91 | return Err(SoftBufferError::PlatformError( 92 | Some(format!( 93 | "Unexpected buffer format {:?}, please call \ 94 | .resize() first to change it to RGBx8888", 95 | native_window_buffer.format() 96 | )), 97 | None, 98 | )); 99 | } 100 | 101 | let buffer = vec![0; native_window_buffer.width() * native_window_buffer.height()]; 102 | 103 | Ok(BufferImpl { 104 | native_window_buffer, 105 | buffer, 106 | marker: PhantomData, 107 | }) 108 | } 109 | 110 | /// Fetch the buffer from the window. 111 | fn fetch(&mut self) -> Result, SoftBufferError> { 112 | Err(SoftBufferError::Unimplemented) 113 | } 114 | } 115 | 116 | pub struct BufferImpl<'a, D: ?Sized, W> { 117 | native_window_buffer: NativeWindowBufferLockGuard<'a>, 118 | buffer: Vec, 119 | marker: PhantomData<(&'a D, &'a W)>, 120 | } 121 | 122 | // TODO: Move to NativeWindowBufferLockGuard? 123 | unsafe impl<'a, D, W> Send for BufferImpl<'a, D, W> {} 124 | 125 | impl<'a, D: HasDisplayHandle, W: HasWindowHandle> BufferInterface for BufferImpl<'a, D, W> { 126 | #[inline] 127 | fn pixels(&self) -> &[u32] { 128 | &self.buffer 129 | } 130 | 131 | #[inline] 132 | fn pixels_mut(&mut self) -> &mut [u32] { 133 | &mut self.buffer 134 | } 135 | 136 | #[inline] 137 | fn age(&self) -> u8 { 138 | 0 139 | } 140 | 141 | // TODO: This function is pretty slow this way 142 | fn present(mut self) -> Result<(), SoftBufferError> { 143 | let input_lines = self.buffer.chunks(self.native_window_buffer.width()); 144 | for (output, input) in self 145 | .native_window_buffer 146 | .lines() 147 | // Unreachable as we checked before that this is a valid, mappable format 148 | .unwrap() 149 | .zip(input_lines) 150 | { 151 | // .lines() removed the stride 152 | assert_eq!(output.len(), input.len() * 4); 153 | 154 | for (i, pixel) in input.iter().enumerate() { 155 | // Swizzle colors from RGBX to BGR 156 | let [b, g, r, _] = pixel.to_le_bytes(); 157 | output[i * 4].write(b); 158 | output[i * 4 + 1].write(g); 159 | output[i * 4 + 2].write(r); 160 | // TODO alpha? 161 | } 162 | } 163 | Ok(()) 164 | } 165 | 166 | fn present_with_damage(self, _damage: &[Rect]) -> Result<(), SoftBufferError> { 167 | // TODO: Android requires the damage rect _at lock time_ 168 | // Since we're faking the backing buffer _anyway_, we could even fake the surface lock 169 | // and lock it here (if it doesn't influence timings). 170 | self.present() 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/backends/cg.rs: -------------------------------------------------------------------------------- 1 | use crate::backend_interface::*; 2 | use crate::error::InitError; 3 | use crate::{Rect, SoftBufferError}; 4 | use objc2::rc::Retained; 5 | use objc2::runtime::{AnyObject, Bool}; 6 | use objc2::{define_class, msg_send, AllocAnyThread, DefinedClass, MainThreadMarker, Message}; 7 | use objc2_core_foundation::{CFRetained, CGPoint}; 8 | use objc2_core_graphics::{ 9 | CGBitmapInfo, CGColorRenderingIntent, CGColorSpace, CGColorSpaceCreateDeviceRGB, 10 | CGDataProviderCreateWithData, CGImageAlphaInfo, CGImageCreate, 11 | }; 12 | use objc2_foundation::{ 13 | ns_string, NSDictionary, NSKeyValueChangeKey, NSKeyValueChangeNewKey, 14 | NSKeyValueObservingOptions, NSNumber, NSObject, NSObjectNSKeyValueObserverRegistration, 15 | NSString, NSValue, 16 | }; 17 | use objc2_quartz_core::{kCAGravityTopLeft, CALayer, CATransaction}; 18 | use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawWindowHandle}; 19 | 20 | use std::ffi::c_void; 21 | use std::marker::PhantomData; 22 | use std::mem::size_of; 23 | use std::num::NonZeroU32; 24 | use std::ops::Deref; 25 | use std::ptr::{self, slice_from_raw_parts_mut, NonNull}; 26 | 27 | define_class!( 28 | #[unsafe(super(NSObject))] 29 | #[name = "SoftbufferObserver"] 30 | #[ivars = Retained] 31 | struct Observer; 32 | 33 | /// NSKeyValueObserving 34 | impl Observer { 35 | #[unsafe(method(observeValueForKeyPath:ofObject:change:context:))] 36 | fn observe_value( 37 | &self, 38 | key_path: Option<&NSString>, 39 | _object: Option<&AnyObject>, 40 | change: Option<&NSDictionary>, 41 | _context: *mut c_void, 42 | ) { 43 | self.update(key_path, change); 44 | } 45 | } 46 | ); 47 | 48 | // SAFETY: The `CALayer` that the observer contains is thread safe. 49 | unsafe impl Send for Observer {} 50 | unsafe impl Sync for Observer {} 51 | 52 | impl Observer { 53 | fn new(layer: &CALayer) -> Retained { 54 | let this = Self::alloc().set_ivars(layer.retain()); 55 | unsafe { msg_send![super(this), init] } 56 | } 57 | 58 | fn update( 59 | &self, 60 | key_path: Option<&NSString>, 61 | change: Option<&NSDictionary>, 62 | ) { 63 | let layer = self.ivars(); 64 | 65 | let change = 66 | change.expect("requested a change dictionary in `addObserver`, but none was provided"); 67 | let new = unsafe { 68 | change 69 | .objectForKey(NSKeyValueChangeNewKey) 70 | .expect("requested change dictionary did not contain `NSKeyValueChangeNewKey`") 71 | }; 72 | 73 | // NOTE: Setting these values usually causes a quarter second animation to occur, which is 74 | // undesirable. 75 | // 76 | // However, since we're setting them inside an observer, there already is a transaction 77 | // ongoing, and as such we don't need to wrap this in a `CATransaction` ourselves. 78 | 79 | if key_path == Some(ns_string!("contentsScale")) { 80 | let new = new.downcast::().unwrap(); 81 | let scale_factor = new.as_cgfloat(); 82 | 83 | // Set the scale factor of the layer to match the root layer when it changes (e.g. if 84 | // moved to a different monitor, or monitor settings changed). 85 | layer.setContentsScale(scale_factor); 86 | } else if key_path == Some(ns_string!("bounds")) { 87 | let new = new.downcast::().unwrap(); 88 | let bounds = new.get_rect().expect("new bounds value was not CGRect"); 89 | 90 | // Set `bounds` and `position` so that the new layer is inside the superlayer. 91 | // 92 | // This differs from just setting the `bounds`, as it also takes into account any 93 | // translation that the superlayer may have that we'd want to preserve. 94 | layer.setFrame(bounds); 95 | } else { 96 | panic!("unknown observed keypath {key_path:?}"); 97 | } 98 | } 99 | } 100 | 101 | pub struct CGImpl { 102 | /// Our layer. 103 | layer: SendCALayer, 104 | /// The layer that our layer was created from. 105 | /// 106 | /// Can also be retrieved from `layer.superlayer()`. 107 | root_layer: SendCALayer, 108 | observer: Retained, 109 | color_space: SendCGColorSpace, 110 | /// The width of the underlying buffer. 111 | width: usize, 112 | /// The height of the underlying buffer. 113 | height: usize, 114 | window_handle: W, 115 | _display: PhantomData, 116 | } 117 | 118 | impl Drop for CGImpl { 119 | fn drop(&mut self) { 120 | // SAFETY: Registered in `new`, must be removed before the observer is deallocated. 121 | unsafe { 122 | self.root_layer 123 | .removeObserver_forKeyPath(&self.observer, ns_string!("contentsScale")); 124 | self.root_layer 125 | .removeObserver_forKeyPath(&self.observer, ns_string!("bounds")); 126 | } 127 | } 128 | } 129 | 130 | impl SurfaceInterface for CGImpl { 131 | type Context = D; 132 | type Buffer<'a> 133 | = BufferImpl<'a, D, W> 134 | where 135 | Self: 'a; 136 | 137 | fn new(window_src: W, _display: &D) -> Result> { 138 | // `NSView`/`UIView` can only be accessed from the main thread. 139 | let _mtm = MainThreadMarker::new().ok_or(SoftBufferError::PlatformError( 140 | Some("can only access Core Graphics handles from the main thread".to_string()), 141 | None, 142 | ))?; 143 | 144 | let root_layer = match window_src.window_handle()?.as_raw() { 145 | RawWindowHandle::AppKit(handle) => { 146 | // SAFETY: The pointer came from `WindowHandle`, which ensures that the 147 | // `AppKitWindowHandle` contains a valid pointer to an `NSView`. 148 | // 149 | // We use `NSObject` here to avoid importing `objc2-app-kit`. 150 | let view: &NSObject = unsafe { handle.ns_view.cast().as_ref() }; 151 | 152 | // Force the view to become layer backed 153 | let _: () = unsafe { msg_send![view, setWantsLayer: Bool::YES] }; 154 | 155 | // SAFETY: `-[NSView layer]` returns an optional `CALayer` 156 | let layer: Option> = unsafe { msg_send![view, layer] }; 157 | layer.expect("failed making the view layer-backed") 158 | } 159 | RawWindowHandle::UiKit(handle) => { 160 | // SAFETY: The pointer came from `WindowHandle`, which ensures that the 161 | // `UiKitWindowHandle` contains a valid pointer to an `UIView`. 162 | // 163 | // We use `NSObject` here to avoid importing `objc2-ui-kit`. 164 | let view: &NSObject = unsafe { handle.ui_view.cast().as_ref() }; 165 | 166 | // SAFETY: `-[UIView layer]` returns `CALayer` 167 | let layer: Retained = unsafe { msg_send![view, layer] }; 168 | layer 169 | } 170 | _ => return Err(InitError::Unsupported(window_src)), 171 | }; 172 | 173 | // Add a sublayer, to avoid interfering with the root layer, since setting the contents of 174 | // e.g. a view-controlled layer is brittle. 175 | let layer = CALayer::new(); 176 | root_layer.addSublayer(&layer); 177 | 178 | // Set the anchor point and geometry. Softbuffer's uses a coordinate system with the origin 179 | // in the top-left corner. 180 | // 181 | // NOTE: This doesn't really matter unless we start modifying the `position` of our layer 182 | // ourselves, but it's nice to have in place. 183 | layer.setAnchorPoint(CGPoint::new(0.0, 0.0)); 184 | layer.setGeometryFlipped(true); 185 | 186 | // Do not use auto-resizing mask. 187 | // 188 | // This is done to work around a bug in macOS 14 and above, where views using auto layout 189 | // may end up setting fractional values as the bounds, and that in turn doesn't propagate 190 | // properly through the auto-resizing mask and with contents gravity. 191 | // 192 | // Instead, we keep the bounds of the layer in sync with the root layer using an observer, 193 | // see below. 194 | // 195 | // layer.setAutoresizingMask(kCALayerHeightSizable | kCALayerWidthSizable); 196 | 197 | let observer = Observer::new(&layer); 198 | // Observe changes to the root layer's bounds and scale factor, and apply them to our layer. 199 | // 200 | // The previous implementation updated the scale factor inside `resize`, but this works 201 | // poorly with transactions, and is generally inefficient. Instead, we update the scale 202 | // factor only when needed because the super layer's scale factor changed. 203 | // 204 | // Note that inherent in this is an explicit design decision: We control the `bounds` and 205 | // `contentsScale` of the layer directly, and instead let the `resize` call that the user 206 | // controls only be the size of the underlying buffer. 207 | // 208 | // SAFETY: Observer deregistered in `Drop` before the observer object is deallocated. 209 | unsafe { 210 | root_layer.addObserver_forKeyPath_options_context( 211 | &observer, 212 | ns_string!("contentsScale"), 213 | NSKeyValueObservingOptions::New | NSKeyValueObservingOptions::Initial, 214 | ptr::null_mut(), 215 | ); 216 | root_layer.addObserver_forKeyPath_options_context( 217 | &observer, 218 | ns_string!("bounds"), 219 | NSKeyValueObservingOptions::New | NSKeyValueObservingOptions::Initial, 220 | ptr::null_mut(), 221 | ); 222 | } 223 | 224 | // Set the content so that it is placed in the top-left corner if it does not have the same 225 | // size as the surface itself. 226 | // 227 | // TODO(madsmtm): Consider changing this to `kCAGravityResize` to stretch the content if 228 | // resized to something that doesn't fit, see #177. 229 | layer.setContentsGravity(unsafe { kCAGravityTopLeft }); 230 | 231 | // Initialize color space here, to reduce work later on. 232 | let color_space = unsafe { CGColorSpaceCreateDeviceRGB() }.unwrap(); 233 | 234 | // Grab initial width and height from the layer (whose properties have just been initialized 235 | // by the observer using `NSKeyValueObservingOptionInitial`). 236 | let size = layer.bounds().size; 237 | let scale_factor = layer.contentsScale(); 238 | let width = (size.width * scale_factor) as usize; 239 | let height = (size.height * scale_factor) as usize; 240 | 241 | Ok(Self { 242 | layer: SendCALayer(layer), 243 | root_layer: SendCALayer(root_layer), 244 | observer, 245 | color_space: SendCGColorSpace(color_space), 246 | width, 247 | height, 248 | _display: PhantomData, 249 | window_handle: window_src, 250 | }) 251 | } 252 | 253 | #[inline] 254 | fn window(&self) -> &W { 255 | &self.window_handle 256 | } 257 | 258 | fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { 259 | self.width = width.get() as usize; 260 | self.height = height.get() as usize; 261 | Ok(()) 262 | } 263 | 264 | fn buffer_mut(&mut self) -> Result, SoftBufferError> { 265 | Ok(BufferImpl { 266 | buffer: vec![0; self.width * self.height].into(), 267 | imp: self, 268 | }) 269 | } 270 | } 271 | 272 | pub struct BufferImpl<'a, D, W> { 273 | imp: &'a mut CGImpl, 274 | buffer: Box<[u32]>, 275 | } 276 | 277 | impl BufferInterface for BufferImpl<'_, D, W> { 278 | #[inline] 279 | fn pixels(&self) -> &[u32] { 280 | &self.buffer 281 | } 282 | 283 | #[inline] 284 | fn pixels_mut(&mut self) -> &mut [u32] { 285 | &mut self.buffer 286 | } 287 | 288 | fn age(&self) -> u8 { 289 | 0 290 | } 291 | 292 | fn present(self) -> Result<(), SoftBufferError> { 293 | unsafe extern "C-unwind" fn release( 294 | _info: *mut c_void, 295 | data: NonNull, 296 | size: usize, 297 | ) { 298 | let data = data.cast::(); 299 | let slice = slice_from_raw_parts_mut(data.as_ptr(), size / size_of::()); 300 | // SAFETY: This is the same slice that we passed to `Box::into_raw` below. 301 | drop(unsafe { Box::from_raw(slice) }) 302 | } 303 | 304 | let data_provider = { 305 | let len = self.buffer.len() * size_of::(); 306 | let buffer: *mut [u32] = Box::into_raw(self.buffer); 307 | // Convert slice pointer to thin pointer. 308 | let data_ptr = buffer.cast::(); 309 | 310 | // SAFETY: The data pointer and length are valid. 311 | // The info pointer can safely be NULL, we don't use it in the `release` callback. 312 | unsafe { 313 | CGDataProviderCreateWithData(ptr::null_mut(), data_ptr, len, Some(release)).unwrap() 314 | } 315 | }; 316 | 317 | let image = unsafe { 318 | CGImageCreate( 319 | self.imp.width, 320 | self.imp.height, 321 | 8, 322 | 32, 323 | self.imp.width * 4, 324 | Some(&self.imp.color_space.0), 325 | // TODO: This looks incorrect! 326 | CGBitmapInfo::ByteOrder32Little | CGBitmapInfo(CGImageAlphaInfo::NoneSkipFirst.0), 327 | Some(&data_provider), 328 | ptr::null(), 329 | false, 330 | CGColorRenderingIntent::RenderingIntentDefault, 331 | ) 332 | } 333 | .unwrap(); 334 | 335 | // The CALayer has a default action associated with a change in the layer contents, causing 336 | // a quarter second fade transition to happen every time a new buffer is applied. This can 337 | // be avoided by wrapping the operation in a transaction and disabling all actions. 338 | CATransaction::begin(); 339 | CATransaction::setDisableActions(true); 340 | 341 | // SAFETY: The contents is `CGImage`, which is a valid class for `contents`. 342 | unsafe { self.imp.layer.setContents(Some(image.as_ref())) }; 343 | 344 | CATransaction::commit(); 345 | Ok(()) 346 | } 347 | 348 | fn present_with_damage(self, _damage: &[Rect]) -> Result<(), SoftBufferError> { 349 | self.present() 350 | } 351 | } 352 | 353 | struct SendCGColorSpace(CFRetained); 354 | // SAFETY: `CGColorSpace` is immutable, and can freely be shared between threads. 355 | unsafe impl Send for SendCGColorSpace {} 356 | unsafe impl Sync for SendCGColorSpace {} 357 | 358 | struct SendCALayer(Retained); 359 | // CALayer is thread safe, like most things in Core Animation, see: 360 | // https://developer.apple.com/documentation/quartzcore/catransaction/1448267-lock?language=objc 361 | // https://stackoverflow.com/questions/76250226/how-to-render-content-of-calayer-on-a-background-thread 362 | unsafe impl Send for SendCALayer {} 363 | unsafe impl Sync for SendCALayer {} 364 | 365 | impl Deref for SendCALayer { 366 | type Target = CALayer; 367 | fn deref(&self) -> &Self::Target { 368 | &self.0 369 | } 370 | } 371 | -------------------------------------------------------------------------------- /src/backends/kms.rs: -------------------------------------------------------------------------------- 1 | //! Backend for DRM/KMS for raw rendering directly to the screen. 2 | //! 3 | //! This strategy uses dumb buffers for rendering. 4 | 5 | use drm::buffer::{Buffer, DrmFourcc}; 6 | use drm::control::dumbbuffer::{DumbBuffer, DumbMapping}; 7 | use drm::control::{ 8 | connector, crtc, framebuffer, plane, ClipRect, Device as CtrlDevice, PageFlipFlags, 9 | }; 10 | use drm::Device; 11 | 12 | use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle}; 13 | 14 | use std::collections::HashSet; 15 | use std::marker::PhantomData; 16 | use std::num::NonZeroU32; 17 | use std::os::unix::io::{AsFd, BorrowedFd}; 18 | use std::sync::Arc; 19 | 20 | use crate::backend_interface::*; 21 | use crate::error::{InitError, SoftBufferError, SwResultExt}; 22 | 23 | #[derive(Debug)] 24 | pub(crate) struct KmsDisplayImpl { 25 | /// The underlying raw device file descriptor. 26 | fd: BorrowedFd<'static>, 27 | 28 | /// Holds a reference to the display. 29 | _display: D, 30 | } 31 | 32 | impl AsFd for KmsDisplayImpl { 33 | fn as_fd(&self) -> BorrowedFd<'_> { 34 | self.fd 35 | } 36 | } 37 | 38 | impl Device for KmsDisplayImpl {} 39 | impl CtrlDevice for KmsDisplayImpl {} 40 | 41 | impl ContextInterface for Arc> { 42 | fn new(display: D) -> Result> 43 | where 44 | D: Sized, 45 | { 46 | let RawDisplayHandle::Drm(drm) = display.display_handle()?.as_raw() else { 47 | return Err(InitError::Unsupported(display)); 48 | }; 49 | if drm.fd == -1 { 50 | return Err(SoftBufferError::IncompleteDisplayHandle.into()); 51 | } 52 | 53 | // SAFETY: Invariants guaranteed by the user. 54 | let fd = unsafe { BorrowedFd::borrow_raw(drm.fd) }; 55 | 56 | Ok(Arc::new(KmsDisplayImpl { 57 | fd, 58 | _display: display, 59 | })) 60 | } 61 | } 62 | 63 | /// All the necessary types for the Drm/Kms backend. 64 | #[derive(Debug)] 65 | pub(crate) struct KmsImpl { 66 | /// The display implementation. 67 | display: Arc>, 68 | 69 | /// The connectors to use. 70 | connectors: Vec, 71 | 72 | /// The CRTC to render to. 73 | crtc: crtc::Info, 74 | 75 | /// The dumb buffer we're using as a buffer. 76 | buffer: Option, 77 | 78 | /// Window handle that we are keeping around. 79 | window_handle: W, 80 | } 81 | 82 | #[derive(Debug)] 83 | struct Buffers { 84 | /// The involved set of buffers. 85 | buffers: [SharedBuffer; 2], 86 | 87 | /// Whether to use the first buffer or the second buffer as the front buffer. 88 | first_is_front: bool, 89 | } 90 | 91 | /// The buffer implementation. 92 | pub(crate) struct BufferImpl<'a, D: ?Sized, W: ?Sized> { 93 | /// The mapping of the dump buffer. 94 | mapping: DumbMapping<'a>, 95 | 96 | /// The framebuffer object of the current front buffer. 97 | front_fb: framebuffer::Handle, 98 | 99 | /// The CRTC handle. 100 | crtc_handle: crtc::Handle, 101 | 102 | /// This is used to change the front buffer. 103 | first_is_front: &'a mut bool, 104 | 105 | /// The current size. 106 | size: (NonZeroU32, NonZeroU32), 107 | 108 | /// The display implementation. 109 | display: &'a KmsDisplayImpl, 110 | 111 | /// Age of the front buffer. 112 | front_age: &'a mut u8, 113 | 114 | /// Age of the back buffer. 115 | back_age: &'a mut u8, 116 | 117 | /// Window reference. 118 | _window: PhantomData<&'a mut W>, 119 | } 120 | 121 | /// The combined frame buffer and dumb buffer. 122 | #[derive(Debug)] 123 | struct SharedBuffer { 124 | /// The frame buffer. 125 | fb: framebuffer::Handle, 126 | 127 | /// The dumb buffer. 128 | db: DumbBuffer, 129 | 130 | /// The age of this buffer. 131 | age: u8, 132 | } 133 | 134 | impl SurfaceInterface for KmsImpl { 135 | type Context = Arc>; 136 | type Buffer<'a> 137 | = BufferImpl<'a, D, W> 138 | where 139 | Self: 'a; 140 | 141 | /// Create a new KMS backend. 142 | fn new(window: W, display: &Arc>) -> Result> { 143 | // Make sure that the window handle is valid. 144 | let RawWindowHandle::Drm(drm) = window.window_handle()?.as_raw() else { 145 | return Err(InitError::Unsupported(window)); 146 | }; 147 | let plane_handle = 148 | NonZeroU32::new(drm.plane).ok_or(SoftBufferError::IncompleteWindowHandle)?; 149 | let plane_handle = plane::Handle::from(plane_handle); 150 | 151 | let plane_info = display 152 | .get_plane(plane_handle) 153 | .swbuf_err("failed to get plane info")?; 154 | let handles = display 155 | .resource_handles() 156 | .swbuf_err("failed to get resource handles")?; 157 | 158 | // Use either the attached CRTC or the primary CRTC. 159 | let crtc = { 160 | let handle = match plane_info.crtc() { 161 | Some(crtc) => crtc, 162 | None => { 163 | tracing::warn!("no CRTC attached to plane, falling back to primary CRTC"); 164 | handles 165 | .filter_crtcs(plane_info.possible_crtcs()) 166 | .first() 167 | .copied() 168 | .swbuf_err("failed to find a primary CRTC")? 169 | } 170 | }; 171 | 172 | // Get info about the CRTC. 173 | display 174 | .get_crtc(handle) 175 | .swbuf_err("failed to get CRTC info")? 176 | }; 177 | 178 | // Figure out all of the encoders that are attached to this CRTC. 179 | let encoders = handles 180 | .encoders 181 | .iter() 182 | .flat_map(|handle| display.get_encoder(*handle)) 183 | .filter(|encoder| encoder.crtc() == Some(crtc.handle())) 184 | .map(|encoder| encoder.handle()) 185 | .collect::>(); 186 | 187 | // Get a list of every connector that the CRTC is connected to via encoders. 188 | let connectors = handles 189 | .connectors 190 | .iter() 191 | .flat_map(|handle| display.get_connector(*handle, false)) 192 | .filter(|connector| { 193 | connector 194 | .current_encoder() 195 | .is_some_and(|encoder| encoders.contains(&encoder)) 196 | }) 197 | .map(|info| info.handle()) 198 | .collect::>(); 199 | 200 | Ok(Self { 201 | crtc, 202 | connectors, 203 | display: display.clone(), 204 | buffer: None, 205 | window_handle: window, 206 | }) 207 | } 208 | 209 | #[inline] 210 | fn window(&self) -> &W { 211 | &self.window_handle 212 | } 213 | 214 | fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { 215 | // Don't resize if we don't have to. 216 | if let Some(buffer) = &self.buffer { 217 | let (buffer_width, buffer_height) = buffer.size(); 218 | if buffer_width == width && buffer_height == height { 219 | return Ok(()); 220 | } 221 | } 222 | 223 | // Create a new buffer set. 224 | let front_buffer = SharedBuffer::new(&self.display, width, height)?; 225 | let back_buffer = SharedBuffer::new(&self.display, width, height)?; 226 | 227 | self.buffer = Some(Buffers { 228 | first_is_front: true, 229 | buffers: [front_buffer, back_buffer], 230 | }); 231 | 232 | Ok(()) 233 | } 234 | 235 | /* 236 | fn fetch(&mut self) -> Result, SoftBufferError> { 237 | // TODO: Implement this! 238 | } 239 | */ 240 | 241 | fn buffer_mut(&mut self) -> Result, SoftBufferError> { 242 | // Map the dumb buffer. 243 | let set = self 244 | .buffer 245 | .as_mut() 246 | .expect("Must set size of surface before calling `buffer_mut()`"); 247 | 248 | let size = set.size(); 249 | 250 | let [first_buffer, second_buffer] = &mut set.buffers; 251 | let (front_buffer, back_buffer) = if set.first_is_front { 252 | (first_buffer, second_buffer) 253 | } else { 254 | (second_buffer, first_buffer) 255 | }; 256 | 257 | let front_fb = front_buffer.fb; 258 | let front_age = &mut front_buffer.age; 259 | let back_age = &mut back_buffer.age; 260 | 261 | let mapping = self 262 | .display 263 | .map_dumb_buffer(&mut front_buffer.db) 264 | .swbuf_err("failed to map dumb buffer")?; 265 | 266 | Ok(BufferImpl { 267 | mapping, 268 | size, 269 | first_is_front: &mut set.first_is_front, 270 | front_fb, 271 | crtc_handle: self.crtc.handle(), 272 | display: &self.display, 273 | front_age, 274 | back_age, 275 | _window: PhantomData, 276 | }) 277 | } 278 | } 279 | 280 | impl Drop for KmsImpl { 281 | fn drop(&mut self) { 282 | // Map the CRTC to the information that was there before. 283 | self.display 284 | .set_crtc( 285 | self.crtc.handle(), 286 | self.crtc.framebuffer(), 287 | self.crtc.position(), 288 | &self.connectors, 289 | self.crtc.mode(), 290 | ) 291 | .ok(); 292 | } 293 | } 294 | 295 | impl BufferInterface for BufferImpl<'_, D, W> { 296 | #[inline] 297 | fn pixels(&self) -> &[u32] { 298 | bytemuck::cast_slice(self.mapping.as_ref()) 299 | } 300 | 301 | #[inline] 302 | fn pixels_mut(&mut self) -> &mut [u32] { 303 | bytemuck::cast_slice_mut(self.mapping.as_mut()) 304 | } 305 | 306 | #[inline] 307 | fn age(&self) -> u8 { 308 | *self.front_age 309 | } 310 | 311 | #[inline] 312 | fn present_with_damage(self, damage: &[crate::Rect]) -> Result<(), SoftBufferError> { 313 | let rectangles = damage 314 | .iter() 315 | .map(|&rect| { 316 | let err = || SoftBufferError::DamageOutOfRange { rect }; 317 | Ok::<_, SoftBufferError>(ClipRect::new( 318 | rect.x.try_into().map_err(|_| err())?, 319 | rect.y.try_into().map_err(|_| err())?, 320 | rect.x 321 | .checked_add(rect.width.get()) 322 | .and_then(|x| x.try_into().ok()) 323 | .ok_or_else(err)?, 324 | rect.y 325 | .checked_add(rect.height.get()) 326 | .and_then(|y| y.try_into().ok()) 327 | .ok_or_else(err)?, 328 | )) 329 | }) 330 | .collect::, _>>()?; 331 | 332 | // Dirty the framebuffer with out damage rectangles. 333 | // 334 | // Some drivers don't support this, so we just ignore the `ENOSYS` error. 335 | // TODO: It would be nice to not have to heap-allocate the above rectangles if we know that 336 | // this is going to fail. Low hanging fruit PR: add a flag that's set to false if this 337 | // returns `ENOSYS` and check that before allocating the above and running this. 338 | match self.display.dirty_framebuffer(self.front_fb, &rectangles) { 339 | Ok(()) => {} 340 | Err(e) if e.raw_os_error() == Some(rustix::io::Errno::NOSYS.raw_os_error()) => {} 341 | Err(e) => { 342 | return Err(SoftBufferError::PlatformError( 343 | Some("failed to dirty framebuffer".into()), 344 | Some(e.into()), 345 | )); 346 | } 347 | } 348 | 349 | // Swap the buffers. 350 | // TODO: Use atomic commits here! 351 | self.display 352 | .page_flip(self.crtc_handle, self.front_fb, PageFlipFlags::EVENT, None) 353 | .swbuf_err("failed to page flip")?; 354 | 355 | // Flip the front and back buffers. 356 | *self.first_is_front = !*self.first_is_front; 357 | 358 | // Set the ages. 359 | *self.front_age = 1; 360 | if *self.back_age != 0 { 361 | *self.back_age += 1; 362 | } 363 | 364 | Ok(()) 365 | } 366 | 367 | #[inline] 368 | fn present(self) -> Result<(), SoftBufferError> { 369 | let (width, height) = self.size; 370 | self.present_with_damage(&[crate::Rect { 371 | x: 0, 372 | y: 0, 373 | width, 374 | height, 375 | }]) 376 | } 377 | } 378 | 379 | impl SharedBuffer { 380 | /// Create a new buffer set. 381 | pub(crate) fn new( 382 | display: &KmsDisplayImpl, 383 | width: NonZeroU32, 384 | height: NonZeroU32, 385 | ) -> Result { 386 | let db = display 387 | .create_dumb_buffer((width.get(), height.get()), DrmFourcc::Xrgb8888, 32) 388 | .swbuf_err("failed to create dumb buffer")?; 389 | let fb = display 390 | .add_framebuffer(&db, 24, 32) 391 | .swbuf_err("failed to add framebuffer")?; 392 | 393 | Ok(SharedBuffer { fb, db, age: 0 }) 394 | } 395 | 396 | /// Get the size of this buffer. 397 | pub(crate) fn size(&self) -> (NonZeroU32, NonZeroU32) { 398 | let (width, height) = self.db.size(); 399 | 400 | NonZeroU32::new(width) 401 | .and_then(|width| NonZeroU32::new(height).map(|height| (width, height))) 402 | .expect("buffer size is zero") 403 | } 404 | } 405 | 406 | impl Buffers { 407 | /// Get the size of this buffer. 408 | pub(crate) fn size(&self) -> (NonZeroU32, NonZeroU32) { 409 | self.buffers[0].size() 410 | } 411 | } 412 | -------------------------------------------------------------------------------- /src/backends/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{ContextInterface, InitError}; 2 | use raw_window_handle::HasDisplayHandle; 3 | 4 | #[cfg(target_os = "android")] 5 | pub(crate) mod android; 6 | #[cfg(target_vendor = "apple")] 7 | pub(crate) mod cg; 8 | #[cfg(kms_platform)] 9 | pub(crate) mod kms; 10 | #[cfg(target_os = "redox")] 11 | pub(crate) mod orbital; 12 | #[cfg(wayland_platform)] 13 | pub(crate) mod wayland; 14 | #[cfg(target_arch = "wasm32")] 15 | pub(crate) mod web; 16 | #[cfg(target_os = "windows")] 17 | pub(crate) mod win32; 18 | #[cfg(x11_platform)] 19 | pub(crate) mod x11; 20 | 21 | impl ContextInterface for D { 22 | fn new(display: D) -> Result> { 23 | Ok(display) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/backends/orbital.rs: -------------------------------------------------------------------------------- 1 | use crate::error::InitError; 2 | use raw_window_handle::{HasDisplayHandle, HasWindowHandle, OrbitalWindowHandle, RawWindowHandle}; 3 | use std::{cmp, marker::PhantomData, num::NonZeroU32, slice, str}; 4 | 5 | use crate::backend_interface::*; 6 | use crate::{Rect, SoftBufferError}; 7 | 8 | struct OrbitalMap { 9 | address: usize, 10 | size: usize, 11 | size_unaligned: usize, 12 | } 13 | 14 | impl OrbitalMap { 15 | unsafe fn new(fd: usize, size_unaligned: usize) -> syscall::Result { 16 | // Page align size 17 | let pages = (size_unaligned + syscall::PAGE_SIZE - 1) / syscall::PAGE_SIZE; 18 | let size = pages * syscall::PAGE_SIZE; 19 | 20 | // Map window buffer 21 | let address = unsafe { 22 | syscall::fmap( 23 | fd, 24 | &syscall::Map { 25 | offset: 0, 26 | size, 27 | flags: syscall::PROT_READ | syscall::PROT_WRITE | syscall::MAP_SHARED, 28 | address: 0, 29 | }, 30 | )? 31 | }; 32 | 33 | Ok(Self { 34 | address, 35 | size, 36 | size_unaligned, 37 | }) 38 | } 39 | 40 | unsafe fn data(&self) -> &[u32] { 41 | unsafe { slice::from_raw_parts(self.address as *const u32, self.size_unaligned / 4) } 42 | } 43 | 44 | unsafe fn data_mut(&mut self) -> &mut [u32] { 45 | unsafe { slice::from_raw_parts_mut(self.address as *mut u32, self.size_unaligned / 4) } 46 | } 47 | } 48 | 49 | impl Drop for OrbitalMap { 50 | fn drop(&mut self) { 51 | unsafe { 52 | // Unmap window buffer on drop 53 | syscall::funmap(self.address, self.size).expect("failed to unmap orbital window"); 54 | } 55 | } 56 | } 57 | 58 | pub struct OrbitalImpl { 59 | handle: ThreadSafeWindowHandle, 60 | width: u32, 61 | height: u32, 62 | presented: bool, 63 | window_handle: W, 64 | _display: PhantomData, 65 | } 66 | 67 | struct ThreadSafeWindowHandle(OrbitalWindowHandle); 68 | unsafe impl Send for ThreadSafeWindowHandle {} 69 | unsafe impl Sync for ThreadSafeWindowHandle {} 70 | 71 | impl OrbitalImpl { 72 | fn window_fd(&self) -> usize { 73 | self.handle.0.window.as_ptr() as usize 74 | } 75 | 76 | // Read the current width and size 77 | fn window_size(&self) -> (usize, usize) { 78 | let mut window_width = 0; 79 | let mut window_height = 0; 80 | 81 | let mut buf: [u8; 4096] = [0; 4096]; 82 | let count = syscall::fpath(self.window_fd(), &mut buf).unwrap(); 83 | let path = str::from_utf8(&buf[..count]).unwrap(); 84 | // orbital:/x/y/w/h/t 85 | let mut parts = path.split('/').skip(3); 86 | if let Some(w) = parts.next() { 87 | window_width = w.parse::().unwrap_or(0); 88 | } 89 | if let Some(h) = parts.next() { 90 | window_height = h.parse::().unwrap_or(0); 91 | } 92 | 93 | (window_width, window_height) 94 | } 95 | 96 | fn set_buffer(&self, buffer: &[u32], width_u32: u32, height_u32: u32) { 97 | // Read the current width and size 98 | let (window_width, window_height) = self.window_size(); 99 | 100 | { 101 | // Map window buffer 102 | let mut window_map = 103 | unsafe { OrbitalMap::new(self.window_fd(), window_width * window_height * 4) } 104 | .expect("failed to map orbital window"); 105 | 106 | // Window buffer is u32 color data in 0xAABBGGRR format 107 | let window_data = unsafe { window_map.data_mut() }; 108 | 109 | // Copy each line, cropping to fit 110 | let width = width_u32 as usize; 111 | let height = height_u32 as usize; 112 | let min_width = cmp::min(width, window_width); 113 | let min_height = cmp::min(height, window_height); 114 | for y in 0..min_height { 115 | let offset_buffer = y * width; 116 | let offset_data = y * window_width; 117 | window_data[offset_data..offset_data + min_width] 118 | .copy_from_slice(&buffer[offset_buffer..offset_buffer + min_width]); 119 | } 120 | 121 | // Window buffer map is dropped here 122 | } 123 | 124 | // Tell orbital to show the latest window data 125 | syscall::fsync(self.window_fd()).expect("failed to sync orbital window"); 126 | } 127 | } 128 | 129 | impl SurfaceInterface for OrbitalImpl { 130 | type Context = D; 131 | type Buffer<'a> 132 | = BufferImpl<'a, D, W> 133 | where 134 | Self: 'a; 135 | 136 | fn new(window: W, _display: &D) -> Result> { 137 | let raw = window.window_handle()?.as_raw(); 138 | let RawWindowHandle::Orbital(handle) = raw else { 139 | return Err(InitError::Unsupported(window)); 140 | }; 141 | 142 | Ok(Self { 143 | handle: ThreadSafeWindowHandle(handle), 144 | width: 0, 145 | height: 0, 146 | presented: false, 147 | window_handle: window, 148 | _display: PhantomData, 149 | }) 150 | } 151 | 152 | #[inline] 153 | fn window(&self) -> &W { 154 | &self.window_handle 155 | } 156 | 157 | fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { 158 | let width = width.get(); 159 | let height = height.get(); 160 | if width != self.width || height != self.height { 161 | self.presented = false; 162 | self.width = width; 163 | self.height = height; 164 | } 165 | Ok(()) 166 | } 167 | 168 | fn buffer_mut(&mut self) -> Result, SoftBufferError> { 169 | let (window_width, window_height) = self.window_size(); 170 | let pixels = if self.width as usize == window_width && self.height as usize == window_height 171 | { 172 | Pixels::Mapping( 173 | unsafe { OrbitalMap::new(self.window_fd(), window_width * window_height * 4) } 174 | .expect("failed to map orbital window"), 175 | ) 176 | } else { 177 | Pixels::Buffer(vec![0; self.width as usize * self.height as usize]) 178 | }; 179 | Ok(BufferImpl { imp: self, pixels }) 180 | } 181 | } 182 | 183 | enum Pixels { 184 | Mapping(OrbitalMap), 185 | Buffer(Vec), 186 | } 187 | 188 | pub struct BufferImpl<'a, D, W> { 189 | imp: &'a mut OrbitalImpl, 190 | pixels: Pixels, 191 | } 192 | 193 | impl BufferInterface for BufferImpl<'_, D, W> { 194 | #[inline] 195 | fn pixels(&self) -> &[u32] { 196 | match &self.pixels { 197 | Pixels::Mapping(mapping) => unsafe { mapping.data() }, 198 | Pixels::Buffer(buffer) => buffer, 199 | } 200 | } 201 | 202 | #[inline] 203 | fn pixels_mut(&mut self) -> &mut [u32] { 204 | match &mut self.pixels { 205 | Pixels::Mapping(mapping) => unsafe { mapping.data_mut() }, 206 | Pixels::Buffer(buffer) => buffer, 207 | } 208 | } 209 | 210 | fn age(&self) -> u8 { 211 | match self.pixels { 212 | Pixels::Mapping(_) if self.imp.presented => 1, 213 | _ => 0, 214 | } 215 | } 216 | 217 | fn present(self) -> Result<(), SoftBufferError> { 218 | match self.pixels { 219 | Pixels::Mapping(mapping) => { 220 | drop(mapping); 221 | syscall::fsync(self.imp.window_fd()).expect("failed to sync orbital window"); 222 | self.imp.presented = true; 223 | } 224 | Pixels::Buffer(buffer) => { 225 | self.imp 226 | .set_buffer(&buffer, self.imp.width, self.imp.height); 227 | } 228 | } 229 | 230 | Ok(()) 231 | } 232 | 233 | fn present_with_damage(self, _damage: &[Rect]) -> Result<(), SoftBufferError> { 234 | self.present() 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /src/backends/wayland/buffer.rs: -------------------------------------------------------------------------------- 1 | use memmap2::MmapMut; 2 | use std::{ 3 | ffi::CStr, 4 | fs::File, 5 | os::unix::prelude::{AsFd, AsRawFd}, 6 | slice, 7 | sync::{ 8 | atomic::{AtomicBool, Ordering}, 9 | Arc, 10 | }, 11 | }; 12 | use wayland_client::{ 13 | protocol::{wl_buffer, wl_shm, wl_shm_pool, wl_surface}, 14 | Connection, Dispatch, QueueHandle, 15 | }; 16 | 17 | use super::State; 18 | 19 | #[cfg(any(target_os = "linux", target_os = "freebsd"))] 20 | fn create_memfile() -> File { 21 | use rustix::fs::{MemfdFlags, SealFlags}; 22 | 23 | let name = unsafe { CStr::from_bytes_with_nul_unchecked("softbuffer\0".as_bytes()) }; 24 | let fd = rustix::fs::memfd_create(name, MemfdFlags::CLOEXEC | MemfdFlags::ALLOW_SEALING) 25 | .expect("Failed to create memfd to store buffer."); 26 | rustix::fs::fcntl_add_seals(&fd, SealFlags::SHRINK | SealFlags::SEAL) 27 | .expect("Failed to seal memfd."); 28 | File::from(fd) 29 | } 30 | 31 | #[cfg(not(any(target_os = "linux", target_os = "freebsd")))] 32 | fn create_memfile() -> File { 33 | use rustix::{fs::Mode, io::Errno, shm::OFlags}; 34 | use std::iter; 35 | 36 | // Use a cached RNG to avoid hammering the thread local. 37 | let mut rng = fastrand::Rng::new(); 38 | 39 | for _ in 0..=4 { 40 | let mut name = String::from("softbuffer-"); 41 | name.extend(iter::repeat_with(|| rng.alphanumeric()).take(7)); 42 | name.push('\0'); 43 | 44 | let name = unsafe { CStr::from_bytes_with_nul_unchecked(name.as_bytes()) }; 45 | // `CLOEXEC` is implied with `shm_open` 46 | let fd = rustix::shm::open( 47 | name, 48 | OFlags::RDWR | OFlags::CREATE | OFlags::EXCL, 49 | Mode::RWXU, 50 | ); 51 | if !matches!(fd, Err(Errno::EXIST)) { 52 | let fd = fd.expect("Failed to create POSIX shm to store buffer."); 53 | let _ = rustix::shm::unlink(name); 54 | return File::from(fd); 55 | } 56 | } 57 | 58 | panic!("Failed to generate non-existent shm name") 59 | } 60 | 61 | // Round size to use for pool for given dimensions, rounding up to power of 2 62 | fn get_pool_size(width: i32, height: i32) -> i32 { 63 | ((width * height * 4) as u32).next_power_of_two() as i32 64 | } 65 | 66 | unsafe fn map_file(file: &File) -> MmapMut { 67 | unsafe { MmapMut::map_mut(file.as_raw_fd()).expect("Failed to map shared memory") } 68 | } 69 | 70 | pub(super) struct WaylandBuffer { 71 | qh: QueueHandle, 72 | tempfile: File, 73 | map: MmapMut, 74 | pool: wl_shm_pool::WlShmPool, 75 | pool_size: i32, 76 | buffer: wl_buffer::WlBuffer, 77 | width: i32, 78 | height: i32, 79 | released: Arc, 80 | pub age: u8, 81 | } 82 | 83 | impl WaylandBuffer { 84 | pub fn new(shm: &wl_shm::WlShm, width: i32, height: i32, qh: &QueueHandle) -> Self { 85 | // Calculate size to use for shm pool 86 | let pool_size = get_pool_size(width, height); 87 | 88 | // Create an `mmap` shared memory 89 | let tempfile = create_memfile(); 90 | let _ = tempfile.set_len(pool_size as u64); 91 | let map = unsafe { map_file(&tempfile) }; 92 | 93 | // Create wayland shm pool and buffer 94 | let pool = shm.create_pool(tempfile.as_fd(), pool_size, qh, ()); 95 | let released = Arc::new(AtomicBool::new(true)); 96 | let buffer = pool.create_buffer( 97 | 0, 98 | width, 99 | height, 100 | width * 4, 101 | wl_shm::Format::Xrgb8888, 102 | qh, 103 | released.clone(), 104 | ); 105 | 106 | Self { 107 | qh: qh.clone(), 108 | map, 109 | tempfile, 110 | pool, 111 | pool_size, 112 | buffer, 113 | width, 114 | height, 115 | released, 116 | age: 0, 117 | } 118 | } 119 | 120 | pub fn resize(&mut self, width: i32, height: i32) { 121 | // If size is the same, there's nothing to do 122 | if self.width != width || self.height != height { 123 | // Destroy old buffer 124 | self.buffer.destroy(); 125 | 126 | // Grow pool, if needed 127 | let size = ((width * height * 4) as u32).next_power_of_two() as i32; 128 | if size > self.pool_size { 129 | let _ = self.tempfile.set_len(size as u64); 130 | self.pool.resize(size); 131 | self.pool_size = size; 132 | self.map = unsafe { map_file(&self.tempfile) }; 133 | } 134 | 135 | // Create buffer with correct size 136 | self.buffer = self.pool.create_buffer( 137 | 0, 138 | width, 139 | height, 140 | width * 4, 141 | wl_shm::Format::Xrgb8888, 142 | &self.qh, 143 | self.released.clone(), 144 | ); 145 | self.width = width; 146 | self.height = height; 147 | } 148 | } 149 | 150 | pub fn attach(&self, surface: &wl_surface::WlSurface) { 151 | self.released.store(false, Ordering::SeqCst); 152 | surface.attach(Some(&self.buffer), 0, 0); 153 | } 154 | 155 | pub fn released(&self) -> bool { 156 | self.released.load(Ordering::SeqCst) 157 | } 158 | 159 | fn len(&self) -> usize { 160 | self.width as usize * self.height as usize 161 | } 162 | 163 | pub unsafe fn mapped_mut(&mut self) -> &mut [u32] { 164 | unsafe { slice::from_raw_parts_mut(self.map.as_mut_ptr() as *mut u32, self.len()) } 165 | } 166 | } 167 | 168 | impl Drop for WaylandBuffer { 169 | fn drop(&mut self) { 170 | self.buffer.destroy(); 171 | self.pool.destroy(); 172 | } 173 | } 174 | 175 | impl Dispatch for State { 176 | fn event( 177 | _: &mut State, 178 | _: &wl_shm_pool::WlShmPool, 179 | _: wl_shm_pool::Event, 180 | _: &(), 181 | _: &Connection, 182 | _: &QueueHandle, 183 | ) { 184 | } 185 | } 186 | 187 | impl Dispatch> for State { 188 | fn event( 189 | _: &mut State, 190 | _: &wl_buffer::WlBuffer, 191 | event: wl_buffer::Event, 192 | released: &Arc, 193 | _: &Connection, 194 | _: &QueueHandle, 195 | ) { 196 | if let wl_buffer::Event::Release = event { 197 | released.store(true, Ordering::SeqCst); 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/backends/wayland/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | backend_interface::*, 3 | error::{InitError, SwResultExt}, 4 | util, Rect, SoftBufferError, 5 | }; 6 | use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle}; 7 | use std::{ 8 | num::{NonZeroI32, NonZeroU32}, 9 | sync::{Arc, Mutex}, 10 | }; 11 | use wayland_client::{ 12 | backend::{Backend, ObjectId}, 13 | globals::{registry_queue_init, GlobalListContents}, 14 | protocol::{wl_registry, wl_shm, wl_surface}, 15 | Connection, Dispatch, EventQueue, Proxy, QueueHandle, 16 | }; 17 | 18 | mod buffer; 19 | use buffer::WaylandBuffer; 20 | 21 | struct State; 22 | 23 | pub struct WaylandDisplayImpl { 24 | conn: Option, 25 | event_queue: Mutex>, 26 | qh: QueueHandle, 27 | shm: wl_shm::WlShm, 28 | 29 | /// The object that owns the display handle. 30 | /// 31 | /// This has to be dropped *after* the `conn` field, because the `conn` field implicitly borrows 32 | /// this. 33 | _display: D, 34 | } 35 | 36 | impl WaylandDisplayImpl { 37 | fn conn(&self) -> &Connection { 38 | self.conn.as_ref().unwrap() 39 | } 40 | } 41 | 42 | impl ContextInterface for Arc> { 43 | fn new(display: D) -> Result> 44 | where 45 | D: Sized, 46 | { 47 | let raw = display.display_handle()?.as_raw(); 48 | let RawDisplayHandle::Wayland(w) = raw else { 49 | return Err(InitError::Unsupported(display)); 50 | }; 51 | 52 | let backend = unsafe { Backend::from_foreign_display(w.display.as_ptr().cast()) }; 53 | let conn = Connection::from_backend(backend); 54 | let (globals, event_queue) = 55 | registry_queue_init(&conn).swbuf_err("Failed to make round trip to server")?; 56 | let qh = event_queue.handle(); 57 | let shm: wl_shm::WlShm = globals 58 | .bind(&qh, 1..=1, ()) 59 | .swbuf_err("Failed to instantiate Wayland Shm")?; 60 | Ok(Arc::new(WaylandDisplayImpl { 61 | conn: Some(conn), 62 | event_queue: Mutex::new(event_queue), 63 | qh, 64 | shm, 65 | _display: display, 66 | })) 67 | } 68 | } 69 | 70 | impl Drop for WaylandDisplayImpl { 71 | fn drop(&mut self) { 72 | // Make sure the connection is dropped first. 73 | self.conn = None; 74 | } 75 | } 76 | 77 | pub struct WaylandImpl { 78 | display: Arc>, 79 | surface: Option, 80 | buffers: Option<(WaylandBuffer, WaylandBuffer)>, 81 | size: Option<(NonZeroI32, NonZeroI32)>, 82 | 83 | /// The pointer to the window object. 84 | /// 85 | /// This has to be dropped *after* the `surface` field, because the `surface` field implicitly 86 | /// borrows this. 87 | window_handle: W, 88 | } 89 | 90 | impl WaylandImpl { 91 | fn surface(&self) -> &wl_surface::WlSurface { 92 | self.surface.as_ref().unwrap() 93 | } 94 | 95 | fn present_with_damage(&mut self, damage: &[Rect]) -> Result<(), SoftBufferError> { 96 | let _ = self 97 | .display 98 | .event_queue 99 | .lock() 100 | .unwrap_or_else(|x| x.into_inner()) 101 | .dispatch_pending(&mut State); 102 | 103 | if let Some((front, back)) = &mut self.buffers { 104 | // Swap front and back buffer 105 | std::mem::swap(front, back); 106 | 107 | front.age = 1; 108 | if back.age != 0 { 109 | back.age += 1; 110 | } 111 | 112 | front.attach(self.surface.as_ref().unwrap()); 113 | 114 | // Like Mesa's EGL/WSI implementation, we damage the whole buffer with `i32::MAX` if 115 | // the compositor doesn't support `damage_buffer`. 116 | // https://bugs.freedesktop.org/show_bug.cgi?id=78190 117 | if self.surface().version() < 4 { 118 | self.surface().damage(0, 0, i32::MAX, i32::MAX); 119 | } else { 120 | for rect in damage { 121 | // Introduced in version 4, it is an error to use this request in version 3 or lower. 122 | let (x, y, width, height) = (|| { 123 | Some(( 124 | i32::try_from(rect.x).ok()?, 125 | i32::try_from(rect.y).ok()?, 126 | i32::try_from(rect.width.get()).ok()?, 127 | i32::try_from(rect.height.get()).ok()?, 128 | )) 129 | })() 130 | .ok_or(SoftBufferError::DamageOutOfRange { rect: *rect })?; 131 | self.surface().damage_buffer(x, y, width, height); 132 | } 133 | } 134 | 135 | self.surface().commit(); 136 | } 137 | 138 | let _ = self 139 | .display 140 | .event_queue 141 | .lock() 142 | .unwrap_or_else(|x| x.into_inner()) 143 | .flush(); 144 | 145 | Ok(()) 146 | } 147 | } 148 | 149 | impl SurfaceInterface 150 | for WaylandImpl 151 | { 152 | type Context = Arc>; 153 | type Buffer<'a> 154 | = BufferImpl<'a, D, W> 155 | where 156 | Self: 'a; 157 | 158 | fn new(window: W, display: &Arc>) -> Result> { 159 | // Get the raw Wayland window. 160 | let raw = window.window_handle()?.as_raw(); 161 | let RawWindowHandle::Wayland(w) = raw else { 162 | return Err(InitError::Unsupported(window)); 163 | }; 164 | 165 | let surface_id = unsafe { 166 | ObjectId::from_ptr( 167 | wl_surface::WlSurface::interface(), 168 | w.surface.as_ptr().cast(), 169 | ) 170 | } 171 | .swbuf_err("Failed to create proxy for surface ID.")?; 172 | let surface = wl_surface::WlSurface::from_id(display.conn(), surface_id) 173 | .swbuf_err("Failed to create proxy for surface ID.")?; 174 | Ok(Self { 175 | display: display.clone(), 176 | surface: Some(surface), 177 | buffers: Default::default(), 178 | size: None, 179 | window_handle: window, 180 | }) 181 | } 182 | 183 | #[inline] 184 | fn window(&self) -> &W { 185 | &self.window_handle 186 | } 187 | 188 | fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { 189 | self.size = Some( 190 | (|| { 191 | let width = NonZeroI32::try_from(width).ok()?; 192 | let height = NonZeroI32::try_from(height).ok()?; 193 | Some((width, height)) 194 | })() 195 | .ok_or(SoftBufferError::SizeOutOfRange { width, height })?, 196 | ); 197 | Ok(()) 198 | } 199 | 200 | fn buffer_mut(&mut self) -> Result, SoftBufferError> { 201 | let (width, height) = self 202 | .size 203 | .expect("Must set size of surface before calling `buffer_mut()`"); 204 | 205 | if let Some((_front, back)) = &mut self.buffers { 206 | // Block if back buffer not released yet 207 | if !back.released() { 208 | let mut event_queue = self 209 | .display 210 | .event_queue 211 | .lock() 212 | .unwrap_or_else(|x| x.into_inner()); 213 | while !back.released() { 214 | event_queue.blocking_dispatch(&mut State).map_err(|err| { 215 | SoftBufferError::PlatformError( 216 | Some("Wayland dispatch failure".to_string()), 217 | Some(Box::new(err)), 218 | ) 219 | })?; 220 | } 221 | } 222 | 223 | // Resize, if buffer isn't large enough 224 | back.resize(width.get(), height.get()); 225 | } else { 226 | // Allocate front and back buffer 227 | self.buffers = Some(( 228 | WaylandBuffer::new( 229 | &self.display.shm, 230 | width.get(), 231 | height.get(), 232 | &self.display.qh, 233 | ), 234 | WaylandBuffer::new( 235 | &self.display.shm, 236 | width.get(), 237 | height.get(), 238 | &self.display.qh, 239 | ), 240 | )); 241 | }; 242 | 243 | let age = self.buffers.as_mut().unwrap().1.age; 244 | Ok(BufferImpl { 245 | stack: util::BorrowStack::new(self, |buffer| { 246 | Ok(unsafe { buffer.buffers.as_mut().unwrap().1.mapped_mut() }) 247 | })?, 248 | age, 249 | }) 250 | } 251 | } 252 | 253 | impl Drop for WaylandImpl { 254 | fn drop(&mut self) { 255 | // Make sure the surface is dropped first. 256 | self.surface = None; 257 | } 258 | } 259 | 260 | pub struct BufferImpl<'a, D: ?Sized, W> { 261 | stack: util::BorrowStack<'a, WaylandImpl, [u32]>, 262 | age: u8, 263 | } 264 | 265 | impl BufferInterface for BufferImpl<'_, D, W> { 266 | #[inline] 267 | fn pixels(&self) -> &[u32] { 268 | self.stack.member() 269 | } 270 | 271 | #[inline] 272 | fn pixels_mut(&mut self) -> &mut [u32] { 273 | self.stack.member_mut() 274 | } 275 | 276 | fn age(&self) -> u8 { 277 | self.age 278 | } 279 | 280 | fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError> { 281 | self.stack.into_container().present_with_damage(damage) 282 | } 283 | 284 | fn present(self) -> Result<(), SoftBufferError> { 285 | let imp = self.stack.into_container(); 286 | let (width, height) = imp 287 | .size 288 | .expect("Must set size of surface before calling `present()`"); 289 | imp.present_with_damage(&[Rect { 290 | x: 0, 291 | y: 0, 292 | // We know width/height will be non-negative 293 | width: width.try_into().unwrap(), 294 | height: height.try_into().unwrap(), 295 | }]) 296 | } 297 | } 298 | 299 | impl Dispatch for State { 300 | fn event( 301 | _: &mut State, 302 | _: &wl_registry::WlRegistry, 303 | _: wl_registry::Event, 304 | _: &GlobalListContents, 305 | _: &Connection, 306 | _: &QueueHandle, 307 | ) { 308 | // Ignore globals added after initialization 309 | } 310 | } 311 | 312 | impl Dispatch for State { 313 | fn event( 314 | _: &mut State, 315 | _: &wl_shm::WlShm, 316 | _: wl_shm::Event, 317 | _: &(), 318 | _: &Connection, 319 | _: &QueueHandle, 320 | ) { 321 | } 322 | } 323 | -------------------------------------------------------------------------------- /src/backends/web.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of software buffering for web targets. 2 | 3 | #![allow(clippy::uninlined_format_args)] 4 | 5 | use js_sys::Object; 6 | use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle}; 7 | use wasm_bindgen::{JsCast, JsValue}; 8 | use web_sys::ImageData; 9 | use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement}; 10 | use web_sys::{OffscreenCanvas, OffscreenCanvasRenderingContext2d}; 11 | 12 | use crate::backend_interface::*; 13 | use crate::error::{InitError, SwResultExt}; 14 | use crate::{util, NoDisplayHandle, NoWindowHandle, Rect, SoftBufferError}; 15 | use std::marker::PhantomData; 16 | use std::num::NonZeroU32; 17 | 18 | /// Display implementation for the web platform. 19 | /// 20 | /// This just caches the document to prevent having to query it every time. 21 | pub struct WebDisplayImpl { 22 | document: web_sys::Document, 23 | _display: D, 24 | } 25 | 26 | impl ContextInterface for WebDisplayImpl { 27 | fn new(display: D) -> Result> { 28 | let raw = display.display_handle()?.as_raw(); 29 | let RawDisplayHandle::Web(..) = raw else { 30 | return Err(InitError::Unsupported(display)); 31 | }; 32 | 33 | let document = web_sys::window() 34 | .swbuf_err("`Window` is not present in this runtime")? 35 | .document() 36 | .swbuf_err("`Document` is not present in this runtime")?; 37 | 38 | Ok(Self { 39 | document, 40 | _display: display, 41 | }) 42 | } 43 | } 44 | 45 | pub struct WebImpl { 46 | /// The handle and context to the canvas that we're drawing to. 47 | canvas: Canvas, 48 | 49 | /// The buffer that we're drawing to. 50 | buffer: Vec, 51 | 52 | /// Buffer has been presented. 53 | buffer_presented: bool, 54 | 55 | /// The current canvas width/height. 56 | size: Option<(NonZeroU32, NonZeroU32)>, 57 | 58 | /// The underlying window handle. 59 | window_handle: W, 60 | 61 | /// The underlying display handle. 62 | _display: PhantomData, 63 | } 64 | 65 | /// Holding canvas and context for [`HtmlCanvasElement`] or [`OffscreenCanvas`], 66 | /// since they have different types. 67 | enum Canvas { 68 | Canvas { 69 | canvas: HtmlCanvasElement, 70 | ctx: CanvasRenderingContext2d, 71 | }, 72 | OffscreenCanvas { 73 | canvas: OffscreenCanvas, 74 | ctx: OffscreenCanvasRenderingContext2d, 75 | }, 76 | } 77 | 78 | impl WebImpl { 79 | fn from_canvas(canvas: HtmlCanvasElement, window: W) -> Result { 80 | let ctx = Self::resolve_ctx(canvas.get_context("2d").ok(), "CanvasRenderingContext2d")?; 81 | 82 | Ok(Self { 83 | canvas: Canvas::Canvas { canvas, ctx }, 84 | buffer: Vec::new(), 85 | buffer_presented: false, 86 | size: None, 87 | window_handle: window, 88 | _display: PhantomData, 89 | }) 90 | } 91 | 92 | fn from_offscreen_canvas(canvas: OffscreenCanvas, window: W) -> Result { 93 | let ctx = Self::resolve_ctx( 94 | canvas.get_context("2d").ok(), 95 | "OffscreenCanvasRenderingContext2d", 96 | )?; 97 | 98 | Ok(Self { 99 | canvas: Canvas::OffscreenCanvas { canvas, ctx }, 100 | buffer: Vec::new(), 101 | buffer_presented: false, 102 | size: None, 103 | window_handle: window, 104 | _display: PhantomData, 105 | }) 106 | } 107 | 108 | fn resolve_ctx( 109 | result: Option>, 110 | name: &str, 111 | ) -> Result { 112 | let ctx = result 113 | .swbuf_err("Canvas already controlled using `OffscreenCanvas`")? 114 | .swbuf_err(format!( 115 | "A canvas context other than `{name}` was already created" 116 | ))? 117 | .dyn_into() 118 | .unwrap_or_else(|_| panic!("`getContext(\"2d\") didn't return a `{name}`")); 119 | 120 | Ok(ctx) 121 | } 122 | 123 | fn present_with_damage(&mut self, damage: &[Rect]) -> Result<(), SoftBufferError> { 124 | let (buffer_width, _buffer_height) = self 125 | .size 126 | .expect("Must set size of surface before calling `present_with_damage()`"); 127 | 128 | let union_damage = if let Some(rect) = util::union_damage(damage) { 129 | rect 130 | } else { 131 | return Ok(()); 132 | }; 133 | 134 | // Create a bitmap from the buffer. 135 | let bitmap: Vec<_> = self 136 | .buffer 137 | .chunks_exact(buffer_width.get() as usize) 138 | .skip(union_damage.y as usize) 139 | .take(union_damage.height.get() as usize) 140 | .flat_map(|row| { 141 | row.iter() 142 | .skip(union_damage.x as usize) 143 | .take(union_damage.width.get() as usize) 144 | }) 145 | .copied() 146 | .flat_map(|pixel| [(pixel >> 16) as u8, (pixel >> 8) as u8, pixel as u8, 255]) 147 | .collect(); 148 | 149 | debug_assert_eq!( 150 | bitmap.len() as u32, 151 | union_damage.width.get() * union_damage.height.get() * 4 152 | ); 153 | 154 | #[cfg(target_feature = "atomics")] 155 | #[allow(non_local_definitions)] 156 | let result = { 157 | // When using atomics, the underlying memory becomes `SharedArrayBuffer`, 158 | // which can't be shared with `ImageData`. 159 | use js_sys::{Uint8Array, Uint8ClampedArray}; 160 | use wasm_bindgen::prelude::wasm_bindgen; 161 | 162 | #[wasm_bindgen] 163 | extern "C" { 164 | #[wasm_bindgen(js_name = ImageData)] 165 | type ImageDataExt; 166 | 167 | #[wasm_bindgen(catch, constructor, js_class = ImageData)] 168 | fn new(array: Uint8ClampedArray, sw: u32) -> Result; 169 | } 170 | 171 | let array = Uint8Array::new_with_length(bitmap.len() as u32); 172 | array.copy_from(&bitmap); 173 | let array = Uint8ClampedArray::new(&array); 174 | ImageDataExt::new(array, union_damage.width.get()) 175 | .map(JsValue::from) 176 | .map(ImageData::unchecked_from_js) 177 | }; 178 | #[cfg(not(target_feature = "atomics"))] 179 | let result = ImageData::new_with_u8_clamped_array( 180 | wasm_bindgen::Clamped(&bitmap), 181 | union_damage.width.get(), 182 | ); 183 | // This should only throw an error if the buffer we pass's size is incorrect. 184 | let image_data = result.unwrap(); 185 | 186 | for rect in damage { 187 | // This can only throw an error if `data` is detached, which is impossible. 188 | self.canvas 189 | .put_image_data( 190 | &image_data, 191 | union_damage.x.into(), 192 | union_damage.y.into(), 193 | (rect.x - union_damage.x).into(), 194 | (rect.y - union_damage.y).into(), 195 | rect.width.get().into(), 196 | rect.height.get().into(), 197 | ) 198 | .unwrap(); 199 | } 200 | 201 | self.buffer_presented = true; 202 | 203 | Ok(()) 204 | } 205 | } 206 | 207 | impl SurfaceInterface for WebImpl { 208 | type Context = WebDisplayImpl; 209 | type Buffer<'a> 210 | = BufferImpl<'a, D, W> 211 | where 212 | Self: 'a; 213 | 214 | fn new(window: W, display: &WebDisplayImpl) -> Result> { 215 | let raw = window.window_handle()?.as_raw(); 216 | let canvas: HtmlCanvasElement = match raw { 217 | RawWindowHandle::Web(handle) => { 218 | display 219 | .document 220 | .query_selector(&format!("canvas[data-raw-handle=\"{}\"]", handle.id)) 221 | // `querySelector` only throws an error if the selector is invalid. 222 | .unwrap() 223 | .swbuf_err("No canvas found with the given id")? 224 | // We already made sure this was a canvas in `querySelector`. 225 | .unchecked_into() 226 | } 227 | RawWindowHandle::WebCanvas(handle) => { 228 | let value: &JsValue = unsafe { handle.obj.cast().as_ref() }; 229 | value.clone().unchecked_into() 230 | } 231 | RawWindowHandle::WebOffscreenCanvas(handle) => { 232 | let value: &JsValue = unsafe { handle.obj.cast().as_ref() }; 233 | let canvas: OffscreenCanvas = value.clone().unchecked_into(); 234 | 235 | return Self::from_offscreen_canvas(canvas, window).map_err(InitError::Failure); 236 | } 237 | _ => return Err(InitError::Unsupported(window)), 238 | }; 239 | 240 | Self::from_canvas(canvas, window).map_err(InitError::Failure) 241 | } 242 | 243 | /// Get the inner window handle. 244 | #[inline] 245 | fn window(&self) -> &W { 246 | &self.window_handle 247 | } 248 | 249 | /// De-duplicates the error handling between `HtmlCanvasElement` and `OffscreenCanvas`. 250 | /// Resize the canvas to the given dimensions. 251 | fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { 252 | if self.size != Some((width, height)) { 253 | self.buffer_presented = false; 254 | self.buffer.resize(total_len(width.get(), height.get()), 0); 255 | self.canvas.set_width(width.get()); 256 | self.canvas.set_height(height.get()); 257 | self.size = Some((width, height)); 258 | } 259 | 260 | Ok(()) 261 | } 262 | 263 | fn buffer_mut(&mut self) -> Result, SoftBufferError> { 264 | Ok(BufferImpl { imp: self }) 265 | } 266 | 267 | fn fetch(&mut self) -> Result, SoftBufferError> { 268 | let (width, height) = self 269 | .size 270 | .expect("Must set size of surface before calling `fetch()`"); 271 | 272 | let image_data = self 273 | .canvas 274 | .get_image_data(0., 0., width.get().into(), height.get().into()) 275 | .ok() 276 | // TODO: Can also error if width or height are 0. 277 | .swbuf_err("`Canvas` contains pixels from a different origin")?; 278 | 279 | Ok(image_data 280 | .data() 281 | .0 282 | .chunks_exact(4) 283 | .map(|chunk| u32::from_be_bytes([0, chunk[0], chunk[1], chunk[2]])) 284 | .collect()) 285 | } 286 | } 287 | 288 | /// Extension methods for the Wasm target on [`Surface`](crate::Surface). 289 | pub trait SurfaceExtWeb: Sized { 290 | /// Creates a new instance of this struct, using the provided [`HtmlCanvasElement`]. 291 | /// 292 | /// # Errors 293 | /// - If the canvas was already controlled by an `OffscreenCanvas`. 294 | /// - If a another context then "2d" was already created for this canvas. 295 | fn from_canvas(canvas: HtmlCanvasElement) -> Result; 296 | 297 | /// Creates a new instance of this struct, using the provided [`OffscreenCanvas`]. 298 | /// 299 | /// # Errors 300 | /// If a another context then "2d" was already created for this canvas. 301 | fn from_offscreen_canvas(offscreen_canvas: OffscreenCanvas) -> Result; 302 | } 303 | 304 | impl SurfaceExtWeb for crate::Surface { 305 | fn from_canvas(canvas: HtmlCanvasElement) -> Result { 306 | let imple = crate::SurfaceDispatch::Web(WebImpl::from_canvas(canvas, NoWindowHandle(()))?); 307 | 308 | Ok(Self { 309 | surface_impl: Box::new(imple), 310 | _marker: PhantomData, 311 | }) 312 | } 313 | 314 | fn from_offscreen_canvas(offscreen_canvas: OffscreenCanvas) -> Result { 315 | let imple = crate::SurfaceDispatch::Web(WebImpl::from_offscreen_canvas( 316 | offscreen_canvas, 317 | NoWindowHandle(()), 318 | )?); 319 | 320 | Ok(Self { 321 | surface_impl: Box::new(imple), 322 | _marker: PhantomData, 323 | }) 324 | } 325 | } 326 | 327 | impl Canvas { 328 | fn set_width(&self, width: u32) { 329 | match self { 330 | Self::Canvas { canvas, .. } => canvas.set_width(width), 331 | Self::OffscreenCanvas { canvas, .. } => canvas.set_width(width), 332 | } 333 | } 334 | 335 | fn set_height(&self, height: u32) { 336 | match self { 337 | Self::Canvas { canvas, .. } => canvas.set_height(height), 338 | Self::OffscreenCanvas { canvas, .. } => canvas.set_height(height), 339 | } 340 | } 341 | 342 | fn get_image_data(&self, sx: f64, sy: f64, sw: f64, sh: f64) -> Result { 343 | match self { 344 | Canvas::Canvas { ctx, .. } => ctx.get_image_data(sx, sy, sw, sh), 345 | Canvas::OffscreenCanvas { ctx, .. } => ctx.get_image_data(sx, sy, sw, sh), 346 | } 347 | } 348 | 349 | // NOTE: suppress the lint because we mirror `CanvasRenderingContext2D.putImageData()`, and 350 | // this is just an internal API used by this module only, so it's not too relevant. 351 | #[allow(clippy::too_many_arguments)] 352 | fn put_image_data( 353 | &self, 354 | imagedata: &ImageData, 355 | dx: f64, 356 | dy: f64, 357 | dirty_x: f64, 358 | dirty_y: f64, 359 | width: f64, 360 | height: f64, 361 | ) -> Result<(), JsValue> { 362 | match self { 363 | Self::Canvas { ctx, .. } => ctx 364 | .put_image_data_with_dirty_x_and_dirty_y_and_dirty_width_and_dirty_height( 365 | imagedata, dx, dy, dirty_x, dirty_y, width, height, 366 | ), 367 | Self::OffscreenCanvas { ctx, .. } => ctx 368 | .put_image_data_with_dirty_x_and_dirty_y_and_dirty_width_and_dirty_height( 369 | imagedata, dx, dy, dirty_x, dirty_y, width, height, 370 | ), 371 | } 372 | } 373 | } 374 | 375 | pub struct BufferImpl<'a, D, W> { 376 | imp: &'a mut WebImpl, 377 | } 378 | 379 | impl BufferInterface for BufferImpl<'_, D, W> { 380 | fn pixels(&self) -> &[u32] { 381 | &self.imp.buffer 382 | } 383 | 384 | fn pixels_mut(&mut self) -> &mut [u32] { 385 | &mut self.imp.buffer 386 | } 387 | 388 | fn age(&self) -> u8 { 389 | if self.imp.buffer_presented { 390 | 1 391 | } else { 392 | 0 393 | } 394 | } 395 | 396 | /// Push the buffer to the canvas. 397 | fn present(self) -> Result<(), SoftBufferError> { 398 | let (width, height) = self 399 | .imp 400 | .size 401 | .expect("Must set size of surface before calling `present()`"); 402 | self.imp.present_with_damage(&[Rect { 403 | x: 0, 404 | y: 0, 405 | width, 406 | height, 407 | }]) 408 | } 409 | 410 | fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError> { 411 | self.imp.present_with_damage(damage) 412 | } 413 | } 414 | 415 | #[inline(always)] 416 | fn total_len(width: u32, height: u32) -> usize { 417 | // Convert width and height to `usize`, then multiply. 418 | width 419 | .try_into() 420 | .ok() 421 | .and_then(|w: usize| height.try_into().ok().and_then(|h| w.checked_mul(h))) 422 | .unwrap_or_else(|| { 423 | panic!( 424 | "Overflow when calculating total length of buffer: {}x{}", 425 | width, height 426 | ); 427 | }) 428 | } 429 | -------------------------------------------------------------------------------- /src/backends/win32.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of software buffering for Windows. 2 | //! 3 | //! This module converts the input buffer into a bitmap and then stretches it to the window. 4 | 5 | use crate::backend_interface::*; 6 | use crate::{Rect, SoftBufferError}; 7 | use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawWindowHandle}; 8 | 9 | use std::io; 10 | use std::marker::PhantomData; 11 | use std::mem; 12 | use std::num::{NonZeroI32, NonZeroU32}; 13 | use std::ptr::{self, NonNull}; 14 | use std::slice; 15 | use std::sync::{mpsc, Mutex, OnceLock}; 16 | use std::thread; 17 | 18 | use windows_sys::Win32::Foundation::HWND; 19 | use windows_sys::Win32::Graphics::Gdi; 20 | 21 | const ZERO_QUAD: Gdi::RGBQUAD = Gdi::RGBQUAD { 22 | rgbBlue: 0, 23 | rgbGreen: 0, 24 | rgbRed: 0, 25 | rgbReserved: 0, 26 | }; 27 | 28 | struct Buffer { 29 | dc: Gdi::HDC, 30 | bitmap: Gdi::HBITMAP, 31 | pixels: NonNull, 32 | width: NonZeroI32, 33 | height: NonZeroI32, 34 | presented: bool, 35 | } 36 | 37 | unsafe impl Send for Buffer {} 38 | 39 | impl Drop for Buffer { 40 | fn drop(&mut self) { 41 | unsafe { 42 | Gdi::DeleteObject(self.bitmap); 43 | } 44 | 45 | Allocator::get().deallocate(self.dc); 46 | } 47 | } 48 | 49 | impl Buffer { 50 | fn new(window_dc: Gdi::HDC, width: NonZeroI32, height: NonZeroI32) -> Self { 51 | let dc = Allocator::get().allocate(window_dc); 52 | assert!(!dc.is_null()); 53 | 54 | // Create a new bitmap info struct. 55 | let bitmap_info = BitmapInfo { 56 | bmi_header: Gdi::BITMAPINFOHEADER { 57 | biSize: mem::size_of::() as u32, 58 | biWidth: width.get(), 59 | biHeight: -height.get(), 60 | biPlanes: 1, 61 | biBitCount: 32, 62 | biCompression: Gdi::BI_BITFIELDS, 63 | biSizeImage: 0, 64 | biXPelsPerMeter: 0, 65 | biYPelsPerMeter: 0, 66 | biClrUsed: 0, 67 | biClrImportant: 0, 68 | }, 69 | bmi_colors: [ 70 | Gdi::RGBQUAD { 71 | rgbRed: 0xff, 72 | ..ZERO_QUAD 73 | }, 74 | Gdi::RGBQUAD { 75 | rgbGreen: 0xff, 76 | ..ZERO_QUAD 77 | }, 78 | Gdi::RGBQUAD { 79 | rgbBlue: 0xff, 80 | ..ZERO_QUAD 81 | }, 82 | ], 83 | }; 84 | 85 | // XXX alignment? 86 | // XXX better to use CreateFileMapping, and pass hSection? 87 | // XXX test return value? 88 | let mut pixels: *mut u32 = ptr::null_mut(); 89 | let bitmap = unsafe { 90 | Gdi::CreateDIBSection( 91 | dc, 92 | &bitmap_info as *const BitmapInfo as *const _, 93 | Gdi::DIB_RGB_COLORS, 94 | &mut pixels as *mut *mut u32 as _, 95 | ptr::null_mut(), 96 | 0, 97 | ) 98 | }; 99 | assert!(!bitmap.is_null()); 100 | let pixels = NonNull::new(pixels).unwrap(); 101 | 102 | unsafe { 103 | Gdi::SelectObject(dc, bitmap); 104 | } 105 | 106 | Self { 107 | dc, 108 | bitmap, 109 | width, 110 | height, 111 | pixels, 112 | presented: false, 113 | } 114 | } 115 | 116 | #[inline] 117 | fn pixels(&self) -> &[u32] { 118 | unsafe { 119 | slice::from_raw_parts( 120 | self.pixels.as_ptr(), 121 | i32::from(self.width) as usize * i32::from(self.height) as usize, 122 | ) 123 | } 124 | } 125 | 126 | #[inline] 127 | fn pixels_mut(&mut self) -> &mut [u32] { 128 | unsafe { 129 | slice::from_raw_parts_mut( 130 | self.pixels.as_ptr(), 131 | i32::from(self.width) as usize * i32::from(self.height) as usize, 132 | ) 133 | } 134 | } 135 | } 136 | 137 | /// The handle to a window for software buffering. 138 | pub struct Win32Impl { 139 | /// The window handle. 140 | window: OnlyUsedFromOrigin, 141 | 142 | /// The device context for the window. 143 | dc: OnlyUsedFromOrigin, 144 | 145 | /// The buffer used to hold the image. 146 | buffer: Option, 147 | 148 | /// The handle for the window. 149 | /// 150 | /// This should be kept alive in order to keep `window` valid. 151 | handle: W, 152 | 153 | /// The display handle. 154 | /// 155 | /// We don't use this, but other code might. 156 | _display: PhantomData, 157 | } 158 | 159 | impl Drop for Win32Impl { 160 | fn drop(&mut self) { 161 | // Release our resources. 162 | Allocator::get().release(self.window.0, self.dc.0); 163 | } 164 | } 165 | 166 | /// The Win32-compatible bitmap information. 167 | #[repr(C)] 168 | struct BitmapInfo { 169 | bmi_header: Gdi::BITMAPINFOHEADER, 170 | bmi_colors: [Gdi::RGBQUAD; 3], 171 | } 172 | 173 | impl Win32Impl { 174 | fn present_with_damage(&mut self, damage: &[Rect]) -> Result<(), SoftBufferError> { 175 | let buffer = self.buffer.as_mut().unwrap(); 176 | unsafe { 177 | for rect in damage.iter().copied() { 178 | let (x, y, width, height) = (|| { 179 | Some(( 180 | i32::try_from(rect.x).ok()?, 181 | i32::try_from(rect.y).ok()?, 182 | i32::try_from(rect.width.get()).ok()?, 183 | i32::try_from(rect.height.get()).ok()?, 184 | )) 185 | })() 186 | .ok_or(SoftBufferError::DamageOutOfRange { rect })?; 187 | Gdi::BitBlt( 188 | self.dc.0, 189 | x, 190 | y, 191 | width, 192 | height, 193 | buffer.dc, 194 | x, 195 | y, 196 | Gdi::SRCCOPY, 197 | ); 198 | } 199 | 200 | // Validate the window. 201 | Gdi::ValidateRect(self.window.0, ptr::null_mut()); 202 | } 203 | buffer.presented = true; 204 | 205 | Ok(()) 206 | } 207 | } 208 | 209 | impl SurfaceInterface for Win32Impl { 210 | type Context = D; 211 | type Buffer<'a> 212 | = BufferImpl<'a, D, W> 213 | where 214 | Self: 'a; 215 | 216 | /// Create a new `Win32Impl` from a `Win32WindowHandle`. 217 | fn new(window: W, _display: &D) -> Result> { 218 | let raw = window.window_handle()?.as_raw(); 219 | let RawWindowHandle::Win32(handle) = raw else { 220 | return Err(crate::InitError::Unsupported(window)); 221 | }; 222 | 223 | // Get the handle to the device context. 224 | // SAFETY: We have confirmed that the window handle is valid. 225 | let hwnd = handle.hwnd.get() as HWND; 226 | let dc = Allocator::get().get_dc(hwnd); 227 | 228 | // GetDC returns null if there is a platform error. 229 | if dc.is_null() { 230 | return Err(SoftBufferError::PlatformError( 231 | Some("Device Context is null".into()), 232 | Some(Box::new(io::Error::last_os_error())), 233 | ) 234 | .into()); 235 | } 236 | 237 | Ok(Self { 238 | dc: dc.into(), 239 | window: hwnd.into(), 240 | buffer: None, 241 | handle: window, 242 | _display: PhantomData, 243 | }) 244 | } 245 | 246 | #[inline] 247 | fn window(&self) -> &W { 248 | &self.handle 249 | } 250 | 251 | fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { 252 | let (width, height) = (|| { 253 | let width = NonZeroI32::try_from(width).ok()?; 254 | let height = NonZeroI32::try_from(height).ok()?; 255 | Some((width, height)) 256 | })() 257 | .ok_or(SoftBufferError::SizeOutOfRange { width, height })?; 258 | 259 | if let Some(buffer) = self.buffer.as_ref() { 260 | if buffer.width == width && buffer.height == height { 261 | return Ok(()); 262 | } 263 | } 264 | 265 | self.buffer = Some(Buffer::new(self.dc.0, width, height)); 266 | 267 | Ok(()) 268 | } 269 | 270 | fn buffer_mut(&mut self) -> Result, SoftBufferError> { 271 | if self.buffer.is_none() { 272 | panic!("Must set size of surface before calling `buffer_mut()`"); 273 | } 274 | 275 | Ok(BufferImpl(self)) 276 | } 277 | 278 | /// Fetch the buffer from the window. 279 | fn fetch(&mut self) -> Result, SoftBufferError> { 280 | Err(SoftBufferError::Unimplemented) 281 | } 282 | } 283 | 284 | pub struct BufferImpl<'a, D, W>(&'a mut Win32Impl); 285 | 286 | impl BufferInterface for BufferImpl<'_, D, W> { 287 | #[inline] 288 | fn pixels(&self) -> &[u32] { 289 | self.0.buffer.as_ref().unwrap().pixels() 290 | } 291 | 292 | #[inline] 293 | fn pixels_mut(&mut self) -> &mut [u32] { 294 | self.0.buffer.as_mut().unwrap().pixels_mut() 295 | } 296 | 297 | fn age(&self) -> u8 { 298 | match self.0.buffer.as_ref() { 299 | Some(buffer) if buffer.presented => 1, 300 | _ => 0, 301 | } 302 | } 303 | 304 | fn present(self) -> Result<(), SoftBufferError> { 305 | let imp = self.0; 306 | let buffer = imp.buffer.as_ref().unwrap(); 307 | imp.present_with_damage(&[Rect { 308 | x: 0, 309 | y: 0, 310 | // We know width/height will be non-negative 311 | width: buffer.width.try_into().unwrap(), 312 | height: buffer.height.try_into().unwrap(), 313 | }]) 314 | } 315 | 316 | fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError> { 317 | let imp = self.0; 318 | imp.present_with_damage(damage) 319 | } 320 | } 321 | 322 | /// Allocator for device contexts. 323 | /// 324 | /// Device contexts can only be allocated or freed on the thread that originated them. 325 | /// So we spawn a thread specifically for allocating and freeing device contexts. 326 | /// This is the interface to that thread. 327 | struct Allocator { 328 | /// The channel for sending commands. 329 | sender: Mutex>, 330 | } 331 | 332 | impl Allocator { 333 | /// Get the global instance of the allocator. 334 | fn get() -> &'static Allocator { 335 | static ALLOCATOR: OnceLock = OnceLock::new(); 336 | ALLOCATOR.get_or_init(|| { 337 | let (sender, receiver) = mpsc::channel::(); 338 | 339 | // Create a thread responsible for DC handling. 340 | thread::Builder::new() 341 | .name(concat!("softbuffer_", env!("CARGO_PKG_VERSION"), "_dc_allocator").into()) 342 | .spawn(move || { 343 | while let Ok(command) = receiver.recv() { 344 | command.handle(); 345 | } 346 | }) 347 | .expect("failed to spawn the DC allocator thread"); 348 | 349 | Allocator { 350 | sender: Mutex::new(sender), 351 | } 352 | }) 353 | } 354 | 355 | /// Send a command to the allocator thread. 356 | fn send_command(&self, cmd: Command) { 357 | self.sender.lock().unwrap().send(cmd).unwrap(); 358 | } 359 | 360 | /// Get the device context for a window. 361 | fn get_dc(&self, window: HWND) -> Gdi::HDC { 362 | let (callback, waiter) = mpsc::sync_channel(1); 363 | 364 | // Send command to the allocator. 365 | self.send_command(Command::GetDc { window, callback }); 366 | 367 | // Wait for the response back. 368 | waiter.recv().unwrap() 369 | } 370 | 371 | /// Allocate a new device context. 372 | fn allocate(&self, dc: Gdi::HDC) -> Gdi::HDC { 373 | let (callback, waiter) = mpsc::sync_channel(1); 374 | 375 | // Send command to the allocator. 376 | self.send_command(Command::Allocate { dc, callback }); 377 | 378 | // Wait for the response back. 379 | waiter.recv().unwrap() 380 | } 381 | 382 | /// Deallocate a device context. 383 | fn deallocate(&self, dc: Gdi::HDC) { 384 | self.send_command(Command::Deallocate(dc)); 385 | } 386 | 387 | /// Release a device context. 388 | fn release(&self, owner: HWND, dc: Gdi::HDC) { 389 | self.send_command(Command::Release { dc, owner }); 390 | } 391 | } 392 | 393 | /// Commands to be sent to the allocator. 394 | enum Command { 395 | /// Call `GetDc` to get the device context for the provided window. 396 | GetDc { 397 | /// The window to provide a device context for. 398 | window: HWND, 399 | 400 | /// Send back the device context. 401 | callback: mpsc::SyncSender, 402 | }, 403 | 404 | /// Allocate a new device context using `GetCompatibleDc`. 405 | Allocate { 406 | /// The DC to be compatible with. 407 | dc: Gdi::HDC, 408 | 409 | /// Send back the device context. 410 | callback: mpsc::SyncSender, 411 | }, 412 | 413 | /// Deallocate a device context. 414 | Deallocate(Gdi::HDC), 415 | 416 | /// Release a window-associated device context. 417 | Release { 418 | /// The device context to release. 419 | dc: Gdi::HDC, 420 | 421 | /// The window that owns this device context. 422 | owner: HWND, 423 | }, 424 | } 425 | 426 | unsafe impl Send for Command {} 427 | 428 | impl Command { 429 | /// Handle this command. 430 | /// 431 | /// This should be called on the allocator thread. 432 | fn handle(self) { 433 | match self { 434 | Self::GetDc { window, callback } => { 435 | // Get the DC and send it back. 436 | let dc = unsafe { Gdi::GetDC(window) }; 437 | callback.send(dc).ok(); 438 | } 439 | 440 | Self::Allocate { dc, callback } => { 441 | // Allocate a DC and send it back. 442 | let dc = unsafe { Gdi::CreateCompatibleDC(dc) }; 443 | callback.send(dc).ok(); 444 | } 445 | 446 | Self::Deallocate(dc) => { 447 | // Deallocate this DC. 448 | unsafe { 449 | Gdi::DeleteDC(dc); 450 | } 451 | } 452 | 453 | Self::Release { dc, owner } => { 454 | // Release this DC. 455 | unsafe { 456 | Gdi::ReleaseDC(owner, dc); 457 | } 458 | } 459 | } 460 | } 461 | } 462 | 463 | struct OnlyUsedFromOrigin(T); 464 | unsafe impl Send for OnlyUsedFromOrigin {} 465 | 466 | impl From for OnlyUsedFromOrigin { 467 | fn from(t: T) -> Self { 468 | Self(t) 469 | } 470 | } 471 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use raw_window_handle::{HandleError, RawDisplayHandle, RawWindowHandle}; 2 | use std::error::Error; 3 | use std::fmt; 4 | use std::num::NonZeroU32; 5 | 6 | #[derive(Debug)] 7 | #[non_exhaustive] 8 | /// A sum type of all of the errors that can occur during the operation of this crate. 9 | pub enum SoftBufferError { 10 | /// A [`raw-window-handle`] error occurred. 11 | /// 12 | /// [`raw-window-handle`]: raw_window_handle 13 | RawWindowHandle(HandleError), 14 | 15 | /// The [`RawDisplayHandle`] passed into [`Context::new`] is not supported by this crate. 16 | /// 17 | /// [`RawDisplayHandle`]: raw_window_handle::RawDisplayHandle 18 | /// [`Context::new`]: crate::Context::new 19 | UnsupportedDisplayPlatform { 20 | /// The platform name of the display that was passed into [`Context::new`]. 21 | /// 22 | /// This is a human-readable string that describes the platform of the display that was 23 | /// passed into [`Context::new`]. The value is not guaranteed to be stable and this 24 | /// exists for debugging purposes only. 25 | /// 26 | /// [`Context::new`]: crate::Context::new 27 | human_readable_display_platform_name: &'static str, 28 | 29 | /// The [`RawDisplayHandle`] that was passed into [`Context::new`]. 30 | /// 31 | /// [`RawDisplayHandle`]: raw_window_handle::RawDisplayHandle 32 | /// [`Context::new`]: crate::Context::new 33 | display_handle: RawDisplayHandle, 34 | }, 35 | 36 | /// The [`RawWindowHandle`] passed into [`Surface::new`] is not supported by this crate. 37 | /// 38 | /// [`RawWindowHandle`]: raw_window_handle::RawWindowHandle 39 | /// [`Surface::new`]: crate::Surface::new 40 | UnsupportedWindowPlatform { 41 | /// The platform name of the window that was passed into [`Surface::new`]. 42 | /// 43 | /// This is a human-readable string that describes the platform of the window that was 44 | /// passed into [`Surface::new`]. The value is not guaranteed to be stable and this 45 | /// exists for debugging purposes only. 46 | /// 47 | /// [`Surface::new`]: crate::Surface::new 48 | human_readable_window_platform_name: &'static str, 49 | 50 | /// The platform name of the display used by the [`Context`]. 51 | /// 52 | /// It is possible for a window to be created on a different type of display than the 53 | /// display that was passed into [`Context::new`]. This is a human-readable string that 54 | /// describes the platform of the display that was passed into [`Context::new`]. The value 55 | /// is not guaranteed to be stable and this exists for debugging purposes only. 56 | /// 57 | /// [`Context`]: crate::Context 58 | /// [`Context::new`]: crate::Context::new 59 | human_readable_display_platform_name: &'static str, 60 | 61 | /// The [`RawWindowHandle`] that was passed into [`Surface::new`]. 62 | /// 63 | /// [`RawWindowHandle`]: raw_window_handle::RawWindowHandle 64 | /// [`Surface::new`]: crate::Surface::new 65 | window_handle: RawWindowHandle, 66 | }, 67 | 68 | /// The [`RawWindowHandle`] passed into [`Surface::new`] is missing necessary fields. 69 | /// 70 | /// [`RawWindowHandle`]: raw_window_handle::RawWindowHandle 71 | /// [`Surface::new`]: crate::Surface::new 72 | IncompleteWindowHandle, 73 | 74 | /// The [`RawDisplayHandle`] passed into [`Context::new`] is missing necessary fields. 75 | /// 76 | /// [`RawDisplayHandle`]: raw_window_handle::RawDisplayHandle 77 | /// [`Context::new`]: crate::Context::new 78 | IncompleteDisplayHandle, 79 | 80 | /// The provided size is outside of the range supported by the backend. 81 | SizeOutOfRange { 82 | /// The width that was out of range. 83 | width: NonZeroU32, 84 | 85 | /// The height that was out of range. 86 | height: NonZeroU32, 87 | }, 88 | 89 | /// The provided damage rect is outside of the range supported by the backend. 90 | DamageOutOfRange { 91 | /// The damage rect that was out of range. 92 | rect: crate::Rect, 93 | }, 94 | 95 | /// A platform-specific backend error occurred. 96 | /// 97 | /// The first field provides a human-readable description of the error. The second field 98 | /// provides the actual error that occurred. Note that the second field is, under the hood, 99 | /// a private wrapper around the actual error, preventing the user from downcasting to the 100 | /// actual error type. 101 | PlatformError(Option, Option>), 102 | 103 | /// This function is unimplemented on this platform. 104 | Unimplemented, 105 | } 106 | 107 | impl fmt::Display for SoftBufferError { 108 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 109 | match self { 110 | Self::RawWindowHandle(err) => fmt::Display::fmt(err, f), 111 | Self::UnsupportedDisplayPlatform { 112 | human_readable_display_platform_name, 113 | display_handle, 114 | } => write!( 115 | f, 116 | "The provided display returned an unsupported platform: {}.\nDisplay handle: {:?}", 117 | human_readable_display_platform_name, display_handle 118 | ), 119 | Self::UnsupportedWindowPlatform { 120 | human_readable_window_platform_name, 121 | human_readable_display_platform_name, 122 | window_handle, 123 | } => write!( 124 | f, 125 | "The provided window returned an unsupported platform: {}, {}.\nWindow handle: {:?}", 126 | human_readable_window_platform_name, human_readable_display_platform_name, window_handle 127 | ), 128 | Self::IncompleteWindowHandle => write!(f, "The provided window handle is null."), 129 | Self::IncompleteDisplayHandle => write!(f, "The provided display handle is null."), 130 | Self::SizeOutOfRange { width, height } => write!( 131 | f, 132 | "Surface size {width}x{height} out of range for backend.", 133 | ), 134 | Self::PlatformError(msg, None) => write!(f, "Platform error: {msg:?}"), 135 | Self::PlatformError(msg, Some(err)) => write!(f, "Platform error: {msg:?}: {err}"), 136 | Self::DamageOutOfRange { rect } => write!( 137 | f, 138 | "Damage rect {}x{} at ({}, {}) out of range for backend.", 139 | rect.width, rect.height, rect.x, rect.y 140 | ), 141 | Self::Unimplemented => write!(f, "This function is unimplemented on this platform."), 142 | } 143 | } 144 | } 145 | 146 | impl std::error::Error for SoftBufferError { 147 | fn source(&self) -> Option<&(dyn Error + 'static)> { 148 | match self { 149 | Self::RawWindowHandle(err) => Some(err), 150 | Self::PlatformError(_, err) => err.as_deref(), 151 | _ => None, 152 | } 153 | } 154 | } 155 | 156 | impl From for SoftBufferError { 157 | fn from(err: HandleError) -> Self { 158 | Self::RawWindowHandle(err) 159 | } 160 | } 161 | 162 | /// Simple unit error type used to bubble up rejected platforms. 163 | pub(crate) enum InitError { 164 | /// Failed to initialize. 165 | Failure(SoftBufferError), 166 | 167 | /// Cannot initialize this handle on this platform. 168 | Unsupported(D), 169 | } 170 | 171 | impl From for InitError { 172 | fn from(err: SoftBufferError) -> Self { 173 | Self::Failure(err) 174 | } 175 | } 176 | 177 | impl From for InitError { 178 | fn from(err: HandleError) -> Self { 179 | Self::Failure(err.into()) 180 | } 181 | } 182 | 183 | /// Convenient wrapper to cast errors into SoftBufferError. 184 | #[allow(dead_code)] 185 | pub(crate) trait SwResultExt { 186 | fn swbuf_err(self, msg: impl Into) -> Result; 187 | } 188 | 189 | impl SwResultExt for Result { 190 | fn swbuf_err(self, msg: impl Into) -> Result { 191 | self.map_err(|e| { 192 | SoftBufferError::PlatformError(Some(msg.into()), Some(Box::new(LibraryError(e)))) 193 | }) 194 | } 195 | } 196 | 197 | impl SwResultExt for Option { 198 | fn swbuf_err(self, msg: impl Into) -> Result { 199 | self.ok_or_else(|| SoftBufferError::PlatformError(Some(msg.into()), None)) 200 | } 201 | } 202 | 203 | /// A wrapper around a library error. 204 | /// 205 | /// This prevents `x11-dl` and `x11rb` from becoming public dependencies, since users cannot downcast 206 | /// to this type. 207 | struct LibraryError(E); 208 | 209 | impl fmt::Debug for LibraryError { 210 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 211 | fmt::Debug::fmt(&self.0, f) 212 | } 213 | } 214 | 215 | impl fmt::Display for LibraryError { 216 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 217 | fmt::Display::fmt(&self.0, f) 218 | } 219 | } 220 | 221 | impl std::error::Error for LibraryError {} 222 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | #![allow(clippy::needless_doctest_main)] 3 | #![deny(unsafe_op_in_unsafe_fn)] 4 | #![warn(missing_docs)] 5 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 6 | 7 | extern crate core; 8 | 9 | mod backend_dispatch; 10 | use backend_dispatch::*; 11 | mod backend_interface; 12 | use backend_interface::*; 13 | mod backends; 14 | mod error; 15 | mod util; 16 | 17 | use std::cell::Cell; 18 | use std::marker::PhantomData; 19 | use std::num::NonZeroU32; 20 | use std::ops; 21 | use std::sync::Arc; 22 | 23 | use error::InitError; 24 | pub use error::SoftBufferError; 25 | 26 | use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle}; 27 | 28 | #[cfg(target_arch = "wasm32")] 29 | pub use backends::web::SurfaceExtWeb; 30 | 31 | /// An instance of this struct contains the platform-specific data that must be managed in order to 32 | /// write to a window on that platform. 33 | pub struct Context { 34 | /// The inner static dispatch object. 35 | context_impl: ContextDispatch, 36 | 37 | /// This is Send+Sync IFF D is Send+Sync. 38 | _marker: PhantomData>, 39 | } 40 | 41 | impl Context { 42 | /// Creates a new instance of this struct, using the provided display. 43 | pub fn new(display: D) -> Result { 44 | match ContextDispatch::new(display) { 45 | Ok(context_impl) => Ok(Self { 46 | context_impl, 47 | _marker: PhantomData, 48 | }), 49 | Err(InitError::Unsupported(display)) => { 50 | let raw = display.display_handle()?.as_raw(); 51 | Err(SoftBufferError::UnsupportedDisplayPlatform { 52 | human_readable_display_platform_name: display_handle_type_name(&raw), 53 | display_handle: raw, 54 | }) 55 | } 56 | Err(InitError::Failure(f)) => Err(f), 57 | } 58 | } 59 | } 60 | 61 | /// A rectangular region of the buffer coordinate space. 62 | #[derive(Clone, Copy, Debug)] 63 | pub struct Rect { 64 | /// x coordinate of top left corner 65 | pub x: u32, 66 | /// y coordinate of top left corner 67 | pub y: u32, 68 | /// width 69 | pub width: NonZeroU32, 70 | /// height 71 | pub height: NonZeroU32, 72 | } 73 | 74 | /// A surface for drawing to a window with software buffers. 75 | pub struct Surface { 76 | /// This is boxed so that `Surface` is the same size on every platform. 77 | surface_impl: Box>, 78 | _marker: PhantomData>, 79 | } 80 | 81 | impl Surface { 82 | /// Creates a new surface for the context for the provided window. 83 | pub fn new(context: &Context, window: W) -> Result { 84 | match SurfaceDispatch::new(window, &context.context_impl) { 85 | Ok(surface_dispatch) => Ok(Self { 86 | surface_impl: Box::new(surface_dispatch), 87 | _marker: PhantomData, 88 | }), 89 | Err(InitError::Unsupported(window)) => { 90 | let raw = window.window_handle()?.as_raw(); 91 | Err(SoftBufferError::UnsupportedWindowPlatform { 92 | human_readable_window_platform_name: window_handle_type_name(&raw), 93 | human_readable_display_platform_name: context.context_impl.variant_name(), 94 | window_handle: raw, 95 | }) 96 | } 97 | Err(InitError::Failure(f)) => Err(f), 98 | } 99 | } 100 | 101 | /// Get a reference to the underlying window handle. 102 | pub fn window(&self) -> &W { 103 | self.surface_impl.window() 104 | } 105 | 106 | /// Set the size of the buffer that will be returned by [`Surface::buffer_mut`]. 107 | /// 108 | /// If the size of the buffer does not match the size of the window, the buffer is drawn 109 | /// in the upper-left corner of the window. It is recommended in most production use cases 110 | /// to have the buffer fill the entire window. Use your windowing library to find the size 111 | /// of the window. 112 | pub fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { 113 | self.surface_impl.resize(width, height) 114 | } 115 | 116 | /// Copies the window contents into a buffer. 117 | /// 118 | /// ## Platform Dependent Behavior 119 | /// 120 | /// - On X11, the window must be visible. 121 | /// - On AppKit, UIKit, Redox and Wayland, this function is unimplemented. 122 | /// - On Web, this will fail if the content was supplied by 123 | /// a different origin depending on the sites CORS rules. 124 | pub fn fetch(&mut self) -> Result, SoftBufferError> { 125 | self.surface_impl.fetch() 126 | } 127 | 128 | /// Return a [`Buffer`] that the next frame should be rendered into. The size must 129 | /// be set with [`Surface::resize`] first. The initial contents of the buffer may be zeroed, or 130 | /// may contain a previous frame. Call [`Buffer::age`] to determine this. 131 | /// 132 | /// ## Platform Dependent Behavior 133 | /// 134 | /// - On DRM/KMS, there is no reliable and sound way to wait for the page flip to happen from within 135 | /// `softbuffer`. Therefore it is the responsibility of the user to wait for the page flip before 136 | /// sending another frame. 137 | pub fn buffer_mut(&mut self) -> Result, SoftBufferError> { 138 | Ok(Buffer { 139 | buffer_impl: self.surface_impl.buffer_mut()?, 140 | _marker: PhantomData, 141 | }) 142 | } 143 | } 144 | 145 | impl AsRef for Surface { 146 | #[inline] 147 | fn as_ref(&self) -> &W { 148 | self.window() 149 | } 150 | } 151 | 152 | impl HasWindowHandle for Surface { 153 | #[inline] 154 | fn window_handle( 155 | &self, 156 | ) -> Result, raw_window_handle::HandleError> { 157 | self.window().window_handle() 158 | } 159 | } 160 | 161 | /// A buffer that can be written to by the CPU and presented to the window. 162 | /// 163 | /// This derefs to a `[u32]`, which depending on the backend may be a mapping into shared memory 164 | /// accessible to the display server, so presentation doesn't require any (client-side) copying. 165 | /// 166 | /// This trusts the display server not to mutate the buffer, which could otherwise be unsound. 167 | /// 168 | /// # Data representation 169 | /// 170 | /// The format of the buffer is as follows. There is one `u32` in the buffer for each pixel in 171 | /// the area to draw. The first entry is the upper-left most pixel. The second is one to the right 172 | /// etc. (Row-major top to bottom left to right one `u32` per pixel). Within each `u32` the highest 173 | /// order 8 bits are to be set to 0. The next highest order 8 bits are the red channel, then the 174 | /// green channel, and then the blue channel in the lowest-order 8 bits. See the examples for 175 | /// one way to build this format using bitwise operations. 176 | /// 177 | /// -------- 178 | /// 179 | /// Pixel format (`u32`): 180 | /// 181 | /// 00000000RRRRRRRRGGGGGGGGBBBBBBBB 182 | /// 183 | /// 0: Bit is 0 184 | /// R: Red channel 185 | /// G: Green channel 186 | /// B: Blue channel 187 | /// 188 | /// # Platform dependent behavior 189 | /// No-copy presentation is currently supported on: 190 | /// - Wayland 191 | /// - X, when XShm is available 192 | /// - Win32 193 | /// - Orbital, when buffer size matches window size 194 | /// 195 | /// Currently [`Buffer::present`] must block copying image data on: 196 | /// - Web 197 | /// - AppKit 198 | /// - UIKit 199 | /// 200 | /// Buffer copies an channel swizzling happen on: 201 | /// - Android 202 | pub struct Buffer<'a, D, W> { 203 | buffer_impl: BufferDispatch<'a, D, W>, 204 | _marker: PhantomData<(Arc, Cell<()>)>, 205 | } 206 | 207 | impl Buffer<'_, D, W> { 208 | /// `age` is the number of frames ago this buffer was last presented. So if the value is 209 | /// `1`, it is the same as the last frame, and if it is `2`, it is the same as the frame 210 | /// before that (for backends using double buffering). If the value is `0`, it is a new 211 | /// buffer that has unspecified contents. 212 | /// 213 | /// This can be used to update only a portion of the buffer. 214 | pub fn age(&self) -> u8 { 215 | self.buffer_impl.age() 216 | } 217 | 218 | /// Presents buffer to the window. 219 | /// 220 | /// # Platform dependent behavior 221 | /// 222 | /// ## Wayland 223 | /// 224 | /// On Wayland, calling this function may send requests to the underlying `wl_surface`. The 225 | /// graphics context may issue `wl_surface.attach`, `wl_surface.damage`, `wl_surface.damage_buffer` 226 | /// and `wl_surface.commit` requests when presenting the buffer. 227 | /// 228 | /// If the caller wishes to synchronize other surface/window changes, such requests must be sent to the 229 | /// Wayland compositor before calling this function. 230 | pub fn present(self) -> Result<(), SoftBufferError> { 231 | self.buffer_impl.present() 232 | } 233 | 234 | /// Presents buffer to the window, with damage regions. 235 | /// 236 | /// # Platform dependent behavior 237 | /// 238 | /// Supported on: 239 | /// - Wayland 240 | /// - X, when XShm is available 241 | /// - Win32 242 | /// - Web 243 | /// 244 | /// Otherwise this is equivalent to [`Self::present`]. 245 | pub fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError> { 246 | self.buffer_impl.present_with_damage(damage) 247 | } 248 | } 249 | 250 | impl ops::Deref for Buffer<'_, D, W> { 251 | type Target = [u32]; 252 | 253 | #[inline] 254 | fn deref(&self) -> &[u32] { 255 | self.buffer_impl.pixels() 256 | } 257 | } 258 | 259 | impl ops::DerefMut for Buffer<'_, D, W> { 260 | #[inline] 261 | fn deref_mut(&mut self) -> &mut [u32] { 262 | self.buffer_impl.pixels_mut() 263 | } 264 | } 265 | 266 | /// There is no display handle. 267 | #[derive(Debug)] 268 | #[allow(dead_code)] 269 | pub struct NoDisplayHandle(core::convert::Infallible); 270 | 271 | impl HasDisplayHandle for NoDisplayHandle { 272 | fn display_handle( 273 | &self, 274 | ) -> Result, raw_window_handle::HandleError> { 275 | match self.0 {} 276 | } 277 | } 278 | 279 | /// There is no window handle. 280 | #[derive(Debug)] 281 | pub struct NoWindowHandle(()); 282 | 283 | impl HasWindowHandle for NoWindowHandle { 284 | fn window_handle( 285 | &self, 286 | ) -> Result, raw_window_handle::HandleError> { 287 | Err(raw_window_handle::HandleError::NotSupported) 288 | } 289 | } 290 | 291 | fn window_handle_type_name(handle: &RawWindowHandle) -> &'static str { 292 | match handle { 293 | RawWindowHandle::Xlib(_) => "Xlib", 294 | RawWindowHandle::Win32(_) => "Win32", 295 | RawWindowHandle::WinRt(_) => "WinRt", 296 | RawWindowHandle::Web(_) => "Web", 297 | RawWindowHandle::Wayland(_) => "Wayland", 298 | RawWindowHandle::AndroidNdk(_) => "AndroidNdk", 299 | RawWindowHandle::AppKit(_) => "AppKit", 300 | RawWindowHandle::Orbital(_) => "Orbital", 301 | RawWindowHandle::UiKit(_) => "UiKit", 302 | RawWindowHandle::Xcb(_) => "XCB", 303 | RawWindowHandle::Drm(_) => "DRM", 304 | RawWindowHandle::Gbm(_) => "GBM", 305 | RawWindowHandle::Haiku(_) => "Haiku", 306 | _ => "Unknown Name", //don't completely fail to compile if there is a new raw window handle type that's added at some point 307 | } 308 | } 309 | 310 | fn display_handle_type_name(handle: &RawDisplayHandle) -> &'static str { 311 | match handle { 312 | RawDisplayHandle::Xlib(_) => "Xlib", 313 | RawDisplayHandle::Web(_) => "Web", 314 | RawDisplayHandle::Wayland(_) => "Wayland", 315 | RawDisplayHandle::AppKit(_) => "AppKit", 316 | RawDisplayHandle::Orbital(_) => "Orbital", 317 | RawDisplayHandle::UiKit(_) => "UiKit", 318 | RawDisplayHandle::Xcb(_) => "XCB", 319 | RawDisplayHandle::Drm(_) => "DRM", 320 | RawDisplayHandle::Gbm(_) => "GBM", 321 | RawDisplayHandle::Haiku(_) => "Haiku", 322 | RawDisplayHandle::Windows(_) => "Windows", 323 | RawDisplayHandle::Android(_) => "Android", 324 | _ => "Unknown Name", //don't completely fail to compile if there is a new raw window handle type that's added at some point 325 | } 326 | } 327 | 328 | #[cfg(not(target_family = "wasm"))] 329 | fn __assert_send() { 330 | fn is_send() {} 331 | fn is_sync() {} 332 | 333 | is_send::>(); 334 | is_sync::>(); 335 | is_send::>(); 336 | is_send::>(); 337 | 338 | /// ```compile_fail 339 | /// use softbuffer::Surface; 340 | /// 341 | /// fn __is_sync() {} 342 | /// __is_sync::>(); 343 | /// ``` 344 | fn __surface_not_sync() {} 345 | /// ```compile_fail 346 | /// use softbuffer::Buffer; 347 | /// 348 | /// fn __is_sync() {} 349 | /// __is_sync::>(); 350 | /// ``` 351 | fn __buffer_not_sync() {} 352 | } 353 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | // Not needed on all platforms 2 | #![allow(dead_code)] 3 | 4 | use std::cmp; 5 | use std::num::NonZeroU32; 6 | 7 | use crate::Rect; 8 | use crate::SoftBufferError; 9 | 10 | /// Takes a mutable reference to a container and a function deriving a 11 | /// reference into it, and stores both, making it possible to get back the 12 | /// reference to the container once the other reference is no longer needed. 13 | /// 14 | /// This should be consistent with stacked borrow rules, and miri seems to 15 | /// accept it at least in simple cases. 16 | pub struct BorrowStack<'a, T: 'a + ?Sized, U: 'a + ?Sized> { 17 | container: *mut T, 18 | member: *mut U, 19 | _phantom: std::marker::PhantomData<&'a mut T>, 20 | } 21 | 22 | unsafe impl<'a, T: 'a + Send + ?Sized, U: 'a + Send + ?Sized> Send for BorrowStack<'a, T, U> {} 23 | 24 | impl<'a, T: 'a + ?Sized, U: 'a + ?Sized> BorrowStack<'a, T, U> { 25 | pub fn new(container: &'a mut T, f: F) -> Result 26 | where 27 | F: for<'b> FnOnce(&'b mut T) -> Result<&'b mut U, SoftBufferError>, 28 | { 29 | let container = container as *mut T; 30 | let member = f(unsafe { &mut *container })? as *mut U; 31 | Ok(Self { 32 | container, 33 | member, 34 | _phantom: std::marker::PhantomData, 35 | }) 36 | } 37 | 38 | pub fn member(&self) -> &U { 39 | unsafe { &*self.member } 40 | } 41 | 42 | pub fn member_mut(&mut self) -> &mut U { 43 | unsafe { &mut *self.member } 44 | } 45 | 46 | pub fn into_container(self) -> &'a mut T { 47 | // SAFETY: Since we consume self and no longer reference member, this 48 | // mutable reference is unique. 49 | unsafe { &mut *self.container } 50 | } 51 | } 52 | 53 | /// Calculates the smallest `Rect` necessary to represent all damaged `Rect`s. 54 | pub(crate) fn union_damage(damage: &[Rect]) -> Option { 55 | struct Region { 56 | left: u32, 57 | top: u32, 58 | bottom: u32, 59 | right: u32, 60 | } 61 | 62 | let region = damage 63 | .iter() 64 | .map(|rect| Region { 65 | left: rect.x, 66 | top: rect.y, 67 | right: rect.x + rect.width.get(), 68 | bottom: rect.y + rect.height.get(), 69 | }) 70 | .reduce(|mut prev, next| { 71 | prev.left = cmp::min(prev.left, next.left); 72 | prev.top = cmp::min(prev.top, next.top); 73 | prev.right = cmp::max(prev.right, next.right); 74 | prev.bottom = cmp::max(prev.bottom, next.bottom); 75 | prev 76 | })?; 77 | 78 | Some(Rect { 79 | x: region.left, 80 | y: region.top, 81 | width: NonZeroU32::new(region.right - region.left) 82 | .expect("`right` must always be bigger then `left`"), 83 | height: NonZeroU32::new(region.bottom - region.top) 84 | .expect("`bottom` must always be bigger then `top`"), 85 | }) 86 | } 87 | 88 | #[cfg(test)] 89 | mod tests { 90 | use super::*; 91 | 92 | #[test] 93 | fn test_borrowstack_slice_int() { 94 | fn f(mut stack: BorrowStack<[u32], u32>) { 95 | assert_eq!(*stack.member(), 3); 96 | *stack.member_mut() = 42; 97 | assert_eq!(stack.into_container(), &[1, 2, 42, 4, 5]); 98 | } 99 | 100 | let mut v = vec![1, 2, 3, 4, 5]; 101 | f(BorrowStack::new(v.as_mut(), |v: &mut [u32]| Ok(&mut v[2])).unwrap()); 102 | assert_eq!(&v, &[1, 2, 42, 4, 5]); 103 | } 104 | } 105 | --------------------------------------------------------------------------------