Initial release of Lumen Wallpaper
This commit is contained in:
+14
@@ -0,0 +1,14 @@
|
|||||||
|
/build/
|
||||||
|
/target/
|
||||||
|
/dist/
|
||||||
|
/CMakeFiles/
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
*.log
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*.tmp
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
.cache/
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## 0.1.0
|
||||||
|
|
||||||
|
Initial public release.
|
||||||
|
|
||||||
|
- Native KDE Plasma 6 wallpaper package and settings page.
|
||||||
|
- Rust/Vulkan renderer with direct Qt command-buffer integration.
|
||||||
|
- Runtime Naga GLSL-to-SPIR-V compilation and a procedural built-in shader.
|
||||||
|
- Per-monitor resolution, frame-rate limiting, speed, pause, and mouse input.
|
||||||
|
- Debounced hot reload and last-working-shader fallback with useful diagnostics.
|
||||||
|
- Per-frame uniform slots and configurable MSAA support.
|
||||||
|
- Self-extracting source-building installer for Fedora, Debian/Ubuntu, and Arch Linux.
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.22)
|
||||||
|
project(lumen-wallpaper VERSION 0.1.0 LANGUAGES CXX)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_AUTOMOC ON)
|
||||||
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
|
find_package(ECM 6.0.0 REQUIRED NO_MODULE)
|
||||||
|
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
|
||||||
|
include(KDEInstallDirs)
|
||||||
|
include(KDECMakeSettings)
|
||||||
|
include(KDECompilerSettings NO_POLICY_SCOPE)
|
||||||
|
|
||||||
|
find_package(Qt6 6.6 REQUIRED COMPONENTS Core Gui Qml Quick)
|
||||||
|
|
||||||
|
set(LUMEN_PACKAGE_ID dev.emil.lumen)
|
||||||
|
set(LUMEN_INSTALL_DIR "${KDE_INSTALL_DATADIR}/plasma/wallpapers/${LUMEN_PACKAGE_ID}")
|
||||||
|
set(LUMEN_RUST_LIBRARY "${CMAKE_BINARY_DIR}/cargo/release/liblumen_renderer.a")
|
||||||
|
|
||||||
|
file(GLOB_RECURSE LUMEN_RUST_SOURCES CONFIGURE_DEPENDS
|
||||||
|
"${CMAKE_SOURCE_DIR}/renderer/*.rs"
|
||||||
|
)
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT "${LUMEN_RUST_LIBRARY}"
|
||||||
|
COMMAND "${CMAKE_COMMAND}" -E env CARGO_TARGET_DIR=${CMAKE_BINARY_DIR}/cargo
|
||||||
|
cargo build --release
|
||||||
|
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||||
|
DEPENDS ${LUMEN_RUST_SOURCES}
|
||||||
|
"${CMAKE_SOURCE_DIR}/Cargo.toml"
|
||||||
|
"${CMAKE_SOURCE_DIR}/Cargo.lock"
|
||||||
|
COMMENT "Building the Rust Vulkan renderer"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
add_custom_target(lumen_renderer_build DEPENDS "${LUMEN_RUST_LIBRARY}")
|
||||||
|
add_library(lumen_renderer STATIC IMPORTED GLOBAL)
|
||||||
|
set_target_properties(lumen_renderer PROPERTIES IMPORTED_LOCATION "${LUMEN_RUST_LIBRARY}")
|
||||||
|
add_dependencies(lumen_renderer lumen_renderer_build)
|
||||||
|
|
||||||
|
add_library(lumenplugin MODULE
|
||||||
|
src/plugin.cpp
|
||||||
|
src/plugin.h
|
||||||
|
src/wallpaperitem.cpp
|
||||||
|
src/wallpaperitem.h
|
||||||
|
src/rendernode.cpp
|
||||||
|
src/rendernode.h
|
||||||
|
src/lumen_ffi.h
|
||||||
|
)
|
||||||
|
target_include_directories(lumenplugin PRIVATE "${CMAKE_SOURCE_DIR}/src")
|
||||||
|
target_link_libraries(lumenplugin PRIVATE
|
||||||
|
Qt6::Core
|
||||||
|
Qt6::Gui
|
||||||
|
Qt6::Qml
|
||||||
|
Qt6::Quick
|
||||||
|
lumen_renderer
|
||||||
|
${CMAKE_DL_LIBS}
|
||||||
|
pthread
|
||||||
|
m
|
||||||
|
)
|
||||||
|
set_target_properties(lumenplugin PROPERTIES
|
||||||
|
PREFIX "lib"
|
||||||
|
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/package/contents/ui/lumen"
|
||||||
|
)
|
||||||
|
|
||||||
|
install(DIRECTORY package/ DESTINATION "${LUMEN_INSTALL_DIR}")
|
||||||
|
install(TARGETS lumenplugin
|
||||||
|
LIBRARY DESTINATION "${LUMEN_INSTALL_DIR}/contents/ui/lumen"
|
||||||
|
)
|
||||||
Generated
+408
@@ -0,0 +1,408 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arrayvec"
|
||||||
|
version = "0.7.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ash"
|
||||||
|
version = "0.38.0+1.3.281"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f"
|
||||||
|
dependencies = [
|
||||||
|
"libloading",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bit-set"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09ec2f926cc3060f09db9ebc5b52823d85268d24bb917e472c0c4bea35780a7d"
|
||||||
|
dependencies = [
|
||||||
|
"bit-vec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bit-vec"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytemuck"
|
||||||
|
version = "1.25.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytemuck_derive"
|
||||||
|
version = "1.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6a1f896587b6f2c069c73d2f0913e2d590c3990285cd2f0b6aa02b786b4c679c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.9",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg_aliases"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "codespan-reporting"
|
||||||
|
version = "0.13.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"termcolor",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crunchy"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "equivalent"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foldhash"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "half"
|
||||||
|
version = "2.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"crunchy",
|
||||||
|
"num-traits",
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.17.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||||
|
dependencies = [
|
||||||
|
"foldhash",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "2.14.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
|
||||||
|
dependencies = [
|
||||||
|
"equivalent",
|
||||||
|
"hashbrown",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libloading"
|
||||||
|
version = "0.8.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libm"
|
||||||
|
version = "0.2.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lumen-wallpaper"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"ash",
|
||||||
|
"bytemuck",
|
||||||
|
"naga",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "naga"
|
||||||
|
version = "30.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a616d2fb8c89516ac2723a581f69d6c18576046bed761bd6b305e5618e6ae130"
|
||||||
|
dependencies = [
|
||||||
|
"arrayvec",
|
||||||
|
"bit-set",
|
||||||
|
"bitflags",
|
||||||
|
"cfg-if",
|
||||||
|
"cfg_aliases",
|
||||||
|
"codespan-reporting",
|
||||||
|
"half",
|
||||||
|
"hashbrown",
|
||||||
|
"indexmap",
|
||||||
|
"libm",
|
||||||
|
"log",
|
||||||
|
"naga-types",
|
||||||
|
"num-traits",
|
||||||
|
"once_cell",
|
||||||
|
"pp-rs",
|
||||||
|
"rustc-hash",
|
||||||
|
"spirv",
|
||||||
|
"thiserror",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "naga-types"
|
||||||
|
version = "30.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "590afbf58a6f4f62873cd5cff4468061844bafa1cdf399cc954537c22d768d49"
|
||||||
|
dependencies = [
|
||||||
|
"hashbrown",
|
||||||
|
"indexmap",
|
||||||
|
"rustc-hash",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"libm",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.21.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pp-rs"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bb458bb7f6e250e6eb79d5026badc10a3ebb8f9a15d1fff0f13d17c71f4d6dee"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-xid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.107"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.47"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-hash"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.229"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
||||||
|
dependencies = [
|
||||||
|
"serde_core",
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_core"
|
||||||
|
version = "1.0.229"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.229"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.9",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "spirv"
|
||||||
|
version = "0.4.0+sdk-1.4.341.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d9571ea910ebd84c86af4b3ed27f9dbdc6ad06f17c5f96146b2b671e2976744f"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.119"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "3.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termcolor"
|
||||||
|
version = "1.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "2.0.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "2.0.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.9",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-xid"
|
||||||
|
version = "0.2.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-util"
|
||||||
|
version = "0.1.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-link"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.61.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy"
|
||||||
|
version = "0.8.63"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e5fe1f8f1b06191a00962174c61aa5005e0bb391a6d80d07e24d115c01a92ed8"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy-derive"
|
||||||
|
version = "0.8.63"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "863ad3ac83293fb4d740aedbfdc9240dd8d1a50c1099acd76ce80ce7c7230c7f"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
[package]
|
||||||
|
name = "lumen-wallpaper"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
rust-version = "1.87"
|
||||||
|
publish = false
|
||||||
|
description = "Procedural GLSL wallpapers for KDE Plasma 6, rendered with Rust and Vulkan"
|
||||||
|
license = "GPL-3.0-or-later"
|
||||||
|
repository = "https://git.shanaty.ru/Emil_Shanaty/Lumen-wallpaper"
|
||||||
|
readme = "README.md"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "lumen_renderer"
|
||||||
|
path = "renderer/src/lib.rs"
|
||||||
|
crate-type = ["staticlib", "rlib"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "lumen-shader-check"
|
||||||
|
path = "renderer/src/bin/shader_check.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
ash = { version = "0.38.0", default-features = false, features = ["loaded"] }
|
||||||
|
bytemuck = { version = "1.22", features = ["derive"] }
|
||||||
|
naga = { version = "30.0.1", features = ["glsl-in", "wgsl-in", "spv-out", "termcolor"] }
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
lto = "thin"
|
||||||
|
codegen-units = 1
|
||||||
|
panic = "unwind"
|
||||||
@@ -0,0 +1,674 @@
|
|||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
the GNU General Public License is intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users. We, the Free Software Foundation, use the
|
||||||
|
GNU General Public License for most of our software; it applies also to
|
||||||
|
any other work released this way by its authors. You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to prevent others from denying you
|
||||||
|
these rights or asking you to surrender the rights. Therefore, you have
|
||||||
|
certain responsibilities if you distribute copies of the software, or if
|
||||||
|
you modify it: responsibilities to respect the freedom of others.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must pass on to the recipients the same
|
||||||
|
freedoms that you received. You must make sure that they, too, receive
|
||||||
|
or can get the source code. And you must show them these terms so they
|
||||||
|
know their rights.
|
||||||
|
|
||||||
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
|
(1) assert copyright on the software, and (2) offer you this License
|
||||||
|
giving you legal permission to copy, distribute and/or modify it.
|
||||||
|
|
||||||
|
For the developers' and authors' protection, the GPL clearly explains
|
||||||
|
that there is no warranty for this free software. For both users' and
|
||||||
|
authors' sake, the GPL requires that modified versions be marked as
|
||||||
|
changed, so that their problems will not be attributed erroneously to
|
||||||
|
authors of previous versions.
|
||||||
|
|
||||||
|
Some devices are designed to deny users access to install or run
|
||||||
|
modified versions of the software inside them, although the manufacturer
|
||||||
|
can do so. This is fundamentally incompatible with the aim of
|
||||||
|
protecting users' freedom to change the software. The systematic
|
||||||
|
pattern of such abuse occurs in the area of products for individuals to
|
||||||
|
use, which is precisely where it is most unacceptable. Therefore, we
|
||||||
|
have designed this version of the GPL to prohibit the practice for those
|
||||||
|
products. If such problems arise substantially in other domains, we
|
||||||
|
stand ready to extend this provision to those domains in future versions
|
||||||
|
of the GPL, as needed to protect the freedom of users.
|
||||||
|
|
||||||
|
Finally, every program is threatened constantly by software patents.
|
||||||
|
States should not allow patents to restrict development and use of
|
||||||
|
software on general-purpose computers, but in those that do, we wish to
|
||||||
|
avoid the special danger that patents applied to a free program could
|
||||||
|
make it effectively proprietary. To prevent this, the GPL assures that
|
||||||
|
patents cannot be used to render the program non-free.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Use with the GNU Affero General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU Affero General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the special requirements of the GNU Affero General Public License,
|
||||||
|
section 13, concerning interaction through a network will apply to the
|
||||||
|
combination as such.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program does terminal interaction, make it output a short
|
||||||
|
notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
|
<program> Copyright (C) <year> <name of author>
|
||||||
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, your program's commands
|
||||||
|
might be different; for a GUI interface, you would use an "about box".
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
|
<https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
The GNU General Public License does not permit incorporating your program
|
||||||
|
into proprietary programs. If your program is a subroutine library, you
|
||||||
|
may consider it more useful to permit linking proprietary applications with
|
||||||
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License. But first, please read
|
||||||
|
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
# Lumen Wallpaper
|
||||||
|
|
||||||
|
Procedural GLSL wallpapers for KDE Plasma 6, rendered natively with Rust and Vulkan.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Lumen is a native Plasma wallpaper package. A small C++/QML bridge connects it to Qt Quick, while shader compilation and Vulkan rendering live in Rust. Frames are recorded directly into Qt's Vulkan command buffer; there is no video, browser, OpenGL fallback, or per-frame CPU copy.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Native wallpaper type and settings in KDE System Settings.
|
||||||
|
- Independent rendering at each monitor's native resolution and device pixel ratio.
|
||||||
|
- Runtime GLSL-to-SPIR-V compilation with Naga.
|
||||||
|
- Shadertoy-style `mainImage()` fragment shaders.
|
||||||
|
- Mouse interaction, animation speed, frame-rate limit, and pause.
|
||||||
|
- Hot reload with debouncing and the last working shader preserved after errors.
|
||||||
|
- Correct per-frame resources for Qt's frames in flight and configurable MSAA.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Linux with KDE Plasma 6 and Qt Quick 6.6 or newer; Wayland is the tested configuration.
|
||||||
|
- A working Vulkan driver. Qt Quick must use its Vulkan backend.
|
||||||
|
- CMake 3.22+, Ninja, a C++20 compiler, and Rust/Cargo 1.87+ to build from source.
|
||||||
|
- Qt 6 development files, Extra CMake Modules, Vulkan headers, Kirigami, and Plasma Workspace.
|
||||||
|
|
||||||
|
The installer can offer to install these packages on Fedora, Debian/Ubuntu, and Arch Linux. System packages are changed only after confirmation. The wallpaper itself installs into `~/.local` and does not require sudo.
|
||||||
|
|
||||||
|
## One-file installer
|
||||||
|
|
||||||
|
Download `lumen-wallpaper-0.1.0-installer.run` and its checksum from [Releases](https://git.shanaty.ru/Emil_Shanaty/Lumen-wallpaper/releases), then run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
chmod +x lumen-wallpaper-0.1.0-installer.run
|
||||||
|
./lumen-wallpaper-0.1.0-installer.run
|
||||||
|
```
|
||||||
|
|
||||||
|
The installer extracts the included source into a temporary directory, checks dependencies, builds the plugin for the laptop's Qt version, installs it, and activates it in the running Plasma session.
|
||||||
|
|
||||||
|
A native Plasma plugin cannot be portable across arbitrary Qt versions as a prebuilt binary. Building on the target machine is intentional and avoids Qt/C++ ABI mismatches.
|
||||||
|
|
||||||
|
Useful options:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./lumen-wallpaper-0.1.0-installer.run --no-deps # do not offer package installation
|
||||||
|
./lumen-wallpaper-0.1.0-installer.run --no-activate # install only
|
||||||
|
./lumen-wallpaper-0.1.0-installer.run --yes # non-interactive confirmation
|
||||||
|
./lumen-wallpaper-0.1.0-installer.run --help
|
||||||
|
```
|
||||||
|
|
||||||
|
`PREFIX` and `BUILD_DIR` can override the installation and build locations for an isolated installation.
|
||||||
|
|
||||||
|
## From source
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://git.shanaty.ru/Emil_Shanaty/Lumen-wallpaper.git
|
||||||
|
cd Lumen-wallpaper
|
||||||
|
./scripts/install.sh
|
||||||
|
./scripts/activate.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
`install.sh` only builds and installs. `activate.sh` backs up the current wallpaper configuration, selects Lumen on all desktops, configures `QSG_RHI_BACKEND=vulkan`, and restarts `plasmashell`.
|
||||||
|
|
||||||
|
To enable it manually, open **System Settings → Wallpaper**, select **Lumen Wallpaper**, and click Apply. The Vulkan environment is loaded for new Plasma sessions from `~/.config/plasma-workspace/env/lumen-vulkan.sh`; log out and back in if necessary.
|
||||||
|
|
||||||
|
To return to the previous wallpaper, select it in the same settings page. Activation backups are kept outside the source package in the build directory.
|
||||||
|
|
||||||
|
## Custom shaders
|
||||||
|
|
||||||
|
Choose a `.frag` or `.glsl` file in the wallpaper settings, or leave the path empty to use the built-in Lumen shader. A minimal shader looks like this:
|
||||||
|
|
||||||
|
```glsl
|
||||||
|
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
||||||
|
vec2 uv = fragCoord / iResolution.xy;
|
||||||
|
float pulse = 0.5 + 0.5 * sin(iTime + uv.x);
|
||||||
|
fragColor = vec4(0.05, 0.25 * pulse, 0.45 * pulse, 1.0);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Available values include `iResolution`, `iTime`, `iTimeDelta`, `iFrameRate`, `iFrame`, `iMouse`, `iDate`, `iScreenIndex`, and `iScreenCount`. Mouse coordinates use Shadertoy's bottom-left origin. On Wayland, pointer input is available while the pointer is over the desktop.
|
||||||
|
|
||||||
|
See `examples/Gradient.frag` for a complete example. Validate a file before selecting it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo run --bin lumen-shader-check -- /absolute/path/to/shader.frag
|
||||||
|
```
|
||||||
|
|
||||||
|
The source directory is not scanned for shaders, and no shader is downloaded automatically.
|
||||||
|
|
||||||
|
### Current limitations
|
||||||
|
|
||||||
|
- Only single-pass image shaders are supported.
|
||||||
|
- Feedback buffers A–D, `iChannel*`, textures, and audio input are not implemented yet.
|
||||||
|
- Layered Qt items and arbitrary offscreen Qt render targets are outside the supported wallpaper-surface path.
|
||||||
|
- OpenGL is not a rendering fallback; Lumen requires Qt Quick's Vulkan backend.
|
||||||
|
|
||||||
|
## Development and release packaging
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo fmt --check
|
||||||
|
cargo check
|
||||||
|
./scripts/install.sh
|
||||||
|
./scripts/package-release.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
The packaging script uses `git archive`, so local build outputs, logs, screenshots, backups, credentials, and other untracked files cannot enter a release archive. Artifacts are written to `dist/`:
|
||||||
|
|
||||||
|
- `lumen-wallpaper-0.1.0-source.tar.gz`
|
||||||
|
- `lumen-wallpaper-0.1.0-installer.run`
|
||||||
|
- `SHA256SUMS`
|
||||||
|
|
||||||
|
Test the self-extracting installer without changing system packages or the active wallpaper:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PREFIX="$PWD/build/test-prefix" ./dist/lumen-wallpaper-0.1.0-installer.run \
|
||||||
|
--no-deps --no-activate
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Lumen Wallpaper is distributed under [GPL-3.0-or-later](LICENSE).
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 180 KiB |
@@ -0,0 +1,10 @@
|
|||||||
|
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
||||||
|
vec2 uv = fragCoord / iResolution.xy;
|
||||||
|
float wave = 0.5 + 0.5 * sin(iTime + uv.x * 6.2831853);
|
||||||
|
vec3 deep = vec3(0.015, 0.025, 0.07);
|
||||||
|
vec3 light = vec3(0.08, 0.65, 0.78);
|
||||||
|
vec3 color = mix(deep, light, wave * (0.35 + 0.65 * uv.y));
|
||||||
|
float glow = exp(-8.0 * dot(uv - 0.5, uv - 0.5));
|
||||||
|
color += vec3(0.25, 0.08, 0.35) * glow;
|
||||||
|
fragColor = vec4(color, 1.0);
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<kcfg>
|
||||||
|
<group name="Wallpaper">
|
||||||
|
<entry name="shaderPath" type="String">
|
||||||
|
<default></default>
|
||||||
|
</entry>
|
||||||
|
<entry name="targetFps" type="Int">
|
||||||
|
<default>30</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="speed" type="Double">
|
||||||
|
<default>1.0</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="running" type="Bool">
|
||||||
|
<default>true</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="mouseEnabled" type="Bool">
|
||||||
|
<default>true</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="watchSource" type="Bool">
|
||||||
|
<default>true</default>
|
||||||
|
</entry>
|
||||||
|
</group>
|
||||||
|
</kcfg>
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls as Controls
|
||||||
|
import QtQuick.Dialogs
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import org.kde.kirigami as Kirigami
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: configRoot
|
||||||
|
|
||||||
|
property alias cfg_shaderPath: shaderPath.text
|
||||||
|
property alias cfg_targetFps: fpsSlider.value
|
||||||
|
property alias cfg_speed: speedSlider.value
|
||||||
|
property alias cfg_running: runningCheck.checked
|
||||||
|
property alias cfg_mouseEnabled: mouseCheck.checked
|
||||||
|
property alias cfg_watchSource: watchCheck.checked
|
||||||
|
|
||||||
|
implicitWidth: 560
|
||||||
|
implicitHeight: form.implicitHeight + Kirigami.Units.gridUnit * 2
|
||||||
|
|
||||||
|
FileDialog {
|
||||||
|
id: shaderDialog
|
||||||
|
title: "Choose a fragment shader"
|
||||||
|
nameFilters: ["Fragment shaders (*.frag *.glsl)", "All files (*)"]
|
||||||
|
onAccepted: shaderPath.text = selectedFile.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.FormLayout {
|
||||||
|
id: form
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
right: parent.right
|
||||||
|
top: parent.top
|
||||||
|
margins: Kirigami.Units.gridUnit
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Kirigami.FormData.label: "Fragment shader:"
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
Controls.TextField {
|
||||||
|
id: shaderPath
|
||||||
|
Layout.fillWidth: true
|
||||||
|
placeholderText: "Built-in Lumen shader"
|
||||||
|
}
|
||||||
|
Controls.Button {
|
||||||
|
text: "Browse…"
|
||||||
|
onClicked: shaderDialog.open()
|
||||||
|
}
|
||||||
|
Controls.Button {
|
||||||
|
text: "Reset"
|
||||||
|
onClicked: shaderPath.text = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Controls.Label {
|
||||||
|
text: "Shadertoy-style GLSL with mainImage(). Files reload automatically when enabled below."
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
Layout.fillWidth: true
|
||||||
|
opacity: 0.7
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Kirigami.FormData.label: "Frame rate:"
|
||||||
|
Controls.Slider {
|
||||||
|
id: fpsSlider
|
||||||
|
from: 5
|
||||||
|
to: 120
|
||||||
|
stepSize: 1
|
||||||
|
value: 30
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
Controls.Label {
|
||||||
|
text: Math.round(fpsSlider.value) + " FPS"
|
||||||
|
Layout.minimumWidth: 64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Kirigami.FormData.label: "Animation speed:"
|
||||||
|
Controls.Slider {
|
||||||
|
id: speedSlider
|
||||||
|
from: 0.1
|
||||||
|
to: 3.0
|
||||||
|
stepSize: 0.05
|
||||||
|
value: 1.0
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
Controls.Label {
|
||||||
|
text: speedSlider.value.toFixed(2) + "×"
|
||||||
|
Layout.minimumWidth: 64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Controls.CheckBox {
|
||||||
|
id: runningCheck
|
||||||
|
Kirigami.FormData.label: "Playback:"
|
||||||
|
text: "Animate wallpaper"
|
||||||
|
checked: true
|
||||||
|
}
|
||||||
|
Controls.CheckBox {
|
||||||
|
id: mouseCheck
|
||||||
|
text: "React to the pointer over the desktop"
|
||||||
|
checked: true
|
||||||
|
}
|
||||||
|
Controls.CheckBox {
|
||||||
|
id: watchCheck
|
||||||
|
text: "Reload the shader when its file changes"
|
||||||
|
checked: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
module dev.emil.lumen
|
||||||
|
plugin lumenplugin
|
||||||
|
classname LumenPlugin
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import QtQuick
|
||||||
|
import org.kde.plasma.plasmoid
|
||||||
|
import "lumen"
|
||||||
|
|
||||||
|
WallpaperItem {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
readonly property var activeConfiguration: {
|
||||||
|
if (typeof wallpaper !== "undefined" && wallpaper && wallpaper.configuration)
|
||||||
|
return wallpaper.configuration
|
||||||
|
return typeof configuration !== "undefined" ? configuration : null
|
||||||
|
}
|
||||||
|
|
||||||
|
function option(name, fallback) {
|
||||||
|
if (!activeConfiguration)
|
||||||
|
return fallback
|
||||||
|
const value = activeConfiguration[name]
|
||||||
|
return value === undefined ? fallback : value
|
||||||
|
}
|
||||||
|
|
||||||
|
VulkanWallpaper {
|
||||||
|
id: engine
|
||||||
|
anchors.fill: parent
|
||||||
|
shaderUrl: {
|
||||||
|
const path = String(root.option("shaderPath", ""))
|
||||||
|
return path.length > 0 ? path : Qt.resolvedUrl("shaders/Lumen.frag")
|
||||||
|
}
|
||||||
|
targetFps: Number(root.option("targetFps", 30))
|
||||||
|
speed: Number(root.option("speed", 1.0))
|
||||||
|
running: Boolean(root.option("running", true))
|
||||||
|
mouseEnabled: Boolean(root.option("mouseEnabled", true))
|
||||||
|
watchSource: Boolean(root.option("watchSource", true))
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: "#111522"
|
||||||
|
visible: engine.errorMessage.length > 0 && !engine.fallbackActive
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: Math.min(parent.width * 0.8, 640)
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
color: "#f3f5fa"
|
||||||
|
font.pixelSize: 16
|
||||||
|
text: "Lumen Wallpaper\n\n" + engine.errorMessage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.bottomMargin: 80
|
||||||
|
height: warningText.implicitHeight + 16
|
||||||
|
color: "#cc421f35"
|
||||||
|
visible: engine.fallbackActive && engine.errorMessage.length > 0
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: warningText
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.margins: 12
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
elide: Text.ElideRight
|
||||||
|
maximumLineCount: 3
|
||||||
|
color: "#ffd9e2"
|
||||||
|
text: "Lumen: using the last working shader\n" + engine.errorMessage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
float hash21(vec2 p) {
|
||||||
|
p = fract(p * vec2(123.34, 456.21));
|
||||||
|
p += dot(p, p + 45.32);
|
||||||
|
return fract(p.x * p.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
float noise21(vec2 p) {
|
||||||
|
vec2 i = floor(p);
|
||||||
|
vec2 f = fract(p);
|
||||||
|
f = f * f * (3.0 - 2.0 * f);
|
||||||
|
return mix(
|
||||||
|
mix(hash21(i), hash21(i + vec2(1.0, 0.0)), f.x),
|
||||||
|
mix(hash21(i + vec2(0.0, 1.0)), hash21(i + vec2(1.0, 1.0)), f.x),
|
||||||
|
f.y
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
float fbm(vec2 p) {
|
||||||
|
float value = 0.0;
|
||||||
|
float amplitude = 0.5;
|
||||||
|
mat2 turn = mat2(0.80, 0.60, -0.60, 0.80);
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
value += amplitude * noise21(p);
|
||||||
|
p = turn * p * 2.03 + vec2(3.1, 1.7);
|
||||||
|
amplitude *= 0.5;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
||||||
|
vec2 uv = fragCoord / iResolution.xy;
|
||||||
|
float unit = min(iResolution.x, iResolution.y);
|
||||||
|
vec2 p = (2.0 * fragCoord - iResolution.xy) / unit;
|
||||||
|
vec2 mouse = (2.0 * iMouse.xy - iResolution.xy) / unit;
|
||||||
|
float t = iTime * 0.12;
|
||||||
|
|
||||||
|
vec2 pointerDelta = p - mouse;
|
||||||
|
float pointerGlow = exp(-dot(pointerDelta, pointerDelta) * 4.0);
|
||||||
|
float pressed = iMouse.z > 0.0 ? 1.0 : 0.0;
|
||||||
|
|
||||||
|
vec2 q = vec2(
|
||||||
|
fbm(p * 0.65 + vec2(t * 0.35, -t * 0.20)),
|
||||||
|
fbm(p * 0.65 + vec2(4.7 - t * 0.24, 2.9 + t * 0.18))
|
||||||
|
);
|
||||||
|
vec2 r = vec2(
|
||||||
|
fbm(p + q * 1.8 + vec2(1.7, 8.2) + t * 0.16),
|
||||||
|
fbm(p + q * 1.8 + vec2(8.3, 2.8) - t * 0.13)
|
||||||
|
);
|
||||||
|
float field = fbm(p * 0.85 + r * 2.2 + pointerDelta * pointerGlow * 0.12);
|
||||||
|
|
||||||
|
float ribbon = 0.5 + 0.5 * sin(10.0 * field + 3.0 * r.x - 1.8 * p.y + t * 1.4);
|
||||||
|
float silk = pow(ribbon, 4.0);
|
||||||
|
float edge = pow(1.0 - abs(ribbon * 2.0 - 1.0), 10.0);
|
||||||
|
|
||||||
|
vec3 color = mix(vec3(0.006, 0.010, 0.032), vec3(0.075, 0.025, 0.16), field);
|
||||||
|
color = mix(color, vec3(0.02, 0.65, 0.72), silk * 0.72);
|
||||||
|
color += vec3(0.65, 0.08, 0.32) * pow(1.0 - field, 5.0) * 0.28;
|
||||||
|
color += vec3(0.08, 0.55, 0.70) * edge * 0.16;
|
||||||
|
color += mix(vec3(0.02, 0.55, 0.68), vec3(0.85, 0.12, 0.38), pressed)
|
||||||
|
* pointerGlow * (0.14 + pressed * 0.18);
|
||||||
|
|
||||||
|
float vignette = 1.0 - smoothstep(0.25, 1.25, length((uv - 0.5) * vec2(1.1, 0.9)));
|
||||||
|
color *= 0.62 + vignette * 0.38;
|
||||||
|
color = 1.0 - exp(-color * 1.35);
|
||||||
|
color += (hash21(fragCoord + fract(iTime) * 91.0) - 0.5) * 0.012;
|
||||||
|
fragColor = vec4(color, 1.0);
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"KPackageStructure": "Plasma/Wallpaper",
|
||||||
|
"KPlugin": {
|
||||||
|
"Authors": [
|
||||||
|
{
|
||||||
|
"Name": "Emil"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Id": "dev.emil.lumen",
|
||||||
|
"License": "GPL-3.0-or-later",
|
||||||
|
"Name": "Lumen Wallpaper",
|
||||||
|
"Version": "0.1.0",
|
||||||
|
"Description": "Procedural GLSL wallpapers rendered natively with Rust and Vulkan",
|
||||||
|
"Website": "https://git.shanaty.ru/Emil_Shanaty/Lumen-wallpaper"
|
||||||
|
},
|
||||||
|
"X-KDE-ParentApp": "org.kde.plasmashell",
|
||||||
|
"X-Plasma-API-Minimum-Version": "6.0"
|
||||||
|
}
|
||||||
Executable
+23
@@ -0,0 +1,23 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
for command_name in awk tail base64 tar mktemp; do
|
||||||
|
command -v "$command_name" >/dev/null 2>&1 || {
|
||||||
|
printf 'Lumen installer: missing command: %s\n' "$command_name" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
done
|
||||||
|
|
||||||
|
ARCHIVE_LINE=$(awk '/^__LUMEN_ARCHIVE_BELOW__$/{print NR + 1; exit}' "$0")
|
||||||
|
if [[ -z $ARCHIVE_LINE ]]; then
|
||||||
|
printf 'Lumen installer: embedded source archive is missing\n' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
WORK_DIR=$(mktemp -d "${TMPDIR:-/tmp}/lumen-wallpaper-installer.XXXXXX")
|
||||||
|
trap 'rm -rf -- "$WORK_DIR"' EXIT
|
||||||
|
|
||||||
|
tail -n +"$ARCHIVE_LINE" "$0" | base64 -d | tar -xz -C "$WORK_DIR"
|
||||||
|
"$WORK_DIR/Lumen-wallpaper-@LUMEN_VERSION@/scripts/install.sh" --deps --activate "$@"
|
||||||
|
|
||||||
|
__LUMEN_ARCHIVE_BELOW__
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
use lumen_renderer::shader::compile_shader;
|
||||||
|
use std::process::ExitCode;
|
||||||
|
|
||||||
|
fn main() -> ExitCode {
|
||||||
|
let Some(path) = std::env::args().nth(1) else {
|
||||||
|
eprintln!("Usage: lumen-shader-check <shader.frag>");
|
||||||
|
return ExitCode::from(2);
|
||||||
|
};
|
||||||
|
let source = match std::fs::read_to_string(&path) {
|
||||||
|
Ok(source) => source,
|
||||||
|
Err(error) => {
|
||||||
|
eprintln!("Cannot read {path}: {error}");
|
||||||
|
return ExitCode::FAILURE;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match compile_shader(&source) {
|
||||||
|
Ok(shader) => {
|
||||||
|
println!(
|
||||||
|
"{path}: valid (vertex: {} words, fragment: {} words)",
|
||||||
|
shader.vertex.len(),
|
||||||
|
shader.fragment.len()
|
||||||
|
);
|
||||||
|
ExitCode::SUCCESS
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
eprintln!("{error}");
|
||||||
|
ExitCode::FAILURE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
use bytemuck::{Pod, Zeroable};
|
||||||
|
use std::ffi::c_void;
|
||||||
|
|
||||||
|
#[repr(C, align(16))]
|
||||||
|
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
|
||||||
|
pub struct LumenUniforms {
|
||||||
|
pub mvp: [f32; 16],
|
||||||
|
pub resolution: [f32; 3],
|
||||||
|
pub time: f32,
|
||||||
|
pub mouse: [f32; 4],
|
||||||
|
pub item_size: [f32; 2],
|
||||||
|
pub time_delta: f32,
|
||||||
|
pub frame_rate: f32,
|
||||||
|
pub date: [f32; 4],
|
||||||
|
pub frame: i32,
|
||||||
|
pub screen_index: i32,
|
||||||
|
pub screen_count: i32,
|
||||||
|
pub sample_rate: f32,
|
||||||
|
pub screen_offset: [f32; 2],
|
||||||
|
pub desktop_resolution: [f32; 2],
|
||||||
|
}
|
||||||
|
|
||||||
|
const _: [(); 160] = [(); std::mem::size_of::<LumenUniforms>()];
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct LumenFrame {
|
||||||
|
pub instance: *mut c_void,
|
||||||
|
pub physical_device: *mut c_void,
|
||||||
|
pub device: *mut c_void,
|
||||||
|
pub command_buffer: *mut c_void,
|
||||||
|
pub render_pass: u64,
|
||||||
|
pub uniforms: LumenUniforms,
|
||||||
|
pub viewport: [f32; 4],
|
||||||
|
pub scissor: [i32; 4],
|
||||||
|
pub sample_count: u32,
|
||||||
|
pub frame_slot: u32,
|
||||||
|
pub frames_in_flight: u32,
|
||||||
|
pub reserved: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
const _: [(); 256] = [(); std::mem::size_of::<LumenFrame>()];
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
pub mod frame;
|
||||||
|
pub mod shader;
|
||||||
|
mod vulkan;
|
||||||
|
|
||||||
|
use crate::frame::LumenFrame;
|
||||||
|
use crate::shader::{compile_shader, CompiledShader};
|
||||||
|
use crate::vulkan::Gpu;
|
||||||
|
use ash::vk::Handle;
|
||||||
|
use std::ffi::{c_char, c_int, CStr, CString};
|
||||||
|
use std::panic::{catch_unwind, AssertUnwindSafe};
|
||||||
|
|
||||||
|
pub struct Renderer {
|
||||||
|
source: String,
|
||||||
|
compiled: Option<CompiledShader>,
|
||||||
|
source_dirty: bool,
|
||||||
|
source_failed: bool,
|
||||||
|
pipeline_dirty: bool,
|
||||||
|
blocked: bool,
|
||||||
|
gpu: Option<Gpu>,
|
||||||
|
error: CString,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Renderer {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
source: String::new(),
|
||||||
|
compiled: None,
|
||||||
|
source_dirty: true,
|
||||||
|
source_failed: false,
|
||||||
|
pipeline_dirty: false,
|
||||||
|
blocked: false,
|
||||||
|
gpu: None,
|
||||||
|
error: CString::new("No shader loaded").unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_shader(&mut self, source: &str) {
|
||||||
|
if self.source == source {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.source = source.to_owned();
|
||||||
|
self.source_dirty = true;
|
||||||
|
self.blocked = false;
|
||||||
|
self.clear_error();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&mut self) {
|
||||||
|
self.gpu = None;
|
||||||
|
self.pipeline_dirty = true;
|
||||||
|
self.blocked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_error(&mut self, message: impl Into<String>) {
|
||||||
|
let message = message.into().replace('\0', " ");
|
||||||
|
self.error =
|
||||||
|
CString::new(message).unwrap_or_else(|_| CString::new("Renderer error").unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_error(&mut self) {
|
||||||
|
self.error = CString::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&mut self, frame: &LumenFrame) -> Result<bool, String> {
|
||||||
|
if self.source_dirty {
|
||||||
|
self.source_dirty = false;
|
||||||
|
match compile_shader(&self.source) {
|
||||||
|
Ok(compiled) => {
|
||||||
|
self.compiled = Some(compiled);
|
||||||
|
self.source_failed = false;
|
||||||
|
self.pipeline_dirty = true;
|
||||||
|
self.blocked = false;
|
||||||
|
self.clear_error();
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
self.source_failed = true;
|
||||||
|
self.set_error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.blocked && self.gpu.is_none() {
|
||||||
|
return Err(self.error.to_string_lossy().into_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(compiled) = self.compiled.clone() else {
|
||||||
|
return Err(self.error.to_string_lossy().into_owned());
|
||||||
|
};
|
||||||
|
|
||||||
|
let device_handle = frame.device as usize as u64;
|
||||||
|
let render_pass = ash::vk::RenderPass::from_raw(frame.render_pass);
|
||||||
|
let samples = crate::vulkan::sample_count_flags(frame.sample_count)?;
|
||||||
|
let frames_in_flight = (frame.frames_in_flight.max(1) as usize).min(16);
|
||||||
|
let resources_changed = self.gpu.as_ref().is_some_and(|gpu| {
|
||||||
|
gpu.device_handle.as_raw() != device_handle
|
||||||
|
|| gpu.frames_in_flight != frames_in_flight
|
||||||
|
|| gpu.samples != samples
|
||||||
|
});
|
||||||
|
if resources_changed {
|
||||||
|
self.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.gpu.is_none() {
|
||||||
|
match unsafe { Gpu::new(frame) } {
|
||||||
|
Ok(gpu) => {
|
||||||
|
self.gpu = Some(gpu);
|
||||||
|
self.pipeline_dirty = true;
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
self.blocked = true;
|
||||||
|
self.set_error(format!("Vulkan initialization failed: {error}"));
|
||||||
|
return Err(self.error.to_string_lossy().into_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let gpu = self.gpu.as_mut().unwrap();
|
||||||
|
if gpu.render_pass != render_pass {
|
||||||
|
gpu.render_pass = render_pass;
|
||||||
|
self.pipeline_dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.pipeline_dirty {
|
||||||
|
let result = unsafe { self.gpu.as_mut().unwrap().create_pipeline(&compiled) };
|
||||||
|
match result {
|
||||||
|
Ok(()) => {
|
||||||
|
self.pipeline_dirty = false;
|
||||||
|
if !self.source_failed {
|
||||||
|
self.clear_error();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
self.set_error(format!("Vulkan pipeline creation failed: {error}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let gpu = self.gpu.as_ref().unwrap();
|
||||||
|
if gpu.pipeline.is_null()
|
||||||
|
|| gpu.pipeline_render_pass != render_pass
|
||||||
|
|| gpu.pipeline_samples != samples
|
||||||
|
{
|
||||||
|
return Err(self.error.to_string_lossy().into_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe { gpu.draw(frame) };
|
||||||
|
Ok(!self.error.to_bytes().is_empty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn lumen_renderer_create() -> *mut Renderer {
|
||||||
|
Box::into_raw(Box::new(Renderer::new()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn lumen_renderer_destroy(renderer: *mut Renderer) {
|
||||||
|
if !renderer.is_null() {
|
||||||
|
drop(Box::from_raw(renderer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn lumen_renderer_set_shader(renderer: *mut Renderer, source: *const c_char) {
|
||||||
|
if renderer.is_null() || source.is_null() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let result = catch_unwind(AssertUnwindSafe(|| {
|
||||||
|
let source = CStr::from_ptr(source).to_string_lossy();
|
||||||
|
(*renderer).set_shader(&source);
|
||||||
|
}));
|
||||||
|
if result.is_err() {
|
||||||
|
(*renderer).set_error("Panic while updating the shader");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn lumen_renderer_reset(renderer: *mut Renderer) {
|
||||||
|
if let Some(renderer) = renderer.as_mut() {
|
||||||
|
let result = catch_unwind(AssertUnwindSafe(|| renderer.reset()));
|
||||||
|
if result.is_err() {
|
||||||
|
renderer.set_error("Panic while releasing Vulkan resources");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn lumen_renderer_render(
|
||||||
|
renderer: *mut Renderer,
|
||||||
|
frame: *const LumenFrame,
|
||||||
|
) -> c_int {
|
||||||
|
if renderer.is_null() || frame.is_null() {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
let renderer = &mut *renderer;
|
||||||
|
match catch_unwind(AssertUnwindSafe(|| renderer.render(&*frame))) {
|
||||||
|
Ok(Ok(true)) => 1,
|
||||||
|
Ok(Ok(false)) => 0,
|
||||||
|
Ok(Err(error)) => {
|
||||||
|
renderer.set_error(error);
|
||||||
|
-1
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
renderer.set_error("Panic in the Vulkan renderer");
|
||||||
|
-1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn lumen_renderer_error(renderer: *const Renderer) -> *const c_char {
|
||||||
|
if renderer.is_null() {
|
||||||
|
return c"Invalid renderer handle".as_ptr();
|
||||||
|
}
|
||||||
|
(*renderer).error.as_ptr()
|
||||||
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
use naga::back::spv;
|
||||||
|
use naga::front::glsl::{Frontend, Options as GlslOptions};
|
||||||
|
use naga::valid::{Capabilities, ValidationFlags, Validator};
|
||||||
|
use naga::ShaderStage;
|
||||||
|
|
||||||
|
const UNIFORMS: &str = r#"
|
||||||
|
layout(std140, set = 0, binding = 0) uniform LumenUniforms {
|
||||||
|
mat4 lwMvp;
|
||||||
|
vec3 iResolution;
|
||||||
|
float iTime;
|
||||||
|
vec4 iMouse;
|
||||||
|
vec2 lwItemSize;
|
||||||
|
float iTimeDelta;
|
||||||
|
float iFrameRate;
|
||||||
|
vec4 iDate;
|
||||||
|
int iFrame;
|
||||||
|
int iScreenIndex;
|
||||||
|
int iScreenCount;
|
||||||
|
float iSampleRate;
|
||||||
|
vec2 iScreenOffset;
|
||||||
|
vec2 iDesktopResolution;
|
||||||
|
};
|
||||||
|
"#;
|
||||||
|
|
||||||
|
const VERTEX_PREFIX: &str = r#"#version 450
|
||||||
|
layout(location = 0) out vec2 lwUv;
|
||||||
|
"#;
|
||||||
|
|
||||||
|
const VERTEX_MAIN: &str = r#"
|
||||||
|
void main() {
|
||||||
|
vec2 position = vec2(
|
||||||
|
float((gl_VertexIndex << 1) & 2),
|
||||||
|
float(gl_VertexIndex & 2)
|
||||||
|
);
|
||||||
|
lwUv = position;
|
||||||
|
gl_Position = lwMvp * vec4(position * lwItemSize, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
const FRAGMENT_PREFIX: &str = r#"#version 450
|
||||||
|
layout(location = 0) in vec2 lwUv;
|
||||||
|
layout(location = 0) out vec4 fragColor;
|
||||||
|
"#;
|
||||||
|
|
||||||
|
const FRAGMENT_MAIN: &str = r#"
|
||||||
|
void main() {
|
||||||
|
vec2 fragCoord = vec2(lwUv.x * iResolution.x, (1.0 - lwUv.y) * iResolution.y);
|
||||||
|
vec4 color = vec4(0.0, 0.0, 0.0, 1.0);
|
||||||
|
mainImage(color, fragCoord);
|
||||||
|
fragColor = color;
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct CompiledShader {
|
||||||
|
pub vertex: Vec<u32>,
|
||||||
|
pub fragment: Vec<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn compile_shader(user_source: &str) -> Result<CompiledShader, String> {
|
||||||
|
let user_source = sanitize_user_source(user_source);
|
||||||
|
let vertex_source = format!("{VERTEX_PREFIX}{UNIFORMS}{VERTEX_MAIN}");
|
||||||
|
let fragment_source = format!("{FRAGMENT_PREFIX}{UNIFORMS}\n{user_source}\n{FRAGMENT_MAIN}");
|
||||||
|
|
||||||
|
Ok(CompiledShader {
|
||||||
|
vertex: compile_stage(&vertex_source, ShaderStage::Vertex)?,
|
||||||
|
fragment: compile_stage(&fragment_source, ShaderStage::Fragment)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sanitize_user_source(source: &str) -> String {
|
||||||
|
source
|
||||||
|
.lines()
|
||||||
|
.map(|line| {
|
||||||
|
let trimmed = line.trim_start();
|
||||||
|
if trimmed.starts_with("#version") || trimmed.starts_with("precision ") {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
line
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_stage(source: &str, stage: ShaderStage) -> Result<Vec<u32>, String> {
|
||||||
|
let stage_name = match stage {
|
||||||
|
ShaderStage::Vertex => "vertex",
|
||||||
|
ShaderStage::Fragment => "fragment",
|
||||||
|
ShaderStage::Compute => "compute",
|
||||||
|
_ => "unsupported",
|
||||||
|
};
|
||||||
|
|
||||||
|
let module = Frontend::default()
|
||||||
|
.parse(&GlslOptions::from(stage), source)
|
||||||
|
.map_err(|errors| {
|
||||||
|
format!(
|
||||||
|
"GLSL {stage_name} compilation failed:\n{}",
|
||||||
|
errors.emit_to_string(source)
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let info = Validator::new(ValidationFlags::all(), Capabilities::default())
|
||||||
|
.validate(&module)
|
||||||
|
.map_err(|error| {
|
||||||
|
format!(
|
||||||
|
"GLSL {stage_name} validation failed:\n{}",
|
||||||
|
error.emit_to_string_with_path(source, "glsl")
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut options = spv::Options::default();
|
||||||
|
options.fake_missing_bindings = false;
|
||||||
|
options.binding_map.insert(
|
||||||
|
naga::ResourceBinding {
|
||||||
|
group: 0,
|
||||||
|
binding: 0,
|
||||||
|
},
|
||||||
|
spv::BindingInfo {
|
||||||
|
descriptor_set: 0,
|
||||||
|
binding: 0,
|
||||||
|
binding_array_size: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
options
|
||||||
|
.flags
|
||||||
|
.remove(spv::WriterFlags::ADJUST_COORDINATE_SPACE);
|
||||||
|
let pipeline = spv::PipelineOptions {
|
||||||
|
shader_stage: stage,
|
||||||
|
entry_point: "main".to_owned(),
|
||||||
|
};
|
||||||
|
|
||||||
|
spv::write_vec(&module, &info, &options, Some(&pipeline))
|
||||||
|
.map_err(|error| format!("SPIR-V {stage_name} generation failed: {error}"))
|
||||||
|
}
|
||||||
@@ -0,0 +1,371 @@
|
|||||||
|
use crate::frame::LumenFrame;
|
||||||
|
use crate::shader::CompiledShader;
|
||||||
|
use ash::vk::{self, Handle};
|
||||||
|
use ash::{Device, Entry, Instance};
|
||||||
|
use std::ffi::{c_void, CStr};
|
||||||
|
use std::mem::size_of;
|
||||||
|
|
||||||
|
pub struct Gpu {
|
||||||
|
_entry: Entry,
|
||||||
|
instance: Instance,
|
||||||
|
device: Device,
|
||||||
|
physical_device: vk::PhysicalDevice,
|
||||||
|
pub device_handle: vk::Device,
|
||||||
|
pub render_pass: vk::RenderPass,
|
||||||
|
pub pipeline_render_pass: vk::RenderPass,
|
||||||
|
descriptor_set_layout: vk::DescriptorSetLayout,
|
||||||
|
pipeline_layout: vk::PipelineLayout,
|
||||||
|
descriptor_pool: vk::DescriptorPool,
|
||||||
|
descriptor_set: vk::DescriptorSet,
|
||||||
|
uniform_buffer: vk::Buffer,
|
||||||
|
uniform_memory: vk::DeviceMemory,
|
||||||
|
uniform_mapped: *mut c_void,
|
||||||
|
pub uniform_stride: u64,
|
||||||
|
pub frames_in_flight: usize,
|
||||||
|
pub samples: vk::SampleCountFlags,
|
||||||
|
pub pipeline_samples: vk::SampleCountFlags,
|
||||||
|
pub pipeline: vk::Pipeline,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Gpu {
|
||||||
|
pub unsafe fn new(frame: &LumenFrame) -> Result<Self, String> {
|
||||||
|
if frame.instance.is_null()
|
||||||
|
|| frame.physical_device.is_null()
|
||||||
|
|| frame.device.is_null()
|
||||||
|
|| frame.command_buffer.is_null()
|
||||||
|
|| frame.render_pass == 0
|
||||||
|
{
|
||||||
|
return Err("Qt did not provide all required Vulkan handles".to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
let entry = Entry::load().map_err(|error| error.to_string())?;
|
||||||
|
let instance_handle = vk::Instance::from_raw(frame.instance as usize as u64);
|
||||||
|
let instance = Instance::load(entry.static_fn(), instance_handle);
|
||||||
|
let device_handle = vk::Device::from_raw(frame.device as usize as u64);
|
||||||
|
let device = Device::load(instance.fp_v1_0(), device_handle);
|
||||||
|
let physical_device = vk::PhysicalDevice::from_raw(frame.physical_device as usize as u64);
|
||||||
|
let properties = instance.get_physical_device_properties(physical_device);
|
||||||
|
let alignment = properties.limits.min_uniform_buffer_offset_alignment.max(1);
|
||||||
|
let uniform_size = size_of::<crate::frame::LumenUniforms>() as u64;
|
||||||
|
let uniform_stride = uniform_size.div_ceil(alignment) * alignment;
|
||||||
|
let samples = sample_count_flags(frame.sample_count)?;
|
||||||
|
|
||||||
|
let mut gpu = Self {
|
||||||
|
_entry: entry,
|
||||||
|
instance,
|
||||||
|
device,
|
||||||
|
physical_device,
|
||||||
|
device_handle,
|
||||||
|
render_pass: vk::RenderPass::from_raw(frame.render_pass),
|
||||||
|
pipeline_render_pass: vk::RenderPass::null(),
|
||||||
|
descriptor_set_layout: vk::DescriptorSetLayout::null(),
|
||||||
|
pipeline_layout: vk::PipelineLayout::null(),
|
||||||
|
descriptor_pool: vk::DescriptorPool::null(),
|
||||||
|
descriptor_set: vk::DescriptorSet::null(),
|
||||||
|
uniform_buffer: vk::Buffer::null(),
|
||||||
|
uniform_memory: vk::DeviceMemory::null(),
|
||||||
|
uniform_mapped: std::ptr::null_mut(),
|
||||||
|
uniform_stride,
|
||||||
|
frames_in_flight: (frame.frames_in_flight.max(1) as usize).min(16),
|
||||||
|
samples,
|
||||||
|
pipeline_samples: vk::SampleCountFlags::empty(),
|
||||||
|
pipeline: vk::Pipeline::null(),
|
||||||
|
};
|
||||||
|
gpu.create_uniform_resources()?;
|
||||||
|
Ok(gpu)
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn create_uniform_resources(&mut self) -> Result<(), String> {
|
||||||
|
let binding = vk::DescriptorSetLayoutBinding::default()
|
||||||
|
.binding(0)
|
||||||
|
.descriptor_type(vk::DescriptorType::UNIFORM_BUFFER_DYNAMIC)
|
||||||
|
.descriptor_count(1)
|
||||||
|
.stage_flags(vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT);
|
||||||
|
let layout_info =
|
||||||
|
vk::DescriptorSetLayoutCreateInfo::default().bindings(std::slice::from_ref(&binding));
|
||||||
|
self.descriptor_set_layout = self
|
||||||
|
.device
|
||||||
|
.create_descriptor_set_layout(&layout_info, None)
|
||||||
|
.map_err(vk_error)?;
|
||||||
|
|
||||||
|
let layouts = [self.descriptor_set_layout];
|
||||||
|
let pipeline_layout_info = vk::PipelineLayoutCreateInfo::default().set_layouts(&layouts);
|
||||||
|
self.pipeline_layout = self
|
||||||
|
.device
|
||||||
|
.create_pipeline_layout(&pipeline_layout_info, None)
|
||||||
|
.map_err(vk_error)?;
|
||||||
|
|
||||||
|
let pool_size = vk::DescriptorPoolSize::default()
|
||||||
|
.ty(vk::DescriptorType::UNIFORM_BUFFER_DYNAMIC)
|
||||||
|
.descriptor_count(1);
|
||||||
|
let pool_sizes = [pool_size];
|
||||||
|
let pool_info = vk::DescriptorPoolCreateInfo::default()
|
||||||
|
.max_sets(1)
|
||||||
|
.pool_sizes(&pool_sizes);
|
||||||
|
self.descriptor_pool = self
|
||||||
|
.device
|
||||||
|
.create_descriptor_pool(&pool_info, None)
|
||||||
|
.map_err(vk_error)?;
|
||||||
|
|
||||||
|
let allocate_info = vk::DescriptorSetAllocateInfo::default()
|
||||||
|
.descriptor_pool(self.descriptor_pool)
|
||||||
|
.set_layouts(&layouts);
|
||||||
|
self.descriptor_set = self
|
||||||
|
.device
|
||||||
|
.allocate_descriptor_sets(&allocate_info)
|
||||||
|
.map_err(vk_error)?[0];
|
||||||
|
|
||||||
|
let buffer_info = vk::BufferCreateInfo::default()
|
||||||
|
.size(self.uniform_stride * self.frames_in_flight as u64)
|
||||||
|
.usage(vk::BufferUsageFlags::UNIFORM_BUFFER)
|
||||||
|
.sharing_mode(vk::SharingMode::EXCLUSIVE);
|
||||||
|
self.uniform_buffer = self
|
||||||
|
.device
|
||||||
|
.create_buffer(&buffer_info, None)
|
||||||
|
.map_err(vk_error)?;
|
||||||
|
|
||||||
|
let requirements = self
|
||||||
|
.device
|
||||||
|
.get_buffer_memory_requirements(self.uniform_buffer);
|
||||||
|
let properties = self
|
||||||
|
.instance
|
||||||
|
.get_physical_device_memory_properties(self.physical_device);
|
||||||
|
let memory_type = (0..properties.memory_type_count)
|
||||||
|
.find(|index| {
|
||||||
|
let supported = requirements.memory_type_bits & (1 << index) != 0;
|
||||||
|
let flags = properties.memory_types[*index as usize].property_flags;
|
||||||
|
let required =
|
||||||
|
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT;
|
||||||
|
supported && flags.contains(required)
|
||||||
|
})
|
||||||
|
.ok_or_else(|| "No host-visible coherent memory for the uniform buffer".to_owned())?;
|
||||||
|
let allocate_info = vk::MemoryAllocateInfo::default()
|
||||||
|
.allocation_size(requirements.size)
|
||||||
|
.memory_type_index(memory_type);
|
||||||
|
self.uniform_memory = self
|
||||||
|
.device
|
||||||
|
.allocate_memory(&allocate_info, None)
|
||||||
|
.map_err(vk_error)?;
|
||||||
|
self.device
|
||||||
|
.bind_buffer_memory(self.uniform_buffer, self.uniform_memory, 0)
|
||||||
|
.map_err(vk_error)?;
|
||||||
|
self.uniform_mapped = self
|
||||||
|
.device
|
||||||
|
.map_memory(
|
||||||
|
self.uniform_memory,
|
||||||
|
0,
|
||||||
|
vk::WHOLE_SIZE,
|
||||||
|
vk::MemoryMapFlags::empty(),
|
||||||
|
)
|
||||||
|
.map_err(vk_error)?;
|
||||||
|
|
||||||
|
let buffer_info = vk::DescriptorBufferInfo::default()
|
||||||
|
.buffer(self.uniform_buffer)
|
||||||
|
.offset(0)
|
||||||
|
.range(size_of::<crate::frame::LumenUniforms>() as u64);
|
||||||
|
let buffer_infos = [buffer_info];
|
||||||
|
let write = vk::WriteDescriptorSet::default()
|
||||||
|
.dst_set(self.descriptor_set)
|
||||||
|
.dst_binding(0)
|
||||||
|
.descriptor_type(vk::DescriptorType::UNIFORM_BUFFER_DYNAMIC)
|
||||||
|
.buffer_info(&buffer_infos);
|
||||||
|
self.device.update_descriptor_sets(&[write], &[]);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn create_pipeline(&mut self, shader: &CompiledShader) -> Result<(), String> {
|
||||||
|
let vertex_module = self.create_shader_module(&shader.vertex)?;
|
||||||
|
let fragment_module = match self.create_shader_module(&shader.fragment) {
|
||||||
|
Ok(module) => module,
|
||||||
|
Err(error) => {
|
||||||
|
self.device.destroy_shader_module(vertex_module, None);
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let result = self.create_pipeline_with_modules(vertex_module, fragment_module);
|
||||||
|
self.device.destroy_shader_module(vertex_module, None);
|
||||||
|
self.device.destroy_shader_module(fragment_module, None);
|
||||||
|
|
||||||
|
let new_pipeline = result?;
|
||||||
|
let old_pipeline = self.pipeline;
|
||||||
|
self.pipeline = new_pipeline;
|
||||||
|
self.pipeline_render_pass = self.render_pass;
|
||||||
|
self.pipeline_samples = self.samples;
|
||||||
|
if !old_pipeline.is_null() {
|
||||||
|
self.device.device_wait_idle().map_err(vk_error)?;
|
||||||
|
self.device.destroy_pipeline(old_pipeline, None);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn create_shader_module(&self, words: &[u32]) -> Result<vk::ShaderModule, String> {
|
||||||
|
let info = vk::ShaderModuleCreateInfo::default().code(words);
|
||||||
|
self.device
|
||||||
|
.create_shader_module(&info, None)
|
||||||
|
.map_err(vk_error)
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn create_pipeline_with_modules(
|
||||||
|
&self,
|
||||||
|
vertex_module: vk::ShaderModule,
|
||||||
|
fragment_module: vk::ShaderModule,
|
||||||
|
) -> Result<vk::Pipeline, String> {
|
||||||
|
let entry_name = CStr::from_bytes_with_nul(b"main\0").unwrap();
|
||||||
|
let stages = [
|
||||||
|
vk::PipelineShaderStageCreateInfo::default()
|
||||||
|
.stage(vk::ShaderStageFlags::VERTEX)
|
||||||
|
.module(vertex_module)
|
||||||
|
.name(entry_name),
|
||||||
|
vk::PipelineShaderStageCreateInfo::default()
|
||||||
|
.stage(vk::ShaderStageFlags::FRAGMENT)
|
||||||
|
.module(fragment_module)
|
||||||
|
.name(entry_name),
|
||||||
|
];
|
||||||
|
let vertex_input = vk::PipelineVertexInputStateCreateInfo::default();
|
||||||
|
let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default()
|
||||||
|
.topology(vk::PrimitiveTopology::TRIANGLE_LIST);
|
||||||
|
let viewport_state = vk::PipelineViewportStateCreateInfo::default()
|
||||||
|
.viewport_count(1)
|
||||||
|
.scissor_count(1);
|
||||||
|
let rasterization = vk::PipelineRasterizationStateCreateInfo::default()
|
||||||
|
.polygon_mode(vk::PolygonMode::FILL)
|
||||||
|
.cull_mode(vk::CullModeFlags::NONE)
|
||||||
|
.front_face(vk::FrontFace::COUNTER_CLOCKWISE)
|
||||||
|
.line_width(1.0);
|
||||||
|
let multisample =
|
||||||
|
vk::PipelineMultisampleStateCreateInfo::default().rasterization_samples(self.samples);
|
||||||
|
let depth_stencil = vk::PipelineDepthStencilStateCreateInfo::default();
|
||||||
|
let color_attachment = vk::PipelineColorBlendAttachmentState::default()
|
||||||
|
.blend_enable(false)
|
||||||
|
.color_write_mask(
|
||||||
|
vk::ColorComponentFlags::R
|
||||||
|
| vk::ColorComponentFlags::G
|
||||||
|
| vk::ColorComponentFlags::B
|
||||||
|
| vk::ColorComponentFlags::A,
|
||||||
|
);
|
||||||
|
let color_attachments = [color_attachment];
|
||||||
|
let color_blend =
|
||||||
|
vk::PipelineColorBlendStateCreateInfo::default().attachments(&color_attachments);
|
||||||
|
let dynamic_states = [vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR];
|
||||||
|
let dynamic = vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_states);
|
||||||
|
let pipeline_info = vk::GraphicsPipelineCreateInfo::default()
|
||||||
|
.stages(&stages)
|
||||||
|
.vertex_input_state(&vertex_input)
|
||||||
|
.input_assembly_state(&input_assembly)
|
||||||
|
.viewport_state(&viewport_state)
|
||||||
|
.rasterization_state(&rasterization)
|
||||||
|
.multisample_state(&multisample)
|
||||||
|
.depth_stencil_state(&depth_stencil)
|
||||||
|
.color_blend_state(&color_blend)
|
||||||
|
.dynamic_state(&dynamic)
|
||||||
|
.layout(self.pipeline_layout)
|
||||||
|
.render_pass(self.render_pass)
|
||||||
|
.subpass(0);
|
||||||
|
|
||||||
|
self.device
|
||||||
|
.create_graphics_pipelines(vk::PipelineCache::null(), &[pipeline_info], None)
|
||||||
|
.map(|pipelines| pipelines[0])
|
||||||
|
.map_err(|(pipelines, error)| {
|
||||||
|
for pipeline in pipelines {
|
||||||
|
self.device.destroy_pipeline(pipeline, None);
|
||||||
|
}
|
||||||
|
vk_error(error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn draw(&self, frame: &LumenFrame) {
|
||||||
|
let slot = frame.frame_slot as usize % self.frames_in_flight;
|
||||||
|
let dynamic_offset = (slot as u64 * self.uniform_stride) as u32;
|
||||||
|
std::ptr::copy_nonoverlapping(
|
||||||
|
&frame.uniforms as *const _ as *const u8,
|
||||||
|
self.uniform_mapped
|
||||||
|
.cast::<u8>()
|
||||||
|
.add(dynamic_offset as usize),
|
||||||
|
size_of::<crate::frame::LumenUniforms>(),
|
||||||
|
);
|
||||||
|
let command_buffer = vk::CommandBuffer::from_raw(frame.command_buffer as usize as u64);
|
||||||
|
self.device.cmd_bind_pipeline(
|
||||||
|
command_buffer,
|
||||||
|
vk::PipelineBindPoint::GRAPHICS,
|
||||||
|
self.pipeline,
|
||||||
|
);
|
||||||
|
self.device.cmd_bind_descriptor_sets(
|
||||||
|
command_buffer,
|
||||||
|
vk::PipelineBindPoint::GRAPHICS,
|
||||||
|
self.pipeline_layout,
|
||||||
|
0,
|
||||||
|
&[self.descriptor_set],
|
||||||
|
&[dynamic_offset],
|
||||||
|
);
|
||||||
|
let viewport = vk::Viewport {
|
||||||
|
x: frame.viewport[0],
|
||||||
|
y: frame.viewport[1],
|
||||||
|
width: frame.viewport[2].max(1.0),
|
||||||
|
height: frame.viewport[3].max(1.0),
|
||||||
|
min_depth: 0.0,
|
||||||
|
max_depth: 1.0,
|
||||||
|
};
|
||||||
|
let scissor = vk::Rect2D {
|
||||||
|
offset: vk::Offset2D {
|
||||||
|
x: frame.scissor[0],
|
||||||
|
y: frame.scissor[1],
|
||||||
|
},
|
||||||
|
extent: vk::Extent2D {
|
||||||
|
width: frame.scissor[2].max(1) as u32,
|
||||||
|
height: frame.scissor[3].max(1) as u32,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
self.device.cmd_set_viewport(command_buffer, 0, &[viewport]);
|
||||||
|
self.device.cmd_set_scissor(command_buffer, 0, &[scissor]);
|
||||||
|
self.device.cmd_draw(command_buffer, 3, 1, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Gpu {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
let _ = self.device.device_wait_idle();
|
||||||
|
if !self.pipeline.is_null() {
|
||||||
|
self.device.destroy_pipeline(self.pipeline, None);
|
||||||
|
}
|
||||||
|
if !self.uniform_mapped.is_null() {
|
||||||
|
self.device.unmap_memory(self.uniform_memory);
|
||||||
|
}
|
||||||
|
if !self.uniform_buffer.is_null() {
|
||||||
|
self.device.destroy_buffer(self.uniform_buffer, None);
|
||||||
|
}
|
||||||
|
if !self.uniform_memory.is_null() {
|
||||||
|
self.device.free_memory(self.uniform_memory, None);
|
||||||
|
}
|
||||||
|
if !self.descriptor_pool.is_null() {
|
||||||
|
self.device
|
||||||
|
.destroy_descriptor_pool(self.descriptor_pool, None);
|
||||||
|
}
|
||||||
|
if !self.pipeline_layout.is_null() {
|
||||||
|
self.device
|
||||||
|
.destroy_pipeline_layout(self.pipeline_layout, None);
|
||||||
|
}
|
||||||
|
if !self.descriptor_set_layout.is_null() {
|
||||||
|
self.device
|
||||||
|
.destroy_descriptor_set_layout(self.descriptor_set_layout, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn sample_count_flags(count: u32) -> Result<vk::SampleCountFlags, String> {
|
||||||
|
match count {
|
||||||
|
1 => Ok(vk::SampleCountFlags::TYPE_1),
|
||||||
|
2 => Ok(vk::SampleCountFlags::TYPE_2),
|
||||||
|
4 => Ok(vk::SampleCountFlags::TYPE_4),
|
||||||
|
8 => Ok(vk::SampleCountFlags::TYPE_8),
|
||||||
|
16 => Ok(vk::SampleCountFlags::TYPE_16),
|
||||||
|
32 => Ok(vk::SampleCountFlags::TYPE_32),
|
||||||
|
64 => Ok(vk::SampleCountFlags::TYPE_64),
|
||||||
|
_ => Err(format!("Unsupported Vulkan sample count: {count}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vk_error(error: vk::Result) -> String {
|
||||||
|
format!("{error}")
|
||||||
|
}
|
||||||
Executable
+49
@@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
BUILD_DIR=${BUILD_DIR:-"$ROOT/build"}
|
||||||
|
PACKAGE_ID=dev.emil.lumen
|
||||||
|
|
||||||
|
for command_name in busctl plasmashell nohup; do
|
||||||
|
command -v "$command_name" >/dev/null 2>&1 || {
|
||||||
|
printf 'Lumen activation: missing command: %s\n' "$command_name" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
done
|
||||||
|
if ! busctl --user call org.kde.plasmashell /PlasmaShell \
|
||||||
|
org.kde.PlasmaShell wallpaper u 0 >/dev/null 2>&1; then
|
||||||
|
printf 'Lumen activation: no running Plasma session was found\n' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
BACKUP_DIR="$BUILD_DIR/activation-backup-$(date +%Y%m%d-%H%M%S)-$$"
|
||||||
|
mkdir -p "$BACKUP_DIR" "$HOME/.config/plasma-workspace/env"
|
||||||
|
: > "$BACKUP_DIR/wallpaper-plugins.txt"
|
||||||
|
for ((screen = 0; ; ++screen)); do
|
||||||
|
if ! description=$(busctl --user call org.kde.plasmashell /PlasmaShell \
|
||||||
|
org.kde.PlasmaShell wallpaper u "$screen" 2>/dev/null); then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
printf '%s\n' "$description" > "$BACKUP_DIR/wallpaper-screen-$screen.txt"
|
||||||
|
if [[ $description =~ \"wallpaperPlugin\"\ s\ \"([^\"]+)\" ]]; then
|
||||||
|
printf '%d %s\n' "$screen" "${BASH_REMATCH[1]}" \
|
||||||
|
>> "$BACKUP_DIR/wallpaper-plugins.txt"
|
||||||
|
else
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [[ ! -s "$BACKUP_DIR/wallpaper-plugins.txt" ]]; then
|
||||||
|
printf 'Lumen activation: could not back up the current wallpaper plugins\n' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s\n' 'export QSG_RHI_BACKEND=vulkan' \
|
||||||
|
> "$HOME/.config/plasma-workspace/env/lumen-vulkan.sh"
|
||||||
|
|
||||||
|
busctl --user call org.kde.plasmashell /PlasmaShell org.kde.PlasmaShell evaluateScript s \
|
||||||
|
"desktops().forEach(function(d) { d.wallpaperPlugin = '$PACKAGE_ID'; });"
|
||||||
|
|
||||||
|
QSG_RHI_BACKEND=vulkan nohup plasmashell --replace \
|
||||||
|
> "$BACKUP_DIR/plasmashell.log" 2>&1 &
|
||||||
|
printf 'Lumen Wallpaper activated. Backup: %s\n' "$BACKUP_DIR"
|
||||||
Executable
+189
@@ -0,0 +1,189 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
PREFIX=${PREFIX:-"$HOME/.local"}
|
||||||
|
BUILD_DIR=${BUILD_DIR:-"$ROOT/build"}
|
||||||
|
INSTALL_DEPS=0
|
||||||
|
ACTIVATE=0
|
||||||
|
ASSUME_YES=0
|
||||||
|
MINIMUM_RUST=1.87.0
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage: scripts/install.sh [options]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--deps Offer to install missing build dependencies
|
||||||
|
--no-deps Do not install dependencies (default)
|
||||||
|
--activate Activate Lumen in the running Plasma session
|
||||||
|
--no-activate Build and install only (default)
|
||||||
|
--yes, -y Assume yes for dependency installation prompts
|
||||||
|
--help Show this help
|
||||||
|
|
||||||
|
Environment:
|
||||||
|
PREFIX Installation prefix (default: ~/.local)
|
||||||
|
BUILD_DIR Build directory (default: ./build)
|
||||||
|
LUMEN_STATE_DIR Activation backups and logs (default: PREFIX/share/lumen-wallpaper)
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
log() {
|
||||||
|
printf 'Lumen installer: %s\n' "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
printf 'Lumen installer: %s\n' "$*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
while (($#)); do
|
||||||
|
case "$1" in
|
||||||
|
--deps) INSTALL_DEPS=1 ;;
|
||||||
|
--no-deps) INSTALL_DEPS=0 ;;
|
||||||
|
--activate) ACTIVATE=1 ;;
|
||||||
|
--no-activate) ACTIVATE=0 ;;
|
||||||
|
--yes|-y) ASSUME_YES=1 ;;
|
||||||
|
--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*) fail "Unknown option: $1" ;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
confirm() {
|
||||||
|
local prompt=$1
|
||||||
|
local answer
|
||||||
|
if ((ASSUME_YES)); then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [[ ! -t 0 ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
read -r -p "$prompt [y/N] " answer
|
||||||
|
[[ $answer == y || $answer == Y || $answer == yes || $answer == YES ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
rust_version_ok() {
|
||||||
|
command -v rustc >/dev/null 2>&1 || return 1
|
||||||
|
local version
|
||||||
|
version=$(rustc --version 2>/dev/null | awk '{print $2}')
|
||||||
|
[[ -n $version ]] || return 1
|
||||||
|
printf '%s\n%s\n' "$MINIMUM_RUST" "$version" | sort -V -C
|
||||||
|
}
|
||||||
|
|
||||||
|
ecm_available() {
|
||||||
|
local probe_dir
|
||||||
|
probe_dir=$(mktemp -d "${TMPDIR:-/tmp}/lumen-ecm-probe.XXXXXX")
|
||||||
|
if (cd "$probe_dir" && cmake --find-package \
|
||||||
|
-DNAME=ECM -DCOMPILER_ID=GNU -DLANGUAGE=CXX -DMODE=EXIST \
|
||||||
|
>/dev/null 2>&1); then
|
||||||
|
rm -rf -- "$probe_dir"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
rm -rf -- "$probe_dir"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies_ready() {
|
||||||
|
local command_name
|
||||||
|
for command_name in cmake ninja c++ cargo rustc pkg-config; do
|
||||||
|
command -v "$command_name" >/dev/null 2>&1 || return 1
|
||||||
|
done
|
||||||
|
rust_version_ok || return 1
|
||||||
|
pkg-config --exists Qt6Core Qt6Gui Qt6Qml Qt6Quick || return 1
|
||||||
|
ecm_available || return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
run_as_root() {
|
||||||
|
if ((EUID == 0)); then
|
||||||
|
"$@"
|
||||||
|
else
|
||||||
|
command -v sudo >/dev/null 2>&1 || fail "sudo is required for system packages"
|
||||||
|
sudo "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
install_dependencies() {
|
||||||
|
if dependencies_ready; then
|
||||||
|
log "Build dependencies are already available"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
((INSTALL_DEPS)) || return
|
||||||
|
confirm "Install missing build dependencies with the system package manager?" \
|
||||||
|
|| fail "Build dependencies are required; install them manually or rerun with --deps"
|
||||||
|
|
||||||
|
if command -v dnf >/dev/null 2>&1; then
|
||||||
|
run_as_root dnf install -y \
|
||||||
|
gcc-c++ cargo rustc cmake ninja-build extra-cmake-modules pkgconf-pkg-config curl \
|
||||||
|
qt6-qtbase-devel qt6-qtdeclarative-devel vulkan-loader-devel vulkan-headers \
|
||||||
|
kf6-kirigami plasma-workspace
|
||||||
|
elif command -v apt-get >/dev/null 2>&1; then
|
||||||
|
run_as_root apt-get update
|
||||||
|
run_as_root apt-get install -y \
|
||||||
|
build-essential cargo rustc cmake ninja-build extra-cmake-modules pkg-config curl \
|
||||||
|
qt6-base-dev qt6-declarative-dev libvulkan-dev \
|
||||||
|
qml6-module-org-kde-kirigami plasma-workspace
|
||||||
|
elif command -v pacman >/dev/null 2>&1; then
|
||||||
|
run_as_root pacman -Sy --needed --noconfirm \
|
||||||
|
base-devel cargo cmake ninja extra-cmake-modules pkgconf curl \
|
||||||
|
qt6-base qt6-declarative vulkan-headers kirigami plasma-workspace
|
||||||
|
else
|
||||||
|
fail "Unsupported package manager. Install CMake, Ninja, a C++ compiler, Rust, Qt6, ECM, Vulkan, Kirigami, and Plasma Workspace manually"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_rust() {
|
||||||
|
if rust_version_ok; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
((INSTALL_DEPS)) || return
|
||||||
|
confirm "Rust $MINIMUM_RUST or newer is required. Install/update it with rustup in your home directory?" \
|
||||||
|
|| fail "Rust $MINIMUM_RUST or newer is required"
|
||||||
|
|
||||||
|
if command -v rustup >/dev/null 2>&1; then
|
||||||
|
rustup toolchain install stable --profile minimal
|
||||||
|
else
|
||||||
|
command -v curl >/dev/null 2>&1 || fail "curl is required to download rustup"
|
||||||
|
local rustup_script
|
||||||
|
rustup_script=$(mktemp "${TMPDIR:-/tmp}/lumen-rustup.XXXXXX")
|
||||||
|
curl --proto '=https' --tlsv1.2 -fsSL https://sh.rustup.rs -o "$rustup_script"
|
||||||
|
sh "$rustup_script" -y --profile minimal --no-modify-path
|
||||||
|
rm -f -- "$rustup_script"
|
||||||
|
fi
|
||||||
|
export PATH="$HOME/.cargo/bin:$PATH"
|
||||||
|
export RUSTUP_TOOLCHAIN=stable
|
||||||
|
rust_version_ok || fail "The Rust toolchain is still older than $MINIMUM_RUST"
|
||||||
|
}
|
||||||
|
|
||||||
|
preflight() {
|
||||||
|
local command_name
|
||||||
|
for command_name in cmake ninja c++ cargo rustc pkg-config; do
|
||||||
|
command -v "$command_name" >/dev/null 2>&1 \
|
||||||
|
|| fail "Missing required command: $command_name"
|
||||||
|
done
|
||||||
|
rust_version_ok || fail "Rust $MINIMUM_RUST or newer is required"
|
||||||
|
pkg-config --exists Qt6Core Qt6Gui Qt6Qml Qt6Quick \
|
||||||
|
|| fail "Qt6 development packages are required"
|
||||||
|
ecm_available || fail "Extra CMake Modules (ECM) are required"
|
||||||
|
}
|
||||||
|
|
||||||
|
install_dependencies
|
||||||
|
ensure_rust
|
||||||
|
preflight
|
||||||
|
|
||||||
|
cmake -S "$ROOT" -B "$BUILD_DIR" -G Ninja \
|
||||||
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DCMAKE_INSTALL_PREFIX="$PREFIX"
|
||||||
|
cmake --build "$BUILD_DIR"
|
||||||
|
cmake --install "$BUILD_DIR"
|
||||||
|
|
||||||
|
log "Installed Lumen Wallpaper to $PREFIX"
|
||||||
|
if ((ACTIVATE)); then
|
||||||
|
BUILD_DIR=${LUMEN_STATE_DIR:-"$PREFIX/share/lumen-wallpaper"} \
|
||||||
|
"$ROOT/scripts/activate.sh"
|
||||||
|
else
|
||||||
|
log "Activate it with scripts/activate.sh or choose Lumen Wallpaper in KDE settings"
|
||||||
|
fi
|
||||||
Executable
+34
@@ -0,0 +1,34 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
VERSION=$(awk -F'"' '/^version =/{print $2; exit}' "$ROOT/Cargo.toml")
|
||||||
|
REF=${1:-HEAD}
|
||||||
|
DIST_DIR=${DIST_DIR:-"$ROOT/dist"}
|
||||||
|
SOURCE_ARCHIVE="$DIST_DIR/lumen-wallpaper-$VERSION-source.tar.gz"
|
||||||
|
INSTALLER="$DIST_DIR/lumen-wallpaper-$VERSION-installer.run"
|
||||||
|
|
||||||
|
command -v git >/dev/null 2>&1 || {
|
||||||
|
printf 'Release packaging requires git\n' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
git -C "$ROOT" rev-parse --verify "$REF^{commit}" >/dev/null
|
||||||
|
mkdir -p "$DIST_DIR"
|
||||||
|
|
||||||
|
git -C "$ROOT" archive --format=tar.gz \
|
||||||
|
--prefix="Lumen-wallpaper-$VERSION/" "$REF" -o "$SOURCE_ARCHIVE"
|
||||||
|
|
||||||
|
if tar -tzf "$SOURCE_ARCHIVE" | grep -E '(^|/)(build|target|dist|\.env)(/|$)|\.(log|swp|tmp)$' >/dev/null; then
|
||||||
|
printf 'Release archive contains a forbidden local artifact\n' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
sed "s/@LUMEN_VERSION@/$VERSION/g" "$ROOT/packaging/installer-stub.sh" > "$INSTALLER"
|
||||||
|
base64 "$SOURCE_ARCHIVE" >> "$INSTALLER"
|
||||||
|
chmod +x "$INSTALLER"
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$DIST_DIR"
|
||||||
|
sha256sum "$(basename "$SOURCE_ARCHIVE")" "$(basename "$INSTALLER")" > SHA256SUMS
|
||||||
|
)
|
||||||
|
printf 'Created %s\nCreated %s\n' "$SOURCE_ARCHIVE" "$INSTALLER"
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
struct LumenRenderer;
|
||||||
|
|
||||||
|
struct alignas(16) LumenUniforms {
|
||||||
|
float mvp[16];
|
||||||
|
float resolution[3];
|
||||||
|
float time;
|
||||||
|
float mouse[4];
|
||||||
|
float itemSize[2];
|
||||||
|
float timeDelta;
|
||||||
|
float frameRate;
|
||||||
|
float date[4];
|
||||||
|
std::int32_t frame;
|
||||||
|
std::int32_t screenIndex;
|
||||||
|
std::int32_t screenCount;
|
||||||
|
float sampleRate;
|
||||||
|
float screenOffset[2];
|
||||||
|
float desktopResolution[2];
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(sizeof(LumenUniforms) == 160);
|
||||||
|
|
||||||
|
struct LumenFrame {
|
||||||
|
void *instance;
|
||||||
|
void *physicalDevice;
|
||||||
|
void *device;
|
||||||
|
void *commandBuffer;
|
||||||
|
std::uint64_t renderPass;
|
||||||
|
LumenUniforms uniforms;
|
||||||
|
float viewport[4];
|
||||||
|
std::int32_t scissor[4];
|
||||||
|
std::uint32_t sampleCount;
|
||||||
|
std::uint32_t frameSlot;
|
||||||
|
std::uint32_t framesInFlight;
|
||||||
|
std::uint32_t reserved;
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(alignof(LumenUniforms) == 16);
|
||||||
|
static_assert(sizeof(LumenFrame) == 256);
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
LumenRenderer *lumen_renderer_create();
|
||||||
|
void lumen_renderer_destroy(LumenRenderer *renderer);
|
||||||
|
void lumen_renderer_set_shader(LumenRenderer *renderer, const char *source);
|
||||||
|
void lumen_renderer_reset(LumenRenderer *renderer);
|
||||||
|
int lumen_renderer_render(LumenRenderer *renderer, const LumenFrame *frame);
|
||||||
|
const char *lumen_renderer_error(const LumenRenderer *renderer);
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
#include "plugin.h"
|
||||||
|
|
||||||
|
#include "wallpaperitem.h"
|
||||||
|
|
||||||
|
#include <qqml.h>
|
||||||
|
|
||||||
|
void LumenPlugin::registerTypes(const char *uri) {
|
||||||
|
qmlRegisterType<VulkanWallpaperItem>(uri, 1, 0, "VulkanWallpaper");
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QQmlExtensionPlugin>
|
||||||
|
|
||||||
|
class LumenPlugin final : public QQmlExtensionPlugin {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid)
|
||||||
|
|
||||||
|
public:
|
||||||
|
void registerTypes(const char *uri) override;
|
||||||
|
};
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
#include "rendernode.h"
|
||||||
|
|
||||||
|
#include "wallpaperitem.h"
|
||||||
|
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QGuiApplication>
|
||||||
|
#include <QMetaObject>
|
||||||
|
#include <QQuickWindow>
|
||||||
|
#include <QScreen>
|
||||||
|
#include <QSGRendererInterface>
|
||||||
|
#include <QVulkanInstance>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
template<typename Handle>
|
||||||
|
void *dispatchableHandle(Handle handle) {
|
||||||
|
if constexpr (std::is_pointer_v<Handle>)
|
||||||
|
return reinterpret_cast<void *>(handle);
|
||||||
|
else
|
||||||
|
return reinterpret_cast<void *>(static_cast<uintptr_t>(handle));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Handle>
|
||||||
|
uint64_t nonDispatchableHandle(Handle handle) {
|
||||||
|
if constexpr (std::is_pointer_v<Handle>)
|
||||||
|
return static_cast<uint64_t>(reinterpret_cast<uintptr_t>(handle));
|
||||||
|
else
|
||||||
|
return static_cast<uint64_t>(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Handle>
|
||||||
|
Handle resource(QSGRendererInterface *renderer, QQuickWindow *window,
|
||||||
|
QSGRendererInterface::Resource type) {
|
||||||
|
void *value = renderer->getResource(window, type);
|
||||||
|
return value ? *static_cast<Handle *>(value) : Handle{};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LumenRenderNode::LumenRenderNode(VulkanWallpaperItem *item)
|
||||||
|
: m_item(item), m_renderer(lumen_renderer_create()) {
|
||||||
|
}
|
||||||
|
|
||||||
|
LumenRenderNode::~LumenRenderNode() {
|
||||||
|
lumen_renderer_destroy(m_renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LumenRenderNode::sync(const WallpaperFrameState &state) {
|
||||||
|
m_state = state;
|
||||||
|
m_itemSize = state.itemSize;
|
||||||
|
m_window = state.window;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LumenRenderNode::render(const RenderState *renderState) {
|
||||||
|
if (!m_window)
|
||||||
|
return;
|
||||||
|
|
||||||
|
QSGRendererInterface *rendererInterface = m_window->rendererInterface();
|
||||||
|
if (!rendererInterface || rendererInterface->graphicsApi() != QSGRendererInterface::Vulkan) {
|
||||||
|
publishStatus(-1, QStringLiteral(
|
||||||
|
"Qt Quick is not using Vulkan. Restart Plasma with QSG_RHI_BACKEND=vulkan."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto *vulkanInstance = static_cast<QVulkanInstance *>(
|
||||||
|
rendererInterface->getResource(m_window, QSGRendererInterface::VulkanInstanceResource));
|
||||||
|
const VkInstance instance = vulkanInstance ? vulkanInstance->vkInstance() : VK_NULL_HANDLE;
|
||||||
|
const VkPhysicalDevice physicalDevice = resource<VkPhysicalDevice>(
|
||||||
|
rendererInterface, m_window, QSGRendererInterface::PhysicalDeviceResource);
|
||||||
|
const VkDevice device = resource<VkDevice>(
|
||||||
|
rendererInterface, m_window, QSGRendererInterface::DeviceResource);
|
||||||
|
const VkCommandBuffer commandBuffer = resource<VkCommandBuffer>(
|
||||||
|
rendererInterface, m_window, QSGRendererInterface::CommandListResource);
|
||||||
|
const VkRenderPass renderPass = resource<VkRenderPass>(
|
||||||
|
rendererInterface, m_window, QSGRendererInterface::RenderPassResource);
|
||||||
|
|
||||||
|
if (!vulkanInstance || instance == VK_NULL_HANDLE || physicalDevice == VK_NULL_HANDLE
|
||||||
|
|| device == VK_NULL_HANDLE || commandBuffer == VK_NULL_HANDLE || renderPass == VK_NULL_HANDLE) {
|
||||||
|
publishStatus(-1, QStringLiteral("Could not acquire the Vulkan resources from Qt Quick."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_loadedSource != m_state.shaderSource) {
|
||||||
|
m_loadedSource = m_state.shaderSource;
|
||||||
|
lumen_renderer_set_shader(m_renderer, m_loadedSource.constData());
|
||||||
|
}
|
||||||
|
|
||||||
|
LumenFrame frame{};
|
||||||
|
frame.instance = dispatchableHandle(instance);
|
||||||
|
frame.physicalDevice = dispatchableHandle(physicalDevice);
|
||||||
|
frame.device = dispatchableHandle(device);
|
||||||
|
frame.commandBuffer = dispatchableHandle(commandBuffer);
|
||||||
|
frame.renderPass = nonDispatchableHandle(renderPass);
|
||||||
|
frame.sampleCount = static_cast<std::uint32_t>(m_state.sampleCount);
|
||||||
|
const auto graphicsState = m_window->graphicsStateInfo();
|
||||||
|
frame.frameSlot = static_cast<std::uint32_t>(std::max(0, graphicsState.currentFrameSlot));
|
||||||
|
frame.framesInFlight = static_cast<std::uint32_t>(std::max(1, graphicsState.framesInFlight));
|
||||||
|
|
||||||
|
const QMatrix4x4 mvp = *renderState->projectionMatrix() * *matrix();
|
||||||
|
std::copy_n(mvp.constData(), 16, frame.uniforms.mvp);
|
||||||
|
|
||||||
|
const qreal dpr = m_state.devicePixelRatio;
|
||||||
|
const qreal pixelWidth = std::max(1.0, m_state.itemSize.width() * dpr);
|
||||||
|
const qreal pixelHeight = std::max(1.0, m_state.itemSize.height() * dpr);
|
||||||
|
frame.uniforms.resolution[0] = static_cast<float>(pixelWidth);
|
||||||
|
frame.uniforms.resolution[1] = static_cast<float>(pixelHeight);
|
||||||
|
frame.uniforms.resolution[2] = static_cast<float>(dpr);
|
||||||
|
frame.uniforms.time = static_cast<float>(m_state.time);
|
||||||
|
for (int i = 0; i < 4; ++i)
|
||||||
|
frame.uniforms.mouse[i] = static_cast<float>(m_state.mouse[i]);
|
||||||
|
frame.uniforms.itemSize[0] = static_cast<float>(m_state.itemSize.width());
|
||||||
|
frame.uniforms.itemSize[1] = static_cast<float>(m_state.itemSize.height());
|
||||||
|
frame.uniforms.timeDelta = static_cast<float>(m_state.timeDelta);
|
||||||
|
frame.uniforms.frameRate = static_cast<float>(m_state.frameRate);
|
||||||
|
|
||||||
|
const QDateTime now = QDateTime::currentDateTime();
|
||||||
|
const QTime time = now.time();
|
||||||
|
frame.uniforms.date[0] = static_cast<float>(now.date().year());
|
||||||
|
frame.uniforms.date[1] = static_cast<float>(now.date().month());
|
||||||
|
frame.uniforms.date[2] = static_cast<float>(now.date().day());
|
||||||
|
frame.uniforms.date[3] = static_cast<float>(QTime(0, 0).secsTo(time))
|
||||||
|
+ static_cast<float>(time.msec()) / 1000.0f;
|
||||||
|
frame.uniforms.frame = static_cast<std::int32_t>(m_state.frame & 0x7fffffff);
|
||||||
|
frame.uniforms.screenIndex = m_state.screenIndex;
|
||||||
|
frame.uniforms.screenCount = m_state.screenCount;
|
||||||
|
frame.uniforms.sampleRate = 48000.0f;
|
||||||
|
frame.uniforms.desktopResolution[0] = frame.uniforms.resolution[0];
|
||||||
|
frame.uniforms.desktopResolution[1] = frame.uniforms.resolution[1];
|
||||||
|
|
||||||
|
const QSize &outputSize = m_state.outputSize;
|
||||||
|
frame.viewport[0] = 0.0f;
|
||||||
|
frame.viewport[1] = 0.0f;
|
||||||
|
frame.viewport[2] = static_cast<float>(outputSize.width());
|
||||||
|
frame.viewport[3] = static_cast<float>(outputSize.height());
|
||||||
|
const QRect scissor = renderState->scissorEnabled()
|
||||||
|
? renderState->scissorRect()
|
||||||
|
: QRect(QPoint(0, 0), outputSize);
|
||||||
|
frame.scissor[0] = scissor.x();
|
||||||
|
frame.scissor[1] = scissor.y();
|
||||||
|
frame.scissor[2] = scissor.width();
|
||||||
|
frame.scissor[3] = scissor.height();
|
||||||
|
|
||||||
|
int status = lumen_renderer_render(m_renderer, &frame);
|
||||||
|
QString message = QString::fromUtf8(lumen_renderer_error(m_renderer));
|
||||||
|
if (!m_state.fileError.isEmpty()) {
|
||||||
|
if (!message.isEmpty())
|
||||||
|
message += QLatin1Char('\n');
|
||||||
|
message += m_state.fileError;
|
||||||
|
if (status == 0)
|
||||||
|
status = 1;
|
||||||
|
}
|
||||||
|
publishStatus(status, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LumenRenderNode::releaseResources() {
|
||||||
|
lumen_renderer_reset(m_renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
QSGRenderNode::StateFlags LumenRenderNode::changedStates() const {
|
||||||
|
return ViewportState | ScissorState;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSGRenderNode::RenderingFlags LumenRenderNode::flags() const {
|
||||||
|
return BoundedRectRendering | OpaqueRendering;
|
||||||
|
}
|
||||||
|
|
||||||
|
QRectF LumenRenderNode::rect() const {
|
||||||
|
return QRectF(QPointF(0, 0), m_itemSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LumenRenderNode::publishStatus(int status, const QString &message) {
|
||||||
|
if (status == m_lastStatus && message == m_lastMessage)
|
||||||
|
return;
|
||||||
|
m_lastStatus = status;
|
||||||
|
m_lastMessage = message;
|
||||||
|
if (!message.isEmpty())
|
||||||
|
qWarning().noquote() << "Lumen Wallpaper:" << message;
|
||||||
|
QPointer<VulkanWallpaperItem> item = m_item;
|
||||||
|
QMetaObject::invokeMethod(item.data(), [item, status, message]() {
|
||||||
|
if (item)
|
||||||
|
item->applyRenderStatus(status, message);
|
||||||
|
}, Qt::QueuedConnection);
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "lumen_ffi.h"
|
||||||
|
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QPointer>
|
||||||
|
#include <QRectF>
|
||||||
|
#include <QSGRenderNode>
|
||||||
|
#include <QSizeF>
|
||||||
|
#include <QVector4D>
|
||||||
|
|
||||||
|
class VulkanWallpaperItem;
|
||||||
|
|
||||||
|
class QQuickWindow;
|
||||||
|
|
||||||
|
struct WallpaperFrameState {
|
||||||
|
QByteArray shaderSource;
|
||||||
|
QString fileError;
|
||||||
|
QSizeF itemSize;
|
||||||
|
QSize outputSize;
|
||||||
|
QQuickWindow *window = nullptr;
|
||||||
|
qreal devicePixelRatio = 1.0;
|
||||||
|
double time = 0.0;
|
||||||
|
double timeDelta = 1.0 / 30.0;
|
||||||
|
double frameRate = 30.0;
|
||||||
|
quint64 frame = 0;
|
||||||
|
QVector4D mouse;
|
||||||
|
int screenIndex = 0;
|
||||||
|
int screenCount = 1;
|
||||||
|
int sampleCount = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
class LumenRenderNode final : public QSGRenderNode {
|
||||||
|
public:
|
||||||
|
explicit LumenRenderNode(VulkanWallpaperItem *item);
|
||||||
|
~LumenRenderNode() override;
|
||||||
|
|
||||||
|
void sync(const WallpaperFrameState &state);
|
||||||
|
void render(const RenderState *state) override;
|
||||||
|
void releaseResources() override;
|
||||||
|
StateFlags changedStates() const override;
|
||||||
|
RenderingFlags flags() const override;
|
||||||
|
QRectF rect() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void publishStatus(int status, const QString &message);
|
||||||
|
|
||||||
|
QPointer<VulkanWallpaperItem> m_item;
|
||||||
|
QQuickWindow *m_window = nullptr;
|
||||||
|
LumenRenderer *m_renderer;
|
||||||
|
WallpaperFrameState m_state;
|
||||||
|
QByteArray m_loadedSource;
|
||||||
|
QSizeF m_itemSize;
|
||||||
|
QString m_lastMessage;
|
||||||
|
int m_lastStatus = -2;
|
||||||
|
};
|
||||||
@@ -0,0 +1,262 @@
|
|||||||
|
#include "wallpaperitem.h"
|
||||||
|
|
||||||
|
#include <QFile>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QGuiApplication>
|
||||||
|
#include <QHoverEvent>
|
||||||
|
#include <QMouseEvent>
|
||||||
|
#include <QQuickWindow>
|
||||||
|
#include <QScreen>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
const QByteArray fallbackShader = R"(
|
||||||
|
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
||||||
|
vec2 uv = fragCoord / iResolution.xy;
|
||||||
|
vec3 color = 0.04 + 0.04 * cos(iTime * 0.2 + uv.xyx + vec3(0.0, 2.0, 4.0));
|
||||||
|
fragColor = vec4(color, 1.0);
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
}
|
||||||
|
|
||||||
|
VulkanWallpaperItem::VulkanWallpaperItem(QQuickItem *parent)
|
||||||
|
: QQuickItem(parent) {
|
||||||
|
setFlag(ItemHasContents, true);
|
||||||
|
setAcceptHoverEvents(true);
|
||||||
|
setAcceptedMouseButtons(Qt::LeftButton);
|
||||||
|
m_shaderSource = fallbackShader;
|
||||||
|
m_renderTimer.setTimerType(Qt::PreciseTimer);
|
||||||
|
m_renderTimer.setSingleShot(false);
|
||||||
|
connect(&m_renderTimer, &QTimer::timeout, this, &VulkanWallpaperItem::tick);
|
||||||
|
m_reloadTimer.setSingleShot(true);
|
||||||
|
m_reloadTimer.setInterval(75);
|
||||||
|
connect(&m_reloadTimer, &QTimer::timeout, this, [this]() {
|
||||||
|
loadShader();
|
||||||
|
updateWatcher();
|
||||||
|
});
|
||||||
|
connect(&m_watcher, &QFileSystemWatcher::fileChanged,
|
||||||
|
this, [this](const QString &) {
|
||||||
|
m_reloadTimer.start();
|
||||||
|
});
|
||||||
|
m_clock.start();
|
||||||
|
m_lastTickNs = m_clock.nsecsElapsed();
|
||||||
|
updateTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanWallpaperItem::setShaderUrl(const QUrl &url) {
|
||||||
|
if (m_shaderUrl == url)
|
||||||
|
return;
|
||||||
|
m_shaderUrl = url;
|
||||||
|
m_shaderFile = url.isLocalFile() ? url.toLocalFile() : url.toString();
|
||||||
|
loadShader();
|
||||||
|
updateWatcher();
|
||||||
|
Q_EMIT shaderUrlChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanWallpaperItem::setTargetFps(int fps) {
|
||||||
|
fps = std::clamp(fps, 1, 240);
|
||||||
|
if (m_targetFps == fps)
|
||||||
|
return;
|
||||||
|
m_targetFps = fps;
|
||||||
|
updateTimer();
|
||||||
|
Q_EMIT targetFpsChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanWallpaperItem::setSpeed(double speed) {
|
||||||
|
speed = std::clamp(speed, 0.0, 10.0);
|
||||||
|
if (qFuzzyCompare(m_speed, speed))
|
||||||
|
return;
|
||||||
|
m_speed = speed;
|
||||||
|
Q_EMIT speedChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanWallpaperItem::setRunning(bool running) {
|
||||||
|
if (m_running == running)
|
||||||
|
return;
|
||||||
|
m_running = running;
|
||||||
|
m_lastTickNs = m_clock.nsecsElapsed();
|
||||||
|
updateTimer();
|
||||||
|
update();
|
||||||
|
Q_EMIT runningChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanWallpaperItem::setMouseEnabled(bool enabled) {
|
||||||
|
if (m_mouseEnabled == enabled)
|
||||||
|
return;
|
||||||
|
m_mouseEnabled = enabled;
|
||||||
|
setAcceptHoverEvents(enabled);
|
||||||
|
setAcceptedMouseButtons(enabled ? Qt::LeftButton : Qt::NoButton);
|
||||||
|
if (!enabled)
|
||||||
|
m_mouse = QVector4D();
|
||||||
|
update();
|
||||||
|
Q_EMIT mouseEnabledChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanWallpaperItem::setWatchSource(bool enabled) {
|
||||||
|
if (m_watchSource == enabled)
|
||||||
|
return;
|
||||||
|
m_watchSource = enabled;
|
||||||
|
updateWatcher();
|
||||||
|
Q_EMIT watchSourceChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanWallpaperItem::applyRenderStatus(int status, const QString &message) {
|
||||||
|
m_renderStatus = status;
|
||||||
|
m_renderError = message;
|
||||||
|
updateErrorState();
|
||||||
|
}
|
||||||
|
|
||||||
|
QSGNode *VulkanWallpaperItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) {
|
||||||
|
auto *node = static_cast<LumenRenderNode *>(oldNode);
|
||||||
|
if (!node)
|
||||||
|
node = new LumenRenderNode(this);
|
||||||
|
|
||||||
|
WallpaperFrameState state;
|
||||||
|
state.shaderSource = m_shaderSource;
|
||||||
|
state.fileError = m_fileError;
|
||||||
|
state.itemSize = size();
|
||||||
|
state.window = window();
|
||||||
|
state.devicePixelRatio = window() ? window()->effectiveDevicePixelRatio() : 1.0;
|
||||||
|
state.outputSize = window() ? window()->size() * state.devicePixelRatio : QSize();
|
||||||
|
state.sampleCount = window() ? std::max(1, window()->format().samples()) : 1;
|
||||||
|
state.time = m_animationTime;
|
||||||
|
state.timeDelta = m_timeDelta;
|
||||||
|
state.frameRate = m_frameRate;
|
||||||
|
state.frame = m_frame;
|
||||||
|
state.mouse = m_mouse;
|
||||||
|
if (window() && window()->screen()) {
|
||||||
|
const auto screens = QGuiApplication::screens();
|
||||||
|
state.screenIndex = std::max(0, static_cast<int>(screens.indexOf(window()->screen())));
|
||||||
|
state.screenCount = screens.size();
|
||||||
|
}
|
||||||
|
node->sync(state);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanWallpaperItem::itemChange(ItemChange change, const ItemChangeData &value) {
|
||||||
|
QQuickItem::itemChange(change, value);
|
||||||
|
if (change == ItemSceneChange) {
|
||||||
|
if (value.window) {
|
||||||
|
connect(value.window, &QQuickWindow::visibleChanged,
|
||||||
|
this, &VulkanWallpaperItem::updateTimer, Qt::UniqueConnection);
|
||||||
|
}
|
||||||
|
updateTimer();
|
||||||
|
} else if (change == ItemDevicePixelRatioHasChanged) {
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanWallpaperItem::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) {
|
||||||
|
QQuickItem::geometryChange(newGeometry, oldGeometry);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanWallpaperItem::hoverMoveEvent(QHoverEvent *event) {
|
||||||
|
setMousePosition(event->position());
|
||||||
|
event->setAccepted(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanWallpaperItem::mouseMoveEvent(QMouseEvent *event) {
|
||||||
|
setMousePosition(event->position());
|
||||||
|
event->accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanWallpaperItem::mousePressEvent(QMouseEvent *event) {
|
||||||
|
setMousePosition(event->position());
|
||||||
|
setMouseDown(true);
|
||||||
|
event->accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanWallpaperItem::mouseReleaseEvent(QMouseEvent *event) {
|
||||||
|
setMousePosition(event->position());
|
||||||
|
setMouseDown(false);
|
||||||
|
event->accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanWallpaperItem::mouseUngrabEvent() {
|
||||||
|
setMouseDown(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanWallpaperItem::loadShader() {
|
||||||
|
QFile file(m_shaderFile);
|
||||||
|
if (m_shaderFile.isEmpty() || !file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
|
m_fileError = QStringLiteral("Cannot open shader: %1").arg(m_shaderFile);
|
||||||
|
updateErrorState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QByteArray source = file.readAll();
|
||||||
|
if (source.trimmed().isEmpty()) {
|
||||||
|
m_fileError = QStringLiteral("Shader is empty: %1").arg(m_shaderFile);
|
||||||
|
updateErrorState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_shaderSource = source;
|
||||||
|
m_fileError.clear();
|
||||||
|
updateErrorState();
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanWallpaperItem::updateWatcher() {
|
||||||
|
const QStringList watched = m_watcher.files();
|
||||||
|
if (!watched.isEmpty())
|
||||||
|
m_watcher.removePaths(watched);
|
||||||
|
if (m_watchSource && !m_shaderFile.isEmpty() && QFileInfo::exists(m_shaderFile))
|
||||||
|
m_watcher.addPath(m_shaderFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanWallpaperItem::updateTimer() {
|
||||||
|
const bool windowVisible = !window() || window()->isVisible();
|
||||||
|
if (m_running && windowVisible && isVisible()) {
|
||||||
|
m_renderTimer.start(std::max(1, 1000 / m_targetFps));
|
||||||
|
} else {
|
||||||
|
m_renderTimer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanWallpaperItem::tick() {
|
||||||
|
const qint64 now = m_clock.nsecsElapsed();
|
||||||
|
const double elapsed = std::clamp((now - m_lastTickNs) / 1'000'000'000.0, 0.0, 0.25);
|
||||||
|
m_lastTickNs = now;
|
||||||
|
m_timeDelta = elapsed > 0.0 ? elapsed : 1.0 / m_targetFps;
|
||||||
|
m_frameRate = 1.0 / m_timeDelta;
|
||||||
|
m_animationTime += elapsed * m_speed;
|
||||||
|
++m_frame;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanWallpaperItem::setMousePosition(const QPointF &position) {
|
||||||
|
if (!m_mouseEnabled)
|
||||||
|
return;
|
||||||
|
const qreal dpr = window() ? window()->effectiveDevicePixelRatio() : 1.0;
|
||||||
|
m_mouse.setX(static_cast<float>(position.x() * dpr));
|
||||||
|
m_mouse.setY(static_cast<float>((height() - position.y()) * dpr));
|
||||||
|
if (!m_running)
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanWallpaperItem::setMouseDown(bool down) {
|
||||||
|
m_mouse.setZ(down ? 1.0f : 0.0f);
|
||||||
|
m_mouse.setW(down ? 1.0f : 0.0f);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanWallpaperItem::updateErrorState() {
|
||||||
|
QString message = m_renderError;
|
||||||
|
if (!m_fileError.isEmpty() && !message.contains(m_fileError)) {
|
||||||
|
if (!message.isEmpty())
|
||||||
|
message += QLatin1Char('\n');
|
||||||
|
message += m_fileError;
|
||||||
|
}
|
||||||
|
const bool fallback = m_renderStatus == 1
|
||||||
|
|| (m_renderStatus != -1 && !m_fileError.isEmpty());
|
||||||
|
if (message != m_errorMessage) {
|
||||||
|
m_errorMessage = message;
|
||||||
|
Q_EMIT errorMessageChanged();
|
||||||
|
}
|
||||||
|
if (fallback != m_fallbackActive) {
|
||||||
|
m_fallbackActive = fallback;
|
||||||
|
Q_EMIT fallbackActiveChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "rendernode.h"
|
||||||
|
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QElapsedTimer>
|
||||||
|
#include <QFileSystemWatcher>
|
||||||
|
#include <QQuickItem>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QUrl>
|
||||||
|
#include <QVector4D>
|
||||||
|
|
||||||
|
class VulkanWallpaperItem : public QQuickItem {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(QUrl shaderUrl READ shaderUrl WRITE setShaderUrl NOTIFY shaderUrlChanged)
|
||||||
|
Q_PROPERTY(int targetFps READ targetFps WRITE setTargetFps NOTIFY targetFpsChanged)
|
||||||
|
Q_PROPERTY(double speed READ speed WRITE setSpeed NOTIFY speedChanged)
|
||||||
|
Q_PROPERTY(bool running READ running WRITE setRunning NOTIFY runningChanged)
|
||||||
|
Q_PROPERTY(bool mouseEnabled READ mouseEnabled WRITE setMouseEnabled NOTIFY mouseEnabledChanged)
|
||||||
|
Q_PROPERTY(bool watchSource READ watchSource WRITE setWatchSource NOTIFY watchSourceChanged)
|
||||||
|
Q_PROPERTY(QString errorMessage READ errorMessage NOTIFY errorMessageChanged)
|
||||||
|
Q_PROPERTY(bool fallbackActive READ fallbackActive NOTIFY fallbackActiveChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit VulkanWallpaperItem(QQuickItem *parent = nullptr);
|
||||||
|
~VulkanWallpaperItem() override = default;
|
||||||
|
|
||||||
|
QUrl shaderUrl() const { return m_shaderUrl; }
|
||||||
|
int targetFps() const { return m_targetFps; }
|
||||||
|
double speed() const { return m_speed; }
|
||||||
|
bool running() const { return m_running; }
|
||||||
|
bool mouseEnabled() const { return m_mouseEnabled; }
|
||||||
|
bool watchSource() const { return m_watchSource; }
|
||||||
|
QString errorMessage() const { return m_errorMessage; }
|
||||||
|
bool fallbackActive() const { return m_fallbackActive; }
|
||||||
|
|
||||||
|
void setShaderUrl(const QUrl &url);
|
||||||
|
void setTargetFps(int fps);
|
||||||
|
void setSpeed(double speed);
|
||||||
|
void setRunning(bool running);
|
||||||
|
void setMouseEnabled(bool enabled);
|
||||||
|
void setWatchSource(bool enabled);
|
||||||
|
void applyRenderStatus(int status, const QString &message);
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void shaderUrlChanged();
|
||||||
|
void targetFpsChanged();
|
||||||
|
void speedChanged();
|
||||||
|
void runningChanged();
|
||||||
|
void mouseEnabledChanged();
|
||||||
|
void watchSourceChanged();
|
||||||
|
void errorMessageChanged();
|
||||||
|
void fallbackActiveChanged();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) override;
|
||||||
|
void itemChange(ItemChange change, const ItemChangeData &value) override;
|
||||||
|
void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override;
|
||||||
|
void hoverMoveEvent(QHoverEvent *event) override;
|
||||||
|
void mouseMoveEvent(QMouseEvent *event) override;
|
||||||
|
void mousePressEvent(QMouseEvent *event) override;
|
||||||
|
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||||
|
void mouseUngrabEvent() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void loadShader();
|
||||||
|
void updateWatcher();
|
||||||
|
void updateTimer();
|
||||||
|
void tick();
|
||||||
|
void setMousePosition(const QPointF &position);
|
||||||
|
void setMouseDown(bool down);
|
||||||
|
void updateErrorState();
|
||||||
|
|
||||||
|
QUrl m_shaderUrl;
|
||||||
|
QString m_shaderFile;
|
||||||
|
QByteArray m_shaderSource;
|
||||||
|
QString m_fileError;
|
||||||
|
QString m_renderError;
|
||||||
|
QString m_errorMessage;
|
||||||
|
QFileSystemWatcher m_watcher;
|
||||||
|
QTimer m_renderTimer;
|
||||||
|
QTimer m_reloadTimer;
|
||||||
|
QElapsedTimer m_clock;
|
||||||
|
qint64 m_lastTickNs = 0;
|
||||||
|
double m_animationTime = 0.0;
|
||||||
|
double m_timeDelta = 1.0 / 30.0;
|
||||||
|
double m_frameRate = 30.0;
|
||||||
|
quint64 m_frame = 0;
|
||||||
|
int m_targetFps = 30;
|
||||||
|
double m_speed = 1.0;
|
||||||
|
bool m_running = true;
|
||||||
|
bool m_mouseEnabled = true;
|
||||||
|
bool m_watchSource = true;
|
||||||
|
bool m_fallbackActive = false;
|
||||||
|
int m_renderStatus = -2;
|
||||||
|
QVector4D m_mouse;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user