initial
This commit is contained in:
commit
cb6fa4450a
15 changed files with 3865 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/target
|
||||
2710
Cargo.lock
generated
Normal file
2710
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
16
Cargo.toml
Normal file
16
Cargo.toml
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
[package]
|
||||
name = "confetti"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
bytemuck = "1.23.1"
|
||||
env_logger = "0.11.8"
|
||||
libloading = "0.8.8"
|
||||
pollster = "0.4.0"
|
||||
rand = "0.9.2"
|
||||
raw-window-handle = "0.6.2"
|
||||
smithay-client-toolkit = "0.19.2"
|
||||
wayland-client = "0.31.11"
|
||||
wgpu = "26.0.1"
|
||||
winit = "0.30.12"
|
||||
15
LICENSE
Normal file
15
LICENSE
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
Creative Commons Attribution-NonCommercial 4.0 International License
|
||||
|
||||
Copyright (c) 2025 Sebastian Kootz
|
||||
|
||||
You are free to:
|
||||
|
||||
- Share: Copy and redistribute the material in any medium or format.
|
||||
- Adapt: Remix, transform, and build upon the material for any purpose, non-commercially.
|
||||
|
||||
Under the following terms:
|
||||
|
||||
- Attribution: You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
|
||||
- Non-Commercial: You may not use the material for commercial purposes.
|
||||
|
||||
You can view the full text of the license here: https://creativecommons.org/licenses/by-nc/4.0/
|
||||
158
README.md
Normal file
158
README.md
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
# Sherlock Confetti
|
||||
|
||||
Sherlock Confetti is a lightweight program that displays a confetti animation
|
||||
as a top-level overlay on Wayland compositors. It provides a fun and colorful
|
||||
visual effect without interfering with your workflow.
|
||||
|
||||
<https://github.com/user-attachments/assets/d4e63a6b-ce41-4faa-8a1e-898570056e84>
|
||||
|
||||
## Features
|
||||
|
||||
- **Wayland-native:** Built specifically for Wayland environments.
|
||||
- **Tiling Window Manager Friendly:** Displays on top without disrupting your
|
||||
window layout.
|
||||
- **High-performance rendering with WGPU:** Utilizes GPU acceleration for
|
||||
smooth animation.
|
||||
- **Shader-driven confetti:** Custom shaders deliver rich, dynamic visual
|
||||
effects.
|
||||
- **Multiple Color Palettes:** Choose from a variety of predefined vibrant and
|
||||
pastel palettes.
|
||||
- **Standalone or Integrated:** Originally created for the custom launcher
|
||||
[Sherlock](https://github.com/Skxxtz/sherlock), but fully functional as a
|
||||
standalone program.
|
||||
|
||||
## Usage
|
||||
|
||||
Simply run the program to enjoy confetti animations on your Wayland session. It
|
||||
can be integrated or triggered by other applications such as launchers or
|
||||
window managers.
|
||||
|
||||
```bash
|
||||
confetti
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
### Build Dependencies
|
||||
- `libxkbcommon`
|
||||
|
||||
### Runtime Dependencies
|
||||
- `vulkan-icd-loader`
|
||||
- `mesa`
|
||||
- `wayland`
|
||||
- `wayland-protocols`
|
||||
|
||||
### <ins>From Source</ins>
|
||||
|
||||
To build Confetti from source, follow these steps.<br>
|
||||
Make sure you have the necessary dependencies installed:
|
||||
|
||||
- `rust` - [How to install rust](https://www.rust-lang.org/tools/install)
|
||||
- `git` - [How to install git](https://github.com/git-guides/install-git)
|
||||
|
||||
1. **Clone the repository**:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/skxxtz/sherlock-confetti.git
|
||||
cd sherlock-confetti
|
||||
```
|
||||
|
||||
2. **Build the project**:
|
||||
|
||||
```bash
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
3. **Install the binary**:
|
||||
|
||||
After the build completes, install the binary to your system:
|
||||
|
||||
```bash
|
||||
sudo cp target/release/confetti /usr/local/bin/
|
||||
```
|
||||
|
||||
### <ins>From Binary</ins>
|
||||
|
||||
Make sure you have the following dependency installed:
|
||||
|
||||
- `tar` - [Tar](https://www.gnu.org/software/tar/)
|
||||
|
||||
1. **Download the archive containing the latest release**:
|
||||
|
||||
The archive can be found [here](https://github.com/Skxxtz/sherlock-confetti/releases/latest).
|
||||
|
||||
2. **Extract the files from the archive**:
|
||||
|
||||
```bash
|
||||
cd ~/Downloads/
|
||||
tar -xzf sherlock-confetti*.tar.gz
|
||||
```
|
||||
|
||||
You can use tab-completion or run `ls` to verify the name of the archive.
|
||||
|
||||
3. **Install the binary**:
|
||||
|
||||
Now move the binary to a location on your `$PATH` environment variable:
|
||||
|
||||
```bash
|
||||
sudo mv confetti /usr/local/bin/
|
||||
```
|
||||
|
||||
Optionally also move the LICENSE file or delete it:
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /usr/share/doc/confetti
|
||||
sudo mv LICENSE /usr/share/doc/confetti/
|
||||
|
||||
# or, to remove it:
|
||||
rm LICENSE
|
||||
```
|
||||
|
||||
### <ins>Build Debian Package</ins>
|
||||
|
||||
To build a `.deb` package directly from the source, follow these steps:<br>
|
||||
Make sure you have the following dependencies installed:
|
||||
|
||||
- `rust` - [How to install rust](https://www.rust-lang.org/tools/install)
|
||||
- `git` - [How to install git](https://github.com/git-guides/install-git)
|
||||
|
||||
1. **Install the `cargo-deb` tool**:
|
||||
|
||||
First, you need to install the `cargo-deb` tool, which simplifies packaging Rust projects as Debian packages:
|
||||
|
||||
```bash
|
||||
cargo install cargo-deb
|
||||
```
|
||||
|
||||
2. **Build the Debian package**:
|
||||
|
||||
After installing `cargo-deb`, run the following command to build the `.deb` package:
|
||||
|
||||
```bash
|
||||
cargo deb
|
||||
```
|
||||
|
||||
This will create a `.deb` package in the `target/debian` directory.
|
||||
|
||||
3. **Install the generated `.deb` package**:
|
||||
|
||||
Once the package is built, you can install it using:
|
||||
|
||||
```bash
|
||||
sudo dpkg -i target/debian/confetti_*.deb
|
||||
```
|
||||
|
||||
You can use tab-completion or `ls target/debian/` to confirm the file name.
|
||||
|
||||
(Make sure to replace the filename if the version number is different.)
|
||||
|
||||
## Palettes
|
||||
|
||||
Palettes can be selected by running `confetti` with the `--palette <name>`
|
||||
flag. The names for available palettes are listed in the graphic below:
|
||||
|
||||
<div align="center" style="text-align:center; border-radius:10px;">
|
||||
<picture>
|
||||
<img alt="color palettes" width="100%" style="border-radius: 10px;" src="assets/palettes.png">
|
||||
</picture>
|
||||
</div>
|
||||
BIN
assets/palettes.png
Normal file
BIN
assets/palettes.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 86 KiB |
BIN
assets/showcase.mp4
Normal file
BIN
assets/showcase.mp4
Normal file
Binary file not shown.
27
flake.lock
generated
Normal file
27
flake.lock
generated
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1759733170,
|
||||
"narHash": "sha256-TXnlsVb5Z8HXZ6mZoeOAIwxmvGHp1g4Dw89eLvIwKVI=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "8913c168d1c56dc49a7718685968f38752171c3b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
98
flake.nix
Normal file
98
flake.nix
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
{
|
||||
description = "Confetti packaged for Nix/NixOS";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
# optional: pin a Rust toolchain via rust-overlay if you need nightly
|
||||
# rust-overlay.url = "github:oxalica/rust-overlay";
|
||||
};
|
||||
|
||||
outputs =
|
||||
{
|
||||
self,
|
||||
nixpkgs, # , rust-overlay
|
||||
}:
|
||||
let
|
||||
systems = [
|
||||
"x86_64-linux"
|
||||
"aarch64-linux"
|
||||
];
|
||||
forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f (import nixpkgs { inherit system; }));
|
||||
in
|
||||
{
|
||||
packages = forAllSystems (
|
||||
pkgs:
|
||||
let
|
||||
lib = pkgs.lib;
|
||||
in
|
||||
{
|
||||
# name it default (and alias to confetti if you like)
|
||||
default = pkgs.rustPlatform.buildRustPackage {
|
||||
pname = "confetti";
|
||||
version = "unstable";
|
||||
src = ./.;
|
||||
cargoHash = "sha256-6lwp5gky+NKhE2IKVI2Qxqe5HT9a8xqWfrJ2e9CK6IE="; # run once to get the real hash
|
||||
|
||||
# NOTE: makeWrapper is needed for wrapProgram
|
||||
nativeBuildInputs = with pkgs; [
|
||||
pkg-config
|
||||
wayland-protocols
|
||||
makeWrapper
|
||||
];
|
||||
|
||||
# These are *runtime* libraries the app needs to load
|
||||
buildInputs = with pkgs; [
|
||||
wayland
|
||||
libxkbcommon
|
||||
vulkan-loader
|
||||
mesa
|
||||
];
|
||||
|
||||
# Ensure the Wayland/Vulkan/libxkbcommon libs are found at runtime
|
||||
postInstall =
|
||||
let
|
||||
libPath = lib.makeLibraryPath [
|
||||
pkgs.wayland
|
||||
pkgs.libxkbcommon
|
||||
pkgs.vulkan-loader
|
||||
pkgs.mesa
|
||||
];
|
||||
in
|
||||
''
|
||||
wrapProgram "$out/bin/confetti" \
|
||||
--prefix LD_LIBRARY_PATH : ${libPath}
|
||||
'';
|
||||
};
|
||||
|
||||
confetti = self.packages.${pkgs.system}.default;
|
||||
}
|
||||
);
|
||||
|
||||
# Expose a runnable `nix run`
|
||||
apps = forAllSystems (pkgs: {
|
||||
default = {
|
||||
type = "app";
|
||||
program = "${self.packages.${pkgs.system}.default}/bin/confetti";
|
||||
};
|
||||
});
|
||||
|
||||
# Nice dev shell for hacking locally (cargo, rustc, system libs)
|
||||
devShells = forAllSystems (pkgs: {
|
||||
default = pkgs.mkShell {
|
||||
nativeBuildInputs = with pkgs; [
|
||||
pkg-config
|
||||
rustc
|
||||
cargo
|
||||
# or: (rust-bin.stable.latest.default) if using rust-overlay
|
||||
wayland-protocols
|
||||
];
|
||||
buildInputs = with pkgs; [
|
||||
wayland
|
||||
libxkbcommon
|
||||
vulkan-loader
|
||||
mesa
|
||||
];
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
||||
11
packager.sh
Executable file
11
packager.sh
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
#!/bin/bash
|
||||
|
||||
read -p "Current version: " version
|
||||
rm -rf ~/.tmp/confetti-release/
|
||||
mkdir -p ~/.tmp/confetti-release/
|
||||
cargo build --release
|
||||
cp target/release/confetti ~/.tmp/confetti-release/
|
||||
cp LICENSE ~/.tmp/confetti-release/LICENSE
|
||||
|
||||
cd ~/.tmp/confetti-release/
|
||||
tar -czf confetti-v${version}-bin-linux-x86_64.tar.gz confetti LICENSE
|
||||
1
result
Symbolic link
1
result
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
/nix/store/rqczppgcgsbvkbi80hvrxgz3aq9byyj8-confetti-unstable
|
||||
169
src/color_palette.rs
Normal file
169
src/color_palette.rs
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
pub enum ColorPalette {
|
||||
Party,
|
||||
Pastel,
|
||||
Earth,
|
||||
Neon,
|
||||
Cool,
|
||||
Sunset,
|
||||
Ocean,
|
||||
Retro,
|
||||
Forest,
|
||||
Candy,
|
||||
}
|
||||
|
||||
type ColorVec = Vec<[f32; 3]>;
|
||||
impl ColorPalette {
|
||||
pub fn get_colors(&self) -> Vec<[f32; 3]> {
|
||||
match &self {
|
||||
Self::Party => Self::party(),
|
||||
Self::Pastel => Self::pastel(),
|
||||
Self::Earth => Self::earth_tones(),
|
||||
Self::Neon => Self::neon(),
|
||||
Self::Cool => Self::cool(),
|
||||
Self::Sunset => Self::sunset(),
|
||||
Self::Ocean => Self::ocean(),
|
||||
Self::Retro => Self::retro(),
|
||||
Self::Forest => Self::forest(),
|
||||
Self::Candy => Self::candy(),
|
||||
}
|
||||
}
|
||||
fn party() -> ColorVec {
|
||||
vec![
|
||||
[1.0, 0.0, 0.0], // bright red
|
||||
[1.0, 0.5, 0.0], // vivid orange
|
||||
[1.0, 1.0, 0.0], // bright yellow
|
||||
[0.0, 1.0, 0.0], // neon green
|
||||
[0.0, 1.0, 1.0], // bright cyan
|
||||
[0.0, 0.0, 1.0], // electric blue
|
||||
[0.7, 0.0, 1.0], // vibrant purple
|
||||
[1.0, 0.0, 1.0], // hot pink
|
||||
[1.0, 0.2, 0.5], // neon pink
|
||||
[1.0, 0.8, 0.0], // gold yellow
|
||||
]
|
||||
}
|
||||
|
||||
fn pastel() -> ColorVec {
|
||||
vec![
|
||||
[1.0, 0.8, 0.8], // pastel pink
|
||||
[0.8, 1.0, 0.8], // pastel green
|
||||
[0.8, 0.8, 1.0], // pastel blue
|
||||
[1.0, 0.9, 0.7], // cream
|
||||
[0.9, 0.8, 1.0], // lavender
|
||||
[1.0, 0.85, 0.85], // light coral
|
||||
[0.85, 1.0, 0.85], // mint
|
||||
[0.85, 0.85, 1.0], // light periwinkle
|
||||
]
|
||||
}
|
||||
|
||||
fn neon() -> ColorVec {
|
||||
vec![
|
||||
[1.0, 0.1, 0.1], // neon red
|
||||
[1.0, 0.5, 0.0], // neon orange
|
||||
[1.0, 1.0, 0.0], // neon yellow
|
||||
[0.0, 1.0, 0.0], // neon green
|
||||
[0.0, 1.0, 1.0], // neon cyan
|
||||
[0.0, 0.1, 1.0], // neon blue
|
||||
[0.6, 0.0, 1.0], // neon purple
|
||||
[1.0, 0.0, 1.0], // neon magenta
|
||||
]
|
||||
}
|
||||
|
||||
fn earth_tones() -> ColorVec {
|
||||
vec![
|
||||
[0.5, 0.3, 0.1], // brown
|
||||
[0.6, 0.4, 0.2], // tan
|
||||
[0.4, 0.5, 0.3], // olive green
|
||||
[0.2, 0.3, 0.1], // dark olive
|
||||
[0.8, 0.7, 0.5], // sand
|
||||
[0.3, 0.2, 0.1], // dark brown
|
||||
]
|
||||
}
|
||||
|
||||
fn cool() -> ColorVec {
|
||||
vec![
|
||||
[0.0, 0.5, 1.0], // sky blue
|
||||
[0.0, 0.7, 0.9], // turquoise
|
||||
[0.0, 0.4, 0.6], // teal
|
||||
[0.3, 0.6, 0.8], // steel blue
|
||||
[0.2, 0.3, 0.5], // navy
|
||||
[0.5, 0.7, 0.9], // light blue
|
||||
]
|
||||
}
|
||||
fn sunset() -> ColorVec {
|
||||
vec![
|
||||
[1.0, 0.4, 0.0], // orange
|
||||
[1.0, 0.7, 0.4], // light orange
|
||||
[0.9, 0.2, 0.3], // deep pink
|
||||
[0.6, 0.0, 0.3], // maroon
|
||||
[0.9, 0.5, 0.1], // gold
|
||||
[1.0, 0.3, 0.0], // fiery red-orange
|
||||
]
|
||||
}
|
||||
|
||||
fn ocean() -> ColorVec {
|
||||
vec![
|
||||
[0.0, 0.5, 0.7], // deep sea blue
|
||||
[0.0, 0.7, 0.9], // aqua
|
||||
[0.2, 0.8, 0.8], // light teal
|
||||
[0.0, 0.3, 0.5], // navy blue
|
||||
[0.1, 0.6, 0.8], // sky blue
|
||||
[0.3, 0.9, 1.0], // bright cyan
|
||||
]
|
||||
}
|
||||
|
||||
fn retro() -> ColorVec {
|
||||
vec![
|
||||
[1.0, 0.3, 0.5], // pink
|
||||
[1.0, 0.6, 0.0], // orange
|
||||
[0.9, 0.8, 0.2], // mustard yellow
|
||||
[0.3, 0.7, 0.6], // teal
|
||||
[0.6, 0.3, 0.6], // purple
|
||||
[0.8, 0.4, 0.2], // burnt sienna
|
||||
]
|
||||
}
|
||||
|
||||
fn forest() -> ColorVec {
|
||||
vec![
|
||||
[0.0, 0.3, 0.0], // dark green
|
||||
[0.1, 0.5, 0.1], // moss green
|
||||
[0.2, 0.6, 0.2], // pine green
|
||||
[0.4, 0.8, 0.4], // leaf green
|
||||
[0.1, 0.4, 0.1], // olive green
|
||||
[0.3, 0.5, 0.3], // fern green
|
||||
]
|
||||
}
|
||||
|
||||
fn candy() -> ColorVec {
|
||||
vec![
|
||||
[1.0, 0.7, 0.8], // cotton candy pink
|
||||
[1.0, 0.9, 0.6], // pale yellow
|
||||
[0.8, 1.0, 0.7], // light lime
|
||||
[0.7, 0.8, 1.0], // baby blue
|
||||
[1.0, 0.6, 0.7], // bubblegum pink
|
||||
[0.9, 0.7, 1.0], // lavender pink
|
||||
]
|
||||
}
|
||||
}
|
||||
impl std::str::FromStr for ColorPalette {
|
||||
type Err = String; // or a custom error type
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.to_lowercase().as_str() {
|
||||
"party" => Ok(ColorPalette::Party),
|
||||
"pastel" => Ok(ColorPalette::Pastel),
|
||||
"earth" => Ok(ColorPalette::Earth),
|
||||
"neon" => Ok(ColorPalette::Neon),
|
||||
"cool" => Ok(ColorPalette::Cool),
|
||||
"sunset" => Ok(ColorPalette::Sunset),
|
||||
"ocean" => Ok(ColorPalette::Ocean),
|
||||
"retro" => Ok(ColorPalette::Retro),
|
||||
"forest" => Ok(ColorPalette::Forest),
|
||||
"candy" => Ok(ColorPalette::Candy),
|
||||
_ => Err(format!("Unknown color pallette: {}", s)),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Default for ColorPalette {
|
||||
fn default() -> Self {
|
||||
Self::Retro
|
||||
}
|
||||
}
|
||||
181
src/implementations.rs
Normal file
181
src/implementations.rs
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
use std::{num::NonZeroU32, time::Duration};
|
||||
|
||||
use smithay_client_toolkit::{
|
||||
compositor::CompositorHandler,
|
||||
delegate_compositor, delegate_layer, delegate_output, delegate_registry, delegate_seat,
|
||||
output::{OutputHandler, OutputState},
|
||||
registry::{ProvidesRegistryState, RegistryState},
|
||||
registry_handlers,
|
||||
seat::{Capability, SeatHandler, SeatState},
|
||||
shell::wlr_layer::{LayerShellHandler, LayerSurface, LayerSurfaceConfigure},
|
||||
};
|
||||
use wayland_client::{
|
||||
Connection, QueueHandle,
|
||||
protocol::{wl_output, wl_seat, wl_surface},
|
||||
};
|
||||
|
||||
use crate::Wgpu;
|
||||
|
||||
delegate_compositor!(Wgpu);
|
||||
delegate_output!(Wgpu);
|
||||
|
||||
delegate_seat!(Wgpu);
|
||||
delegate_layer!(Wgpu);
|
||||
delegate_registry!(Wgpu);
|
||||
|
||||
impl LayerShellHandler for Wgpu {
|
||||
fn closed(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _layer: &LayerSurface) {
|
||||
self.exit = true;
|
||||
}
|
||||
|
||||
fn configure(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
qh: &QueueHandle<Self>,
|
||||
_layer: &LayerSurface,
|
||||
configure: LayerSurfaceConfigure,
|
||||
_serial: u32,
|
||||
) {
|
||||
self.width = NonZeroU32::new(configure.new_size.0).map_or(256, NonZeroU32::get);
|
||||
self.height = NonZeroU32::new(configure.new_size.1).map_or(256, NonZeroU32::get);
|
||||
|
||||
let config = wgpu::SurfaceConfiguration {
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
format: wgpu::TextureFormat::Bgra8Unorm,
|
||||
width: self.width,
|
||||
height: self.height,
|
||||
present_mode: wgpu::PresentMode::Fifo,
|
||||
alpha_mode: wgpu::CompositeAlphaMode::PreMultiplied,
|
||||
view_formats: vec![],
|
||||
desired_maximum_frame_latency: 0,
|
||||
};
|
||||
|
||||
// Initiate the first draw.
|
||||
if self.first_configure {
|
||||
self.first_configure = false;
|
||||
self.surface.configure(&self.device, &config);
|
||||
}
|
||||
loop {
|
||||
if self.exit {
|
||||
break;
|
||||
}
|
||||
self.draw(qh);
|
||||
std::thread::sleep(Duration::from_millis(16));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CompositorHandler for Wgpu {
|
||||
fn scale_factor_changed(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
_surface: &wl_surface::WlSurface,
|
||||
_new_factor: i32,
|
||||
) {
|
||||
// Not needed for this example.
|
||||
}
|
||||
|
||||
fn transform_changed(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
_surface: &wl_surface::WlSurface,
|
||||
_new_transform: wl_output::Transform,
|
||||
) {
|
||||
// Not needed for this example.
|
||||
}
|
||||
|
||||
fn frame(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
_surface: &wl_surface::WlSurface,
|
||||
_time: u32,
|
||||
) {
|
||||
}
|
||||
|
||||
fn surface_enter(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
_surface: &wl_surface::WlSurface,
|
||||
_output: &wl_output::WlOutput,
|
||||
) {
|
||||
// Not needed for this example.
|
||||
}
|
||||
|
||||
fn surface_leave(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
_surface: &wl_surface::WlSurface,
|
||||
_output: &wl_output::WlOutput,
|
||||
) {
|
||||
// Not needed for this example.
|
||||
}
|
||||
}
|
||||
|
||||
impl OutputHandler for Wgpu {
|
||||
fn output_state(&mut self) -> &mut OutputState {
|
||||
&mut self.output_state
|
||||
}
|
||||
|
||||
fn new_output(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
_output: wl_output::WlOutput,
|
||||
) {
|
||||
}
|
||||
|
||||
fn update_output(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
_output: wl_output::WlOutput,
|
||||
) {
|
||||
}
|
||||
|
||||
fn output_destroyed(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
_output: wl_output::WlOutput,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl SeatHandler for Wgpu {
|
||||
fn seat_state(&mut self) -> &mut SeatState {
|
||||
&mut self.seat_state
|
||||
}
|
||||
|
||||
fn new_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_seat::WlSeat) {}
|
||||
fn new_capability(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
_seat: wl_seat::WlSeat,
|
||||
_capability: Capability,
|
||||
) {
|
||||
}
|
||||
|
||||
fn remove_capability(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
_: wl_seat::WlSeat,
|
||||
_capability: Capability,
|
||||
) {
|
||||
}
|
||||
|
||||
fn remove_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_seat::WlSeat) {}
|
||||
}
|
||||
|
||||
impl ProvidesRegistryState for Wgpu {
|
||||
fn registry(&mut self) -> &mut RegistryState {
|
||||
&mut self.registry_state
|
||||
}
|
||||
registry_handlers![OutputState];
|
||||
}
|
||||
438
src/main.rs
Normal file
438
src/main.rs
Normal file
|
|
@ -0,0 +1,438 @@
|
|||
use rand::Rng;
|
||||
use raw_window_handle::{
|
||||
RawDisplayHandle, RawWindowHandle, WaylandDisplayHandle, WaylandWindowHandle,
|
||||
};
|
||||
use smithay_client_toolkit::{
|
||||
compositor::CompositorState,
|
||||
output::OutputState,
|
||||
registry::RegistryState,
|
||||
seat::SeatState,
|
||||
shell::{
|
||||
WaylandSurface,
|
||||
wlr_layer::{Anchor, LayerShell, LayerSurface},
|
||||
},
|
||||
};
|
||||
use std::{borrow::Cow, env::args, ptr::NonNull, str::FromStr, time::Instant};
|
||||
use wayland_client::{Connection, Proxy, QueueHandle, globals::registry_queue_init};
|
||||
use wgpu::{BindGroup, Buffer, util::DeviceExt};
|
||||
|
||||
use crate::color_palette::ColorPalette;
|
||||
|
||||
mod color_palette;
|
||||
mod implementations;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
struct Vertex {
|
||||
position: [f32; 2],
|
||||
}
|
||||
impl Vertex {
|
||||
#[allow(dead_code)]
|
||||
fn triangle(x: f32, y: f32, w: f32, h: f32) -> [Self; 3] {
|
||||
let x0 = x;
|
||||
let x1 = x + w;
|
||||
let y0 = y;
|
||||
let y1 = y + h;
|
||||
|
||||
[
|
||||
Vertex { position: [x0, y0] },
|
||||
Vertex { position: [x1, y0] },
|
||||
Vertex { position: [x1, y1] },
|
||||
]
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
fn rectangle(x: f32, y: f32, w: f32, h: f32) -> [Self; 6] {
|
||||
let x0 = x;
|
||||
let x1 = x + w;
|
||||
let y0 = y;
|
||||
let y1 = y + h;
|
||||
|
||||
[
|
||||
Vertex { position: [x0, y0] }, // Triangle 1
|
||||
Vertex { position: [x1, y0] },
|
||||
Vertex { position: [x1, y1] },
|
||||
Vertex { position: [x0, y0] }, // Triangle 2
|
||||
Vertex { position: [x1, y1] },
|
||||
Vertex { position: [x0, y1] },
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
struct InstanceData {
|
||||
direction: [f32; 2],
|
||||
color: [f32; 3],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
struct Uniforms {
|
||||
time: f32,
|
||||
}
|
||||
|
||||
impl Uniforms {
|
||||
fn new() -> Self {
|
||||
Self { time: 0.0 }
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
||||
let conn = Connection::connect_to_env().unwrap();
|
||||
let (globals, mut event_queue) = registry_queue_init(&conn).unwrap();
|
||||
let qh = event_queue.handle();
|
||||
|
||||
// Initialize xdg_shell handlers so we can select the correct adapter
|
||||
let compositor_state =
|
||||
CompositorState::bind(&globals, &qh).expect("wl_compositor not available");
|
||||
let layer_state = LayerShell::bind(&globals, &qh).expect("layer_shell not available");
|
||||
|
||||
let surface = compositor_state.create_surface(&qh);
|
||||
// Create the window for adapter selection
|
||||
let layer = layer_state.create_layer_surface(
|
||||
&qh,
|
||||
surface,
|
||||
smithay_client_toolkit::shell::wlr_layer::Layer::Top,
|
||||
Some(""),
|
||||
None,
|
||||
);
|
||||
layer.set_anchor(Anchor::TOP | Anchor::BOTTOM | Anchor::LEFT | Anchor::RIGHT);
|
||||
let (width, height) = (400, 400);
|
||||
layer.set_size(0, 0); // 0 width = stretch to full width
|
||||
layer.set_opaque_region(None);
|
||||
layer.commit();
|
||||
|
||||
// Initialize wgpu
|
||||
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
|
||||
backends: wgpu::Backends::all(),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
// Create the raw window handle for the surface.
|
||||
let raw_display_handle = RawDisplayHandle::Wayland(WaylandDisplayHandle::new(
|
||||
NonNull::new(conn.backend().display_ptr() as *mut _).unwrap(),
|
||||
));
|
||||
let raw_window_handle = RawWindowHandle::Wayland(WaylandWindowHandle::new(
|
||||
NonNull::new(layer.wl_surface().id().as_ptr() as *mut _).unwrap(),
|
||||
));
|
||||
|
||||
let surface = unsafe {
|
||||
instance
|
||||
.create_surface_unsafe(wgpu::SurfaceTargetUnsafe::RawHandle {
|
||||
raw_display_handle,
|
||||
raw_window_handle,
|
||||
})
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
// Pick a supported adapter
|
||||
let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
compatible_surface: Some(&surface),
|
||||
..Default::default()
|
||||
}))
|
||||
.expect("Failed to find suitable adapter");
|
||||
|
||||
let (device, queue) = pollster::block_on(adapter.request_device(&Default::default()))
|
||||
.expect("Failed to request device");
|
||||
|
||||
let mut surface_config = surface.get_default_config(&adapter, 200, 200).unwrap();
|
||||
surface_config.alpha_mode = wgpu::CompositeAlphaMode::PreMultiplied;
|
||||
surface_config.format = wgpu::TextureFormat::Bgra8Unorm;
|
||||
|
||||
let (layout, group, uniform_buffer, uniforms) = create_uniforms(&device);
|
||||
let (vertex_buffer, instance_buffer, vertex_count, instance_count) =
|
||||
create_vertex_buffer(&device, width as f32, height as f32);
|
||||
|
||||
let render_pipeline = create_pipeline(&device, surface_config.format, &layout);
|
||||
|
||||
let mut wgpu = Wgpu {
|
||||
registry_state: RegistryState::new(&globals),
|
||||
seat_state: SeatState::new(&globals, &qh),
|
||||
output_state: OutputState::new(&globals, &qh),
|
||||
|
||||
start_time: Instant::now(),
|
||||
first_configure: true,
|
||||
exit: false,
|
||||
width: 256,
|
||||
height: 256,
|
||||
window: layer,
|
||||
device,
|
||||
surface,
|
||||
queue,
|
||||
render_pipeline,
|
||||
group,
|
||||
|
||||
uniforms,
|
||||
uniform_buffer,
|
||||
vertex_buffer,
|
||||
instance_buffer,
|
||||
vertex_count,
|
||||
instance_count,
|
||||
};
|
||||
|
||||
// We don't draw immediately, the configure will notify us when to first draw.
|
||||
loop {
|
||||
event_queue.blocking_dispatch(&mut wgpu).unwrap();
|
||||
|
||||
if wgpu.exit {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// On exit we must destroy the surface before the window is destroyed.
|
||||
drop(wgpu.surface);
|
||||
drop(wgpu.window);
|
||||
}
|
||||
struct Wgpu {
|
||||
registry_state: RegistryState,
|
||||
seat_state: SeatState,
|
||||
output_state: OutputState,
|
||||
|
||||
start_time: Instant,
|
||||
exit: bool,
|
||||
first_configure: bool,
|
||||
width: u32,
|
||||
height: u32,
|
||||
window: LayerSurface,
|
||||
|
||||
device: wgpu::Device,
|
||||
queue: wgpu::Queue,
|
||||
surface: wgpu::Surface<'static>,
|
||||
render_pipeline: wgpu::RenderPipeline,
|
||||
group: wgpu::BindGroup,
|
||||
|
||||
uniforms: Uniforms,
|
||||
uniform_buffer: Buffer,
|
||||
vertex_buffer: Buffer,
|
||||
instance_buffer: Buffer,
|
||||
vertex_count: u32,
|
||||
instance_count: u32,
|
||||
}
|
||||
|
||||
impl Wgpu {
|
||||
fn draw(&mut self, _qh: &QueueHandle<Self>) {
|
||||
let elapsed = self.start_time.elapsed().as_secs_f32();
|
||||
self.update_time(elapsed);
|
||||
if elapsed > 3.0 {
|
||||
self.exit = true
|
||||
}
|
||||
let surface_texture = self
|
||||
.surface
|
||||
.get_current_texture()
|
||||
.expect("Failed to acquire next swap chain texture");
|
||||
let texture_view = surface_texture
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
let mut encoder = self
|
||||
.device
|
||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
|
||||
{
|
||||
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: None,
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: &texture_view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
depth_slice: None,
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
rpass.set_bind_group(0, &self.group, &[]);
|
||||
rpass.set_pipeline(&self.render_pipeline);
|
||||
rpass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
|
||||
rpass.set_vertex_buffer(1, self.instance_buffer.slice(..));
|
||||
rpass.draw(0..self.vertex_count, 0..self.instance_count);
|
||||
}
|
||||
self.queue.submit(Some(encoder.finish()));
|
||||
surface_texture.present();
|
||||
}
|
||||
pub fn update_time(&mut self, time: f32) {
|
||||
self.uniforms.time = time;
|
||||
self.queue
|
||||
.write_buffer(&self.uniform_buffer, 0, bytemuck::bytes_of(&self.uniforms));
|
||||
}
|
||||
}
|
||||
|
||||
fn create_pipeline(
|
||||
device: &wgpu::Device,
|
||||
swap_chain_format: wgpu::TextureFormat,
|
||||
bind_group_layout: &wgpu::BindGroupLayout,
|
||||
) -> wgpu::RenderPipeline {
|
||||
// Load theushaders from disk
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: None,
|
||||
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))),
|
||||
});
|
||||
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Render Pipeline Layout"),
|
||||
bind_group_layouts: &[bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
let vertex_buffer_layout = wgpu::VertexBufferLayout {
|
||||
array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
|
||||
step_mode: wgpu::VertexStepMode::Vertex,
|
||||
attributes: &[wgpu::VertexAttribute {
|
||||
format: wgpu::VertexFormat::Float32x2,
|
||||
offset: 0,
|
||||
shader_location: 0,
|
||||
}],
|
||||
};
|
||||
let instance_buffer_layout = wgpu::VertexBufferLayout {
|
||||
array_stride: std::mem::size_of::<InstanceData>() as wgpu::BufferAddress,
|
||||
step_mode: wgpu::VertexStepMode::Instance,
|
||||
attributes: &[
|
||||
wgpu::VertexAttribute {
|
||||
format: wgpu::VertexFormat::Float32x2, // v_start
|
||||
offset: 0,
|
||||
shader_location: 1,
|
||||
},
|
||||
wgpu::VertexAttribute {
|
||||
format: wgpu::VertexFormat::Float32x3, // color
|
||||
offset: std::mem::size_of::<[f32; 2]>() as wgpu::BufferAddress,
|
||||
shader_location: 2,
|
||||
},
|
||||
],
|
||||
};
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: None,
|
||||
layout: Some(&pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: Some("vs_main"),
|
||||
buffers: &[vertex_buffer_layout, instance_buffer_layout],
|
||||
compilation_options: Default::default(),
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: Some("fs_main"),
|
||||
compilation_options: Default::default(),
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format: swap_chain_format,
|
||||
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
// strip_index_format: None,
|
||||
// front_face: wgpu::FrontFace::Ccw,
|
||||
// cull_mode: Some(wgpu::Face::Back),
|
||||
// // Setting this to anything other than Fill requires Features::POLYGON_MODE_LINE
|
||||
// // or Features::POLYGON_MODE_POINT
|
||||
// polygon_mode: wgpu::PolygonMode::Fill,
|
||||
// // Requires Features::DEPTH_CLIP_CONTROL
|
||||
// unclipped_depth: false,
|
||||
// // Requires Features::CONSERVATIVE_RASTERIZATION
|
||||
// conservative: false,
|
||||
..Default::default()
|
||||
},
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState::default(),
|
||||
multiview: None,
|
||||
cache: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn create_uniforms(device: &wgpu::Device) -> (wgpu::BindGroupLayout, BindGroup, Buffer, Uniforms) {
|
||||
let uniforms = Uniforms::new();
|
||||
let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Uniform Buffer"),
|
||||
contents: bytemuck::bytes_of(&uniforms),
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
});
|
||||
|
||||
let uniform_bind_group_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
entries: &[wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
label: Some("uniform_bind_group_layout"),
|
||||
});
|
||||
|
||||
let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &uniform_bind_group_layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: uniform_buffer.as_entire_binding(),
|
||||
}],
|
||||
label: Some("uniform_bind_group"),
|
||||
});
|
||||
|
||||
(
|
||||
uniform_bind_group_layout,
|
||||
uniform_bind_group,
|
||||
uniform_buffer,
|
||||
uniforms,
|
||||
)
|
||||
}
|
||||
|
||||
fn create_vertex_buffer(
|
||||
device: &wgpu::Device,
|
||||
width: f32,
|
||||
height: f32,
|
||||
) -> (Buffer, Buffer, u32, u32) {
|
||||
// Only 1 rectangle vertices here, since instances define position:
|
||||
let rectangle = Vertex::rectangle(0.0, 0.0, 0.00002 * height, 0.00002 * width);
|
||||
let args = args().collect::<Vec<String>>();
|
||||
let pallette = extreact_flag_value::<ColorPalette>(&args, "--pallette").unwrap_or_default();
|
||||
let colors = pallette.get_colors();
|
||||
let color_count = colors.len();
|
||||
|
||||
let mut rng = rand::rng();
|
||||
let instances = (0..200)
|
||||
.map(|_| {
|
||||
let x = rng.random_range(-1.0..1.0) as f32;
|
||||
let y_max = (1.0 - x * x).sqrt() * 2.5;
|
||||
let y = rng.random_range(-0.5..y_max);
|
||||
InstanceData {
|
||||
direction: [x * 1.2, y],
|
||||
color: colors
|
||||
.get(rng.random_range(0..color_count))
|
||||
.unwrap()
|
||||
.clone(),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<InstanceData>>();
|
||||
|
||||
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Rectangle Vertex Buffer"),
|
||||
contents: bytemuck::cast_slice(&rectangle),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
});
|
||||
|
||||
let instance_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Instance Buffer"),
|
||||
contents: bytemuck::cast_slice(&instances),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
});
|
||||
|
||||
(
|
||||
vertex_buffer,
|
||||
instance_buffer,
|
||||
rectangle.len() as u32,
|
||||
instances.len() as u32,
|
||||
)
|
||||
}
|
||||
|
||||
fn extreact_flag_value<T: FromStr>(args: &Vec<String>, name: &str) -> Option<T> {
|
||||
let pos = args.iter().position(|arg| arg == name)?;
|
||||
let val = args.get(pos + 1)?;
|
||||
if val.starts_with("-") {
|
||||
return None;
|
||||
}
|
||||
val.parse::<T>().ok()
|
||||
}
|
||||
40
src/shader.wgsl
Normal file
40
src/shader.wgsl
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
struct Uniforms {
|
||||
time: f32,
|
||||
};
|
||||
|
||||
struct VertexInput {
|
||||
@location(0) position: vec2<f32>,
|
||||
@location(1) v_start: vec2<f32>,
|
||||
@location(2) color: vec3<f32>, // Add color if you want per-vertex or per-instance colors
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) color: vec3<f32>, // Pass color to fragment shader
|
||||
};
|
||||
|
||||
@group(0) @binding(0)
|
||||
var<uniform> uniforms: Uniforms;
|
||||
|
||||
var<private> g: vec2<f32> = vec2<f32>(0.0, -0.2);
|
||||
|
||||
@vertex
|
||||
fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
let t = uniforms.time;
|
||||
let decay = 0.01;
|
||||
|
||||
let new_x = clamp(input.position.x + input.v_start.x * t - t * decay, -1.0, 1.0);
|
||||
let new_y = input.position.y + input.v_start.y * t - t * t;
|
||||
|
||||
var output: VertexOutput;
|
||||
output.position = vec4<f32>(new_x, new_y, 0.0, 1.0);
|
||||
output.color = input.color;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
|
||||
return vec4<f32>(input.color, 1.0);
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue