├── crates ├── vcolor │ ├── src │ │ ├── lib.rs │ │ └── scheme.rs │ └── Cargo.toml ├── vtty │ ├── src │ │ └── lib.rs │ └── Cargo.toml ├── vshell │ ├── src │ │ ├── lib.rs │ │ ├── grid │ │ │ ├── cell.rs │ │ │ └── row.rs │ │ ├── input.rs │ │ └── color.rs │ └── Cargo.toml ├── vui │ ├── src │ │ ├── format │ │ │ ├── mod.rs │ │ │ └── markdown.rs │ │ ├── vulkan │ │ │ ├── buffer │ │ │ │ ├── mod.rs │ │ │ │ ├── gpu_vec.rs │ │ │ │ └── buffer.rs │ │ │ ├── sync │ │ │ │ ├── mod.rs │ │ │ │ ├── semaphore_pool.rs │ │ │ │ ├── semaphore.rs │ │ │ │ └── fence.rs │ │ │ ├── descriptor_set │ │ │ │ ├── mod.rs │ │ │ │ ├── descriptor_set.rs │ │ │ │ ├── descriptor_set_layout.rs │ │ │ │ └── descriptor_pool.rs │ │ │ ├── command_buffer │ │ │ │ ├── mod.rs │ │ │ │ ├── command_buffer.rs │ │ │ │ ├── one_time_submit_command_pool.rs │ │ │ │ └── command_pool.rs │ │ │ ├── mod.rs │ │ │ ├── allocator │ │ │ │ ├── allocation.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── passthrough.rs │ │ │ │ ├── composable.rs │ │ │ │ └── locked_memory.rs │ │ │ ├── render_device │ │ │ │ ├── gpu_queue.rs │ │ │ │ ├── physical_device.rs │ │ │ │ ├── swapchain │ │ │ │ │ ├── images.rs │ │ │ │ │ └── selection.rs │ │ │ │ ├── mod.rs │ │ │ │ └── queue_family_indices.rs │ │ │ ├── ffi.rs │ │ │ ├── render_pass │ │ │ │ └── mod.rs │ │ │ ├── pipeline │ │ │ │ ├── mod.rs │ │ │ │ ├── layout.rs │ │ │ │ └── shader.rs │ │ │ ├── image │ │ │ │ ├── sampler.rs │ │ │ │ ├── view.rs │ │ │ │ └── mod.rs │ │ │ ├── instance │ │ │ │ └── mod.rs │ │ │ ├── framebuffer │ │ │ │ └── mod.rs │ │ │ └── window_surface.rs │ │ ├── pipeline │ │ │ ├── mod.rs │ │ │ └── per_frame.rs │ │ ├── ui │ │ │ ├── font │ │ │ │ ├── default │ │ │ │ │ ├── Roobert-Bold.ttf │ │ │ │ │ ├── Roobert-Light.ttf │ │ │ │ │ ├── Roobert-Medium.ttf │ │ │ │ │ └── Roobert-Regular.ttf │ │ │ │ ├── layout.rs │ │ │ │ └── rasterize.rs │ │ │ ├── color │ │ │ │ ├── style.rs │ │ │ │ ├── gradient.rs │ │ │ │ └── mod.rs │ │ │ ├── primitives │ │ │ │ ├── mod.rs │ │ │ │ ├── dimensions.rs │ │ │ │ ├── dimension_list │ │ │ │ │ ├── axis.rs │ │ │ │ │ └── mod.rs │ │ │ │ └── rect.rs │ │ │ ├── mod.rs │ │ │ ├── input.rs │ │ │ ├── widgets │ │ │ │ ├── container │ │ │ │ │ └── constraint.rs │ │ │ │ ├── prelude.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── style │ │ │ │ │ └── mod.rs │ │ │ │ ├── composite │ │ │ │ │ ├── composed_message.rs │ │ │ │ │ └── mod.rs │ │ │ │ ├── element.rs │ │ │ │ ├── image.rs │ │ │ │ ├── row.rs │ │ │ │ ├── col.rs │ │ │ │ ├── hsplit.rs │ │ │ │ ├── align.rs │ │ │ │ └── window.rs │ │ │ ├── id.rs │ │ │ ├── internal_state.rs │ │ │ └── ui.rs │ │ ├── graphics │ │ │ ├── triangles │ │ │ │ ├── shaders │ │ │ │ │ ├── passthrough.frag.spirv │ │ │ │ │ ├── passthrough.vert.spirv │ │ │ │ │ ├── passthrough.frag │ │ │ │ │ └── passthrough.vert │ │ │ │ └── mod.rs │ │ │ ├── mod.rs │ │ │ ├── vertex.rs │ │ │ ├── sprite.rs │ │ │ └── rectangle.rs │ │ ├── errors │ │ │ ├── render_pass.rs │ │ │ ├── graphics.rs │ │ │ ├── debug.rs │ │ │ ├── allocator.rs │ │ │ ├── descriptor.rs │ │ │ ├── frame.rs │ │ │ ├── framebuffer.rs │ │ │ ├── sync.rs │ │ │ ├── image.rs │ │ │ ├── pipeline.rs │ │ │ ├── buffer.rs │ │ │ ├── mod.rs │ │ │ ├── command_buffer.rs │ │ │ ├── instance.rs │ │ │ ├── window.rs │ │ │ ├── vulkan.rs │ │ │ └── render_device.rs │ │ ├── asset_loader │ │ │ ├── mod.rs │ │ │ ├── combined_image_sampler.rs │ │ │ ├── error.rs │ │ │ └── mipmap_data.rs │ │ ├── msaa │ │ │ ├── error.rs │ │ │ ├── render_target.rs │ │ │ └── depth_target.rs │ │ ├── math │ │ │ └── mod.rs │ │ └── lib.rs │ └── Cargo.toml └── vterm │ ├── build.rs │ ├── Cargo.toml │ └── src │ ├── cli.rs │ ├── logger.rs │ └── terminal.rs ├── .husky └── pre-commit ├── assets ├── windows │ ├── wix │ │ ├── vterm.wxs │ │ └── license.rtf │ ├── vterm.ico │ ├── vterm.rc │ └── vterm.manifest ├── images │ └── rust.png └── linux │ └── vterm.desktop ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── 0_feature_request.yml │ ├── 2_crash_report.yml │ └── 1_bug_report.yml ├── FUNDING.yml └── pull_request_template.md ├── CODE_OF_CONDUCT.md ├── .prettierignore ├── .gitignore ├── rust-toolchain.toml ├── .editorconfig ├── .cargo └── config.toml ├── rustfmt.toml ├── scripts └── copy_images.sh ├── .prettierrc ├── deny.toml ├── Makefile ├── README.md ├── Cargo.toml ├── package.json └── INSTALL.md /crates/vcolor/src/lib.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | bun test 2 | -------------------------------------------------------------------------------- /assets/windows/wix/vterm.wxs: -------------------------------------------------------------------------------- 1 | TODO -------------------------------------------------------------------------------- /assets/windows/wix/license.rtf: -------------------------------------------------------------------------------- 1 | TODO -------------------------------------------------------------------------------- /crates/vtty/src/lib.rs: -------------------------------------------------------------------------------- 1 | // TODO(nuii): PTTY 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /crates/vshell/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod color; 2 | mod grid; 3 | mod input; 4 | -------------------------------------------------------------------------------- /crates/vui/src/format/mod.rs: -------------------------------------------------------------------------------- 1 | pub use markdown::MdList; 2 | 3 | mod markdown; 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [nuIIpointerexception] 2 | patreon: nuIIpointerexception 3 | -------------------------------------------------------------------------------- /assets/images/rust.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuIIpointerexception/vterm/HEAD/assets/images/rust.png -------------------------------------------------------------------------------- /assets/windows/vterm.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuIIpointerexception/vterm/HEAD/assets/windows/vterm.ico -------------------------------------------------------------------------------- /crates/vui/src/vulkan/buffer/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::{buffer::Buffer, gpu_vec::GpuVec}; 2 | 3 | mod buffer; 4 | mod gpu_vec; 5 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | The Code of Conduct for this repository can be found online at [viable.gg/coc](https: 4 | -------------------------------------------------------------------------------- /crates/vui/src/pipeline/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::{frame_pipeline::FramePipeline, per_frame::PerFrame}; 2 | 3 | mod frame_pipeline; 4 | mod per_frame; 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # rust 2 | /target/ 3 | **/*.rs.bk 4 | /target 5 | 6 | # js 7 | pnpm-lock.yaml 8 | package-lock.json 9 | yarn.lock 10 | bun.lockb -------------------------------------------------------------------------------- /crates/vui/src/ui/font/default/Roobert-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuIIpointerexception/vterm/HEAD/crates/vui/src/ui/font/default/Roobert-Bold.ttf -------------------------------------------------------------------------------- /crates/vui/src/ui/font/default/Roobert-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuIIpointerexception/vterm/HEAD/crates/vui/src/ui/font/default/Roobert-Light.ttf -------------------------------------------------------------------------------- /crates/vui/src/ui/font/default/Roobert-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuIIpointerexception/vterm/HEAD/crates/vui/src/ui/font/default/Roobert-Medium.ttf -------------------------------------------------------------------------------- /crates/vui/src/ui/font/default/Roobert-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuIIpointerexception/vterm/HEAD/crates/vui/src/ui/font/default/Roobert-Regular.ttf -------------------------------------------------------------------------------- /crates/vui/src/ui/color/style.rs: -------------------------------------------------------------------------------- 1 | use super::{Color, Gradient}; 2 | 3 | #[derive(Debug, Clone, Copy)] 4 | pub enum Style { 5 | Color(Color), 6 | Gradient(Gradient), 7 | } 8 | -------------------------------------------------------------------------------- /crates/vui/src/vulkan/sync/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::{fence::Fence, semaphore::Semaphore, semaphore_pool::SemaphorePool}; 2 | 3 | mod fence; 4 | mod semaphore; 5 | mod semaphore_pool; 6 | -------------------------------------------------------------------------------- /crates/vui/src/graphics/triangles/shaders/passthrough.frag.spirv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuIIpointerexception/vterm/HEAD/crates/vui/src/graphics/triangles/shaders/passthrough.frag.spirv -------------------------------------------------------------------------------- /crates/vui/src/graphics/triangles/shaders/passthrough.vert.spirv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuIIpointerexception/vterm/HEAD/crates/vui/src/graphics/triangles/shaders/passthrough.vert.spirv -------------------------------------------------------------------------------- /assets/windows/vterm.rc: -------------------------------------------------------------------------------- 1 | #define IDI_ICON 0x101 2 | 3 | IDI_ICON ICON "vterm.ico" 4 | 5 | #define RT_MANIFEST 24 6 | #define APP_MANIFEST 1 7 | 8 | APP_MANIFEST RT_MANIFEST vterm.manifest 9 | 10 | -------------------------------------------------------------------------------- /crates/vui/src/errors/render_pass.rs: -------------------------------------------------------------------------------- 1 | use ::ash::vk; 2 | use thiserror::Error; 3 | 4 | #[derive(Debug, Error)] 5 | pub enum RenderPassError { 6 | #[error("Unable to create a new render pass")] 7 | UnableToCreateRenderPass(#[source] vk::Result), 8 | } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # rust 2 | /target/ 3 | **/*.rs.bk 4 | /target 5 | 6 | # ide 7 | .idea/ 8 | .vscode/ 9 | 10 | # javascript 11 | node_modules/ 12 | bun.lockb 13 | yarn.lock 14 | pnpm-lock.yaml 15 | package-lock.json 16 | .husky 17 | 18 | # other 19 | .DS_Store 20 | -------------------------------------------------------------------------------- /crates/vui/src/vulkan/descriptor_set/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::{ 2 | descriptor_pool::DescriptorPool, descriptor_set::DescriptorSet, 3 | descriptor_set_layout::DescriptorSetLayout, 4 | }; 5 | 6 | mod descriptor_pool; 7 | mod descriptor_set; 8 | mod descriptor_set_layout; 9 | -------------------------------------------------------------------------------- /crates/vui/src/ui/primitives/mod.rs: -------------------------------------------------------------------------------- 1 | mod dimension_list; 2 | mod dimensions; 3 | mod rect; 4 | mod tile; 5 | 6 | pub use self::{ 7 | dimension_list::{Axis, DimensionList, Justify, SpaceBetween}, 8 | dimensions::Dimensions, 9 | rect::Rect, 10 | tile::Tile, 11 | }; 12 | -------------------------------------------------------------------------------- /crates/vui/src/vulkan/command_buffer/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::{ 2 | command_buffer::CommandBuffer, command_pool::CommandPool, 3 | one_time_submit_command_pool::OneTimeSubmitCommandPool, 4 | }; 5 | 6 | mod command_buffer; 7 | mod command_pool; 8 | mod one_time_submit_command_pool; 9 | -------------------------------------------------------------------------------- /crates/vui/src/asset_loader/mod.rs: -------------------------------------------------------------------------------- 1 | mod asset_loader; 2 | mod combined_image_sampler; 3 | mod error; 4 | mod mipmap_data; 5 | 6 | pub use self::{ 7 | asset_loader::AssetLoader, combined_image_sampler::CombinedImageSampler, 8 | error::AssetLoaderError, mipmap_data::MipmapData, 9 | }; 10 | -------------------------------------------------------------------------------- /crates/vui/src/ui/color/gradient.rs: -------------------------------------------------------------------------------- 1 | use super::Color; 2 | 3 | #[derive(Debug, Clone, Copy)] 4 | pub struct Gradient { 5 | pub start: Color, 6 | pub end: Color, 7 | } 8 | 9 | impl Gradient { 10 | pub fn new(start: Color, end: Color) -> Self { 11 | Self { start, end } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /crates/vui/src/errors/graphics.rs: -------------------------------------------------------------------------------- 1 | use ::thiserror::Error; 2 | 3 | #[derive(Debug, Error)] 4 | pub enum ImmediateModeGraphicsError { 5 | #[error("The per frame resources for swapchain image {} were not available! The last frame may not have been ended properly.", .0)] 6 | FrameResourcesUnavailable(usize), 7 | } 8 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" 3 | profile = "minimal" 4 | components = ["rustfmt", "clippy", "rust-src"] 5 | targets = [ 6 | "x86_64-apple-darwin", 7 | "aarch64-apple-darwin", 8 | "x86_64-unknown-linux-gnu", 9 | "x86_64-pc-windows-msvc", 10 | "aarch64-pc-windows-msvc", 11 | ] 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # All files 7 | [*] 8 | charset = utf-8 9 | end_of_line = lf 10 | indent_size = 4 11 | indent_style = space 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | 15 | [*.yml] 16 | indent_size = 2 -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | # .cargo/config.toml 2 | [unstable] 3 | profile-rustflags = true 4 | 5 | [profile.release] 6 | rustflags = ["-Ccodegen-units=1", "-Zlocation-detail=none"] 7 | 8 | [target.aarch64-apple-darwin] 9 | rustflags = [ 10 | # optimize for m1, will work on later versions too... 11 | "-Ctarget-cpu=apple-m1", 12 | ] 13 | -------------------------------------------------------------------------------- /assets/linux/vterm.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Application 3 | TryExec=vterm 4 | Exec=vterm 5 | Icon=vterm 6 | Terminal=false 7 | Categories=System;TerminalEmulator;Development 8 | 9 | Name=vterm 10 | GenericName=Terminal 11 | Comment=A high-performance, vulkan based terminal emulator 12 | StartupNotify=true 13 | StartupWMClass=vterm -------------------------------------------------------------------------------- /crates/vui/src/vulkan/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod allocator; 2 | pub mod buffer; 3 | pub mod command_buffer; 4 | pub mod descriptor_set; 5 | pub mod ffi; 6 | pub mod framebuffer; 7 | pub mod image; 8 | pub mod instance; 9 | pub mod pipeline; 10 | pub mod render_device; 11 | pub mod render_pass; 12 | pub mod window_surface; 13 | 14 | pub mod sync; 15 | -------------------------------------------------------------------------------- /crates/vui/src/msaa/error.rs: -------------------------------------------------------------------------------- 1 | use ::thiserror::Error; 2 | 3 | use crate::errors::VulkanError; 4 | 5 | #[derive(Debug, Error)] 6 | pub enum MSAAError { 7 | #[error("Unable to pick a supported depth format")] 8 | UnableToPickDepthFormat, 9 | 10 | #[error(transparent)] 11 | UnexpectedVulkanError(#[from] VulkanError), 12 | } 13 | -------------------------------------------------------------------------------- /crates/vui/src/graphics/mod.rs: -------------------------------------------------------------------------------- 1 | use ::anyhow::Result; 2 | 3 | pub use self::{rectangle::Rectangle, sprite::Sprite, vertex::Vertex}; 4 | 5 | mod rectangle; 6 | mod sprite; 7 | mod vertex; 8 | 9 | pub mod triangles; 10 | 11 | pub trait VertexStream { 12 | fn push_vertices(&mut self, vertices: &[Vertex], indices: &[u32]) -> Result<()>; 13 | } 14 | -------------------------------------------------------------------------------- /crates/vui/src/errors/debug.rs: -------------------------------------------------------------------------------- 1 | use ::thiserror::Error; 2 | 3 | use crate::errors::RenderDeviceError; 4 | 5 | #[derive(Debug, Error)] 6 | pub enum VulkanDebugError { 7 | #[error(transparent)] 8 | UnexpectedRenderDeviceError(#[from] RenderDeviceError), 9 | 10 | #[error(transparent)] 11 | UnknownRuntimeError(#[from] anyhow::Error), 12 | } 13 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Release Notes: 2 | 3 | - Added/Fixed/Improved ... ([#](https://github.com/ 4 | nuiipointerexception/vterm/issues/)). 5 | 6 | Optionally, include screenshots / media showcasing your addition that can be included in the release notes. 7 | 8 | ### Or... 9 | 10 | Release Notes: 11 | 12 | - N/A 13 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | unstable_features = true 2 | max_width = 100 3 | imports_granularity = "Crate" 4 | group_imports = "StdExternalCrate" 5 | binop_separator = "Back" 6 | comment_width = 100 7 | reorder_imports = true 8 | tab_spaces = 4 9 | trailing_semicolon = true 10 | use_field_init_shorthand = true 11 | use_small_heuristics = "Max" 12 | wrap_comments = true 13 | spaces_around_ranges = true 14 | newline_style = "Unix" 15 | -------------------------------------------------------------------------------- /crates/vui/src/errors/allocator.rs: -------------------------------------------------------------------------------- 1 | use ::ash::vk; 2 | use thiserror::Error; 3 | 4 | #[derive(Debug, Error)] 5 | pub enum AllocatorError { 6 | #[error("failed to allocate memory using the Vulkan device")] 7 | LogicalDeviceAllocationFailed(#[source] vk::Result), 8 | 9 | #[error("no memory type could be found for flags {:?} and requirements {:?}", .0, .1)] 10 | MemoryTypeNotFound(vk::MemoryPropertyFlags, vk::MemoryRequirements), 11 | } 12 | -------------------------------------------------------------------------------- /assets/windows/vterm.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2, unaware 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /crates/vtty/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vtty" 3 | description = "a pty library for vterm." 4 | version.workspace = true 5 | authors.workspace = true 6 | edition.workspace = true 7 | readme.workspace = true 8 | license.workspace = true 9 | keywords.workspace = true 10 | repository.workspace = true 11 | documentation.workspace = true 12 | 13 | [lib] 14 | path = "src/lib.rs" 15 | crate-type = ["cdylib", "rlib"] 16 | 17 | [dependencies] 18 | log.workspace = true 19 | -------------------------------------------------------------------------------- /crates/vcolor/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vcolor" 3 | description = "a color handling utility for vterm." 4 | version.workspace = true 5 | authors.workspace = true 6 | edition.workspace = true 7 | readme.workspace = true 8 | license.workspace = true 9 | keywords.workspace = true 10 | repository.workspace = true 11 | documentation.workspace = true 12 | 13 | [lib] 14 | path = "src/lib.rs" 15 | crate-type = ["cdylib", "rlib"] 16 | 17 | [dependencies] 18 | log.workspace = true 19 | -------------------------------------------------------------------------------- /crates/vui/src/vulkan/allocator/allocation.rs: -------------------------------------------------------------------------------- 1 | use ash::vk; 2 | 3 | #[derive(Clone, Debug, Copy, PartialEq, Eq)] 4 | pub struct Allocation { 5 | pub memory: vk::DeviceMemory, 6 | pub offset: vk::DeviceSize, 7 | pub byte_size: vk::DeviceSize, 8 | pub memory_type_index: u32, 9 | } 10 | 11 | impl Allocation { 12 | pub fn null() -> Allocation { 13 | Self { memory: vk::DeviceMemory::null(), offset: 0, byte_size: 0, memory_type_index: 0 } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /scripts/copy_images.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ### 4 | # @desc Copy images to the home directory 5 | ### 6 | 7 | SOURCE_DIR="assets/images" 8 | DEST_DIR="$HOME/.vterm/assets/images" 9 | 10 | mkdir -p "$DEST_DIR" 11 | 12 | if diff -r "$SOURCE_DIR" "$DEST_DIR" > /dev/null; then 13 | echo "Images are already up to date." 14 | else 15 | rm -rf "$DEST_DIR"/* 16 | cp -r "$SOURCE_DIR"/* "$DEST_DIR" 17 | 18 | echo "Images have been successfully copied to $DEST_DIR" 19 | fi 20 | -------------------------------------------------------------------------------- /crates/vui/src/errors/descriptor.rs: -------------------------------------------------------------------------------- 1 | use ::ash::vk; 2 | use thiserror::Error; 3 | 4 | #[derive(Debug, Error)] 5 | pub enum DescriptorSetError { 6 | #[error("Unable to create the descriptor set layout")] 7 | UnableToCreateLayout(#[source] vk::Result), 8 | 9 | #[error("Unable to create the descriptor pool")] 10 | UnableToCreatePool(#[source] vk::Result), 11 | 12 | #[error("Unable to allocate descriptors from the pool")] 13 | UnableToAllocateDescriptors(#[source] vk::Result), 14 | } 15 | -------------------------------------------------------------------------------- /crates/vui/src/errors/frame.rs: -------------------------------------------------------------------------------- 1 | use ::thiserror::Error; 2 | use ash::vk; 3 | 4 | use crate::errors::VulkanError; 5 | 6 | #[derive(Debug, Error)] 7 | pub enum FrameError { 8 | #[error("The swapchain needs to be rebuilt")] 9 | SwapchainNeedsRebuild, 10 | 11 | #[error(transparent)] 12 | VkError(vk::Result), 13 | 14 | #[error(transparent)] 15 | UnexpectedRuntimeError(#[from] anyhow::Error), 16 | 17 | #[error(transparent)] 18 | UnexpectedVulkanError(#[from] VulkanError), 19 | } 20 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "tabWidth": 4, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": true, 7 | "quoteProps": "as-needed", 8 | "bracketSpacing": false, 9 | "trailingComma": "all", 10 | "endOfLine": "lf", 11 | "arrowParens": "always", 12 | "jsxSingleQuote": false, 13 | "proseWrap": "preserve", 14 | "htmlWhitespaceSensitivity": "css", 15 | "embeddedLanguageFormatting": "auto", 16 | "plugins": ["prettier-plugin-toml", "prettier-plugin-sh"] 17 | } -------------------------------------------------------------------------------- /crates/vui/src/vulkan/render_device/gpu_queue.rs: -------------------------------------------------------------------------------- 1 | use ash::vk; 2 | 3 | #[derive(Debug, Clone, Copy)] 4 | pub struct GpuQueue { 5 | pub queue: vk::Queue, 6 | pub family_id: u32, 7 | pub index: u32, 8 | } 9 | 10 | impl GpuQueue { 11 | pub fn from_raw(queue: vk::Queue, family_id: u32, index: u32) -> Self { 12 | Self { queue, family_id, index } 13 | } 14 | 15 | pub fn is_same(&self, queue: &GpuQueue) -> bool { 16 | self.family_id == queue.family_id && self.index == queue.index 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /crates/vui/src/ui/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{math, Mat4}; 2 | 3 | pub mod color; 4 | pub mod font; 5 | mod id; 6 | mod input; 7 | mod internal_state; 8 | pub mod primitives; 9 | mod ui; 10 | pub mod widgets; 11 | 12 | pub use self::{ 13 | font::Font, 14 | id::{id_hash, Id}, 15 | input::Input, 16 | internal_state::InternalState, 17 | ui::{UIState, UI}, 18 | }; 19 | 20 | pub fn ui_screen_space_projection(viewport: primitives::Dimensions) -> Mat4 { 21 | math::projections::ortho(0.0, viewport.width, viewport.height, 0.0, 0.0, 1.0) 22 | } 23 | -------------------------------------------------------------------------------- /crates/vui/src/errors/framebuffer.rs: -------------------------------------------------------------------------------- 1 | use ::ash::vk; 2 | use thiserror::Error; 3 | 4 | use crate::errors::VulkanDebugError; 5 | 6 | #[derive(Debug, Error)] 7 | pub enum FramebufferError { 8 | #[error("Unable to create the framebuffer")] 9 | UnableToCreateFramebuffer(#[source] vk::Result), 10 | 11 | #[error("Unable to create a framebuffer for swapchain image {}", .0)] 12 | UnableToCreateSwapchainFramebuffer(usize, #[source] vk::Result), 13 | 14 | #[error(transparent)] 15 | UnexpectedVulkanDebugError(#[from] VulkanDebugError), 16 | } 17 | -------------------------------------------------------------------------------- /crates/vui/src/math/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod projections { 2 | use crate::Mat4; 3 | 4 | pub fn ortho(left: f32, right: f32, bottom: f32, top: f32, near: f32, far: f32) -> Mat4 { 5 | let mh = 2.0 / (right - left); 6 | let bh = (right + left) / (left - right); 7 | let mv = 2.0 / (bottom - top); 8 | let bv = (top + bottom) / (top - bottom); 9 | let mz = 1.0 / (far - near); 10 | let bz = near / (near - far); 11 | Mat4::new(mh, 0.0, 0.0, bh, 0.0, mv, 0.0, bv, 0.0, 0.0, mz, bz, 0.0, 0.0, 0.0, 1.0) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /crates/vui/src/graphics/triangles/shaders/passthrough.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects: enable 3 | #extension GL_EXT_nonuniform_qualifier : require 4 | 5 | layout(location = 0) in vec4 vertex_color; 6 | layout(location = 1) in vec2 uv; 7 | layout(location = 2) flat in int texIndex; 8 | 9 | layout(location = 0) out vec4 frag_color; 10 | 11 | layout(binding = 2) uniform sampler2D textures[]; 12 | 13 | void main() { 14 | vec4 tex_color = texture(textures[nonuniformEXT(texIndex)], uv); 15 | frag_color = tex_color * vertex_color; 16 | } 17 | -------------------------------------------------------------------------------- /crates/vui/src/asset_loader/combined_image_sampler.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use crate::vulkan::image::{sampler::Sampler, view::ImageView}; 4 | 5 | #[derive(Clone)] 6 | pub struct CombinedImageSampler { 7 | pub image_view: Arc, 8 | pub sampler: Arc, 9 | } 10 | 11 | impl CombinedImageSampler { 12 | pub fn of(image_view: ImageView, sampler: Sampler) -> Self { 13 | Self::new(Arc::new(image_view), Arc::new(sampler)) 14 | } 15 | 16 | pub fn new(image_view: Arc, sampler: Arc) -> Self { 17 | Self { image_view, sampler } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /crates/vui/src/ui/input.rs: -------------------------------------------------------------------------------- 1 | use crate::{vec2, Vec2}; 2 | 3 | #[derive(Debug, Copy, Clone)] 4 | pub struct Input { 5 | pub mouse_position: Vec2, 6 | } 7 | 8 | impl Input { 9 | pub fn new() -> Self { 10 | Self { mouse_position: vec2(0.0, 0.0) } 11 | } 12 | 13 | pub fn handle_event(&mut self, event: &winit::event::WindowEvent) { 14 | match event { 15 | winit::event::WindowEvent::CursorMoved { position, .. } => { 16 | self.mouse_position = vec2(position.x as f32, position.y as f32); 17 | } 18 | _ => {} 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /crates/vshell/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vshell" 3 | description = "backend for vterm / terminal emulation library." 4 | version.workspace = true 5 | authors.workspace = true 6 | edition.workspace = true 7 | readme.workspace = true 8 | license.workspace = true 9 | keywords.workspace = true 10 | repository.workspace = true 11 | documentation.workspace = true 12 | 13 | [lib] 14 | path = "src/lib.rs" 15 | crate-type = ["cdylib", "rlib"] 16 | 17 | [dependencies] 18 | log.workspace = true 19 | vui.workspace = true 20 | winit.workspace = true 21 | bitflags = "1.3.2" 22 | parking_lot = "0.12.3" 23 | smallvec = "1.13.2" 24 | -------------------------------------------------------------------------------- /crates/vui/src/asset_loader/error.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use ::image::ImageError; 4 | use thiserror::Error; 5 | 6 | use crate::errors::VulkanError; 7 | 8 | #[derive(Debug, Error)] 9 | pub enum AssetLoaderError { 10 | #[error("An unexpected Vulkan error occured!")] 11 | VulkanErrorWhileLoadingAssets(#[from] VulkanError), 12 | 13 | #[error("Unable to open the texture file")] 14 | UnableToOpenFile(#[from] io::Error), 15 | 16 | #[error("Unable to decode the texture file into rgba.")] 17 | UnableToDecodeImage(#[from] ImageError), 18 | 19 | #[error("Image not found")] 20 | ImageNotFound, 21 | } 22 | -------------------------------------------------------------------------------- /crates/vui/src/errors/sync.rs: -------------------------------------------------------------------------------- 1 | use ash::vk; 2 | use thiserror::Error; 3 | 4 | #[derive(Debug, Error)] 5 | pub enum FenceError { 6 | #[error("Unable to create a new fence")] 7 | UnableToCreateFence(#[source] vk::Result), 8 | 9 | #[error("Error while waiting for fence")] 10 | UnexpectedWaitError(#[source] vk::Result), 11 | 12 | #[error("Error while resetting fence")] 13 | UnexpectedResetError(#[source] vk::Result), 14 | } 15 | 16 | #[derive(Debug, Error)] 17 | pub enum SemaphoreError { 18 | #[error("Unable to create a new semaphore")] 19 | UnableToCreateSemaphore(#[source] vk::Result), 20 | } 21 | -------------------------------------------------------------------------------- /crates/vui/src/ui/widgets/container/constraint.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Copy, Clone, PartialEq)] 2 | pub enum Constraint { 3 | FixedMaxSize(f32), 4 | 5 | PercentMaxSize(f32), 6 | 7 | NoConstraint, 8 | } 9 | 10 | impl Default for Constraint { 11 | fn default() -> Self { 12 | Self::NoConstraint 13 | } 14 | } 15 | 16 | impl Constraint { 17 | pub(super) fn apply(&self, value: f32) -> f32 { 18 | match *self { 19 | Constraint::FixedMaxSize(max) => value.min(max), 20 | Constraint::PercentMaxSize(percentage) => value * percentage, 21 | Constraint::NoConstraint => value, 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /crates/vui/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vui" 3 | description = "ui library for vterm." 4 | version.workspace = true 5 | authors.workspace = true 6 | edition.workspace = true 7 | readme.workspace = true 8 | license.workspace = true 9 | keywords.workspace = true 10 | repository.workspace = true 11 | documentation.workspace = true 12 | 13 | [lib] 14 | name = "vui" 15 | path = "src/lib.rs" 16 | 17 | [dependencies] 18 | winit.workspace = true 19 | ash.workspace = true 20 | ash-window.workspace = true 21 | nalgebra.workspace = true 22 | memoffset.workspace = true 23 | image.workspace = true 24 | ab_glyph.workspace = true 25 | thiserror.workspace = true 26 | anyhow.workspace = true 27 | log.workspace = true 28 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | [advisories] 2 | db-path = "~/.cargo/advisory-db" 3 | db-urls = ["https://github.com/rustsec/advisory-db"] 4 | yanked = "warn" 5 | version = 2 6 | 7 | [licenses] 8 | allow = [ 9 | "MIT", 10 | "Apache-2.0", 11 | "BSD-2-Clause", 12 | "BSD-3-Clause", 13 | "CC0-1.0", 14 | "ISC", 15 | "MPL-2.0", 16 | "Unicode-DFS-2016", 17 | "Apache-2.0 WITH LLVM-exception", 18 | ] 19 | 20 | [bans] 21 | multiple-versions = "deny" 22 | wildcards = "allow" 23 | highlight = "all" 24 | skip-tree = [{ name = "windows-sys" }, { name = "bitflags" }] 25 | skip = [ 26 | # duplicates 27 | { name = "toml_edit" }, 28 | { name = "syn" }, 29 | { name = "redox_syscall" }, 30 | { name = "libredox" }, 31 | ] 32 | -------------------------------------------------------------------------------- /crates/vui/src/errors/image.rs: -------------------------------------------------------------------------------- 1 | use ::ash::vk; 2 | use thiserror::Error; 3 | 4 | use crate::errors::AllocatorError; 5 | 6 | #[derive(Debug, Error)] 7 | pub enum ImageError { 8 | #[error("Unable to create a new image")] 9 | UnableToCreateImage(#[source] vk::Result), 10 | 11 | #[error("Unable to allocate memory for a new image")] 12 | UnableToAllocateImageMemory(#[from] AllocatorError), 13 | 14 | #[error("Unable to bind memory to the new image")] 15 | UnableToBindImageMemory(#[source] vk::Result), 16 | 17 | #[error("Unable to create Image View")] 18 | UnableToCreateView(#[source] vk::Result), 19 | 20 | #[error("Unable to create image sampler")] 21 | UnableToCreateSampler(#[source] vk::Result), 22 | } 23 | -------------------------------------------------------------------------------- /crates/vui/src/vulkan/ffi.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::{CStr, CString}, 3 | os::raw::c_char, 4 | }; 5 | 6 | /// # Safety 7 | /// 8 | /// This function is unsafe because it dereferences the pointers in the input 9 | /// slice. 10 | /// 11 | /// # Panics 12 | /// 13 | /// This function panics if any of the input pointers are null. 14 | pub unsafe fn to_os_ptrs(strings: &Vec<*const i8>) -> (Vec, Vec<*const c_char>) { 15 | let mut c_strings = Vec::with_capacity(strings.len()); 16 | let mut c_ptrs = Vec::with_capacity(strings.len()); 17 | 18 | for s in strings { 19 | let c_str = CStr::from_ptr(*s); 20 | c_strings.push(c_str.to_owned()); 21 | c_ptrs.push(c_str.as_ptr()); 22 | } 23 | 24 | (c_strings, c_ptrs) 25 | } 26 | -------------------------------------------------------------------------------- /crates/vterm/build.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | 3 | fn main() { 4 | let mut version = String::from(env!("CARGO_PKG_VERSION")); 5 | if let Some(commit_hash) = commit_hash() { 6 | version = format!("{version} ({commit_hash})"); 7 | } 8 | println!("cargo:rustc-env=VERSION={version}"); 9 | 10 | #[cfg(windows)] 11 | embed_resource::compile("../../assets/windows/vterm.rc", embed_resource::NONE); 12 | } 13 | 14 | fn commit_hash() -> Option { 15 | Command::new("git") 16 | .args(["rev-parse", "--short", "HEAD"]) 17 | .output() 18 | .ok() 19 | .filter(|output| output.status.success()) 20 | .and_then(|output| String::from_utf8(output.stdout).ok()) 21 | .map(|hash| hash.trim().into()) 22 | } 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TARGET = vterm 2 | 3 | ASSETS_DIR = assets 4 | 5 | all: help 6 | 7 | help: ## Print this help message 8 | @grep -E '^[a-zA-Z._-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 9 | 10 | copy_assets: ## Copy images to the home directory 11 | @./scripts/copy_images.sh 12 | 13 | clean: ## Remove all build artifacts 14 | @cargo clean 15 | 16 | build: ## Build optimized binary for current platform 17 | @platform=$$(uname -s | tr '[:upper:]' '[:lower:]'); \ 18 | if [ "$$platform" != "linux" ]; then \ 19 | echo "Unsupported platform: $$platform. Only Linux is supported."; \ 20 | exit 1; \ 21 | fi; \ 22 | command="cargo build --release --"; \ 23 | $$command 24 | 25 | .PHONY: build clean help run copy_assets 26 | -------------------------------------------------------------------------------- /crates/vui/src/errors/pipeline.rs: -------------------------------------------------------------------------------- 1 | use ::ash::vk; 2 | use thiserror::Error; 3 | 4 | #[derive(Debug, Error)] 5 | pub enum PipelineError { 6 | #[error("The shader's source bytes must be evenly divisible into u32 words")] 7 | InvalidSourceLengthInShaderSPIRV, 8 | 9 | #[error("Improper bytes found in compiled SPIRV shader module source")] 10 | InvalidBytesInShaderSPIRV(#[source] core::array::TryFromSliceError), 11 | 12 | #[error("Unable to create the shader module")] 13 | UnableToCreateShaderModule(#[source] vk::Result), 14 | 15 | #[error("Unable to create the pipeline layout")] 16 | UnableToCreatePipelineLayout(#[source] vk::Result), 17 | 18 | #[error("Unable to create graphics pipeline")] 19 | UnableToCreateGraphicsPipeline(#[source] vk::Result), 20 | } 21 | -------------------------------------------------------------------------------- /crates/vui/src/graphics/triangles/shaders/passthrough.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects: enable 3 | 4 | struct Vertex 5 | { 6 | vec4 pos; 7 | vec4 rgba; 8 | vec2 uv; 9 | int texIndex; 10 | }; 11 | 12 | layout(std140, set=0, binding=0) readonly buffer SBO { Vertex data[]; } sbo; 13 | layout(set=0, binding=1) readonly uniform UniformBufferObject { 14 | mat4 view_projection; 15 | } ubo; 16 | 17 | layout(location = 0) out vec4 vertex_color; 18 | layout(location = 1) out vec2 uv; 19 | layout(location = 2) flat out int texIndex; 20 | 21 | void main() { 22 | Vertex vert = sbo.data[gl_VertexIndex]; 23 | vertex_color = vert.rgba; 24 | uv = vert.uv; 25 | texIndex = vert.texIndex; 26 | gl_Position = ubo.view_projection * vert.pos; 27 | } 28 | -------------------------------------------------------------------------------- /crates/vshell/src/grid/cell.rs: -------------------------------------------------------------------------------- 1 | use crate::color::Color; 2 | 3 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 4 | pub struct Style { 5 | pub fg: Color, 6 | pub bg: Color, 7 | pub bold: bool, 8 | pub underline: bool, 9 | pub italics: bool, 10 | } 11 | 12 | impl Default for Style { 13 | fn default() -> Self { 14 | Self { fg: Color::WHITE, bg: Color::BLACK, bold: false, underline: false, italics: false } 15 | } 16 | } 17 | 18 | #[derive(Debug, Copy, Clone)] 19 | pub struct Cell { 20 | pub c: Option, 21 | pub style: Style, 22 | } 23 | 24 | impl Cell { 25 | pub fn new() -> Self { 26 | Self { c: None, style: Default::default() } 27 | } 28 | } 29 | 30 | impl Default for Cell { 31 | fn default() -> Self { 32 | Self::new() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /crates/vui/src/errors/buffer.rs: -------------------------------------------------------------------------------- 1 | use ::ash::vk; 2 | use thiserror::Error; 3 | 4 | use crate::errors::AllocatorError; 5 | 6 | #[derive(Debug, Error)] 7 | pub enum BufferError { 8 | #[error("Unable to map device memory")] 9 | UnableToMapDeviceMemory(#[source] vk::Result), 10 | 11 | #[error("Device memory pointer was not found, did you try calling .map()?")] 12 | NoMappedPointerFound, 13 | 14 | #[error( 15 | "Unable to create a new device buffer for {} bytes with flags {:?}", 16 | .size, 17 | .usage 18 | )] 19 | UnableToCreateBuffer { size: u64, usage: vk::BufferUsageFlags, source: vk::Result }, 20 | 21 | #[error(transparent)] 22 | UnableToAllocateBufferMemory(#[from] AllocatorError), 23 | 24 | #[error("Unable to bind device memory to buffer")] 25 | UnableToBindDeviceMemory(#[source] vk::Result), 26 | } 27 | -------------------------------------------------------------------------------- /crates/vui/src/vulkan/sync/semaphore_pool.rs: -------------------------------------------------------------------------------- 1 | use ::std::sync::Arc; 2 | 3 | use crate::{ 4 | errors::SemaphoreError, 5 | vulkan::{render_device::RenderDevice, sync::Semaphore}, 6 | }; 7 | 8 | pub struct SemaphorePool { 9 | recycled_semaphores: Vec, 10 | pub vk_dev: Arc, 11 | } 12 | 13 | impl SemaphorePool { 14 | pub fn new(vk_dev: Arc) -> Self { 15 | Self { recycled_semaphores: vec![], vk_dev } 16 | } 17 | 18 | pub fn get_semaphore(&mut self) -> Result { 19 | if let Some(recycled) = self.recycled_semaphores.pop() { 20 | Ok(recycled) 21 | } else { 22 | Semaphore::new(self.vk_dev.clone()) 23 | } 24 | } 25 | 26 | pub fn return_semaphore(&mut self, semaphore: Semaphore) { 27 | self.recycled_semaphores.push(semaphore); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /crates/vui/src/format/markdown.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | pub struct MdList<'data, T>(pub &'data [T]); 4 | 5 | impl<'data, T> fmt::Debug for MdList<'data, T> 6 | where 7 | T: fmt::Debug, 8 | { 9 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 10 | f.write_str("\n")?; 11 | for entry in self.0 { 12 | if f.alternate() { 13 | f.write_fmt(format_args!("- {:#?}\n", entry))?; 14 | } else { 15 | f.write_fmt(format_args!("- {:?}\n", entry))?; 16 | } 17 | } 18 | Ok(()) 19 | } 20 | } 21 | 22 | impl<'data, T> fmt::Display for MdList<'data, T> 23 | where 24 | T: fmt::Display, 25 | { 26 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 27 | f.write_str("\n")?; 28 | for entry in self.0 { 29 | f.write_fmt(format_args!("- {}\n", entry))?; 30 | } 31 | Ok(()) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /crates/vui/src/vulkan/sync/semaphore.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use ash::vk; 4 | 5 | use crate::{errors::SemaphoreError, vulkan::render_device::RenderDevice}; 6 | 7 | pub struct Semaphore { 8 | pub raw: vk::Semaphore, 9 | pub vk_dev: Arc, 10 | } 11 | 12 | impl Semaphore { 13 | pub fn new(vk_dev: Arc) -> Result { 14 | let create_info = vk::SemaphoreCreateInfo { ..Default::default() }; 15 | let raw = unsafe { 16 | vk_dev 17 | .logical_device 18 | .create_semaphore(&create_info, None) 19 | .map_err(SemaphoreError::UnableToCreateSemaphore)? 20 | }; 21 | Ok(Self { raw, vk_dev }) 22 | } 23 | } 24 | 25 | impl Drop for Semaphore { 26 | fn drop(&mut self) { 27 | unsafe { 28 | self.vk_dev.logical_device.destroy_semaphore(self.raw, None); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /crates/vui/src/vulkan/render_pass/mod.rs: -------------------------------------------------------------------------------- 1 | use ::std::sync::Arc; 2 | use ash::vk; 3 | 4 | use crate::{errors::RenderPassError, vulkan::render_device::RenderDevice}; 5 | 6 | pub struct RenderPass { 7 | pub raw: vk::RenderPass, 8 | pub vk_dev: Arc, 9 | } 10 | 11 | impl RenderPass { 12 | pub fn new( 13 | vk_dev: Arc, 14 | create_info: &vk::RenderPassCreateInfo, 15 | ) -> Result { 16 | let raw = unsafe { 17 | vk_dev 18 | .logical_device 19 | .create_render_pass(create_info, None) 20 | .map_err(RenderPassError::UnableToCreateRenderPass)? 21 | }; 22 | Ok(Self { raw, vk_dev }) 23 | } 24 | } 25 | 26 | impl Drop for RenderPass { 27 | fn drop(&mut self) { 28 | unsafe { 29 | self.vk_dev.logical_device.destroy_render_pass(self.raw, None); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /crates/vui/src/asset_loader/mipmap_data.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Debug)] 2 | pub struct MipmapData { 3 | pub width: u32, 4 | pub height: u32, 5 | pub data: Vec, 6 | } 7 | 8 | impl MipmapData { 9 | pub fn allocate(width: u32, height: u32, initial_value: [u8; 4]) -> Self { 10 | let mut data = vec![0; (width * height * 4) as usize]; 11 | for i in 0 .. width * height { 12 | data[(i * 4 + 0) as usize] = initial_value[0]; 13 | data[(i * 4 + 1) as usize] = initial_value[1]; 14 | data[(i * 4 + 2) as usize] = initial_value[2]; 15 | data[(i * 4 + 3) as usize] = initial_value[3]; 16 | } 17 | Self { width, height, data } 18 | } 19 | 20 | pub fn write_pixel(&mut self, x: u32, y: u32, value: [u8; 4]) { 21 | let index = ((x + y * self.width) * 4) as usize; 22 | for i in 0 .. 4 { 23 | self.data[index + i] = value[i]; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /crates/vui/src/graphics/vertex.rs: -------------------------------------------------------------------------------- 1 | use crate::{ui::color::Color, Vec2, Vec3}; 2 | 3 | #[repr(C)] 4 | #[derive(Debug, Copy, Clone)] 5 | pub struct Vertex { 6 | pub pos: [f32; 4], 7 | 8 | pub rgba: [f32; 4], 9 | 10 | pub uv: [f32; 2], 11 | 12 | pub texture_index: i32, 13 | 14 | pub _pad: i32, 15 | } 16 | 17 | impl Default for Vertex { 18 | fn default() -> Self { 19 | Self { 20 | pos: [0.0, 0.0, 0.0, 1.0], 21 | rgba: [1.0, 1.0, 1.0, 1.0], 22 | uv: [0.0, 0.0], 23 | texture_index: 0, 24 | _pad: 0, 25 | } 26 | } 27 | } 28 | 29 | impl Vertex { 30 | pub fn new(pos: Vec3, rgba: Color, uv: Vec2, texture_index: i32) -> Vertex { 31 | Self { 32 | pos: [pos.x, pos.y, pos.z, 1.0], 33 | rgba: [rgba.r, rgba.g, rgba.b, rgba.a], 34 | uv: [uv.x, uv.y], 35 | texture_index, 36 | _pad: 0, 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /crates/vui/src/ui/widgets/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use crate::{ 2 | gen_id, 3 | ui::{ 4 | id::id_hash, 5 | primitives::{Axis, Justify, SpaceBetween}, 6 | widgets::{ 7 | Align, Col, Constraint, Container, Element, HAlignment, HSplit, Image, Label, 8 | LabelStyle, Row, VAlignment, Widget, Window, WithContainer, 9 | }, 10 | Font, Id, 11 | }, 12 | }; 13 | 14 | pub fn align(widget: W) -> Align 15 | where 16 | W: Widget, 17 | { 18 | Align::new(widget) 19 | } 20 | 21 | pub fn label(font: &Font, text: &str) -> Label { 22 | Label::new(font, text) 23 | } 24 | 25 | pub fn img(width: f32, height: f32, texture_index: i32) -> Image { 26 | Image::new(width, height, texture_index) 27 | } 28 | 29 | pub fn col() -> Col { 30 | Col::new() 31 | } 32 | 33 | pub fn row() -> Row { 34 | Row::new() 35 | } 36 | 37 | pub fn hsplit() -> HSplit { 38 | HSplit::new() 39 | } 40 | -------------------------------------------------------------------------------- /crates/vui/src/errors/mod.rs: -------------------------------------------------------------------------------- 1 | pub use crate::errors::{ 2 | allocator::AllocatorError, 3 | buffer::BufferError, 4 | command_buffer::{CommandBufferError, CommandResult}, 5 | debug::VulkanDebugError, 6 | descriptor::DescriptorSetError, 7 | frame::FrameError, 8 | framebuffer::FramebufferError, 9 | graphics::ImmediateModeGraphicsError, 10 | image::ImageError, 11 | instance::InstanceError, 12 | pipeline::PipelineError, 13 | render_device::{PhysicalDeviceError, QueueSelectionError, RenderDeviceError, SwapchainError}, 14 | render_pass::RenderPassError, 15 | sync::{FenceError, SemaphoreError}, 16 | vulkan::VulkanError, 17 | window::{WindowError, WindowSurfaceError}, 18 | }; 19 | 20 | mod allocator; 21 | mod buffer; 22 | mod command_buffer; 23 | mod debug; 24 | mod descriptor; 25 | mod frame; 26 | mod framebuffer; 27 | mod graphics; 28 | mod image; 29 | mod instance; 30 | mod pipeline; 31 | mod render_device; 32 | mod render_pass; 33 | mod sync; 34 | mod vulkan; 35 | mod window; 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/0_feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: "Use this template to request a feature for vterm" 3 | labels: ["feature"] 4 | body: 5 | - type: checkboxes 6 | attributes: 7 | label: Check for existing issues 8 | description: 9 | Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a 10 | `+1` (👍) on it. 11 | options: 12 | - label: Completed 13 | required: true 14 | - type: textarea 15 | attributes: 16 | label: Describe the feature 17 | description: A clear and concise description of what you want to happen. 18 | validations: 19 | required: true 20 | - type: textarea 21 | attributes: 22 | label: | 23 | If applicable, add some sort of preview to help us visualize your idea. 24 | description: Drag images into the text input below 25 | validations: 26 | required: false 27 | -------------------------------------------------------------------------------- /crates/vui/src/vulkan/allocator/mod.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use ::ash::vk; 4 | 5 | pub use self::{ 6 | allocation::Allocation, composable::ComposableAllocator, locked_memory::LockedMemoryAllocator, 7 | passthrough::PassthroughAllocator, 8 | }; 9 | use crate::{errors::AllocatorError, vulkan::render_device::RenderDevice}; 10 | 11 | mod allocation; 12 | mod composable; 13 | mod locked_memory; 14 | mod passthrough; 15 | 16 | pub trait MemoryAllocator: Send + Sync { 17 | unsafe fn allocate_memory( 18 | &self, 19 | memory_requirements: vk::MemoryRequirements, 20 | property_flags: vk::MemoryPropertyFlags, 21 | ) -> Result; 22 | 23 | unsafe fn free(&self, allocation: &Allocation) -> Result<(), AllocatorError>; 24 | } 25 | 26 | pub fn create_default_allocator(vk_dev: Arc) -> Arc { 27 | let locked_allocator = 28 | LockedMemoryAllocator::new(vk_dev.clone(), PassthroughAllocator::new(vk_dev.clone())); 29 | Arc::new(locked_allocator) 30 | } 31 | -------------------------------------------------------------------------------- /crates/vui/src/errors/command_buffer.rs: -------------------------------------------------------------------------------- 1 | use ::ash::vk; 2 | use thiserror::Error; 3 | 4 | #[derive(Debug, Error)] 5 | pub enum CommandBufferError { 6 | #[error("Unable to create a new command buffer pool")] 7 | UnableToCreateCommandPool(#[source] vk::Result), 8 | 9 | #[error("Unable to allocate a command buffer from the command pool")] 10 | UnableToAllocateBuffer(#[source] vk::Result), 11 | 12 | #[error("Unable to reset the command pool")] 13 | UnableToResetPool(#[source] vk::Result), 14 | 15 | #[error("Unable to begin the command buffer")] 16 | UnableToBeginCommandBuffer(#[source] vk::Result), 17 | 18 | #[error("Unable to end the command buffer")] 19 | UnableToEndCommandBuffer(#[source] vk::Result), 20 | 21 | #[error("Unable to submit the command buffer for execution")] 22 | UnableToSubmitCommandBuffer(#[source] vk::Result), 23 | 24 | #[error("Error while waiting for the device to idle.")] 25 | UnableToWaitForDeviceIdle(#[source] vk::Result), 26 | } 27 | 28 | pub type CommandResult = Result; 29 | -------------------------------------------------------------------------------- /crates/vshell/src/grid/row.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ops::{Index, IndexMut}, 3 | slice::Iter, 4 | }; 5 | 6 | use crate::grid::cell::Cell; 7 | 8 | #[derive(Debug, Clone)] 9 | pub struct Row { 10 | pub inner: Vec, 11 | } 12 | 13 | impl Row { 14 | pub fn new(columns: usize) -> Self { 15 | let mut inner = Vec::with_capacity(columns); 16 | 17 | inner.resize(columns, Cell::default()); 18 | 19 | Self { inner } 20 | } 21 | 22 | pub fn reset(&mut self) { 23 | for cell in &mut self.inner { 24 | cell.c = None; 25 | } 26 | } 27 | } 28 | 29 | impl Index for Row { 30 | type Output = Cell; 31 | 32 | fn index(&self, index: usize) -> &Self::Output { 33 | &self.inner[index] 34 | } 35 | } 36 | 37 | impl IndexMut for Row { 38 | fn index_mut(&mut self, index: usize) -> &mut Self::Output { 39 | &mut self.inner[index] 40 | } 41 | } 42 | 43 | impl<'a> IntoIterator for &'a Row { 44 | type Item = &'a Cell; 45 | type IntoIter = Iter<'a, Cell>; 46 | 47 | fn into_iter(self) -> Self::IntoIter { 48 | self.inner.iter() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /crates/vcolor/src/scheme.rs: -------------------------------------------------------------------------------- 1 | /// This is the default color scheme used by Viable in every application. 2 | /// This is subject to change, but for now i need to store the colors somewhere i guess. 3 | 4 | // primar 5 | 6 | pub const PRIMARY_BACKGROUND: &str = "#262427"; 7 | pub const PRIMARY_FOREGROUND: &str = "#fcfcfa"; 8 | 9 | // cursor 10 | pub const CURSOR_TEXT: &str = "#000000"; 11 | pub const CURSOR_BACKGROUND: &str = "#fcfcfa"; 12 | 13 | // normal 14 | pub const NORMAL_BLACK: &str = "#262427"; 15 | pub const NORMAL_RED: &str = "#ff7272"; 16 | pub const NORMAL_GREEN: &str = "#bcdf59"; 17 | pub const NORMAL_YELLOW: &str = "#ffca58"; 18 | pub const NORMAL_BLUE: &str = "#49cae4"; 19 | pub const NORMAL_MAGENTA: &str = "#a093e2"; 20 | pub const NORMAL_CYAN: &str = "#aee8f4"; 21 | pub const NORMAL_WHITE: &str = "#fcfcfa"; 22 | 23 | // bright 24 | pub const BRIGHT_BLACK: &str = "#545452"; 25 | pub const BRIGHT_RED: &str = "#ff7272"; 26 | pub const BRIGHT_GREEN: &str = "#bcdf59"; 27 | pub const BRIGHT_YELLOW: &str = "#ffca58"; 28 | pub const BRIGHT_BLUE: &str = "#49cae4"; 29 | pub const BRIGHT_MAGENTA: &str = "#a093e2"; 30 | pub const BRIGHT_CYAN: &str = "#aee8f4"; 31 | pub const BRIGHT_WHITE: &str = "#fcfcfa"; 32 | -------------------------------------------------------------------------------- /crates/vterm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vterm" 3 | description = "A cross-platform, vulkan based terminal emulator" 4 | version.workspace = true 5 | authors.workspace = true 6 | edition.workspace = true 7 | readme.workspace = true 8 | license.workspace = true 9 | keywords.workspace = true 10 | repository.workspace = true 11 | documentation.workspace = true 12 | 13 | [[bin]] 14 | name = "vterm" 15 | path = "src/main.rs" 16 | 17 | [dependencies] 18 | dirs.workspace = true 19 | fs_extra.workspace = true 20 | vui.workspace = true 21 | anyhow.workspace = true 22 | winit.workspace = true 23 | log.workspace = true 24 | ash.workspace = true 25 | ash-window.workspace = true 26 | 27 | [target.'cfg(windows)'.dependencies] 28 | windows-sys = { version = "0.52", features = [ 29 | "Win32_System_Console", 30 | "Win32_Foundation", 31 | "Win32_Security", 32 | "Win32_System_LibraryLoader", 33 | "Win32_System_Threading", 34 | "Win32_System_WindowsProgramming", 35 | "Win32_System_IO", 36 | "Win32_Graphics_Gdi", 37 | "Win32_UI_Shell", 38 | "Win32_UI_WindowsAndMessaging", 39 | ] } 40 | 41 | [features] 42 | default = ["wayland", "x11"] 43 | x11 = ["winit/x11"] 44 | wayland = ["winit/wayland", "winit/wayland-dlopen"] 45 | nightly = [] 46 | -------------------------------------------------------------------------------- /crates/vui/src/pipeline/per_frame.rs: -------------------------------------------------------------------------------- 1 | use ::std::sync::Arc; 2 | 3 | use crate::{ 4 | errors::VulkanError, 5 | vulkan::{ 6 | command_buffer::{CommandBuffer, CommandPool}, 7 | render_device::RenderDevice, 8 | sync::{Fence, Semaphore}, 9 | }, 10 | }; 11 | 12 | pub struct PerFrame { 13 | pub acquire_semaphore: Option, 14 | 15 | pub release_semaphore: Semaphore, 16 | 17 | pub queue_submit_fence: Fence, 18 | 19 | pub command_buffer: CommandBuffer, 20 | 21 | pub command_pool: Arc, 22 | } 23 | 24 | impl PerFrame { 25 | pub fn new(vk_dev: Arc) -> Result { 26 | let acquire_semaphore = None; 27 | let release_semaphore = Semaphore::new(vk_dev.clone())?; 28 | let queue_submit_fence = Fence::new(vk_dev.clone())?; 29 | 30 | let command_pool = Arc::new(CommandPool::new_transient_graphics_pool(vk_dev.clone())?); 31 | let command_buffer = CommandBuffer::new_primary(command_pool.clone())?; 32 | 33 | Ok(Self { 34 | acquire_semaphore, 35 | release_semaphore, 36 | queue_submit_fence, 37 | command_pool, 38 | command_buffer, 39 | }) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vterm 2 | 3 | ### A cross-platform, Vulkan terminal emulator written in Rust. 4 | 5 | ### The project is not usable in its current state and is subject to change a lot. 6 | 7 | ### Project Status 8 | 9 | - [x] Windowing 10 | - [x] Rendering pipeline 11 | - [x] Image drawing 12 | - [x] Font drawing 13 | - [x] Custom font loading 14 | - [ ] Layouting for UI layer (Bugged right now) 15 | - [ ] Ptty 16 | - [ ] Handle input 17 | - [ ] Terminal buffer to screen 18 | - [ ] Tabs 19 | - [ ] Draggable / multi window 20 | - [ ] Menus, settings 21 | - [ ] Config 22 | 23 | ## Dependencies 24 | 25 | - Vulkan SDK 26 | - Rust nightly 27 | 28 | ## Installation 29 | 30 | For detailed installation instructions, please refer to the [INSTALL.md](./INSTALL.md) file. 31 | 32 | #### Note: Is your platform not supported? Either wait or contribute. 33 | 34 | #### Note: There is a small execution barrier (~200ms) before the terminal opens on NVIDIA cards. This is a driver-related issue. It has something to do with `vkCreateInstance` and `vkCreateDevice` being extremely slow on NVIDIA cards. I am hoping to improve it as much as possible, but the biggest overhead lies in the lack of driver optimization. So I think we can expect improvements soon. 35 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["crates/vui", "crates/vterm", "crates/vshell", "crates/vtty"] 3 | default-members = ["crates/vterm"] 4 | resolver = "2" 5 | 6 | [workspace.package] 7 | version = "0.1.0" 8 | authors = ["nuii "] 9 | edition = "2021" 10 | license = "MPL-2.0" 11 | readme = "README.md" 12 | keywords = ["graphics", "terminal"] 13 | repository = "https://github.com/viablegg/vterm" 14 | homepage = "https://viable.gg/vterm" 15 | documentation = "https://github.com/viablegg/vterm#readme" 16 | 17 | [workspace.dependencies] 18 | vui = { path = "crates/vui" } 19 | vshell = { path = "crates/vshell" } 20 | vtty = { path = "crates/vtty" } 21 | ab_glyph = "0.2.26" 22 | winit = { version = "0.30.2", default-features = false, features = ["rwh_06"] } 23 | anyhow = "1.0.44" 24 | log = "0.4.13" 25 | ash = { version = "0.38.0", features = ["linked"] } 26 | ash-window = "0.13.0" 27 | nalgebra = "*" 28 | memoffset = "*" 29 | thiserror = "1.0.29" 30 | image = "0.25.1" 31 | bitflags = "2.5.0" 32 | fs_extra = "1.3.0" 33 | 34 | libc = "0.2.155" 35 | dirs = "5.0.1" 36 | mio = "1.0.0" 37 | signal-hook = "0.3.10" 38 | iovec = "0.1.1" 39 | 40 | [profile.release] 41 | opt-level = "z" 42 | lto = "thin" 43 | debug = 1 44 | codegen-units = 1 45 | panic = "abort" 46 | strip = true 47 | -------------------------------------------------------------------------------- /crates/vui/src/vulkan/allocator/passthrough.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use ::ash::vk; 4 | 5 | use crate::vulkan::{ 6 | allocator::{Allocation, AllocatorError, ComposableAllocator}, 7 | render_device::RenderDevice, 8 | }; 9 | 10 | pub struct PassthroughAllocator { 11 | vk_dev: Arc, 12 | } 13 | 14 | impl PassthroughAllocator { 15 | pub fn new(vk_dev: Arc) -> Self { 16 | Self { vk_dev } 17 | } 18 | } 19 | 20 | impl ComposableAllocator for PassthroughAllocator { 21 | unsafe fn allocate( 22 | &mut self, 23 | allocate_info: vk::MemoryAllocateInfo, 24 | _alignment: u64, 25 | ) -> Result { 26 | Ok(Allocation { 27 | memory: self 28 | .vk_dev 29 | .logical_device 30 | .allocate_memory(&allocate_info, None) 31 | .map_err(AllocatorError::LogicalDeviceAllocationFailed)?, 32 | offset: 0, 33 | byte_size: allocate_info.allocation_size, 34 | memory_type_index: allocate_info.memory_type_index, 35 | }) 36 | } 37 | 38 | unsafe fn free(&mut self, allocation: &Allocation) -> Result<(), AllocatorError> { 39 | self.vk_dev.logical_device.free_memory(allocation.memory, None); 40 | Ok(()) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /crates/vui/src/vulkan/pipeline/mod.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use ash::vk; 4 | 5 | use crate::{ 6 | errors::PipelineError, 7 | vulkan::{pipeline::layout::PipelineLayout, render_device::RenderDevice}, 8 | }; 9 | 10 | pub mod layout; 11 | pub mod shader; 12 | 13 | pub struct Pipeline { 14 | pub pipeline_layout: Arc, 15 | 16 | pub raw: vk::Pipeline, 17 | 18 | pub bind_point: vk::PipelineBindPoint, 19 | 20 | pub vk_dev: Arc, 21 | } 22 | 23 | impl Pipeline { 24 | pub fn new_graphics_pipeline( 25 | create_info: vk::GraphicsPipelineCreateInfo, 26 | pipeline_layout: Arc, 27 | vk_dev: Arc, 28 | ) -> Result { 29 | let raw = unsafe { 30 | vk_dev 31 | .logical_device 32 | .create_graphics_pipelines(vk::PipelineCache::null(), &[create_info], None) 33 | .map_err(|(_, err)| PipelineError::UnableToCreateGraphicsPipeline(err))?[0] 34 | }; 35 | Ok(Self { pipeline_layout, raw, bind_point: vk::PipelineBindPoint::GRAPHICS, vk_dev }) 36 | } 37 | } 38 | 39 | impl Drop for Pipeline { 40 | fn drop(&mut self) { 41 | unsafe { 42 | self.vk_dev.logical_device.destroy_pipeline(self.raw, None); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "prepare": "husky init", 4 | "format": "cargo fmt --all && prettier --write '**/*.{js,jsx,ts,tsx,json,css,scss,md,html,toml,sh}'", 5 | "lint": "cargo clippy --fix --allow-dirty", 6 | "test": "cargo test --", 7 | "dev": "cargo run --", 8 | "trace": "cargo run -- --log --log-level=Trace", 9 | "warn": "cargo run -- --log --log-level=Warn", 10 | "info": "cargo run -- --log --log-level=Info", 11 | "build": "make build", 12 | "release": "make release", 13 | "license": "cargo deny check" 14 | }, 15 | "devDependencies": { 16 | "husky": "^9.0.11", 17 | "lint-staged": "^15.2.7", 18 | "prettier": "^3.3.2", 19 | "prettier-plugin-rust": "^0.1.9", 20 | "prettier-plugin-sh": "^0.14.0", 21 | "prettier-plugin-toml": "^2.0.1" 22 | }, 23 | "lint-staged": { 24 | "*.rs": [ 25 | "cargo fmt --all", 26 | "cargo clippy --fix --allow-dirty", 27 | "cargo test --", 28 | "cargo deny check" 29 | ], 30 | "*.{js,jsx,ts,tsx,json,css,scss,md,html,toml,sh}": [ 31 | "prettier --write" 32 | ] 33 | }, 34 | "husky": { 35 | "hooks": { 36 | "pre-commit": "lint-staged" 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2_crash_report.yml: -------------------------------------------------------------------------------- 1 | name: Crash Report 2 | description: | 3 | Use this template for crash reports. 4 | labels: ["crash"] 5 | body: 6 | - type: checkboxes 7 | attributes: 8 | label: Check for existing issues 9 | description: 10 | Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a 11 | `+1` (👍) on it. 12 | options: 13 | - label: Completed 14 | required: true 15 | - type: textarea 16 | attributes: 17 | label: Describe the crash / provide steps to reproduce it 18 | description: A clear and concise description of what the crash is. 19 | validations: 20 | required: true 21 | - type: textarea 22 | id: environment 23 | attributes: 24 | label: Environment 25 | description: | 26 | - OS: [e.g. archlinux] 27 | - vterm Version: [e.g. 0.1.0] 28 | validations: 29 | required: true 30 | - type: textarea 31 | attributes: 32 | label: If applicable, attach any further logs or debug discoveries here. 33 | description: | 34 | We currently do not have proper logs, but will improve this in the future. 35 | validations: 36 | required: false 37 | -------------------------------------------------------------------------------- /crates/vui/src/vulkan/render_device/physical_device.rs: -------------------------------------------------------------------------------- 1 | use ash::vk; 2 | 3 | use crate::{ 4 | errors::PhysicalDeviceError, 5 | vulkan::{render_device::QueueFamilyIndices, window_surface::WindowSurface}, 6 | }; 7 | 8 | pub fn find_optimal( 9 | ash: &ash::Instance, 10 | window_surface: &WindowSurface, 11 | ) -> Result { 12 | let physical_devices = unsafe { 13 | ash.enumerate_physical_devices().map_err(PhysicalDeviceError::UnableToEnumerateDevices)? 14 | }; 15 | let physical_device = physical_devices 16 | .iter() 17 | .find(|device| is_device_suitable(ash, device, window_surface)) 18 | .ok_or(PhysicalDeviceError::NoSuitableDeviceFound)?; 19 | Ok(*physical_device) 20 | } 21 | 22 | fn is_device_suitable( 23 | ash: &ash::Instance, 24 | physical_device: &vk::PhysicalDevice, 25 | window_surface: &WindowSurface, 26 | ) -> bool { 27 | let queues_supported = QueueFamilyIndices::find(ash, physical_device, window_surface).is_ok(); 28 | 29 | let format_available = unsafe { !window_surface.supported_formats(physical_device).is_empty() }; 30 | 31 | let presentation_mode_available = 32 | unsafe { !window_surface.supported_presentation_modes(physical_device).is_empty() }; 33 | 34 | queues_supported && format_available && presentation_mode_available 35 | } 36 | -------------------------------------------------------------------------------- /crates/vui/src/ui/primitives/dimensions.rs: -------------------------------------------------------------------------------- 1 | use crate::{ui::primitives::Rect, vec2, Vec2}; 2 | 3 | #[derive(Debug, Copy, Clone, PartialEq)] 4 | pub struct Dimensions { 5 | pub width: f32, 6 | pub height: f32, 7 | } 8 | 9 | impl Dimensions { 10 | pub fn new(width: f32, height: f32) -> Self { 11 | Self { width, height } 12 | } 13 | 14 | #[inline] 15 | pub fn min(&self, other: &Self) -> Self { 16 | Self::new(self.width.min(other.width), self.height.min(other.height)) 17 | } 18 | 19 | #[inline] 20 | pub fn max(&self, other: &Self) -> Self { 21 | Self::new(self.width.max(other.width), self.height.max(other.height)) 22 | } 23 | 24 | pub fn as_rect(&self) -> Rect { 25 | Rect::new(0.0, 0.0, self.height, self.width) 26 | } 27 | } 28 | 29 | impl Into for Vec2 { 30 | fn into(self) -> Dimensions { 31 | Dimensions { width: self.x, height: self.y } 32 | } 33 | } 34 | 35 | impl Into for Dimensions { 36 | fn into(self) -> Vec2 { 37 | vec2(self.width, self.height) 38 | } 39 | } 40 | 41 | impl Into for (i32, i32) { 42 | fn into(self) -> Dimensions { 43 | Dimensions { width: self.0 as f32, height: self.1 as f32 } 44 | } 45 | } 46 | 47 | impl Into for (f32, f32) { 48 | fn into(self) -> Dimensions { 49 | Dimensions { width: self.0, height: self.1 } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /crates/vui/src/errors/instance.rs: -------------------------------------------------------------------------------- 1 | use ash::vk; 2 | use thiserror::Error; 3 | 4 | #[derive(Debug, Error)] 5 | pub enum InstanceError { 6 | #[error("Unable to setup the Vulkan debug callback")] 7 | DebugMessengerCreateFailed(#[source] vk::Result), 8 | 9 | #[error("Unable to list the available Vulkan extensions on this platform")] 10 | UnableToListAvailableExtensions(#[source] vk::Result), 11 | 12 | #[error("Required extensions are not available on this platform: {:?}", .0)] 13 | RequiredExtensionsNotFound(Vec), 14 | 15 | #[error("Unable to list the available Vulkan layers on this platform")] 16 | UnableToListAvailableLayers(#[source] vk::Result), 17 | 18 | #[error("Required layers are not available on this platform: {:?}", .0)] 19 | RequiredLayersNotFound(Vec), 20 | 21 | #[error("Error while creating the Vulkan function loader")] 22 | VulkanLoadingError(#[source] ash::LoadingError), 23 | 24 | #[error("Error while creating the Vulkan function loader")] 25 | InvalidDebugLayerName(#[source] std::str::Utf8Error), 26 | 27 | #[error("Unable to create the Vulkan instance")] 28 | UnableToCreateInstance(#[source] vk::Result), 29 | 30 | #[error("Unable to create the logical device")] 31 | UnableToCreateLogicalDevice(#[source] vk::Result), 32 | 33 | #[error("Error while waiting for the Vulkan device to idle")] 34 | UnableToWaitIdle(#[source] vk::Result), 35 | } 36 | -------------------------------------------------------------------------------- /crates/vui/src/ui/widgets/mod.rs: -------------------------------------------------------------------------------- 1 | use ::anyhow::Result; 2 | 3 | pub use self::{ 4 | align::{Align, HAlignment, VAlignment}, 5 | col::Col, 6 | composite::{ComposedElement, ComposedMessage, Composite, CompositeWidget}, 7 | container::{Constraint, Container, WithContainer}, 8 | element::Element, 9 | hsplit::HSplit, 10 | image::Image, 11 | label::{Label, LabelStyle}, 12 | row::Row, 13 | style::{CompositeStyle, Drawable, FillStyle, Style}, 14 | window::Window, 15 | }; 16 | use crate::{ 17 | graphics::triangles::Frame, 18 | ui::{primitives::Dimensions, Input, InternalState}, 19 | Vec2, 20 | }; 21 | 22 | mod align; 23 | mod col; 24 | mod composite; 25 | mod container; 26 | mod element; 27 | mod hsplit; 28 | mod image; 29 | mod label; 30 | mod row; 31 | mod style; 32 | mod window; 33 | 34 | pub mod prelude; 35 | 36 | pub trait Widget { 37 | fn handle_event( 38 | &mut self, 39 | internal_state: &mut InternalState, 40 | input: &Input, 41 | event: &winit::event::WindowEvent, 42 | ) -> Result>; 43 | 44 | fn draw_frame(&mut self, internal_state: &mut InternalState, frame: &mut Frame) -> Result<()>; 45 | 46 | fn dimensions( 47 | &mut self, 48 | internal_state: &mut InternalState, 49 | max_size: &Dimensions, 50 | ) -> Dimensions; 51 | 52 | fn set_top_left_position(&mut self, internal_state: &mut InternalState, position: Vec2); 53 | } 54 | -------------------------------------------------------------------------------- /crates/vui/src/ui/font/layout.rs: -------------------------------------------------------------------------------- 1 | use ::ab_glyph::{FontArc, Glyph, PxScaleFont, ScaleFont}; 2 | 3 | use crate::ui::Font; 4 | 5 | impl Font { 6 | pub(super) fn layout_text>( 7 | font: &PxScaleFont, 8 | content: T, 9 | ) -> Vec { 10 | let v_advance = (font.line_gap() + font.height()).ceil() as u32; 11 | let mut glyphs = Vec::with_capacity(content.as_ref().len()); 12 | let mut line_number = 1; 13 | let mut cursor_x = 0.0; 14 | let mut cursor_y = v_advance as f32; 15 | let mut prev_glyph_id = None; 16 | 17 | for char in content.as_ref().chars() { 18 | if char == '\n' { 19 | line_number += 1; 20 | cursor_x = 0.0; 21 | cursor_y = (line_number * v_advance) as f32; 22 | prev_glyph_id = None; 23 | } else if !char.is_control() { 24 | let glyph_id = font.glyph_id(char); 25 | if let Some(prev_id) = prev_glyph_id { 26 | cursor_x += font.kern(prev_id, glyph_id); 27 | } 28 | let position = ab_glyph::point(cursor_x.round(), cursor_y.round()); 29 | glyphs.push(font.glyph_id(char).with_scale_and_position(font.scale(), position)); 30 | cursor_x += font.h_advance(glyph_id); 31 | prev_glyph_id = Some(glyph_id); 32 | } 33 | } 34 | 35 | glyphs 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /crates/vui/src/ui/widgets/style/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::ui::color::{Color, Gradient}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub enum FillStyle { 5 | Color(Color), 6 | Gradient(Gradient), 7 | None, 8 | } 9 | 10 | impl From for FillStyle { 11 | fn from(color: Color) -> Self { 12 | FillStyle::Color(color) 13 | } 14 | } 15 | 16 | impl From for FillStyle { 17 | fn from(gradient: Gradient) -> Self { 18 | FillStyle::Gradient(gradient) 19 | } 20 | } 21 | 22 | impl Style for FillStyle { 23 | fn apply_to(&mut self, target: &mut dyn Drawable) { 24 | target.color(self.clone()); 25 | } 26 | } 27 | 28 | pub trait Drawable { 29 | fn color(&mut self, color: FillStyle); 30 | } 31 | 32 | pub trait Style { 33 | fn apply_to(&mut self, target: &mut dyn Drawable); 34 | } 35 | 36 | #[derive(Clone, Copy)] 37 | pub struct CompositeStyle { 38 | pub background: FillStyle, 39 | pub border: FillStyle, 40 | } 41 | 42 | impl CompositeStyle { 43 | pub fn new() -> Self { 44 | CompositeStyle { background: FillStyle::None, border: FillStyle::None } 45 | } 46 | 47 | pub fn with_background(mut self, style: FillStyle) -> Self { 48 | self.background = style; 49 | self 50 | } 51 | 52 | pub fn with_border(mut self, style: FillStyle) -> Self { 53 | self.border = style; 54 | self 55 | } 56 | } 57 | 58 | impl Default for CompositeStyle { 59 | fn default() -> Self { 60 | CompositeStyle::new() 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /crates/vui/src/ui/id.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)] 2 | pub struct Id(u32); 3 | 4 | impl Id { 5 | pub fn new(value: u32) -> Self { 6 | Self(value) 7 | } 8 | } 9 | 10 | pub const fn id_hash(content: &str, line: u32, column: u32, seed: &str) -> u32 { 11 | let mut hash = 3581u32; 12 | { 13 | let content_bytes = content.as_bytes(); 14 | let mut i: usize = 0; 15 | while i < content_bytes.len() { 16 | hash = hash.wrapping_mul(33).wrapping_add(content_bytes[i] as u32); 17 | i += 1; 18 | } 19 | } 20 | { 21 | let seed_bytes = seed.as_bytes(); 22 | let mut j: usize = 0; 23 | while j < seed_bytes.len() { 24 | hash = hash.wrapping_mul(33).wrapping_add(seed_bytes[j] as u32); 25 | j += 1; 26 | } 27 | } 28 | hash = hash.wrapping_mul(33).wrapping_add(line); 29 | hash = hash.wrapping_mul(33).wrapping_add(column); 30 | return hash; 31 | } 32 | 33 | #[macro_export] 34 | macro_rules! gen_id { 35 | ($str:literal) => {{ 36 | let id: u32 = ccthw::ui::id_hash(file!(), line!(), column!(), &format!("{:?}", $str)); 37 | ccthw::ui::Id::new(id) 38 | }}; 39 | ($expr:expr) => {{ 40 | let id: u32 = id_hash(file!(), line!(), column!(), $expr); 41 | Id::new(id) 42 | }}; 43 | () => {{ 44 | const ID: u32 = ccthw::ui::id_hash(file!(), line!(), column!(), "seed"); 45 | ccthw::ui::Id::new(ID) 46 | }}; 47 | } 48 | -------------------------------------------------------------------------------- /crates/vui/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(trait_upcasting)] 2 | 3 | pub mod asset_loader; 4 | pub mod errors; 5 | pub mod format; 6 | pub mod graphics; 7 | pub mod math; 8 | pub mod msaa; 9 | pub mod pipeline; 10 | pub mod ui; 11 | pub mod vulkan; 12 | 13 | pub type Mat4 = nalgebra::Matrix4; 14 | pub type Vec2 = nalgebra::Vector2; 15 | pub type Vec3 = nalgebra::Vector3; 16 | pub type Vec4 = nalgebra::Vector4; 17 | 18 | #[inline] 19 | pub fn vec2(x: f32, y: f32) -> Vec2 { 20 | Vec2::new(x, y) 21 | } 22 | 23 | #[inline] 24 | pub fn vec3(x: f32, y: f32, z: f32) -> Vec3 { 25 | Vec3::new(x, y, z) 26 | } 27 | 28 | #[inline] 29 | pub fn vec4(x: f32, y: f32, z: f32, w: f32) -> Vec4 { 30 | Vec4::new(x, y, z, w) 31 | } 32 | 33 | #[macro_export] 34 | macro_rules! builder_field { 35 | ($field:ident, $field_type:ty) => { 36 | pub fn $field(self, $field: $field_type) -> Self { 37 | Self { $field, ..self } 38 | } 39 | }; 40 | } 41 | 42 | #[macro_export] 43 | macro_rules! builder_field_into { 44 | ($field:ident, $field_type:ty) => { 45 | pub fn $field(self, $field: T) -> Self 46 | where 47 | T: Into<$field_type>, 48 | { 49 | Self { $field: $field.into(), ..self } 50 | } 51 | }; 52 | } 53 | 54 | #[macro_export] 55 | macro_rules! builder_field_some { 56 | ($field:ident, $field_type:ty) => { 57 | pub fn $field(self, $field: $field_type) -> Self { 58 | Self { $field: Some($field), ..self } 59 | } 60 | }; 61 | } 62 | -------------------------------------------------------------------------------- /crates/vui/src/vulkan/image/sampler.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use ash::vk; 4 | 5 | use crate::{errors::ImageError, vulkan::render_device::RenderDevice}; 6 | 7 | pub struct Sampler { 8 | pub raw: vk::Sampler, 9 | pub vk_dev: Arc, 10 | } 11 | 12 | impl Sampler { 13 | pub fn linear(vk_dev: Arc) -> Result { 14 | let sampler_create_info = vk::SamplerCreateInfo { 15 | flags: vk::SamplerCreateFlags::empty(), 16 | mag_filter: vk::Filter::LINEAR, 17 | min_filter: vk::Filter::LINEAR, 18 | mipmap_mode: vk::SamplerMipmapMode::LINEAR, 19 | mip_lod_bias: 0.0, 20 | address_mode_u: vk::SamplerAddressMode::CLAMP_TO_EDGE, 21 | address_mode_v: vk::SamplerAddressMode::CLAMP_TO_EDGE, 22 | address_mode_w: vk::SamplerAddressMode::CLAMP_TO_EDGE, 23 | ..Default::default() 24 | }; 25 | Sampler::new(vk_dev, sampler_create_info) 26 | } 27 | 28 | pub fn new( 29 | vk_dev: Arc, 30 | sampler_create_info: vk::SamplerCreateInfo, 31 | ) -> Result { 32 | let raw = unsafe { 33 | vk_dev 34 | .logical_device 35 | .create_sampler(&sampler_create_info, None) 36 | .map_err(ImageError::UnableToCreateSampler)? 37 | }; 38 | Ok(Self { raw, vk_dev }) 39 | } 40 | } 41 | 42 | impl Drop for Sampler { 43 | fn drop(&mut self) { 44 | unsafe { 45 | self.vk_dev.logical_device.destroy_sampler(self.raw, None); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/1_bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: | 3 | Use this template for **non-crash-related** bug reports. 4 | labels: ["bug"] 5 | body: 6 | - type: checkboxes 7 | attributes: 8 | label: Check for existing issues 9 | description: 10 | Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a 11 | `+1` (👍) on it. 12 | options: 13 | - label: Completed 14 | required: true 15 | - type: textarea 16 | attributes: 17 | label: Describe the bug / provide steps to reproduce it 18 | description: A clear and concise description of what the bug is. 19 | validations: 20 | required: true 21 | - type: textarea 22 | id: environment 23 | attributes: 24 | label: Environment 25 | description: | 26 | - OS: [e.g. archlinux] 27 | - vterm Version: [e.g. 0.1.0] 28 | validations: 29 | required: true 30 | - type: textarea 31 | attributes: 32 | label: If applicable, add mockups / screenshots to help explain present your vision of the feature 33 | description: Drag issues into the text input below 34 | validations: 35 | required: false 36 | - type: textarea 37 | attributes: 38 | label: If applicable, attach any further logs or debug discoveries here. 39 | description: | 40 | We currently do not have proper logs, but will improve this in the future. 41 | validations: 42 | required: false 43 | -------------------------------------------------------------------------------- /crates/vshell/src/input.rs: -------------------------------------------------------------------------------- 1 | use std::sync::mpsc::Sender; 2 | 3 | use winit::{ 4 | event::KeyEvent, 5 | keyboard::{Key, ModifiersState, NamedKey}, 6 | }; 7 | 8 | /// InputState processes input events and sends them to the terminal. 9 | pub struct InputState { 10 | rtx: Sender>, 11 | } 12 | 13 | impl InputState { 14 | pub fn new(rtx: Sender>) -> Self { 15 | Self { rtx } 16 | } 17 | 18 | pub fn apply_keyboard(&mut self, input: KeyEvent, mods: &ModifiersState) { 19 | let text = match input.logical_key { 20 | Key::Named(NamedKey::Backspace) => Some("\u{8}"), 21 | Key::Named(NamedKey::Enter) => Some("\r"), 22 | Key::Named(NamedKey::ArrowUp) => Some("\x1bOA"), 23 | Key::Named(NamedKey::ArrowDown) => Some("\x1bOB"), 24 | Key::Named(NamedKey::ArrowRight) => Some("\x1bOC"), 25 | Key::Named(NamedKey::ArrowLeft) => Some("\x1bOD"), 26 | Key::Named(NamedKey::Tab) => Some("\t"), 27 | Key::Named(NamedKey::Escape) => Some("\x1b"), 28 | _ => { 29 | if mods.control_key() { 30 | let n = input.logical_key.to_text().unwrap().chars().next().unwrap(); 31 | let mut m = n as u8; 32 | m &= 0b1001_1111; 33 | let _ = self.rtx.send(vec![m]); 34 | None 35 | } else { 36 | Some(input.logical_key.to_text().unwrap()) 37 | } 38 | } 39 | }; 40 | if let Some(text) = text { 41 | let _ = self.rtx.send(text.into()); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /crates/vui/src/vulkan/render_device/swapchain/images.rs: -------------------------------------------------------------------------------- 1 | use ash::vk; 2 | 3 | use crate::{errors::SwapchainError, vulkan::render_device::RenderDevice}; 4 | 5 | impl RenderDevice { 6 | pub(crate) fn create_image_views( 7 | &self, 8 | format: vk::Format, 9 | swapchain_images: &Vec, 10 | ) -> Result, SwapchainError> { 11 | let mut image_views = vec![]; 12 | for (i, image) in swapchain_images.iter().enumerate() { 13 | let create_info = vk::ImageViewCreateInfo { 14 | image: *image, 15 | format, 16 | view_type: vk::ImageViewType::TYPE_2D, 17 | subresource_range: vk::ImageSubresourceRange { 18 | aspect_mask: vk::ImageAspectFlags::COLOR, 19 | base_mip_level: 0, 20 | level_count: 1, 21 | base_array_layer: 0, 22 | layer_count: 1, 23 | }, 24 | components: vk::ComponentMapping { 25 | r: vk::ComponentSwizzle::IDENTITY, 26 | g: vk::ComponentSwizzle::IDENTITY, 27 | b: vk::ComponentSwizzle::IDENTITY, 28 | a: vk::ComponentSwizzle::IDENTITY, 29 | }, 30 | ..Default::default() 31 | }; 32 | let view = unsafe { 33 | self.logical_device 34 | .create_image_view(&create_info, None) 35 | .map_err(|err| SwapchainError::UnableToCreateSwapchainImageView(i, err))? 36 | }; 37 | image_views.push(view); 38 | } 39 | 40 | Ok(image_views) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /crates/vui/src/vulkan/allocator/composable.rs: -------------------------------------------------------------------------------- 1 | use ::ash::vk; 2 | 3 | use crate::vulkan::allocator::{Allocation, AllocatorError}; 4 | 5 | pub trait ComposableAllocator: Send + Sync { 6 | /// # Safety 7 | /// 8 | /// The `allocate` function is unsafe because it directly interacts with 9 | /// Vulkan's memory allocation. 10 | /// - `allocate_info` must be a valid `vk::MemoryAllocateInfo` structure. 11 | /// - `alignment` must be properly aligned as required by Vulkan. 12 | /// - Proper synchronization must be ensured when accessing the allocator to avoid race 13 | /// conditions. 14 | unsafe fn allocate( 15 | &mut self, 16 | allocate_info: vk::MemoryAllocateInfo, 17 | alignment: u64, 18 | ) -> Result; 19 | 20 | /// # Safety 21 | /// 22 | /// The `free` function is unsafe because it directly interacts with 23 | /// Vulkan's memory deallocation. 24 | /// - `allocation` must be a valid `Allocation` that was previously allocated by this allocator. 25 | /// - Proper synchronization must be ensured when accessing the allocator to avoid race 26 | /// conditions. 27 | unsafe fn free(&mut self, allocation: &Allocation) -> Result<(), AllocatorError>; 28 | } 29 | 30 | impl ComposableAllocator for Box { 31 | unsafe fn allocate( 32 | &mut self, 33 | allocate_info: vk::MemoryAllocateInfo, 34 | size_in_bytes: u64, 35 | ) -> Result { 36 | self.as_mut().allocate(allocate_info, size_in_bytes) 37 | } 38 | 39 | unsafe fn free(&mut self, allocation: &Allocation) -> Result<(), AllocatorError> { 40 | self.as_mut().free(allocation) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /crates/vshell/src/color.rs: -------------------------------------------------------------------------------- 1 | use vui::Vec4; 2 | 3 | #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] 4 | pub struct Color(pub [u8; 3]); 5 | 6 | impl Color { 7 | pub const BLACK: Self = Self::from_rgb(46, 51, 63); 8 | pub const DARK_GRAY: Self = Self::from_rgb(96, 96, 96); 9 | pub const GRAY: Self = Self::from_rgb(160, 160, 160); 10 | pub const LIGHT_GRAY: Self = Self::from_rgb(220, 220, 220); 11 | pub const WHITE: Self = Self::from_rgb(229, 232, 239); 12 | pub const MAGENTA: Self = Self::from_rgb(180, 141, 172); 13 | pub const CYAN: Self = Self::from_rgb(136, 191, 207); 14 | 15 | pub const BROWN: Self = Self::from_rgb(165, 42, 42); 16 | pub const DARK_RED: Self = Self::from_rgb(0x8b, 0, 0); 17 | pub const RED: Self = Self::from_rgb(190, 96, 105); 18 | pub const LIGHT_RED: Self = Self::from_rgb(255, 128, 128); 19 | 20 | pub const YELLOW: Self = Self::from_rgb(235, 202, 138); 21 | pub const LIGHT_YELLOW: Self = Self::from_rgb(170, 170, 0xe0); 22 | pub const KHAKI: Self = Self::from_rgb(240, 230, 140); 23 | 24 | pub const DARK_GREEN: Self = Self::from_rgb(0, 0x64, 0); 25 | pub const GREEN: Self = Self::from_rgb(163, 189, 139); 26 | pub const LIGHT_GREEN: Self = Self::from_rgb(0x90, 0xee, 0x90); 27 | 28 | pub const DARK_BLUE: Self = Self::from_rgb(0, 0, 0x8b); 29 | pub const BLUE: Self = Self::from_rgb(129, 160, 192); 30 | pub const LIGHT_BLUE: Self = Self::from_rgb(0xad, 0xd8, 0xe6); 31 | 32 | pub const fn from_rgb(r: u8, g: u8, b: u8) -> Self { 33 | Self([r, g, b]) 34 | } 35 | } 36 | 37 | impl From for Vec4 { 38 | fn from(c: Color) -> Self { 39 | Vec4::new((c.0[0] as f32) / 255.0, (c.0[1] as f32) / 255.0, (c.0[2] as f32) / 255.0, 1.0) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /crates/vui/src/vulkan/sync/fence.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use ash::vk; 4 | 5 | use crate::{errors::FenceError, vulkan::render_device::RenderDevice}; 6 | 7 | pub struct Fence { 8 | pub raw: vk::Fence, 9 | 10 | pub vk_dev: Arc, 11 | } 12 | 13 | impl Fence { 14 | pub fn new(vk_dev: Arc) -> Result { 15 | let raw = { 16 | let create_info = 17 | vk::FenceCreateInfo { flags: vk::FenceCreateFlags::SIGNALED, ..Default::default() }; 18 | unsafe { 19 | vk_dev 20 | .logical_device 21 | .create_fence(&create_info, None) 22 | .map_err(FenceError::UnableToCreateFence)? 23 | } 24 | }; 25 | Ok(Self { raw, vk_dev }) 26 | } 27 | 28 | pub fn wait_and_reset(&self) -> Result<(), FenceError> { 29 | self.wait()?; 30 | self.reset() 31 | } 32 | 33 | pub fn wait(&self) -> Result<(), FenceError> { 34 | unsafe { 35 | self.vk_dev 36 | .logical_device 37 | .wait_for_fences(&[self.raw], true, u64::MAX) 38 | .map_err(FenceError::UnexpectedWaitError)?; 39 | } 40 | Ok(()) 41 | } 42 | 43 | pub fn reset(&self) -> Result<(), FenceError> { 44 | unsafe { 45 | self.vk_dev 46 | .logical_device 47 | .reset_fences(&[self.raw]) 48 | .map_err(FenceError::UnexpectedResetError)?; 49 | } 50 | Ok(()) 51 | } 52 | } 53 | 54 | impl Drop for Fence { 55 | fn drop(&mut self) { 56 | unsafe { 57 | self.vk_dev.logical_device.destroy_fence(self.raw, None); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /crates/vui/src/ui/widgets/composite/composed_message.rs: -------------------------------------------------------------------------------- 1 | use ::anyhow::Result; 2 | 3 | use crate::{ 4 | graphics::triangles::Frame, 5 | ui::{ 6 | primitives::Dimensions, 7 | widgets::{Element, Widget}, 8 | Input, InternalState, 9 | }, 10 | Vec2, 11 | }; 12 | 13 | #[derive(Debug, Copy, Clone)] 14 | pub enum ComposedMessage { 15 | Internal(I), 16 | External(E), 17 | } 18 | 19 | pub struct ComposedElement(pub Element); 20 | 21 | impl Widget> for ComposedElement 22 | where 23 | E: 'static, 24 | { 25 | fn handle_event( 26 | &mut self, 27 | internal_state: &mut InternalState, 28 | input: &Input, 29 | event: &winit::event::WindowEvent, 30 | ) -> Result>> { 31 | self.0 32 | .handle_event(internal_state, input, event) 33 | .map(|opt| opt.map(ComposedMessage::External)) 34 | } 35 | 36 | fn draw_frame(&mut self, internal_state: &mut InternalState, frame: &mut Frame) -> Result<()> { 37 | self.0.draw_frame(internal_state, frame) 38 | } 39 | 40 | fn dimensions( 41 | &mut self, 42 | internal_state: &mut InternalState, 43 | max_size: &Dimensions, 44 | ) -> Dimensions { 45 | self.0.dimensions(internal_state, max_size) 46 | } 47 | 48 | fn set_top_left_position(&mut self, internal_state: &mut InternalState, position: Vec2) { 49 | self.0.set_top_left_position(internal_state, position); 50 | } 51 | } 52 | 53 | impl Into>> for Element 54 | where 55 | E: 'static, 56 | { 57 | fn into(self) -> Element> { 58 | Element::new(ComposedElement(self)) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /crates/vui/src/errors/window.rs: -------------------------------------------------------------------------------- 1 | use ash::vk; 2 | use thiserror::Error; 3 | 4 | use crate::errors::{InstanceError, RenderDeviceError, SwapchainError}; 5 | 6 | #[derive(Error, Debug)] 7 | pub enum WindowError { 8 | #[error("Vulkan is not supported on this device")] 9 | VulkanNotSupported, 10 | 11 | #[error("The Window could not be created")] 12 | WindowCreateFailed, 13 | 14 | #[error("The Window's event receiver has already been taken")] 15 | EventReceiverLost, 16 | 17 | #[error("There is no primary monitor available to this instance")] 18 | NoPrimaryMonitor, 19 | 20 | #[error("There is no video mode associated with the primary monitor")] 21 | PrimaryVideoModeMissing, 22 | 23 | #[error("Unable to determine the required vulkan extensions for this platform")] 24 | RequiredExtensionsUnavailable, 25 | 26 | #[error("Unexpected instance error")] 27 | UnexpectedInstanceError(#[from] InstanceError), 28 | 29 | #[error("Unable to create the Vulkan surface")] 30 | UnableToCreateSurface(#[source] vk::Result), 31 | 32 | #[error("Unable to create the Vulkan render device")] 33 | UnexpectedRenderDeviceError(#[from] RenderDeviceError), 34 | 35 | #[error("Unexpected swapchain error")] 36 | UnexpectedSwapchainError(#[from] SwapchainError), 37 | 38 | #[error("Unable to create winit window")] 39 | UnableToCreateWindow, 40 | } 41 | 42 | #[derive(Debug, Error)] 43 | pub enum WindowSurfaceError { 44 | #[error("Unable to determine if the device can present images with this queue")] 45 | UnableToCheckPhysicalDeviceSurfaceSupport(#[source] vk::Result), 46 | 47 | #[error("Unable to get the surface capabilities for a physical device")] 48 | UnableToGetPhysicalDeviceSurfaceCapabilities(#[source] vk::Result), 49 | } 50 | -------------------------------------------------------------------------------- /crates/vui/src/ui/internal_state.rs: -------------------------------------------------------------------------------- 1 | use ::std::{ 2 | any::{Any, TypeId}, 3 | collections::HashMap, 4 | }; 5 | 6 | use crate::ui::Id; 7 | 8 | pub struct InternalState { 9 | widget_states: HashMap>, 10 | } 11 | 12 | impl InternalState { 13 | pub fn new() -> Self { 14 | Self { widget_states: HashMap::new() } 15 | } 16 | 17 | pub fn get_state(&mut self, id: &Id) -> &S 18 | where 19 | S: Default + 'static, 20 | { 21 | self.check_missing::(id); 22 | 23 | self.widget_states.get(id).unwrap().downcast_ref().unwrap() 24 | } 25 | 26 | pub fn get_state_mut(&mut self, id: &Id) -> &mut S 27 | where 28 | S: Default + 'static, 29 | { 30 | self.check_missing::(id); 31 | 32 | self.widget_states.get_mut(id).unwrap().downcast_mut().unwrap() 33 | } 34 | 35 | fn check_missing(&mut self, id: &Id) 36 | where 37 | S: Default + 'static, 38 | { 39 | let needs_insert = if let Some(state) = self.widget_states.get(id) { 40 | let wrong_type = state.as_ref().type_id() != TypeId::of::(); 41 | if wrong_type { 42 | log::error!( 43 | "Unable to downcast existing Widget state for {:?}! \ 44 | Are your UI IDs unique? Expected {:?} but found {:?}", 45 | id, 46 | TypeId::of::(), 47 | state.type_id() 48 | ); 49 | } 50 | wrong_type 51 | } else { 52 | true 53 | }; 54 | 55 | if needs_insert { 56 | log::trace!("Creating new state for {:?}", id); 57 | self.widget_states.insert(*id, Box::new(S::default())); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /crates/vui/src/vulkan/pipeline/layout.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use ash::vk; 4 | 5 | use crate::{ 6 | errors::PipelineError, 7 | vulkan::{descriptor_set::DescriptorSetLayout, render_device::RenderDevice}, 8 | }; 9 | 10 | pub struct PipelineLayout { 11 | pub descriptor_layouts: Vec>, 12 | 13 | pub raw: vk::PipelineLayout, 14 | 15 | pub vk_dev: Arc, 16 | } 17 | 18 | impl PipelineLayout { 19 | pub fn new( 20 | vk_dev: Arc, 21 | descriptor_layouts: &[Arc], 22 | push_constant_ranges: &[vk::PushConstantRange], 23 | ) -> Result { 24 | let raw_descriptor_layout_ptrs: Vec = 25 | descriptor_layouts.iter().map(|layout| layout.raw).collect(); 26 | let pipeline_layout_create_info = vk::PipelineLayoutCreateInfo { 27 | p_set_layouts: raw_descriptor_layout_ptrs.as_ptr(), 28 | set_layout_count: descriptor_layouts.len() as u32, 29 | p_push_constant_ranges: push_constant_ranges.as_ptr(), 30 | push_constant_range_count: push_constant_ranges.len() as u32, 31 | ..Default::default() 32 | }; 33 | let raw = unsafe { 34 | vk_dev 35 | .logical_device 36 | .create_pipeline_layout(&pipeline_layout_create_info, None) 37 | .map_err(PipelineError::UnableToCreatePipelineLayout)? 38 | }; 39 | Ok(Self { raw, descriptor_layouts: descriptor_layouts.to_owned(), vk_dev }) 40 | } 41 | } 42 | 43 | impl Drop for PipelineLayout { 44 | fn drop(&mut self) { 45 | unsafe { 46 | self.vk_dev.logical_device.destroy_pipeline_layout(self.raw, None); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /crates/vui/src/vulkan/image/view.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use ash::vk; 4 | 5 | use crate::vulkan::{ 6 | image::{Image, ImageError}, 7 | render_device::RenderDevice, 8 | }; 9 | 10 | pub struct ImageView { 11 | pub raw: vk::ImageView, 12 | 13 | pub image: Arc, 14 | 15 | pub vk_dev: Arc, 16 | } 17 | 18 | impl ImageView { 19 | pub fn new( 20 | image: Arc, 21 | create_info: &vk::ImageViewCreateInfo, 22 | ) -> Result { 23 | let raw = unsafe { 24 | image 25 | .vk_dev 26 | .logical_device 27 | .create_image_view(create_info, None) 28 | .map_err(ImageError::UnableToCreateView)? 29 | }; 30 | Ok(Self { raw, vk_dev: image.vk_dev.clone(), image }) 31 | } 32 | 33 | pub fn new_2d( 34 | image: Arc, 35 | format: vk::Format, 36 | aspect_mask: vk::ImageAspectFlags, 37 | ) -> Result { 38 | let create_info = vk::ImageViewCreateInfo { 39 | flags: vk::ImageViewCreateFlags::empty(), 40 | image: image.raw, 41 | view_type: vk::ImageViewType::TYPE_2D, 42 | format, 43 | subresource_range: vk::ImageSubresourceRange { 44 | aspect_mask, 45 | base_mip_level: 0, 46 | level_count: 1, 47 | base_array_layer: 0, 48 | layer_count: 1, 49 | }, 50 | ..Default::default() 51 | }; 52 | Self::new(image, &create_info) 53 | } 54 | } 55 | 56 | impl Drop for ImageView { 57 | fn drop(&mut self) { 58 | unsafe { 59 | self.vk_dev.logical_device.destroy_image_view(self.raw, None); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /crates/vui/src/errors/vulkan.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | use crate::errors::{ 4 | AllocatorError, BufferError, CommandBufferError, DescriptorSetError, FenceError, 5 | FramebufferError, ImageError, InstanceError, PhysicalDeviceError, PipelineError, 6 | QueueSelectionError, RenderDeviceError, RenderPassError, SemaphoreError, SwapchainError, 7 | VulkanDebugError, WindowSurfaceError, 8 | }; 9 | 10 | #[derive(Debug, Error)] 11 | pub enum VulkanError { 12 | #[error(transparent)] 13 | InstanceError(#[from] InstanceError), 14 | 15 | #[error(transparent)] 16 | PhysicalDeviceError(#[from] PhysicalDeviceError), 17 | 18 | #[error(transparent)] 19 | QueueSelectionError(#[from] QueueSelectionError), 20 | 21 | #[error(transparent)] 22 | RenderDeviceError(#[from] RenderDeviceError), 23 | 24 | #[error(transparent)] 25 | SwapchainError(#[from] SwapchainError), 26 | 27 | #[error(transparent)] 28 | SemaphorePoolError(#[from] SemaphoreError), 29 | 30 | #[error(transparent)] 31 | WindowSurfaceError(#[from] WindowSurfaceError), 32 | 33 | #[error(transparent)] 34 | AllocatorError(#[from] AllocatorError), 35 | 36 | #[error(transparent)] 37 | BufferError(#[from] BufferError), 38 | 39 | #[error(transparent)] 40 | PipelineError(#[from] PipelineError), 41 | 42 | #[error(transparent)] 43 | VulkanDebugError(#[from] VulkanDebugError), 44 | 45 | #[error(transparent)] 46 | FramebufferError(#[from] FramebufferError), 47 | 48 | #[error(transparent)] 49 | FenceError(#[from] FenceError), 50 | 51 | #[error(transparent)] 52 | CommandBufferError(#[from] CommandBufferError), 53 | 54 | #[error(transparent)] 55 | RenderPassError(#[from] RenderPassError), 56 | 57 | #[error(transparent)] 58 | DescriptorSetError(#[from] DescriptorSetError), 59 | 60 | #[error(transparent)] 61 | ImageError(#[from] ImageError), 62 | } 63 | -------------------------------------------------------------------------------- /crates/vterm/src/cli.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use log::LevelFilter; 4 | 5 | pub struct Args { 6 | pub disable_validation: bool, 7 | pub window_protocol: Option, 8 | pub command: Vec, 9 | pub log: bool, 10 | pub log_level: LevelFilter, 11 | } 12 | 13 | pub enum WindowProtocol { 14 | Wayland, 15 | X11, 16 | } 17 | 18 | impl Args { 19 | pub fn parse() -> Self { 20 | let args: Vec = std::env::args().collect(); 21 | let wayland = args.contains(&"--wayland".to_string()); 22 | let x11 = args.contains(&"--x11".to_string()); 23 | 24 | if wayland && x11 { 25 | panic!("Cannot specify both --wayland and --x11"); 26 | } 27 | 28 | let window_protocol = match (wayland, x11) { 29 | (true, false) => Some(WindowProtocol::Wayland), 30 | (false, true) => Some(WindowProtocol::X11), 31 | _ => None, 32 | }; 33 | 34 | let log_level = args 35 | .iter() 36 | .find_map(|arg| { 37 | if arg.starts_with("--log-level=") { 38 | let level_str = arg.split('=').nth(1)?; 39 | LevelFilter::from_str(level_str).ok() 40 | } else { 41 | None 42 | } 43 | }) 44 | .unwrap_or(LevelFilter::Error); 45 | 46 | Args { 47 | disable_validation: args.contains(&"--disable-validation".to_string()), 48 | window_protocol, 49 | command: args.clone().into_iter().skip(1).collect(), 50 | log: args.contains(&"--log".to_string()), 51 | log_level, 52 | } 53 | } 54 | 55 | pub fn command(&self) -> Option<(&str, &[String])> { 56 | if self.command.is_empty() { 57 | return None; 58 | } 59 | 60 | Some((&self.command[0], &self.command[1 ..])) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /crates/vui/src/vulkan/instance/mod.rs: -------------------------------------------------------------------------------- 1 | use ash::{khr::swapchain, vk, Entry}; 2 | 3 | use crate::errors::InstanceError; 4 | 5 | pub struct Instance { 6 | pub ash: ash::Instance, 7 | pub entry: Entry, 8 | } 9 | 10 | impl Instance { 11 | pub fn new(instance: ash::Instance, entry: &Entry) -> Result { 12 | Ok(Self { ash: instance, entry: entry.clone() }) 13 | } 14 | 15 | pub fn create_logical_device( 16 | &self, 17 | physical_device: &vk::PhysicalDevice, 18 | queue_create_infos: &[vk::DeviceQueueCreateInfo], 19 | ) -> Result { 20 | let extensions = vec![swapchain::NAME.as_ptr()]; 21 | let features = vk::PhysicalDeviceFeatures::default().fill_mode_non_solid(true); 22 | let mut vk_11_features = vk::PhysicalDeviceVulkan11Features::default() 23 | .uniform_and_storage_buffer16_bit_access(true); 24 | 25 | let mut vk_12_features = vk::PhysicalDeviceVulkan12Features::default() 26 | .shader_sampled_image_array_non_uniform_indexing(true) 27 | .runtime_descriptor_array(true) 28 | .descriptor_binding_partially_bound(true) 29 | .descriptor_binding_variable_descriptor_count(true); 30 | let create_info = vk::DeviceCreateInfo::default() 31 | .queue_create_infos(queue_create_infos) 32 | .enabled_extension_names(&extensions) 33 | .enabled_features(&features) 34 | .push_next(&mut vk_11_features) 35 | .push_next(&mut vk_12_features); 36 | 37 | unsafe { 38 | self.ash 39 | .create_device(*physical_device, &create_info, None) 40 | .map_err(InstanceError::UnableToCreateLogicalDevice) 41 | } 42 | } 43 | } 44 | 45 | impl Drop for Instance { 46 | fn drop(&mut self) { 47 | unsafe { 48 | self.ash.destroy_instance(None); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /crates/vui/src/vulkan/image/mod.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use ash::vk; 4 | 5 | use crate::{ 6 | errors::ImageError, 7 | vulkan::{ 8 | allocator::{Allocation, MemoryAllocator}, 9 | render_device::RenderDevice, 10 | }, 11 | }; 12 | 13 | pub mod sampler; 14 | pub mod view; 15 | 16 | pub struct Image { 17 | pub raw: vk::Image, 18 | 19 | pub create_info: vk::ImageCreateInfo<'static>, 20 | 21 | pub allocation: Allocation, 22 | 23 | pub vk_alloc: Arc, 24 | 25 | pub vk_dev: Arc, 26 | } 27 | 28 | impl Image { 29 | pub fn new( 30 | vk_dev: Arc, 31 | vk_alloc: Arc, 32 | create_info: &vk::ImageCreateInfo<'static>, 33 | memory_property_flags: vk::MemoryPropertyFlags, 34 | ) -> Result { 35 | let raw = unsafe { 36 | vk_dev 37 | .logical_device 38 | .create_image(create_info, None) 39 | .map_err(ImageError::UnableToCreateImage)? 40 | }; 41 | let memory_requirements = 42 | unsafe { vk_dev.logical_device.get_image_memory_requirements(raw) }; 43 | 44 | let allocation = 45 | unsafe { vk_alloc.allocate_memory(memory_requirements, memory_property_flags)? }; 46 | 47 | unsafe { 48 | vk_dev 49 | .logical_device 50 | .bind_image_memory(raw, allocation.memory, allocation.offset) 51 | .map_err(ImageError::UnableToBindImageMemory)?; 52 | } 53 | 54 | Ok(Self { raw, create_info: *create_info, allocation, vk_alloc, vk_dev }) 55 | } 56 | } 57 | 58 | impl Drop for Image { 59 | fn drop(&mut self) { 60 | unsafe { 61 | self.vk_dev.logical_device.destroy_image(self.raw, None); 62 | self.vk_alloc.free(&self.allocation).expect("unable to free the image's memory"); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /crates/vui/src/ui/widgets/element.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Mutex}; 2 | 3 | use ::anyhow::Result; 4 | 5 | use crate::{ 6 | graphics::triangles::Frame, 7 | ui::{ 8 | primitives::{Dimensions, Rect}, 9 | widgets::{CompositeStyle, Widget}, 10 | Input, InternalState, 11 | }, 12 | Vec2, 13 | }; 14 | 15 | #[derive(Clone)] 16 | pub struct Element { 17 | pub(crate) widget: Arc>>, 18 | } 19 | 20 | impl Element { 21 | pub fn new(widget: impl Widget + 'static) -> Self { 22 | Self { widget: Arc::new(Mutex::new(widget)) } 23 | } 24 | } 25 | 26 | impl Widget for Element { 27 | fn handle_event( 28 | &mut self, 29 | internal_state: &mut InternalState, 30 | input: &Input, 31 | event: &winit::event::WindowEvent, 32 | ) -> Result> { 33 | let mut widget = self.widget.lock().unwrap(); 34 | widget.handle_event(internal_state, input, event) 35 | } 36 | 37 | fn draw_frame(&mut self, internal_state: &mut InternalState, frame: &mut Frame) -> Result<()> { 38 | let mut widget = self.widget.lock().unwrap(); 39 | widget.draw_frame(internal_state, frame) 40 | } 41 | 42 | fn dimensions( 43 | &mut self, 44 | internal_state: &mut InternalState, 45 | max_size: &Dimensions, 46 | ) -> Dimensions { 47 | let mut widget = self.widget.lock().unwrap(); 48 | widget.dimensions(internal_state, max_size) 49 | } 50 | 51 | fn set_top_left_position(&mut self, internal_state: &mut InternalState, position: Vec2) { 52 | let mut widget = self.widget.lock().unwrap(); 53 | widget.set_top_left_position(internal_state, position) 54 | } 55 | } 56 | 57 | #[allow(unused)] 58 | pub trait StylableWidget: Widget { 59 | fn bg(&self) -> CompositeStyle; 60 | fn top_left(&self) -> Vec2; 61 | fn bounds(&self) -> Rect; 62 | } 63 | -------------------------------------------------------------------------------- /crates/vui/src/vulkan/command_buffer/command_buffer.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use ::ash::vk; 4 | 5 | use crate::{ 6 | errors::{CommandBufferError, CommandResult}, 7 | vulkan::{command_buffer::CommandPool, render_device::RenderDevice}, 8 | }; 9 | 10 | #[derive(Clone)] 11 | pub struct CommandBuffer { 12 | pub raw: vk::CommandBuffer, 13 | 14 | pub pool: Arc, 15 | 16 | pub vk_dev: Arc, 17 | } 18 | 19 | impl CommandBuffer { 20 | pub fn new( 21 | pool: Arc, 22 | command_level: vk::CommandBufferLevel, 23 | ) -> Result { 24 | let raw = unsafe { pool.allocate_command_buffer(command_level)? }; 25 | Ok(Self { raw, vk_dev: pool.vk_dev.clone(), pool }) 26 | } 27 | 28 | pub fn new_primary(pool: Arc) -> Result { 29 | Self::new(pool, vk::CommandBufferLevel::PRIMARY) 30 | } 31 | 32 | pub unsafe fn begin_one_time_submit(&self) -> CommandResult<&Self> { 33 | let begin_info = vk::CommandBufferBeginInfo { 34 | flags: vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT, 35 | ..Default::default() 36 | }; 37 | self.vk_dev 38 | .logical_device 39 | .begin_command_buffer(self.raw, &begin_info) 40 | .map_err(CommandBufferError::UnableToBeginCommandBuffer)?; 41 | Ok(self) 42 | } 43 | 44 | pub unsafe fn end_commands(&self) -> CommandResult<()> { 45 | self.vk_dev 46 | .logical_device 47 | .end_command_buffer(self.raw) 48 | .map_err(CommandBufferError::UnableToEndCommandBuffer)?; 49 | Ok(()) 50 | } 51 | 52 | pub unsafe fn end_renderpass(&self) -> &Self { 53 | self.vk_dev.logical_device.cmd_end_render_pass(self.raw); 54 | self 55 | } 56 | } 57 | 58 | impl Drop for CommandBuffer { 59 | fn drop(&mut self) { 60 | unsafe { 61 | self.pool.free_command_buffer(self.raw); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /crates/vui/src/vulkan/descriptor_set/descriptor_set.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use ::ash::vk; 4 | 5 | use crate::vulkan::{ 6 | image::{sampler::Sampler, view::ImageView}, 7 | render_device::RenderDevice, 8 | }; 9 | 10 | pub struct DescriptorSet { 11 | pub raw: vk::DescriptorSet, 12 | 13 | pub vk_dev: Arc, 14 | } 15 | 16 | impl DescriptorSet { 17 | pub unsafe fn bind_buffer( 18 | &self, 19 | binding: u32, 20 | buffer: &vk::Buffer, 21 | descriptor_type: vk::DescriptorType, 22 | ) { 23 | let descriptor_buffer_info = 24 | vk::DescriptorBufferInfo { buffer: *buffer, offset: 0, range: vk::WHOLE_SIZE }; 25 | let write = vk::WriteDescriptorSet { 26 | dst_set: self.raw, 27 | dst_binding: binding, 28 | dst_array_element: 0, 29 | descriptor_count: 1, 30 | descriptor_type, 31 | p_image_info: std::ptr::null(), 32 | p_texel_buffer_view: std::ptr::null(), 33 | p_buffer_info: &descriptor_buffer_info, 34 | ..Default::default() 35 | }; 36 | self.vk_dev.logical_device.update_descriptor_sets(&[write], &[]); 37 | } 38 | 39 | pub unsafe fn bind_combined_image_sampler( 40 | &self, 41 | binding: u32, 42 | array_element: u32, 43 | image_view: &ImageView, 44 | sampler: &Sampler, 45 | ) { 46 | let descriptor_image_info = vk::DescriptorImageInfo { 47 | sampler: sampler.raw, 48 | image_view: image_view.raw, 49 | image_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, 50 | }; 51 | let write = vk::WriteDescriptorSet { 52 | dst_set: self.raw, 53 | dst_binding: binding, 54 | dst_array_element: array_element, 55 | descriptor_count: 1, 56 | descriptor_type: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, 57 | p_image_info: &descriptor_image_info, 58 | ..Default::default() 59 | }; 60 | self.vk_dev.logical_device.update_descriptor_sets(&[write], &[]); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /crates/vterm/src/logger.rs: -------------------------------------------------------------------------------- 1 | use std::{panic::PanicInfo, sync::OnceLock, time::Instant}; 2 | 3 | use log::{error, Level, LevelFilter, Metadata, Record}; 4 | 5 | use crate::cli::Args; 6 | 7 | struct Logger { 8 | time_start: Instant, 9 | log_enabled: bool, 10 | log_level: LevelFilter, 11 | } 12 | 13 | impl log::Log for Logger { 14 | fn enabled(&self, metadata: &Metadata) -> bool { 15 | self.log_enabled && metadata.level() <= self.log_level 16 | } 17 | 18 | fn log(&self, record: &Record) { 19 | if self.enabled(record.metadata()) { 20 | let time_now = Instant::now(); 21 | let time = (time_now - self.time_start).as_secs_f64(); 22 | let level = match record.level() { 23 | Level::Error => "\x1B[1;31mERRO\x1B[0m", 24 | Level::Warn => "\x1B[1;33mWARN\x1B[0m", 25 | Level::Info => "\x1B[1;32mINFO\x1B[0m", 26 | Level::Debug => "\x1B[1;36mDEBG\x1B[0m", 27 | Level::Trace => "\x1B[1;34mTRCE\x1B[0m", 28 | }; 29 | println!("[{time:>12.6}] {level} {}", record.args()); 30 | } 31 | } 32 | 33 | fn flush(&self) {} 34 | } 35 | 36 | static LOGGER: OnceLock = OnceLock::new(); 37 | 38 | pub fn initialize_logger(args: &Args) { 39 | let time_start = Instant::now(); 40 | let log_enabled = args.log; // Assuming `args.log` is a boolean indicating whether logging is enabled 41 | let log_level = args.log_level; 42 | let logger = LOGGER.get_or_init(|| Logger { time_start, log_enabled, log_level }); 43 | log::set_logger(logger).unwrap(); 44 | log::set_max_level(log_level); 45 | } 46 | 47 | pub fn initialize_panic_hook() { 48 | std::panic::set_hook(Box::new(panic_hook)); 49 | } 50 | 51 | fn panic_hook(info: &PanicInfo) { 52 | let full_message = info.to_string(); 53 | let message = 54 | if let Some((_, message)) = full_message.split_once('\n') { message } else { "panic" }; 55 | if let Some(location) = info.location() { 56 | error!("{message}, \x1B[1mlocation:\x1B[0m {location}"); 57 | } else { 58 | error!("{message}"); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /crates/vui/src/graphics/sprite.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | graphics::{triangles::Frame, Vertex}, 3 | ui::color::Color, 4 | vec2, vec3, Vec2, 5 | }; 6 | 7 | #[derive(Debug, Copy, Clone)] 8 | pub struct Sprite { 9 | pub width: f32, 10 | pub height: f32, 11 | pub position: Vec2, 12 | pub angle_in_radians: f32, 13 | pub depth: f32, 14 | pub texture_index: i32, 15 | } 16 | 17 | impl Default for Sprite { 18 | fn default() -> Self { 19 | Self { 20 | width: 0.0, 21 | height: 0.0, 22 | position: vec2(0.0, 0.0), 23 | angle_in_radians: 0.0, 24 | depth: 0.0, 25 | texture_index: 0, 26 | } 27 | } 28 | } 29 | 30 | impl Sprite { 31 | /// Draws the sprite to the frame. 32 | pub fn draw(&self, vertices: &mut Frame) -> anyhow::Result<()> { 33 | let hw = 0.5 * self.width; 34 | let hh = 0.5 * self.height; 35 | let depth = self.depth; 36 | let texture_index = self.texture_index; 37 | let color = Color::new(1.0, 1.0, 1.0, 1.0); 38 | 39 | vertices.push_vertex(Vertex::new( 40 | vec3(self.position.x - hw, self.position.y - hh, depth), 41 | color, 42 | vec2(0.0, 0.0), 43 | texture_index, 44 | ))?; 45 | vertices.push_vertex(Vertex::new( 46 | vec3(self.position.x + hw, self.position.y - hh, depth), 47 | color, 48 | vec2(1.0, 0.0), 49 | texture_index, 50 | ))?; 51 | vertices.push_vertex(Vertex::new( 52 | vec3(self.position.x + hw, self.position.y + hh, depth), 53 | color, 54 | vec2(1.0, 1.0), 55 | texture_index, 56 | ))?; 57 | vertices.push_vertex(Vertex::new( 58 | vec3(self.position.x - hw, self.position.y + hh, depth), 59 | color, 60 | vec2(0.0, 1.0), 61 | texture_index, 62 | ))?; 63 | 64 | vertices.push_indices(&[0, 1, 2, 0, 2, 3])?; 65 | 66 | Ok(()) 67 | } 68 | 69 | /// Sets the center position of the sprite. 70 | pub fn set_center(&mut self, center: Vec2) { 71 | self.position = center; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | We do not currently have a binary available for installation. However, you can build it yourself from source or install it via the AUR if you are on Arch Linux. 4 | 5 | ## Platforms 6 | 7 | - [Arch Linux](#arch-linux) 8 | - [Manual from Source](#manual-from-source) 9 | - [Windows (Work in Progress)](#windows) 10 | - [macOS (Work in Progress)](#macos) 11 | 12 | ## Arch Linux 13 | 14 | You can install `vterm` via the AUR: 15 | 16 | ```sh 17 | yay -S vterm-git 18 | ``` 19 | 20 | or if you prefer `paru`: 21 | 22 | ```sh 23 | paru -S vterm-git 24 | ``` 25 | 26 | ### Dependencies 27 | 28 | To build `vterm` on Arch Linux, you need the following dependencies: 29 | 30 | - `vulkan-icd-loader` (or `vulkan-icd-loader-git` from AUR) 31 | - `cargo` (or `rust-nightly-bin`, `cargo-git`, `rustup-git`, `rust`, `rustup` from AUR) (make) 32 | - `git` (or `git-git` from AUR) (make) 33 | - `rustup` (or `rustup-git` from AUR) (make) 34 | - `vulkan-headers` (or `vulkan-headers-git` from AUR) (make) 35 | 36 | Optional: 37 | 38 | - `npm` (or `npm-git`, `bun`, `deno` from AUR) (make) 39 | 40 | ## Manual from Source 41 | 42 | ### Dependencies 43 | 44 | To build `vterm` from source, you need the following dependencies: 45 | 46 | - `vulkan-icd-loader` 47 | - `cargo` 48 | - `git` 49 | - `just` 50 | - `rustup` 51 | - `vulkan-headers` 52 | 53 | ### Building 54 | 55 | To run the binary directly, use: 56 | 57 | ```sh 58 | cargo run 59 | cargo run --release 60 | ``` 61 | 62 | ### Optimized Build 63 | 64 | For a highly optimized build, you have several options: 65 | 66 | 1. **Using a JavaScript package manager**: 67 | You can use `bun`, `npm`, `deno`, or any JavaScript package manager to run the build and run commands: 68 | 69 | ```sh 70 | bun run build 71 | ``` 72 | 73 | 2. **Manually**: 74 | ```sh 75 | cargo build -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort --release 76 | ``` 77 | 78 | ## Windows (Work in Progress) 79 | 80 | Support for Windows is currently a work in progress. Please check back later for updates. 81 | 82 | ## macOS (Work in Progress) 83 | 84 | Support for macOS is currently a work in progress. Please check back later for updates. 85 | -------------------------------------------------------------------------------- /crates/vui/src/vulkan/framebuffer/mod.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use ::ash::vk; 4 | 5 | use crate::{ 6 | errors::FramebufferError, 7 | vulkan::{render_device::RenderDevice, render_pass::RenderPass}, 8 | }; 9 | 10 | pub struct Framebuffer { 11 | pub raw: vk::Framebuffer, 12 | 13 | pub render_pass: Arc, 14 | 15 | pub extent: vk::Extent2D, 16 | 17 | pub vk_dev: Arc, 18 | } 19 | 20 | impl Framebuffer { 21 | pub fn with_swapchain_color_attachments( 22 | vk_dev: Arc, 23 | render_pass: &Arc, 24 | ) -> Result, FramebufferError> { 25 | vk_dev.with_swapchain(|swapchain| -> Result, FramebufferError> { 26 | let mut framebuffers = vec![]; 27 | for i in 0 .. swapchain.image_views.len() { 28 | let framebuffer = Self::with_attachments( 29 | vk_dev.clone(), 30 | render_pass, 31 | &[swapchain.image_views[i]], 32 | swapchain.extent, 33 | )?; 34 | framebuffers.push(framebuffer); 35 | } 36 | Ok(framebuffers) 37 | }) 38 | } 39 | 40 | pub fn with_attachments( 41 | vk_dev: Arc, 42 | render_pass: &Arc, 43 | images: &[vk::ImageView], 44 | extent: vk::Extent2D, 45 | ) -> Result { 46 | let create_info = vk::FramebufferCreateInfo { 47 | flags: vk::FramebufferCreateFlags::empty(), 48 | render_pass: render_pass.raw, 49 | attachment_count: images.len() as u32, 50 | p_attachments: images.as_ptr(), 51 | width: extent.width, 52 | height: extent.height, 53 | layers: 1, 54 | ..Default::default() 55 | }; 56 | let raw = unsafe { 57 | vk_dev 58 | .logical_device 59 | .create_framebuffer(&create_info, None) 60 | .map_err(FramebufferError::UnableToCreateFramebuffer)? 61 | }; 62 | Ok(Self { raw, extent, render_pass: render_pass.clone(), vk_dev }) 63 | } 64 | } 65 | 66 | impl Drop for Framebuffer { 67 | fn drop(&mut self) { 68 | unsafe { 69 | self.vk_dev.logical_device.destroy_framebuffer(self.raw, None); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /crates/vui/src/vulkan/render_device/mod.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Mutex; 2 | 3 | use ash::vk; 4 | 5 | pub use crate::vulkan::render_device::{ 6 | gpu_queue::GpuQueue, queue_family_indices::QueueFamilyIndices, swapchain::Swapchain, 7 | }; 8 | use crate::{ 9 | errors::RenderDeviceError, 10 | vulkan::{instance::Instance, window_surface::WindowSurface}, 11 | }; 12 | 13 | mod gpu_queue; 14 | mod physical_device; 15 | mod queue_family_indices; 16 | mod swapchain; 17 | 18 | pub struct RenderDevice { 19 | #[allow(unused)] 20 | pub physical_device: vk::PhysicalDevice, 21 | 22 | pub logical_device: ash::Device, 23 | 24 | pub graphics_queue: GpuQueue, 25 | 26 | pub present_queue: GpuQueue, 27 | 28 | pub swapchain: Mutex>, 29 | 30 | pub window_surface: WindowSurface, 31 | 32 | pub instance: Instance, 33 | } 34 | 35 | impl RenderDevice { 36 | pub fn new( 37 | instance: Instance, 38 | window_surface: WindowSurface, 39 | ) -> Result { 40 | let physical_device = physical_device::find_optimal(&instance.ash, &window_surface)?; 41 | 42 | let queue_family_indices = 43 | QueueFamilyIndices::find(&instance.ash, &physical_device, &window_surface)?; 44 | let logical_device = instance.create_logical_device( 45 | &physical_device, 46 | &queue_family_indices.as_queue_create_infos(), 47 | )?; 48 | let (graphics_queue, present_queue) = queue_family_indices.get_queues(&logical_device); 49 | 50 | let vk_dev = Self { 51 | instance, 52 | physical_device, 53 | logical_device, 54 | graphics_queue, 55 | present_queue, 56 | window_surface, 57 | swapchain: Mutex::new(None), 58 | }; 59 | 60 | Ok(vk_dev) 61 | } 62 | } 63 | 64 | impl Drop for RenderDevice { 65 | fn drop(&mut self) { 66 | unsafe { 67 | let mut swapchain = 68 | self.swapchain.lock().expect("Unable to acquire the swapchain mutex"); 69 | if let Some(swapchain) = swapchain.take() { 70 | self.destroy_swapchain(swapchain).expect("Error while destroying the swapchain"); 71 | } 72 | self.logical_device 73 | .device_wait_idle() 74 | .expect("Error while waiting for device work to finish"); 75 | self.logical_device.destroy_device(None); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /crates/vui/src/vulkan/descriptor_set/descriptor_set_layout.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::c_void, sync::Arc}; 2 | 3 | use ::ash::vk; 4 | 5 | use crate::{errors::DescriptorSetError, vulkan::render_device::RenderDevice}; 6 | 7 | pub struct DescriptorSetLayout { 8 | pub raw: vk::DescriptorSetLayout, 9 | 10 | pub vk_dev: Arc, 11 | } 12 | 13 | impl DescriptorSetLayout { 14 | pub fn new( 15 | vk_dev: Arc, 16 | bindings: &[vk::DescriptorSetLayoutBinding], 17 | ) -> Result { 18 | let bindings_and_flags: Vec<_> = bindings 19 | .iter() 20 | .map(|binding| (*binding, vk::DescriptorBindingFlags::empty())) 21 | .collect(); 22 | Self::new_with_flags(vk_dev, &bindings_and_flags) 23 | } 24 | 25 | pub fn new_with_flags( 26 | vk_dev: Arc, 27 | bindings_and_flags: &[(vk::DescriptorSetLayoutBinding, vk::DescriptorBindingFlags)], 28 | ) -> Result { 29 | let flags: Vec = 30 | bindings_and_flags.iter().map(|(_binding, flag)| *flag).collect(); 31 | let bindings: Vec = 32 | bindings_and_flags.iter().map(|(binding, _flag)| *binding).collect(); 33 | let binding_flags_create_info = vk::DescriptorSetLayoutBindingFlagsCreateInfo { 34 | binding_count: flags.len() as u32, 35 | p_binding_flags: flags.as_ptr(), 36 | ..Default::default() 37 | }; 38 | let create_info = vk::DescriptorSetLayoutCreateInfo { 39 | p_next: &binding_flags_create_info 40 | as *const vk::DescriptorSetLayoutBindingFlagsCreateInfo 41 | as *const c_void, 42 | flags: vk::DescriptorSetLayoutCreateFlags::empty(), 43 | p_bindings: bindings.as_ptr(), 44 | binding_count: bindings.len() as u32, 45 | ..Default::default() 46 | }; 47 | let raw = unsafe { 48 | vk_dev 49 | .logical_device 50 | .create_descriptor_set_layout(&create_info, None) 51 | .map_err(DescriptorSetError::UnableToCreateLayout)? 52 | }; 53 | Ok(Self { raw, vk_dev }) 54 | } 55 | } 56 | 57 | impl Drop for DescriptorSetLayout { 58 | fn drop(&mut self) { 59 | unsafe { 60 | self.vk_dev.logical_device.destroy_descriptor_set_layout(self.raw, None); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /crates/vui/src/vulkan/pipeline/shader.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use ash::vk; 4 | 5 | use crate::{errors::PipelineError, vulkan::render_device::RenderDevice}; 6 | 7 | const DEFAULT_ENTRY_POINT: &'static [u8] = b"main\0"; 8 | 9 | pub struct ShaderModule { 10 | pub raw: vk::ShaderModule, 11 | pub vk_dev: Arc, 12 | } 13 | 14 | impl ShaderModule { 15 | pub fn from_spirv( 16 | vk_dev: Arc, 17 | source: &'static [u8], 18 | ) -> Result { 19 | let source_u32 = Self::copy_to_u32(source)?; 20 | let create_info = vk::ShaderModuleCreateInfo { 21 | p_code: source_u32.as_ptr(), 22 | code_size: source_u32.len() * std::mem::size_of::(), 23 | ..Default::default() 24 | }; 25 | let shader_module = unsafe { 26 | vk_dev 27 | .logical_device 28 | .create_shader_module(&create_info, None) 29 | .map_err(PipelineError::UnableToCreateShaderModule)? 30 | }; 31 | Ok(Self { raw: shader_module, vk_dev }) 32 | } 33 | 34 | pub fn stage_create_info( 35 | &self, 36 | stage: vk::ShaderStageFlags, 37 | ) -> vk::PipelineShaderStageCreateInfo { 38 | vk::PipelineShaderStageCreateInfo { 39 | stage, 40 | module: self.raw, 41 | p_name: DEFAULT_ENTRY_POINT.as_ptr() as *const i8, 42 | ..Default::default() 43 | } 44 | } 45 | } 46 | 47 | impl Drop for ShaderModule { 48 | fn drop(&mut self) { 49 | unsafe { 50 | self.vk_dev.logical_device.destroy_shader_module(self.raw, None); 51 | } 52 | } 53 | } 54 | 55 | impl ShaderModule { 56 | fn copy_to_u32(bytes: &'static [u8]) -> Result, PipelineError> { 57 | use std::convert::TryInto; 58 | 59 | const U32_SIZE: usize = std::mem::size_of::(); 60 | if bytes.len() % U32_SIZE != 0 { 61 | return Err(PipelineError::InvalidSourceLengthInShaderSPIRV); 62 | } 63 | 64 | let mut buffer: Vec = vec![]; 65 | let mut input: &[u8] = &bytes; 66 | while input.len() > 0 { 67 | let (int_slice, rest) = input.split_at(U32_SIZE); 68 | input = rest; 69 | let word = u32::from_le_bytes( 70 | int_slice.try_into().map_err(PipelineError::InvalidBytesInShaderSPIRV)?, 71 | ); 72 | buffer.push(word); 73 | } 74 | 75 | Ok(buffer) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /crates/vui/src/vulkan/command_buffer/one_time_submit_command_pool.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use ::ash::vk; 4 | 5 | use crate::{ 6 | errors::CommandBufferError, 7 | vulkan::{ 8 | command_buffer::{CommandBuffer, CommandPool}, 9 | render_device::{GpuQueue, RenderDevice}, 10 | }, 11 | }; 12 | 13 | #[derive(Clone)] 14 | pub struct OneTimeSubmitCommandPool { 15 | pool: Arc, 16 | cmd: CommandBuffer, 17 | queue: GpuQueue, 18 | 19 | pub vk_dev: Arc, 20 | } 21 | 22 | impl OneTimeSubmitCommandPool { 23 | pub fn new(vk_dev: Arc, queue: &GpuQueue) -> Result { 24 | let pool = Arc::new(CommandPool::new( 25 | vk_dev.clone(), 26 | queue, 27 | vk::CommandPoolCreateFlags::TRANSIENT, 28 | )?); 29 | let cmd = CommandBuffer::new_primary(pool.clone())?; 30 | Ok(Self { pool, cmd, queue: *queue, vk_dev }) 31 | } 32 | 33 | pub fn submit_sync_commands(&self, func: Func) -> Result 34 | where 35 | Func: FnOnce(&Arc, vk::CommandBuffer) -> T, 36 | { 37 | self.pool.reset()?; 38 | unsafe { 39 | let begin_info = vk::CommandBufferBeginInfo { 40 | flags: vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT, 41 | ..Default::default() 42 | }; 43 | self.vk_dev 44 | .logical_device 45 | .begin_command_buffer(self.cmd.raw, &begin_info) 46 | .map_err(CommandBufferError::UnableToBeginCommandBuffer)?; 47 | 48 | let result: T = func(&self.vk_dev, self.cmd.raw); 49 | 50 | self.vk_dev 51 | .logical_device 52 | .end_command_buffer(self.cmd.raw) 53 | .map_err(CommandBufferError::UnableToEndCommandBuffer)?; 54 | 55 | let submit_info = vk::SubmitInfo { 56 | command_buffer_count: 1, 57 | p_command_buffers: &self.cmd.raw, 58 | ..Default::default() 59 | }; 60 | self.vk_dev 61 | .logical_device 62 | .queue_submit(self.queue.queue, &[submit_info], vk::Fence::null()) 63 | .map_err(CommandBufferError::UnableToSubmitCommandBuffer)?; 64 | self.vk_dev 65 | .logical_device 66 | .device_wait_idle() 67 | .map_err(CommandBufferError::UnableToWaitForDeviceIdle)?; 68 | 69 | Ok(result) 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /crates/vui/src/vulkan/allocator/locked_memory.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Mutex}; 2 | 3 | use ash::vk; 4 | 5 | use crate::vulkan::{ 6 | allocator::{Allocation, AllocatorError, ComposableAllocator, MemoryAllocator}, 7 | render_device::RenderDevice, 8 | }; 9 | 10 | pub struct LockedMemoryAllocator { 11 | composed_allocator: Mutex, 12 | vk_dev: Arc, 13 | } 14 | 15 | impl LockedMemoryAllocator { 16 | pub fn new(vk_dev: Arc, allocator: Alloc) -> Self { 17 | Self { composed_allocator: Mutex::new(allocator), vk_dev } 18 | } 19 | } 20 | 21 | impl MemoryAllocator for LockedMemoryAllocator { 22 | unsafe fn allocate_memory( 23 | &self, 24 | memory_requirements: vk::MemoryRequirements, 25 | property_flags: vk::MemoryPropertyFlags, 26 | ) -> Result { 27 | let memory_properties = self 28 | .vk_dev 29 | .instance 30 | .ash 31 | .get_physical_device_memory_properties(self.vk_dev.physical_device); 32 | let memory_type_index = memory_properties 33 | .memory_types 34 | .iter() 35 | .enumerate() 36 | .find(|(i, memory_type)| { 37 | let i = *i as u32; 38 | let type_supported = (memory_requirements.memory_type_bits & (1 << i)) != 0; 39 | let properties_supported = memory_type.property_flags.contains(property_flags); 40 | type_supported && properties_supported 41 | }) 42 | .map(|(i, _memory_type)| i as u32) 43 | .ok_or(AllocatorError::MemoryTypeNotFound(property_flags, memory_requirements))?; 44 | 45 | let allocate_info = vk::MemoryAllocateInfo { 46 | memory_type_index, 47 | allocation_size: memory_requirements.size, 48 | ..Default::default() 49 | }; 50 | 51 | let mut allocator = self 52 | .composed_allocator 53 | .lock() 54 | .expect("unable to acquire the composed memory allocator lock"); 55 | allocator.allocate(allocate_info, memory_requirements.alignment) 56 | } 57 | 58 | unsafe fn free(&self, allocation: &Allocation) -> Result<(), AllocatorError> { 59 | let mut allocator = self 60 | .composed_allocator 61 | .lock() 62 | .expect("unable to acquire the composed memory allocator lock"); 63 | allocator.free(allocation) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /crates/vui/src/errors/render_device.rs: -------------------------------------------------------------------------------- 1 | use ::ash::vk; 2 | use thiserror::Error; 3 | 4 | use crate::errors::{InstanceError, WindowSurfaceError}; 5 | 6 | #[derive(Debug, Error)] 7 | pub enum PhysicalDeviceError { 8 | #[error("Unable to enumerate physical devices")] 9 | UnableToEnumerateDevices(#[source] vk::Result), 10 | 11 | #[error("No suitable physical device could be found for this application")] 12 | NoSuitableDeviceFound, 13 | } 14 | 15 | #[derive(Debug, Error)] 16 | pub enum QueueSelectionError { 17 | #[error("Unable to find a suitable graphics queue")] 18 | UnableToFindGraphicsQueue, 19 | 20 | #[error("Unable to find a suitable presentation queue")] 21 | UnableToFindPresentQueue, 22 | } 23 | 24 | #[derive(Debug, Error)] 25 | pub enum RenderDeviceError { 26 | #[error("Unexpected physical device error")] 27 | UnexpectedPhysicalDeviceError(#[from] PhysicalDeviceError), 28 | 29 | #[error("Unexpected queue selection error")] 30 | UnexpectedQueueSelectionError(#[from] QueueSelectionError), 31 | 32 | #[error("Unexpected Vulkan instance error")] 33 | UnexpectedInstanceError(#[from] InstanceError), 34 | 35 | #[error("Unable to set debug name, {}, for {:?}", .0, .1)] 36 | UnableToSetDebugName(String, vk::ObjectType, #[source] vk::Result), 37 | 38 | #[error("Unexpected Vulkan instance error")] 39 | WindowSurfaceNotCreated, 40 | } 41 | 42 | #[derive(Debug, Error)] 43 | pub enum SwapchainError { 44 | #[error("Unexpected window error in the swapchain")] 45 | UnexpectedWindowError(#[from] WindowSurfaceError), 46 | 47 | #[error("Unable to create the swapchain")] 48 | UnableToCreateSwapchain(#[source] vk::Result), 49 | 50 | #[error("Unable to get swapchain images")] 51 | UnableToGetSwapchainImages(#[source] vk::Result), 52 | 53 | #[error("Unable to create a view for swapchain image {}", .0)] 54 | UnableToCreateSwapchainImageView(usize, #[source] vk::Result), 55 | 56 | #[error("Unexpected render device error")] 57 | UnexpectedRenderDeviceError(#[from] RenderDeviceError), 58 | 59 | #[error("Unable to drain graphics queue when destroying the old swapchain")] 60 | UnableToDrainGraphicsQueue(#[source] vk::Result), 61 | 62 | #[error("Unable to drain presentation queue when destroying the old swapchain")] 63 | UnableToDrainPresentQueue(#[source] vk::Result), 64 | 65 | #[error("Unable to wait for device idle when destroying the old swapchain")] 66 | UnableToWaitForDeviceIdle(#[source] vk::Result), 67 | 68 | #[error("The swapchain is invalid and needs to be rebuilt")] 69 | NeedsRebuild, 70 | } 71 | -------------------------------------------------------------------------------- /crates/vui/src/ui/primitives/dimension_list/axis.rs: -------------------------------------------------------------------------------- 1 | use crate::{ui::primitives::Dimensions, vec2, Vec2}; 2 | 3 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 4 | pub enum Axis { 5 | Horizontal, 6 | Vertical, 7 | } 8 | 9 | impl Axis { 10 | pub(super) fn sum(&self, original: &Dimensions, to_add: &Dimensions) -> Dimensions { 11 | match *self { 12 | Axis::Horizontal => Dimensions::new(original.width + to_add.width, original.height), 13 | Axis::Vertical => Dimensions::new(original.width, original.height + to_add.height), 14 | } 15 | } 16 | 17 | pub(super) fn sub(&self, original: &Dimensions, to_sub: &Dimensions) -> Dimensions { 18 | match *self { 19 | Axis::Horizontal => { 20 | Dimensions::new((original.width - to_sub.width).abs(), original.height) 21 | } 22 | Axis::Vertical => { 23 | Dimensions::new(original.width, (original.height - to_sub.height).abs()) 24 | } 25 | } 26 | } 27 | 28 | pub(super) fn add_scalar(&self, original: &Dimensions, to_add: f32) -> Dimensions { 29 | match *self { 30 | Axis::Horizontal => Dimensions::new(original.width + to_add, original.height), 31 | Axis::Vertical => Dimensions::new(original.width, original.height + to_add), 32 | } 33 | } 34 | 35 | pub(super) fn max(&self, original: &Dimensions, to_compare: &Dimensions) -> Dimensions { 36 | match *self { 37 | Axis::Horizontal => { 38 | Dimensions::new(original.width.max(to_compare.width), original.height) 39 | } 40 | Axis::Vertical => { 41 | Dimensions::new(original.width, original.height.max(to_compare.height)) 42 | } 43 | } 44 | } 45 | 46 | pub(super) fn min(&self, original: &Dimensions, to_compare: &Dimensions) -> Dimensions { 47 | match *self { 48 | Axis::Horizontal => { 49 | Dimensions::new(original.width.min(to_compare.width), original.height) 50 | } 51 | Axis::Vertical => { 52 | Dimensions::new(original.width, original.height.min(to_compare.height)) 53 | } 54 | } 55 | } 56 | 57 | pub(super) fn get(&self, dimensions: &Dimensions) -> f32 { 58 | match *self { 59 | Axis::Horizontal => dimensions.width, 60 | Axis::Vertical => dimensions.height, 61 | } 62 | } 63 | 64 | pub(super) fn vec2(&self, value: f32) -> Vec2 { 65 | match *self { 66 | Axis::Horizontal => vec2(value, 0.0), 67 | Axis::Vertical => vec2(0.0, value), 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /crates/vui/src/msaa/render_target.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use ::ash::vk; 4 | 5 | use crate::{ 6 | errors::VulkanError, 7 | msaa::MSAARenderPass, 8 | vulkan::{ 9 | allocator::MemoryAllocator, 10 | image::{view::ImageView, Image}, 11 | render_device::RenderDevice, 12 | }, 13 | }; 14 | 15 | impl MSAARenderPass { 16 | pub(super) fn create_msaa_render_target( 17 | vk_dev: Arc, 18 | vk_alloc: Arc, 19 | ) -> Result, VulkanError> { 20 | let samples = Self::pick_max_supported_msaa_count(&vk_dev, vk::SampleCountFlags::TYPE_4); 21 | let (swap_extent, format) = vk_dev.with_swapchain(|swap| (swap.extent, swap.format)); 22 | let create_info = vk::ImageCreateInfo { 23 | flags: vk::ImageCreateFlags::empty(), 24 | image_type: vk::ImageType::TYPE_2D, 25 | extent: vk::Extent3D { width: swap_extent.width, height: swap_extent.height, depth: 1 }, 26 | mip_levels: 1, 27 | array_layers: 1, 28 | format, 29 | samples, 30 | tiling: vk::ImageTiling::OPTIMAL, 31 | initial_layout: vk::ImageLayout::UNDEFINED, 32 | usage: vk::ImageUsageFlags::COLOR_ATTACHMENT | 33 | vk::ImageUsageFlags::TRANSIENT_ATTACHMENT, 34 | sharing_mode: vk::SharingMode::EXCLUSIVE, 35 | ..Default::default() 36 | }; 37 | let msaa_render_target = Arc::new(Image::new( 38 | vk_dev.clone(), 39 | vk_alloc, 40 | &create_info, 41 | vk::MemoryPropertyFlags::DEVICE_LOCAL, 42 | )?); 43 | let view = 44 | Arc::new(ImageView::new_2d(msaa_render_target, format, vk::ImageAspectFlags::COLOR)?); 45 | Ok(view) 46 | } 47 | 48 | fn pick_max_supported_msaa_count( 49 | vk_dev: &RenderDevice, 50 | desired: vk::SampleCountFlags, 51 | ) -> vk::SampleCountFlags { 52 | let props = 53 | unsafe { vk_dev.instance.ash.get_physical_device_properties(vk_dev.physical_device) }; 54 | let supported_samples = props 55 | .limits 56 | .framebuffer_color_sample_counts 57 | .min(props.limits.framebuffer_depth_sample_counts); 58 | 59 | [ 60 | vk::SampleCountFlags::TYPE_64, 61 | vk::SampleCountFlags::TYPE_32, 62 | vk::SampleCountFlags::TYPE_16, 63 | vk::SampleCountFlags::TYPE_8, 64 | vk::SampleCountFlags::TYPE_4, 65 | vk::SampleCountFlags::TYPE_2, 66 | ] 67 | .iter() 68 | .find(|&&count| supported_samples.contains(count)) 69 | .map_or(vk::SampleCountFlags::TYPE_1, |&count| desired.min(count)) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /crates/vterm/src/terminal.rs: -------------------------------------------------------------------------------- 1 | use ::vui::{ 2 | asset_loader::AssetLoader, 3 | ui::{font::FontFamily, widgets::prelude::*, UIState}, 4 | }; 5 | use vui::ui::{color::Color, font::FontConfig}; 6 | 7 | #[derive(Debug, Copy, Clone, PartialEq)] 8 | pub enum TerminalMessage { 9 | // Add messages here. 10 | } 11 | 12 | pub struct Terminal { 13 | em: f32, 14 | label_1: Element, 15 | label_2: Element, 16 | image: Element, 17 | label_3: Element, 18 | } 19 | 20 | impl Terminal { 21 | pub fn new(content_scale: f32, asset_loader: &mut AssetLoader) -> anyhow::Result { 22 | let em = 16.0 * content_scale; 23 | let font_32 = FontFamily::new(FontConfig::default(), 2.0 * em, asset_loader)?; 24 | let font_64 = FontFamily::new(FontConfig::default(), 4.0 * em, asset_loader)?; 25 | 26 | let image_dir = dirs::home_dir().unwrap().join(".vterm/assets/images/"); 27 | let img_rust = asset_loader.read_texture(image_dir.join("rust.png"))?; 28 | let label_1 = label( 29 | &font_64.medium, 30 | " 31 | Powered by an easy-to-use, 32 | developer friendly gui library", 33 | ) 34 | .gradient(Color::new(1.0, 1.0, 1.0, 1.0), Color::new(1.0, 1.0, 1.0, 0.3)) 35 | .into(); 36 | 37 | let label_2 = label( 38 | &font_32.light, 39 | " 40 | vterm was built to save developer time 41 | and allow highly customizable terminal workflows. 42 | Use extensions and themes to make your terminal 43 | look and feel like you want it to.", 44 | ) 45 | .gradient(Color::new(1.0, 1.0, 1.0, 1.0), Color::new(1.0, 1.0, 1.0, 0.3)) 46 | .into(); 47 | 48 | let image = img(em * 20.0, em * 20.0, img_rust).into(); 49 | 50 | let label_3 = label(&font_32.light, "Multi-window/tab ai and gpu accelerated.") 51 | .gradient(Color::new(1.0, 1.0, 1.0, 1.0), Color::new(1.0, 1.0, 1.0, 0.3)) 52 | .into(); 53 | 54 | Ok(Self { em, label_1, label_2, image, label_3 }) 55 | } 56 | } 57 | 58 | impl UIState for Terminal { 59 | type Message = TerminalMessage; 60 | 61 | fn view(&mut self) -> Element { 62 | align( 63 | col() 64 | .child(self.label_1.clone()) 65 | .child(self.label_2.clone()) 66 | .child(self.image.clone()) 67 | .child(self.label_3.clone()) 68 | .space_between(SpaceBetween::Fixed(self.em)) 69 | .container() 70 | .padding(0.5 * self.em), 71 | ) 72 | .alignment(HAlignment::Center, VAlignment::Center) 73 | .into() 74 | } 75 | 76 | fn update(&mut self, message: &TerminalMessage) { 77 | match *message { 78 | // handle stuff here. 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /crates/vui/src/vulkan/window_surface.rs: -------------------------------------------------------------------------------- 1 | use ash::{khr::surface, vk}; 2 | 3 | use crate::errors::WindowSurfaceError; 4 | 5 | pub struct WindowSurface { 6 | pub loader: surface::Instance, 7 | pub khr: vk::SurfaceKHR, 8 | } 9 | 10 | impl WindowSurface { 11 | pub fn new(surface_khr: vk::SurfaceKHR, surface_loader: surface::Instance) -> Self { 12 | Self { loader: surface_loader, khr: surface_khr } 13 | } 14 | 15 | /// # Safety 16 | /// This function is unsafe because it calls Vulkan functions. 17 | /// - The caller must ensure that the surface is valid. 18 | /// - The caller must ensure that the surface loader is valid. 19 | pub unsafe fn get_physical_device_surface_support( 20 | &self, 21 | physical_device: &vk::PhysicalDevice, 22 | queue_family_index: u32, 23 | ) -> Result { 24 | self.loader 25 | .get_physical_device_surface_support(*physical_device, queue_family_index, self.khr) 26 | .map_err(WindowSurfaceError::UnableToCheckPhysicalDeviceSurfaceSupport) 27 | } 28 | 29 | /// # Safety 30 | /// This function is unsafe because it calls Vulkan functions. 31 | /// - The caller must ensure that the surface is valid. 32 | /// - The caller must ensure that the surface loader is valid. 33 | pub unsafe fn supported_formats( 34 | &self, 35 | physical_device: &vk::PhysicalDevice, 36 | ) -> Vec { 37 | self.loader 38 | .get_physical_device_surface_formats(*physical_device, self.khr) 39 | .unwrap_or_else(|_| vec![]) 40 | } 41 | 42 | /// # Safety 43 | /// This function is unsafe because it calls Vulkan functions. 44 | /// - The caller must ensure that the surface is valid. 45 | /// - The caller must ensure that the surface loader is valid. 46 | pub unsafe fn supported_presentation_modes( 47 | &self, 48 | physical_device: &vk::PhysicalDevice, 49 | ) -> Vec { 50 | self.loader 51 | .get_physical_device_surface_present_modes(*physical_device, self.khr) 52 | .unwrap_or_else(|_| vec![]) 53 | } 54 | 55 | /// # Safety 56 | /// This function is unsafe because it calls Vulkan functions. 57 | /// - The caller must ensure that the surface is valid. 58 | /// - The caller must ensure that the surface loader is valid. 59 | pub unsafe fn surface_capabilities( 60 | &self, 61 | physical_device: &vk::PhysicalDevice, 62 | ) -> Result { 63 | self.loader 64 | .get_physical_device_surface_capabilities(*physical_device, self.khr) 65 | .map_err(WindowSurfaceError::UnableToGetPhysicalDeviceSurfaceCapabilities) 66 | } 67 | } 68 | 69 | impl Drop for WindowSurface { 70 | fn drop(&mut self) { 71 | unsafe { 72 | self.loader.destroy_surface(self.khr, None); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /crates/vui/src/vulkan/buffer/gpu_vec.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use ::ash::vk; 4 | 5 | use crate::{ 6 | errors::BufferError, 7 | vulkan::{allocator::MemoryAllocator, buffer::Buffer, render_device::RenderDevice}, 8 | }; 9 | 10 | #[derive(Clone)] 11 | pub struct GpuVec { 12 | pub buffer: Buffer, 13 | 14 | capacity: u32, 15 | 16 | length: u32, 17 | 18 | usage_flags: vk::BufferUsageFlags, 19 | 20 | _phantom_data: std::marker::PhantomData, 21 | } 22 | 23 | impl GpuVec { 24 | pub fn new( 25 | vk_dev: Arc, 26 | vk_alloc: Arc, 27 | buffer_usage_flags: vk::BufferUsageFlags, 28 | initial_capacity: u32, 29 | ) -> Result { 30 | let mut buffer = Buffer::new( 31 | vk_dev, 32 | vk_alloc, 33 | buffer_usage_flags, 34 | vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT, 35 | Self::element_count_to_bytes(initial_capacity), 36 | )?; 37 | buffer.map()?; 38 | Ok(Self { 39 | buffer, 40 | capacity: initial_capacity, 41 | length: 0, 42 | usage_flags: buffer_usage_flags, 43 | _phantom_data: std::marker::PhantomData::default(), 44 | }) 45 | } 46 | 47 | pub fn push_back(&mut self, value: T) -> Result { 48 | let mut replaced = false; 49 | if self.length == self.capacity { 50 | self.grow(self.length * 2)?; 51 | replaced = true; 52 | } 53 | let data = self.buffer.data_mut()?; 54 | data[self.len()] = value; 55 | self.length = self.length + 1; 56 | Ok(replaced) 57 | } 58 | 59 | pub fn clear(&mut self) { 60 | self.length = 0; 61 | } 62 | 63 | pub fn len(&self) -> usize { 64 | self.length as usize 65 | } 66 | 67 | pub fn len_bytes(&self) -> u64 { 68 | Self::element_count_to_bytes(self.length) 69 | } 70 | } 71 | 72 | impl GpuVec { 73 | fn element_count_to_bytes(count: u32) -> u64 { 74 | (count as u64) * (std::mem::size_of::() as u64) 75 | } 76 | 77 | fn grow(&mut self, desired_capacity: u32) -> Result<(), BufferError> { 78 | let mut buffer = Buffer::new( 79 | self.buffer.vk_dev.clone(), 80 | self.buffer.vk_alloc.clone(), 81 | self.usage_flags, 82 | vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT, 83 | Self::element_count_to_bytes(desired_capacity), 84 | )?; 85 | buffer.map()?; 86 | self.capacity = desired_capacity; 87 | 88 | { 89 | let new_data = buffer.data_mut::()?; 90 | let old_data = self.buffer.data::()?; 91 | new_data[.. old_data.len()].copy_from_slice(old_data); 92 | } 93 | 94 | std::mem::swap(&mut self.buffer, &mut buffer); 95 | Ok(()) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /crates/vui/src/ui/widgets/image.rs: -------------------------------------------------------------------------------- 1 | use super::{element::StylableWidget, CompositeStyle}; 2 | use crate::{ 3 | graphics::{triangles::Frame, Sprite}, 4 | ui::{ 5 | primitives::{Dimensions, Rect}, 6 | widgets::{Element, Widget}, 7 | Input, InternalState, 8 | }, 9 | Vec2, 10 | }; 11 | 12 | pub struct Image { 13 | sprite: Sprite, 14 | bounds: Rect, 15 | } 16 | 17 | impl Image { 18 | pub fn new(width: f32, height: f32, texture_index: i32) -> Self { 19 | let center = Vec2::new(width / 2.0, height / 2.0); 20 | let sprite = Sprite { 21 | width, 22 | height, 23 | position: center, 24 | angle_in_radians: 0.0, 25 | depth: 0.0, 26 | texture_index, 27 | }; 28 | let bounds = Rect::new(0.0, 0.0, width, height); 29 | 30 | Self { sprite, bounds } 31 | } 32 | 33 | pub fn size(mut self, width: f32, height: f32) -> Self { 34 | self.sprite.width = width; 35 | self.sprite.height = height; 36 | self.sprite.position = Vec2::new(width / 2.0, height / 2.0); 37 | self.bounds = Rect::new(0.0, 0.0, width, height); 38 | self 39 | } 40 | } 41 | 42 | impl Widget for Image { 43 | fn handle_event( 44 | &mut self, 45 | _internal_state: &mut InternalState, 46 | _input: &Input, 47 | _event: &winit::event::WindowEvent, 48 | ) -> Result, anyhow::Error> { 49 | Ok(None) 50 | } 51 | 52 | fn draw_frame( 53 | &mut self, 54 | _internal_state: &mut InternalState, 55 | frame: &mut Frame, 56 | ) -> anyhow::Result<()> { 57 | self.sprite.draw(frame)?; 58 | Ok(()) 59 | } 60 | 61 | fn dimensions( 62 | &mut self, 63 | _internal_state: &mut InternalState, 64 | max_size: &Dimensions, 65 | ) -> Dimensions { 66 | self.bounds.dimensions().min(max_size) 67 | } 68 | 69 | fn set_top_left_position(&mut self, _internal_state: &mut InternalState, position: Vec2) { 70 | let center = Vec2::new( 71 | position.x + self.bounds.width() / 2.0, 72 | position.y + self.bounds.height() / 2.0, 73 | ); 74 | self.sprite.position = center; 75 | self.bounds = Rect::new(position.x, position.y, self.bounds.width(), self.bounds.height()); 76 | } 77 | } 78 | 79 | impl StylableWidget for Image { 80 | fn bg(&self) -> CompositeStyle { 81 | CompositeStyle::default() 82 | } 83 | 84 | fn top_left(&self) -> Vec2 { 85 | self.bounds.top_left() 86 | } 87 | 88 | fn bounds(&self) -> Rect { 89 | self.bounds 90 | } 91 | } 92 | 93 | impl Into> for Image 94 | where 95 | Message: 'static, 96 | { 97 | fn into(self) -> Element { 98 | Element::new(self) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /crates/vui/src/vulkan/render_device/swapchain/selection.rs: -------------------------------------------------------------------------------- 1 | use ash::vk; 2 | 3 | use crate::{errors::SwapchainError, format::MdList, vulkan::render_device::RenderDevice}; 4 | 5 | impl RenderDevice { 6 | pub(crate) fn choose_image_count(&self) -> Result { 7 | let capabilities = 8 | unsafe { self.window_surface.surface_capabilities(&self.physical_device)? }; 9 | 10 | let proposed_image_count = capabilities.min_image_count + 1; 11 | if capabilities.max_image_count > 0 { 12 | Ok(std::cmp::min(proposed_image_count, capabilities.max_image_count)) 13 | } else { 14 | Ok(proposed_image_count) 15 | } 16 | } 17 | 18 | pub(crate) fn choose_surface_format(&self) -> vk::SurfaceFormatKHR { 19 | let formats = unsafe { self.window_surface.supported_formats(&self.physical_device) }; 20 | 21 | log::debug!("available formats: {:#?}", MdList(&formats)); 22 | 23 | let format = formats 24 | .iter() 25 | .cloned() 26 | .find(|format| { 27 | format.color_space == vk::ColorSpaceKHR::SRGB_NONLINEAR && 28 | format.format == vk::Format::B8G8R8A8_SRGB 29 | }) 30 | .unwrap_or_else(|| formats[0]); 31 | 32 | log::debug!("chosen format {:#?}", format); 33 | 34 | format 35 | } 36 | 37 | pub(crate) fn choose_present_mode(&self) -> vk::PresentModeKHR { 38 | let modes = 39 | unsafe { self.window_surface.supported_presentation_modes(&self.physical_device) }; 40 | 41 | // prefer immediate mode, but fallback to mailbox or fifo 42 | let mode = 43 | modes.iter().cloned().find(|&m| m == vk::PresentModeKHR::IMMEDIATE).unwrap_or_else( 44 | || { 45 | if modes.contains(&vk::PresentModeKHR::MAILBOX) { 46 | vk::PresentModeKHR::MAILBOX 47 | } else { 48 | vk::PresentModeKHR::FIFO 49 | } 50 | }, 51 | ); 52 | 53 | mode 54 | } 55 | 56 | pub(crate) fn choose_swap_extent( 57 | &self, 58 | framebuffer_size: (u32, u32), 59 | ) -> Result { 60 | let capabilities = 61 | unsafe { self.window_surface.surface_capabilities(&self.physical_device)? }; 62 | 63 | if capabilities.current_extent.width != u32::MAX { 64 | Ok(capabilities.current_extent) 65 | } else { 66 | let (width, height) = framebuffer_size; 67 | let extent = vk::Extent2D { 68 | width: width.clamp( 69 | capabilities.min_image_extent.width, 70 | capabilities.max_image_extent.width, 71 | ), 72 | height: height.clamp( 73 | capabilities.min_image_extent.height, 74 | capabilities.max_image_extent.height, 75 | ), 76 | }; 77 | Ok(extent) 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /crates/vui/src/msaa/depth_target.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use ::ash::vk; 4 | 5 | use crate::{ 6 | errors::VulkanError, 7 | msaa::{MSAAError, MSAARenderPass}, 8 | vulkan::{ 9 | allocator::MemoryAllocator, 10 | image::{view::ImageView, Image}, 11 | render_device::RenderDevice, 12 | }, 13 | }; 14 | 15 | impl MSAARenderPass { 16 | pub(super) fn create_depth_target( 17 | msaa_render_target: &ImageView, 18 | vk_dev: Arc, 19 | vk_alloc: Arc, 20 | ) -> Result, MSAAError> { 21 | let format = Self::take_first_supported_depth_format( 22 | &vk_dev, 23 | &[ 24 | vk::Format::D32_SFLOAT_S8_UINT, 25 | vk::Format::D24_UNORM_S8_UINT, 26 | vk::Format::D16_UNORM_S8_UINT, 27 | ], 28 | )?; 29 | let create_info = vk::ImageCreateInfo { 30 | flags: vk::ImageCreateFlags::empty(), 31 | image_type: vk::ImageType::TYPE_2D, 32 | extent: msaa_render_target.image.create_info.extent, 33 | mip_levels: 1, 34 | array_layers: 1, 35 | format, 36 | samples: msaa_render_target.image.create_info.samples, 37 | tiling: vk::ImageTiling::OPTIMAL, 38 | initial_layout: vk::ImageLayout::UNDEFINED, 39 | usage: vk::ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT | 40 | vk::ImageUsageFlags::TRANSIENT_ATTACHMENT, 41 | sharing_mode: vk::SharingMode::EXCLUSIVE, 42 | ..Default::default() 43 | }; 44 | let depth_target = Arc::new( 45 | Image::new( 46 | vk_dev.clone(), 47 | vk_alloc, 48 | &create_info, 49 | vk::MemoryPropertyFlags::DEVICE_LOCAL, 50 | ) 51 | .map_err(VulkanError::ImageError)?, 52 | ); 53 | let view = Arc::new( 54 | ImageView::new_2d( 55 | depth_target, 56 | format, 57 | vk::ImageAspectFlags::DEPTH | vk::ImageAspectFlags::STENCIL, 58 | ) 59 | .map_err(VulkanError::ImageError)?, 60 | ); 61 | Ok(view) 62 | } 63 | 64 | fn take_first_supported_depth_format( 65 | vk_dev: &RenderDevice, 66 | candidates: &[vk::Format], 67 | ) -> Result { 68 | for format in candidates { 69 | let format_properties = unsafe { 70 | vk_dev 71 | .instance 72 | .ash 73 | .get_physical_device_format_properties(vk_dev.physical_device, *format) 74 | }; 75 | if (format_properties.optimal_tiling_features & 76 | vk::FormatFeatureFlags::DEPTH_STENCIL_ATTACHMENT) == 77 | vk::FormatFeatureFlags::DEPTH_STENCIL_ATTACHMENT 78 | { 79 | return Ok(*format); 80 | } 81 | } 82 | 83 | Err(MSAAError::UnableToPickDepthFormat) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /crates/vui/src/ui/widgets/row.rs: -------------------------------------------------------------------------------- 1 | use ::anyhow::Result; 2 | 3 | use crate::{ 4 | graphics::triangles::Frame, 5 | ui::{ 6 | primitives::{DimensionList, Dimensions, Justify, SpaceBetween}, 7 | widgets::{Element, Widget}, 8 | Input, InternalState, 9 | }, 10 | Vec2, 11 | }; 12 | 13 | pub struct Row { 14 | children: Vec<(Element, Justify)>, 15 | child_dimensions: DimensionList, 16 | } 17 | 18 | impl Row { 19 | pub fn new() -> Self { 20 | Self { children: vec![], child_dimensions: DimensionList::horizontal() } 21 | } 22 | 23 | pub fn space_between(self, space_between: SpaceBetween) -> Self { 24 | Self { child_dimensions: self.child_dimensions.space_between(space_between), ..self } 25 | } 26 | 27 | pub fn child(mut self, child: W, justify: Justify) -> Self 28 | where 29 | W: Into>, 30 | { 31 | self.children.push((child.into(), justify)); 32 | self 33 | } 34 | } 35 | 36 | impl Widget for Row { 37 | fn handle_event( 38 | &mut self, 39 | internal_state: &mut InternalState, 40 | input: &Input, 41 | event: &winit::event::WindowEvent, 42 | ) -> Result> { 43 | for (child, _) in &mut self.children { 44 | if let Some(message) = child.handle_event(internal_state, input, event)? { 45 | return Ok(Some(message)); 46 | } 47 | } 48 | Ok(None) 49 | } 50 | 51 | fn draw_frame(&mut self, internal_state: &mut InternalState, frame: &mut Frame) -> Result<()> { 52 | for (child, _) in &mut self.children { 53 | child.draw_frame(internal_state, frame)?; 54 | } 55 | Ok(()) 56 | } 57 | 58 | fn dimensions( 59 | &mut self, 60 | internal_state: &mut InternalState, 61 | max_size: &Dimensions, 62 | ) -> Dimensions { 63 | if self.children.is_empty() { 64 | return Dimensions::new(0.0, 0.0); 65 | } 66 | 67 | self.child_dimensions.set_max_size(max_size); 68 | 69 | let mut remaining_size = *max_size; 70 | 71 | for (child, justify) in &mut self.children { 72 | let child_bounds = child.dimensions(internal_state, &remaining_size); 73 | 74 | remaining_size = self.child_dimensions.add_child_dimensions(child_bounds, *justify); 75 | } 76 | 77 | self.child_dimensions.dimensions() 78 | } 79 | 80 | fn set_top_left_position(&mut self, internal_state: &mut InternalState, position: Vec2) { 81 | let positions = self.child_dimensions.compute_child_positions(); 82 | for ((child, _), child_pos) in self.children.iter_mut().zip(positions.iter()) { 83 | child.set_top_left_position(internal_state, position + child_pos); 84 | } 85 | } 86 | } 87 | 88 | impl Into> for Row 89 | where 90 | Message: 'static, 91 | { 92 | fn into(self) -> Element { 93 | Element::new(self) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /crates/vui/src/vulkan/command_buffer/command_pool.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use ::ash::vk; 4 | 5 | use crate::{ 6 | errors::CommandBufferError, 7 | vulkan::render_device::{GpuQueue, RenderDevice}, 8 | }; 9 | 10 | pub struct CommandPool { 11 | pub raw: vk::CommandPool, 12 | 13 | pub vk_dev: Arc, 14 | } 15 | 16 | impl CommandPool { 17 | pub fn new( 18 | vk_dev: Arc, 19 | queue: &GpuQueue, 20 | flags: vk::CommandPoolCreateFlags, 21 | ) -> Result { 22 | let raw = { 23 | let create_info = vk::CommandPoolCreateInfo { 24 | queue_family_index: queue.family_id, 25 | flags, 26 | ..Default::default() 27 | }; 28 | unsafe { 29 | vk_dev 30 | .logical_device 31 | .create_command_pool(&create_info, None) 32 | .map_err(CommandBufferError::UnableToCreateCommandPool)? 33 | } 34 | }; 35 | Ok(Self { raw, vk_dev }) 36 | } 37 | 38 | pub fn new_transient_graphics_pool( 39 | vk_dev: Arc, 40 | ) -> Result { 41 | Self::new(vk_dev.clone(), &vk_dev.graphics_queue, vk::CommandPoolCreateFlags::TRANSIENT) 42 | } 43 | 44 | pub unsafe fn allocate_command_buffers( 45 | &self, 46 | level: vk::CommandBufferLevel, 47 | command_buffer_count: u32, 48 | ) -> Result, CommandBufferError> { 49 | let create_info = vk::CommandBufferAllocateInfo { 50 | command_pool: self.raw, 51 | level, 52 | command_buffer_count, 53 | ..Default::default() 54 | }; 55 | let buffer = self 56 | .vk_dev 57 | .logical_device 58 | .allocate_command_buffers(&create_info) 59 | .map_err(CommandBufferError::UnableToAllocateBuffer)?; 60 | Ok(buffer) 61 | } 62 | 63 | pub unsafe fn allocate_command_buffer( 64 | &self, 65 | level: vk::CommandBufferLevel, 66 | ) -> Result { 67 | let buffers = self.allocate_command_buffers(level, 1)?; 68 | Ok(buffers[0]) 69 | } 70 | 71 | pub unsafe fn free_command_buffers(&self, command_buffers: &[vk::CommandBuffer]) { 72 | self.vk_dev.logical_device.free_command_buffers(self.raw, command_buffers); 73 | } 74 | 75 | pub unsafe fn free_command_buffer(&self, command_buffer: vk::CommandBuffer) { 76 | self.free_command_buffers(&[command_buffer]); 77 | } 78 | 79 | pub fn reset(&self) -> Result<(), CommandBufferError> { 80 | unsafe { 81 | self.vk_dev 82 | .logical_device 83 | .reset_command_pool(self.raw, vk::CommandPoolResetFlags::empty()) 84 | .map_err(CommandBufferError::UnableToResetPool)?; 85 | } 86 | Ok(()) 87 | } 88 | } 89 | 90 | impl Drop for CommandPool { 91 | fn drop(&mut self) { 92 | unsafe { self.vk_dev.logical_device.destroy_command_pool(self.raw, None) } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /crates/vui/src/ui/font/rasterize.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use ab_glyph::{FontArc, Glyph, GlyphId, PxScaleFont, ScaleFont}; 4 | 5 | use crate::{ 6 | asset_loader::MipmapData, 7 | ui::{primitives::Rect, Font}, 8 | }; 9 | 10 | impl Font { 11 | pub(super) fn layout_chars( 12 | font: &PxScaleFont, 13 | padding: u32, 14 | max_width: u32, 15 | content: T, 16 | ) -> Vec 17 | where 18 | T: Iterator, 19 | { 20 | let v_advance = ((font.line_gap() + font.height()).ceil() as u32) + padding; 21 | let mut glyphs: Vec = Vec::with_capacity(content.size_hint().0); 22 | let mut line_number = 1; 23 | let mut cursor_x = 0.0; 24 | let mut cursor_y = v_advance as f32; 25 | 26 | for char in content { 27 | let h_advance = font.h_advance(font.glyph_id(char)); 28 | 29 | if ((cursor_x + h_advance) as u32) + padding > max_width { 30 | line_number += 1; 31 | cursor_x = 0.0; 32 | cursor_y = (line_number * v_advance) as f32; 33 | } 34 | 35 | glyphs.push( 36 | font.glyph_id(char) 37 | .with_scale_and_position(font.scale(), ab_glyph::point(cursor_x, cursor_y)), 38 | ); 39 | 40 | cursor_x += h_advance + (padding as f32); 41 | cursor_x = cursor_x.round(); 42 | cursor_y = cursor_y.round(); 43 | } 44 | 45 | glyphs 46 | } 47 | 48 | pub(super) fn rasterize_glyphs( 49 | font: &PxScaleFont, 50 | glyphs: &[Glyph], 51 | ) -> (MipmapData, HashMap) { 52 | let mut max_width = 0; 53 | let mut max_height = 0; 54 | let mut outlines = HashMap::with_capacity(glyphs.len()); 55 | for glyph in glyphs { 56 | if let Some(outline) = font.outline_glyph(glyph.clone()) { 57 | let bounds = outline.px_bounds(); 58 | max_width = max_width.max(bounds.max.x as u32); 59 | max_height = max_height.max(bounds.max.y as u32); 60 | outlines.insert(glyph.id, outline); 61 | } 62 | } 63 | 64 | let mut glyph_texture_coords = HashMap::with_capacity(outlines.len()); 65 | let mut rasterized_glyphs = 66 | MipmapData::allocate(max_width, max_height, [0xff, 0xff, 0xff, 0x00]); 67 | 68 | for (glyph_id, outline) in outlines { 69 | let bounds = outline.px_bounds(); 70 | let texture_coords = Rect::new( 71 | bounds.min.y / (max_height as f32), 72 | bounds.min.x / (max_width as f32), 73 | bounds.max.y / (max_height as f32), 74 | bounds.max.x / (max_width as f32), 75 | ); 76 | let basex = bounds.min.x as u32; 77 | let basey = bounds.min.y as u32; 78 | outline.draw(|x, y, coverage| { 79 | rasterized_glyphs.write_pixel( 80 | basex + x, 81 | basey + y, 82 | [0xff, 0xff, 0xff, (coverage * 255.0) as u8], 83 | ); 84 | }); 85 | glyph_texture_coords.insert(glyph_id, texture_coords); 86 | } 87 | 88 | (rasterized_glyphs, glyph_texture_coords) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /crates/vui/src/ui/widgets/col.rs: -------------------------------------------------------------------------------- 1 | use ::anyhow::Result; 2 | 3 | use crate::{ 4 | graphics::triangles::Frame, 5 | ui::{ 6 | primitives::{DimensionList, Dimensions, Justify, SpaceBetween}, 7 | widgets::{Element, Widget}, 8 | Input, InternalState, 9 | }, 10 | Vec2, 11 | }; 12 | 13 | pub struct Col { 14 | children: Vec>, 15 | child_dimensions: DimensionList, 16 | justify: Justify, 17 | } 18 | 19 | impl Col { 20 | pub fn new() -> Self { 21 | Self { 22 | children: vec![], 23 | child_dimensions: DimensionList::vertical(), 24 | justify: Justify::Begin, 25 | } 26 | } 27 | 28 | pub fn space_between(mut self, space_between: SpaceBetween) -> Self { 29 | self.child_dimensions = self.child_dimensions.space_between(space_between); 30 | self 31 | } 32 | 33 | pub fn child(mut self, child: W) -> Self 34 | where 35 | W: Into>, 36 | { 37 | self.children.push(child.into()); 38 | self 39 | } 40 | 41 | pub fn justify(mut self, justify: Justify) -> Self { 42 | self.justify = justify; 43 | self 44 | } 45 | } 46 | 47 | impl Widget for Col { 48 | fn handle_event( 49 | &mut self, 50 | internal_state: &mut InternalState, 51 | input: &Input, 52 | event: &winit::event::WindowEvent, 53 | ) -> Result> { 54 | for child in &mut self.children { 55 | if let Some(message) = child.handle_event(internal_state, input, event)? { 56 | return Ok(Some(message)); 57 | } 58 | } 59 | Ok(None) 60 | } 61 | 62 | fn draw_frame(&mut self, internal_state: &mut InternalState, frame: &mut Frame) -> Result<()> { 63 | for child in &mut self.children { 64 | child.draw_frame(internal_state, frame)?; 65 | } 66 | Ok(()) 67 | } 68 | 69 | fn dimensions( 70 | &mut self, 71 | internal_state: &mut InternalState, 72 | max_size: &Dimensions, 73 | ) -> Dimensions { 74 | if self.children.is_empty() { 75 | return Dimensions::new(0.0, 0.0); 76 | } 77 | 78 | self.child_dimensions.set_max_size(max_size); 79 | 80 | let mut remaining_size = *max_size; 81 | for child in &mut self.children { 82 | let child_bounds = child.dimensions(internal_state, &remaining_size); 83 | remaining_size = self.child_dimensions.add_child_dimensions(child_bounds, self.justify); 84 | } 85 | 86 | self.child_dimensions.dimensions() 87 | } 88 | 89 | fn set_top_left_position(&mut self, internal_state: &mut InternalState, position: Vec2) { 90 | let positions = self.child_dimensions.compute_child_positions(); 91 | for (child, child_pos) in self.children.iter_mut().zip(positions.iter()) { 92 | child.set_top_left_position(internal_state, position + *child_pos); 93 | } 94 | } 95 | } 96 | 97 | impl Into> for Col 98 | where 99 | Message: 'static, 100 | { 101 | fn into(self) -> Element { 102 | Element::new(self) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /crates/vui/src/ui/widgets/hsplit.rs: -------------------------------------------------------------------------------- 1 | use ::anyhow::Result; 2 | 3 | use crate::{ 4 | graphics::triangles::Frame, 5 | ui::{ 6 | primitives::Dimensions, 7 | widgets::{Element, Widget}, 8 | Input, InternalState, 9 | }, 10 | vec2, Vec2, 11 | }; 12 | 13 | pub struct HSplit { 14 | left: Option>, 15 | right: Option>, 16 | midpoint_offset: f32, 17 | } 18 | 19 | impl HSplit { 20 | pub fn new() -> Self { 21 | Self { left: None, right: None, midpoint_offset: 0.0 } 22 | } 23 | 24 | pub fn left(self, element: E) -> Self 25 | where 26 | E: Into>, 27 | { 28 | Self { left: Some(element.into()), ..self } 29 | } 30 | 31 | pub fn right(self, element: E) -> Self 32 | where 33 | E: Into>, 34 | { 35 | Self { right: Some(element.into()), ..self } 36 | } 37 | } 38 | 39 | impl Widget for HSplit { 40 | fn handle_event( 41 | &mut self, 42 | internal_state: &mut InternalState, 43 | input: &Input, 44 | event: &winit::event::WindowEvent, 45 | ) -> Result> { 46 | if let Some(elem) = &mut self.left { 47 | if let Some(message) = elem.handle_event(internal_state, input, event)? { 48 | return Ok(Some(message)); 49 | } 50 | } 51 | if let Some(elem) = &mut self.right { 52 | if let Some(message) = elem.handle_event(internal_state, input, event)? { 53 | return Ok(Some(message)); 54 | } 55 | } 56 | Ok(None) 57 | } 58 | 59 | fn draw_frame(&mut self, internal_state: &mut InternalState, frame: &mut Frame) -> Result<()> { 60 | if let Some(elem) = &mut self.left { 61 | elem.draw_frame(internal_state, frame)?; 62 | } 63 | if let Some(elem) = &mut self.right { 64 | elem.draw_frame(internal_state, frame)?; 65 | } 66 | Ok(()) 67 | } 68 | 69 | fn dimensions( 70 | &mut self, 71 | internal_state: &mut InternalState, 72 | max_size: &Dimensions, 73 | ) -> Dimensions { 74 | let half_size = Dimensions::new(0.5 * max_size.width, max_size.height); 75 | 76 | if let Some(elem) = &mut self.left { 77 | elem.dimensions(internal_state, &half_size); 78 | } 79 | if let Some(elem) = &mut self.right { 80 | elem.dimensions(internal_state, &half_size); 81 | } 82 | 83 | self.midpoint_offset = 0.5 * max_size.width; 84 | 85 | *max_size 86 | } 87 | 88 | fn set_top_left_position(&mut self, internal_state: &mut InternalState, position: Vec2) { 89 | if let Some(elem) = &mut self.left { 90 | elem.set_top_left_position(internal_state, position); 91 | } 92 | 93 | if let Some(elem) = &mut self.right { 94 | elem.set_top_left_position(internal_state, position + vec2(self.midpoint_offset, 0.0)) 95 | } 96 | } 97 | } 98 | 99 | impl Into> for HSplit 100 | where 101 | Message: 'static, 102 | { 103 | fn into(self) -> Element { 104 | Element::new(self) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /crates/vui/src/vulkan/render_device/queue_family_indices.rs: -------------------------------------------------------------------------------- 1 | use ash::vk; 2 | 3 | use crate::{ 4 | errors::QueueSelectionError, 5 | vulkan::{render_device::GpuQueue, window_surface::WindowSurface}, 6 | }; 7 | 8 | const SINGLE_QUEUE_PRIORITY: [f32; 1] = [1.0]; 9 | 10 | pub struct QueueFamilyIndices { 11 | graphics_family_index: u32, 12 | 13 | present_family_index: u32, 14 | } 15 | 16 | impl QueueFamilyIndices { 17 | pub fn find( 18 | ash: &ash::Instance, 19 | physical_device: &vk::PhysicalDevice, 20 | window_surface: &WindowSurface, 21 | ) -> Result { 22 | let queue_families = 23 | unsafe { ash.get_physical_device_queue_family_properties(*physical_device) }; 24 | 25 | let mut graphics_family = None; 26 | let mut present_family = None; 27 | 28 | queue_families.iter().enumerate().for_each(|(i, family)| { 29 | if family.queue_flags.contains(vk::QueueFlags::GRAPHICS) { 30 | graphics_family = Some(i as u32); 31 | } 32 | 33 | let present_support = unsafe { 34 | window_surface.get_physical_device_surface_support(&physical_device, i as u32) 35 | }; 36 | match present_support { 37 | Ok(true) => { 38 | present_family = Some(i as u32); 39 | } 40 | Err(ref error) => { 41 | log::warn!("Error while checking surface support for device: {:?}", error); 42 | } 43 | _ => {} 44 | } 45 | }); 46 | 47 | let graphics_family_index = 48 | graphics_family.ok_or(QueueSelectionError::UnableToFindGraphicsQueue)?; 49 | 50 | let present_family_index = 51 | present_family.ok_or(QueueSelectionError::UnableToFindPresentQueue)?; 52 | 53 | Ok(Self { graphics_family_index, present_family_index }) 54 | } 55 | 56 | pub fn as_queue_create_infos(&self) -> Vec { 57 | let mut create_infos = vec![vk::DeviceQueueCreateInfo { 58 | queue_family_index: self.graphics_family_index, 59 | p_queue_priorities: SINGLE_QUEUE_PRIORITY.as_ptr(), 60 | queue_count: 1, 61 | ..Default::default() 62 | }]; 63 | 64 | if self.graphics_family_index != self.present_family_index { 65 | create_infos.push(vk::DeviceQueueCreateInfo { 66 | queue_family_index: self.present_family_index, 67 | p_queue_priorities: SINGLE_QUEUE_PRIORITY.as_ptr(), 68 | queue_count: 1, 69 | ..Default::default() 70 | }); 71 | } 72 | 73 | create_infos 74 | } 75 | 76 | pub fn get_queues(&self, logical_device: &ash::Device) -> (GpuQueue, GpuQueue) { 77 | let raw_graphics_queue = 78 | unsafe { logical_device.get_device_queue(self.graphics_family_index, 0) }; 79 | let graphics_queue = GpuQueue::from_raw(raw_graphics_queue, self.graphics_family_index, 0); 80 | 81 | let is_same_family = self.graphics_family_index == self.present_family_index; 82 | let present_queue = if is_same_family { 83 | graphics_queue 84 | } else { 85 | let raw_present_queue = 86 | unsafe { logical_device.get_device_queue(self.present_family_index, 0) }; 87 | GpuQueue::from_raw(raw_present_queue, self.present_family_index, 0) 88 | }; 89 | 90 | (graphics_queue, present_queue) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /crates/vui/src/ui/widgets/composite/mod.rs: -------------------------------------------------------------------------------- 1 | use ::anyhow::Result; 2 | 3 | pub use self::composed_message::{ComposedElement, ComposedMessage}; 4 | use crate::{ 5 | graphics::triangles::Frame, 6 | ui::{ 7 | primitives::Dimensions, 8 | widgets::{Element, Widget}, 9 | Id, Input, InternalState, 10 | }, 11 | Vec2, 12 | }; 13 | 14 | mod composed_message; 15 | 16 | pub trait CompositeWidget { 17 | type State; 18 | 19 | fn id(&self) -> &Id; 20 | 21 | fn view(&mut self, state: &Self::State) -> Element>; 22 | 23 | fn update(&self, state: &mut Self::State, event: IMessage) -> Result<()>; 24 | } 25 | 26 | pub struct Composite 27 | where 28 | CW: CompositeWidget, 29 | { 30 | composite: CW, 31 | current_view: Option>>, 32 | } 33 | 34 | impl Composite 35 | where 36 | CW: CompositeWidget, 37 | { 38 | pub fn new(composite: CW) -> Self { 39 | Self { composite, current_view: None } 40 | } 41 | } 42 | 43 | impl Widget 44 | for Composite 45 | where 46 | CW: CompositeWidget + 'static, 47 | CW::State: 'static + Default, 48 | { 49 | fn handle_event( 50 | &mut self, 51 | internal_state: &mut InternalState, 52 | input: &Input, 53 | event: &winit::event::WindowEvent, 54 | ) -> Result> { 55 | if self.current_view.is_none() { 56 | let current_state = internal_state.get_state::(self.composite.id()); 57 | self.current_view = Some(self.composite.view(current_state)); 58 | } 59 | let result = 60 | self.current_view.as_mut().unwrap().handle_event(internal_state, input, event)?; 61 | match result { 62 | Some(ComposedMessage::Internal(internal)) => { 63 | let state = internal_state.get_state_mut::(self.composite.id()); 64 | self.composite.update(state, internal)?; 65 | return Ok(None); 66 | } 67 | Some(ComposedMessage::External(ext)) => { 68 | return Ok(Some(ext)); 69 | } 70 | None => { 71 | return Ok(None); 72 | } 73 | } 74 | } 75 | 76 | fn draw_frame(&mut self, internal_state: &mut InternalState, frame: &mut Frame) -> Result<()> { 77 | self.current_view.as_mut().unwrap().draw_frame(internal_state, frame) 78 | } 79 | 80 | fn dimensions( 81 | &mut self, 82 | internal_state: &mut InternalState, 83 | max_size: &Dimensions, 84 | ) -> Dimensions { 85 | if self.current_view.is_none() { 86 | let current_state = internal_state.get_state::(self.composite.id()); 87 | self.current_view = Some(self.composite.view(current_state)); 88 | } 89 | self.current_view.as_mut().unwrap().dimensions(internal_state, max_size) 90 | } 91 | 92 | fn set_top_left_position(&mut self, internal_state: &mut InternalState, position: Vec2) { 93 | if self.current_view.is_none() { 94 | let current_state = internal_state.get_state::(self.composite.id()); 95 | self.current_view = Some(self.composite.view(current_state)); 96 | } 97 | self.current_view.as_mut().unwrap().set_top_left_position(internal_state, position); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /crates/vui/src/ui/widgets/align.rs: -------------------------------------------------------------------------------- 1 | use ::anyhow::Result; 2 | 3 | use crate::{ 4 | builder_field, 5 | graphics::triangles::Frame, 6 | ui::{ 7 | primitives::Dimensions, 8 | widgets::{Element, Widget}, 9 | Input, InternalState, 10 | }, 11 | vec2, Vec2, 12 | }; 13 | 14 | #[derive(Debug, Copy, Clone)] 15 | pub enum HAlignment { 16 | Center, 17 | Left, 18 | Right, 19 | } 20 | 21 | #[derive(Debug, Copy, Clone)] 22 | pub enum VAlignment { 23 | Center, 24 | Bottom, 25 | Top, 26 | } 27 | 28 | #[derive(Debug, Copy, Clone)] 29 | pub struct Align> { 30 | horizontal_alignment: HAlignment, 31 | vertical_alignment: VAlignment, 32 | child: W, 33 | child_offset: Vec2, 34 | _phantom_data: std::marker::PhantomData, 35 | } 36 | 37 | impl> Align { 38 | pub fn new(child: W) -> Self { 39 | Self { 40 | horizontal_alignment: HAlignment::Center, 41 | vertical_alignment: VAlignment::Center, 42 | child, 43 | child_offset: vec2(0.0, 0.0), 44 | _phantom_data: Default::default(), 45 | } 46 | } 47 | 48 | builder_field!(horizontal_alignment, HAlignment); 49 | builder_field!(vertical_alignment, VAlignment); 50 | 51 | pub fn alignment(self, horizontal: HAlignment, vertical: VAlignment) -> Self { 52 | self.horizontal_alignment(horizontal).vertical_alignment(vertical) 53 | } 54 | } 55 | 56 | impl> Widget for Align { 57 | fn handle_event( 58 | &mut self, 59 | internal_state: &mut InternalState, 60 | input: &Input, 61 | event: &winit::event::WindowEvent, 62 | ) -> Result> { 63 | self.child.handle_event(internal_state, input, event) 64 | } 65 | 66 | fn draw_frame(&mut self, internal_state: &mut InternalState, frame: &mut Frame) -> Result<()> { 67 | self.child.draw_frame(internal_state, frame) 68 | } 69 | 70 | fn dimensions( 71 | &mut self, 72 | internal_state: &mut InternalState, 73 | max_size: &Dimensions, 74 | ) -> Dimensions { 75 | let child_dimensions = self.child.dimensions(internal_state, max_size); 76 | let remaining_width = max_size.width - child_dimensions.width; 77 | let remaining_height = max_size.height - child_dimensions.height; 78 | 79 | self.child_offset = vec2( 80 | match self.horizontal_alignment { 81 | HAlignment::Left => 0.0, 82 | HAlignment::Center => 0.5 * remaining_width, 83 | HAlignment::Right => remaining_width, 84 | }, 85 | match self.vertical_alignment { 86 | VAlignment::Top => 0.0, 87 | VAlignment::Center => 0.5 * remaining_height, 88 | VAlignment::Bottom => remaining_height, 89 | }, 90 | ); 91 | self.child_offset.x = self.child_offset.x.round(); 92 | self.child_offset.y = self.child_offset.y.round(); 93 | 94 | *max_size 95 | } 96 | 97 | fn set_top_left_position(&mut self, internal_state: &mut InternalState, position: Vec2) { 98 | self.child.set_top_left_position(internal_state, position + self.child_offset); 99 | } 100 | } 101 | 102 | impl Into> for Align 103 | where 104 | Message: 'static + std::fmt::Debug + Copy + Clone, 105 | W: Widget + 'static, 106 | { 107 | fn into(self) -> Element { 108 | Element::new(self) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /crates/vui/src/ui/widgets/window.rs: -------------------------------------------------------------------------------- 1 | use ::anyhow::Result; 2 | 3 | use crate::{ 4 | gen_id, 5 | ui::{ 6 | id_hash, 7 | primitives::{Justify, SpaceBetween}, 8 | widgets::{ 9 | Col, ComposedMessage, Composite, CompositeWidget, Container, Element, Label, Row, 10 | WithContainer, 11 | }, 12 | Font, Id, 13 | }, 14 | }; 15 | 16 | #[derive(Default, Debug, Copy, Clone, Eq, PartialEq)] 17 | pub enum WindowState { 18 | #[default] 19 | Hidden, 20 | Visible, 21 | } 22 | 23 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 24 | pub enum WindowEvent { 25 | ShowWindow, 26 | HideWindow, 27 | } 28 | 29 | pub struct Window { 30 | id: Id, 31 | font: Font, 32 | title: String, 33 | contents: Option>, 34 | } 35 | 36 | impl Window 37 | where 38 | Message: 'static + std::fmt::Debug + Copy + Clone, 39 | { 40 | pub fn new(font: Font, title: impl Into) -> Self { 41 | let owned_title = title.into(); 42 | Self { id: gen_id!(&owned_title), font, title: owned_title, contents: None } 43 | } 44 | 45 | pub fn contents(self, contents: impl Into>) -> Self { 46 | Self { contents: Some(contents.into()), ..self } 47 | } 48 | } 49 | 50 | impl CompositeWidget for Window 51 | where 52 | Message: 'static + std::fmt::Debug + Copy + Clone, 53 | { 54 | type State = WindowState; 55 | 56 | fn id(&self) -> &Id { 57 | &self.id 58 | } 59 | 60 | fn view(&mut self, state: &Self::State) -> Element> { 61 | match state { 62 | WindowState::Hidden => { 63 | let top_bar = Row::new() 64 | .child(Label::new(&self.font, &self.title), Justify::Center) 65 | .space_between(SpaceBetween::EvenSpaceBetween); 66 | 67 | Col::new().child(top_bar).into() 68 | } 69 | WindowState::Visible => { 70 | let top_bar = Row::new() 71 | .child(Label::new(&self.font, &self.title), Justify::Center) 72 | .space_between(SpaceBetween::EvenSpaceBetween); 73 | 74 | let contents: Element = self.contents.take().unwrap(); 75 | 76 | Col::new().child(top_bar).child(contents).into() 77 | } 78 | } 79 | } 80 | 81 | fn update(&self, state: &mut Self::State, event: WindowEvent) -> Result<()> { 82 | match event { 83 | WindowEvent::HideWindow => { 84 | *state = WindowState::Hidden; 85 | } 86 | WindowEvent::ShowWindow => { 87 | *state = WindowState::Visible; 88 | } 89 | } 90 | Ok(()) 91 | } 92 | } 93 | 94 | impl WithContainer> for Window 95 | where 96 | Message: 'static + std::fmt::Debug + Copy + Clone, 97 | { 98 | fn container(self) -> Container> { 99 | let result: Element = self.into(); 100 | result.container() 101 | } 102 | } 103 | 104 | impl Into> for Window 105 | where 106 | Message: 'static + std::fmt::Debug + Copy + Clone, 107 | { 108 | fn into(self) -> Element { 109 | Element::new(Composite::new(self)) 110 | } 111 | } 112 | 113 | impl Into> for Composite> 114 | where 115 | Message: 'static + std::fmt::Debug + Copy + Clone, 116 | { 117 | fn into(self) -> Element { 118 | Element::new(self) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /crates/vui/src/vulkan/buffer/buffer.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use ::ash::vk; 4 | 5 | use crate::{ 6 | errors::BufferError, 7 | vulkan::{ 8 | allocator::{Allocation, MemoryAllocator}, 9 | render_device::RenderDevice, 10 | }, 11 | }; 12 | 13 | #[derive(Clone)] 14 | pub struct Buffer { 15 | pub raw: vk::Buffer, 16 | 17 | pub allocation: Allocation, 18 | 19 | pub mapped_ptr: Option<*mut std::ffi::c_void>, 20 | 21 | pub vk_alloc: Arc, 22 | 23 | pub vk_dev: Arc, 24 | } 25 | 26 | impl Buffer { 27 | pub fn new( 28 | vk_dev: Arc, 29 | vk_alloc: Arc, 30 | buffer_usage_flags: vk::BufferUsageFlags, 31 | memory_property_flags: vk::MemoryPropertyFlags, 32 | size_in_bytes: u64, 33 | ) -> Result { 34 | let create_info = vk::BufferCreateInfo { 35 | size: size_in_bytes, 36 | usage: buffer_usage_flags, 37 | sharing_mode: vk::SharingMode::EXCLUSIVE, 38 | ..Default::default() 39 | }; 40 | let buffer_handle = unsafe { 41 | vk_dev.logical_device.create_buffer(&create_info, None).map_err(|err| { 42 | BufferError::UnableToCreateBuffer { 43 | size: size_in_bytes, 44 | usage: buffer_usage_flags, 45 | source: err, 46 | } 47 | })? 48 | }; 49 | let allocation = unsafe { 50 | let buffer_memory_requirements = 51 | vk_dev.logical_device.get_buffer_memory_requirements(buffer_handle); 52 | vk_alloc.allocate_memory(buffer_memory_requirements, memory_property_flags)? 53 | }; 54 | unsafe { 55 | vk_dev 56 | .logical_device 57 | .bind_buffer_memory(buffer_handle, allocation.memory, allocation.offset) 58 | .map_err(BufferError::UnableToBindDeviceMemory)?; 59 | } 60 | 61 | Ok(Self { raw: buffer_handle, allocation, mapped_ptr: None, vk_alloc, vk_dev }) 62 | } 63 | 64 | pub fn map(&mut self) -> Result<(), BufferError> { 65 | let ptr = unsafe { 66 | self.vk_dev 67 | .logical_device 68 | .map_memory( 69 | self.allocation.memory, 70 | self.allocation.offset, 71 | self.allocation.byte_size, 72 | vk::MemoryMapFlags::empty(), 73 | ) 74 | .map_err(BufferError::UnableToMapDeviceMemory)? 75 | }; 76 | self.mapped_ptr = Some(ptr); 77 | Ok(()) 78 | } 79 | 80 | pub fn unmap(&mut self) { 81 | unsafe { 82 | self.vk_dev.logical_device.unmap_memory(self.allocation.memory); 83 | } 84 | self.mapped_ptr = None; 85 | } 86 | 87 | pub fn data<'element, Element: 'element + Copy>( 88 | &self, 89 | ) -> Result<&'element [Element], BufferError> { 90 | let ptr = self.mapped_ptr.ok_or(BufferError::NoMappedPointerFound)?; 91 | let elements = (self.allocation.byte_size as usize) / std::mem::size_of::(); 92 | let data = unsafe { std::slice::from_raw_parts(ptr as *const Element, elements) }; 93 | Ok(data) 94 | } 95 | 96 | pub fn data_mut<'element, Element: 'element + Copy>( 97 | &self, 98 | ) -> Result<&'element mut [Element], BufferError> { 99 | let ptr = self.mapped_ptr.ok_or(BufferError::NoMappedPointerFound)?; 100 | let elements = (self.allocation.byte_size as usize) / std::mem::size_of::(); 101 | let data = unsafe { std::slice::from_raw_parts_mut(ptr as *mut Element, elements) }; 102 | Ok(data) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /crates/vui/src/graphics/triangles/mod.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use ::ash::vk; 4 | 5 | pub use self::frame::Frame; 6 | pub use crate::errors::ImmediateModeGraphicsError; 7 | use crate::{ 8 | asset_loader::CombinedImageSampler, 9 | errors::VulkanError, 10 | msaa::MSAARenderPass, 11 | vulkan::{ 12 | allocator::MemoryAllocator, command_buffer::CommandBuffer, pipeline::Pipeline, 13 | render_device::RenderDevice, 14 | }, 15 | }; 16 | 17 | mod frame; 18 | mod pipeline; 19 | 20 | pub struct Triangles { 21 | textures: Vec, 22 | 23 | pipeline: Pipeline, 24 | 25 | frames: Vec>, 26 | 27 | vk_alloc: Arc, 28 | 29 | vk_dev: Arc, 30 | } 31 | 32 | impl Triangles { 33 | pub fn new( 34 | msaa_renderpass: &MSAARenderPass, 35 | textures: &[CombinedImageSampler], 36 | vk_alloc: Arc, 37 | vk_dev: Arc, 38 | ) -> Result { 39 | let pipeline = pipeline::create_pipeline( 40 | msaa_renderpass, 41 | textures.len() as u32, 42 | false, 43 | vk_dev.clone(), 44 | )?; 45 | let frames = { 46 | let mut frames = vec![]; 47 | for _ in 0 .. vk_dev.swapchain_image_count() { 48 | let frame = Frame::new( 49 | vk_dev.clone(), 50 | vk_alloc.clone(), 51 | textures, 52 | &pipeline.pipeline_layout.descriptor_layouts[0], 53 | )?; 54 | frames.push(Some(frame)); 55 | } 56 | frames 57 | }; 58 | Ok(Self { textures: textures.to_owned(), pipeline, frames, vk_alloc, vk_dev }) 59 | } 60 | 61 | pub fn rebuild_swapchain_resources( 62 | &mut self, 63 | msaa_renderpass: &MSAARenderPass, 64 | ) -> Result<(), VulkanError> { 65 | self.pipeline = pipeline::create_pipeline( 66 | msaa_renderpass, 67 | self.textures.len() as u32, 68 | false, 69 | self.vk_dev.clone(), 70 | )?; 71 | self.frames = { 72 | let mut frames = vec![]; 73 | for _ in 0 .. self.vk_dev.swapchain_image_count() { 74 | let frame = Frame::new( 75 | self.vk_dev.clone(), 76 | self.vk_alloc.clone(), 77 | &self.textures, 78 | &self.pipeline.pipeline_layout.descriptor_layouts[0], 79 | )?; 80 | frames.push(Some(frame)); 81 | } 82 | frames 83 | }; 84 | Ok(()) 85 | } 86 | 87 | pub fn acquire_frame( 88 | &mut self, 89 | swapchain_image_index: usize, 90 | ) -> Result { 91 | let mut frame = self.frames[swapchain_image_index] 92 | .take() 93 | .ok_or(ImmediateModeGraphicsError::FrameResourcesUnavailable(swapchain_image_index))?; 94 | frame.clear(); 95 | Ok(frame) 96 | } 97 | 98 | /// # Safety 99 | /// 100 | /// The caller must ensure that the command buffer is in the recording 101 | /// state. 102 | pub unsafe fn complete_frame( 103 | &mut self, 104 | cmd: &CommandBuffer, 105 | mut frame: Frame, 106 | swapchain_image_index: usize, 107 | ) -> Result<(), VulkanError> { 108 | self.vk_dev.logical_device.cmd_bind_pipeline( 109 | cmd.raw, 110 | vk::PipelineBindPoint::GRAPHICS, 111 | self.pipeline.raw, 112 | ); 113 | frame.write_frame_commands(cmd, &self.pipeline.pipeline_layout); 114 | self.frames[swapchain_image_index] = Some(frame); 115 | Ok(()) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /crates/vui/src/graphics/rectangle.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | graphics::{triangles::Frame, Vertex}, 3 | ui::{ 4 | color::Color, 5 | widgets::{CompositeStyle, Drawable, FillStyle}, 6 | }, 7 | vec2, vec3, Vec2, 8 | }; 9 | 10 | pub struct Rectangle { 11 | pub width: f32, 12 | pub height: f32, 13 | pub position: Vec2, 14 | pub depth: f32, 15 | pub style: CompositeStyle, 16 | } 17 | 18 | impl Rectangle { 19 | /// Creates a new rectangle with the given width, height, position, and 20 | /// depth. 21 | /// 22 | /// Args: 23 | /// - width: f32 24 | /// - height: f32 25 | /// - position: Vec2 26 | /// - depth: f32 27 | /// - style: CompositeStyle 28 | /// 29 | /// Returns: 30 | /// - A new rectangle with the given width, height, position, and depth. 31 | pub fn new(width: f32, height: f32, position: Vec2, depth: f32, style: CompositeStyle) -> Self { 32 | Self { width, height, position, depth, style } 33 | } 34 | 35 | /// Draws the rectangle to the frame while applying the style. 36 | pub fn draw(&self, frame: &mut Frame) -> anyhow::Result<()> { 37 | let hw = 0.5 * self.width; 38 | let hh = 0.5 * self.height; 39 | let depth = self.depth; 40 | 41 | let mut drawable = DrawableRect { 42 | vertices: vec![ 43 | Vertex::new( 44 | vec3(self.position.x - hw, self.position.y - hh, depth), 45 | Color::new(0.0, 0.0, 0.0, 0.0), 46 | vec2(0.0, 0.0), 47 | 0, 48 | ), 49 | Vertex::new( 50 | vec3(self.position.x + hw, self.position.y - hh, depth), 51 | Color::new(0.0, 0.0, 0.0, 0.0), 52 | vec2(1.0, 0.0), 53 | 0, 54 | ), 55 | Vertex::new( 56 | vec3(self.position.x + hw, self.position.y + hh, depth), 57 | Color::new(0.0, 0.0, 0.0, 0.0), 58 | vec2(1.0, 1.0), 59 | 0, 60 | ), 61 | Vertex::new( 62 | vec3(self.position.x - hw, self.position.y + hh, depth), 63 | Color::new(0.0, 0.0, 0.0, 0.0), 64 | vec2(0.0, 1.0), 65 | 0, 66 | ), 67 | ], 68 | indices: vec![0, 1, 2, 0, 2, 3], 69 | }; 70 | 71 | drawable.color(self.style.background); 72 | 73 | for vertex in drawable.vertices { 74 | frame.push_vertex(vertex)?; 75 | } 76 | frame.push_indices(&drawable.indices)?; 77 | 78 | Ok(()) 79 | } 80 | } 81 | 82 | struct DrawableRect { 83 | vertices: Vec, 84 | indices: Vec, 85 | } 86 | 87 | impl Drawable for DrawableRect { 88 | fn color(&mut self, color: FillStyle) { 89 | match color { 90 | FillStyle::Color(color) => { 91 | for vertex in &mut self.vertices { 92 | vertex.rgba = [color.r, color.g, color.b, color.a]; 93 | } 94 | } 95 | FillStyle::Gradient(gradient) => { 96 | let num_vertices = self.vertices.len(); 97 | for (i, vertex) in self.vertices.iter_mut().enumerate() { 98 | let t = (i as f32) / ((num_vertices - 1) as f32); 99 | vertex.rgba = [ 100 | gradient.start.r * (1.0 - t) + gradient.end.r * t, 101 | gradient.start.g * (1.0 - t) + gradient.end.g * t, 102 | gradient.start.b * (1.0 - t) + gradient.end.b * t, 103 | gradient.start.a * (1.0 - t) + gradient.end.a * t, 104 | ]; 105 | } 106 | } 107 | FillStyle::None => (), 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /crates/vui/src/ui/ui.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use ::anyhow::Result; 4 | 5 | use crate::{ 6 | graphics::triangles::Frame, 7 | ui::{ 8 | primitives::{Dimensions, Rect}, 9 | ui_screen_space_projection, 10 | widgets::{Element, Widget}, 11 | Input, InternalState, 12 | }, 13 | vec2, Mat4, 14 | }; 15 | 16 | pub trait UIState { 17 | type Message: 'static; 18 | 19 | fn view(&mut self) -> Element; 20 | 21 | fn update(&mut self, message: &Self::Message); 22 | } 23 | 24 | pub struct UI { 25 | viewport: Rect, 26 | projection: Mat4, 27 | custom: C, 28 | current_view: Element, 29 | internal_state: InternalState, 30 | input: Input, 31 | 32 | fps: f32, 33 | frame_times: [Duration; 100], 34 | frame_time_index: usize, 35 | last_fps_draw_time: std::time::Instant, 36 | } 37 | 38 | impl UI { 39 | pub fn new(viewport: Dimensions, mut custom_ui: C) -> Self { 40 | let mut ui = Self { 41 | viewport: Rect::new(0.0, 0.0, viewport.height, viewport.width), 42 | projection: ui_screen_space_projection(viewport), 43 | current_view: custom_ui.view(), 44 | custom: custom_ui, 45 | internal_state: InternalState::new(), 46 | input: Input::new(), 47 | fps: 0.0, 48 | frame_times: [Duration::new(0, 0); 100], 49 | frame_time_index: 0, 50 | last_fps_draw_time: std::time::Instant::now(), 51 | }; 52 | ui.layout(); 53 | ui 54 | } 55 | 56 | pub fn handle_event(&mut self, event: &winit::event::WindowEvent) -> Result> 57 | where 58 | C::Message: 'static, 59 | { 60 | use winit::event::WindowEvent; 61 | 62 | self.input.handle_event(event); 63 | if let WindowEvent::Resized(size) = *event { 64 | self.viewport = Rect::new(0.0, 0.0, size.height as f32, size.width as f32); 65 | self.projection = 66 | ui_screen_space_projection(Dimensions::new(size.width as f32, size.height as f32)); 67 | } 68 | 69 | let message_opt = 70 | self.current_view.handle_event(&mut self.internal_state, &self.input, event)?; 71 | 72 | if message_opt.is_some() { 73 | self.flush(); 74 | } else { 75 | self.layout(); 76 | } 77 | 78 | Ok(message_opt) 79 | } 80 | 81 | pub fn state(&self) -> &C { 82 | &self.custom 83 | } 84 | 85 | pub fn state_mut(&mut self) -> &mut C { 86 | &mut self.custom 87 | } 88 | 89 | pub fn draw_frame(&mut self, frame: &mut Frame) -> Result<()> { 90 | self.flush(); 91 | 92 | let frame_start = std::time::Instant::now(); 93 | 94 | let _ = frame.set_view_projection(self.projection); 95 | self.current_view.draw_frame(&mut self.internal_state, frame)?; 96 | 97 | let frame_time = frame_start.elapsed(); 98 | self.frame_times[self.frame_time_index] = frame_time; 99 | self.frame_time_index = (self.frame_time_index + 1) % self.frame_times.len(); 100 | 101 | let elapsed_time = frame_start - self.last_fps_draw_time; 102 | if elapsed_time >= Duration::from_secs(1) { 103 | let total_frame_time: Duration = self.frame_times.iter().sum(); 104 | let average_frame_time = total_frame_time / (self.frame_times.len() as u32); 105 | 106 | if average_frame_time.as_secs_f32() > 0.0 { 107 | self.fps = 1.0 / average_frame_time.as_secs_f32(); 108 | } 109 | 110 | self.last_fps_draw_time = frame_start; 111 | } 112 | 113 | Ok(()) 114 | } 115 | } 116 | 117 | impl UI { 118 | fn flush(&mut self) { 119 | self.current_view = self.custom.view(); 120 | self.layout(); 121 | } 122 | 123 | fn layout(&mut self) { 124 | let _root_widget_dimensions = 125 | self.current_view.dimensions(&mut self.internal_state, &self.viewport.dimensions()); 126 | self.current_view.set_top_left_position(&mut self.internal_state, vec2(0.0, 0.0)); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /crates/vui/src/vulkan/descriptor_set/descriptor_pool.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::c_void, sync::Arc}; 2 | 3 | use ash::vk; 4 | 5 | use crate::{ 6 | errors::DescriptorSetError, 7 | vulkan::{ 8 | descriptor_set::{DescriptorSet, DescriptorSetLayout}, 9 | render_device::RenderDevice, 10 | }, 11 | }; 12 | 13 | pub struct DescriptorPool { 14 | pub raw: vk::DescriptorPool, 15 | 16 | pub vk_dev: Arc, 17 | } 18 | 19 | impl DescriptorPool { 20 | pub fn new( 21 | vk_dev: Arc, 22 | descriptor_count: u32, 23 | sizes: &[vk::DescriptorPoolSize], 24 | ) -> Result { 25 | let create_info = vk::DescriptorPoolCreateInfo { 26 | flags: vk::DescriptorPoolCreateFlags::empty(), 27 | max_sets: descriptor_count, 28 | pool_size_count: sizes.len() as u32, 29 | p_pool_sizes: sizes.as_ptr(), 30 | ..Default::default() 31 | }; 32 | let raw = unsafe { 33 | vk_dev 34 | .logical_device 35 | .create_descriptor_pool(&create_info, None) 36 | .map_err(DescriptorSetError::UnableToCreatePool)? 37 | }; 38 | Ok(Self { raw, vk_dev }) 39 | } 40 | 41 | pub fn allocate( 42 | &self, 43 | layout: &DescriptorSetLayout, 44 | count: u32, 45 | ) -> Result, DescriptorSetError> { 46 | let mut layouts = vec![]; 47 | for _ in 0 .. count { 48 | layouts.push(layout.raw); 49 | } 50 | let allocate_info = vk::DescriptorSetAllocateInfo { 51 | descriptor_pool: self.raw, 52 | descriptor_set_count: layouts.len() as u32, 53 | p_set_layouts: layouts.as_ptr(), 54 | ..Default::default() 55 | }; 56 | let raw_sets = unsafe { 57 | self.vk_dev 58 | .logical_device 59 | .allocate_descriptor_sets(&allocate_info) 60 | .map_err(DescriptorSetError::UnableToAllocateDescriptors)? 61 | }; 62 | let descriptor_sets: Vec = raw_sets 63 | .into_iter() 64 | .map(|raw| DescriptorSet { raw, vk_dev: self.vk_dev.clone() }) 65 | .collect(); 66 | Ok(descriptor_sets) 67 | } 68 | 69 | pub fn allocate_with_variable_counts( 70 | &self, 71 | layout: &DescriptorSetLayout, 72 | descriptor_set_count: u32, 73 | variable_binding_count: u32, 74 | ) -> Result, DescriptorSetError> { 75 | let mut descriptor_set_counts = vec![]; 76 | let mut layouts = vec![]; 77 | for _ in 0 .. descriptor_set_count { 78 | layouts.push(layout.raw); 79 | descriptor_set_counts.push(variable_binding_count); 80 | } 81 | let variable_descriptor_alloc_info = vk::DescriptorSetVariableDescriptorCountAllocateInfo { 82 | descriptor_set_count: layouts.len() as u32, 83 | p_descriptor_counts: descriptor_set_counts.as_ptr(), 84 | ..Default::default() 85 | }; 86 | let allocate_info = vk::DescriptorSetAllocateInfo { 87 | p_next: &variable_descriptor_alloc_info 88 | as *const vk::DescriptorSetVariableDescriptorCountAllocateInfo 89 | as *const c_void, 90 | descriptor_pool: self.raw, 91 | descriptor_set_count: layouts.len() as u32, 92 | p_set_layouts: layouts.as_ptr(), 93 | ..Default::default() 94 | }; 95 | let raw_sets = unsafe { 96 | self.vk_dev 97 | .logical_device 98 | .allocate_descriptor_sets(&allocate_info) 99 | .map_err(DescriptorSetError::UnableToAllocateDescriptors)? 100 | }; 101 | let descriptor_sets: Vec = raw_sets 102 | .into_iter() 103 | .map(|raw| DescriptorSet { raw, vk_dev: self.vk_dev.clone() }) 104 | .collect(); 105 | Ok(descriptor_sets) 106 | } 107 | } 108 | 109 | impl Drop for DescriptorPool { 110 | fn drop(&mut self) { 111 | unsafe { 112 | self.vk_dev.logical_device.destroy_descriptor_pool(self.raw, None); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /crates/vui/src/ui/color/mod.rs: -------------------------------------------------------------------------------- 1 | mod gradient; 2 | mod style; 3 | 4 | pub use gradient::Gradient; 5 | pub use style::Style; 6 | 7 | /// A color with red, green, blue and alpha components. 8 | /// The components are in the range [0, 1]. 9 | #[derive(Clone, Copy, PartialEq, Default)] 10 | pub struct Color { 11 | pub r: f32, 12 | pub g: f32, 13 | pub b: f32, 14 | pub a: f32, 15 | } 16 | 17 | impl Color { 18 | /// Create a new color with the given red, green, blue and alpha components. 19 | pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self { 20 | Color { r, g, b, a } 21 | } 22 | 23 | /// Linearly interpolate between two colors. 24 | pub fn lerp(self, other: Self, t: f32) -> Self { 25 | let inv_t = 1.0 - t; 26 | Color::new( 27 | self.r * inv_t + other.r * t, 28 | self.g * inv_t + other.g * t, 29 | self.b * inv_t + other.b * t, 30 | self.a * inv_t + other.a * t, 31 | ) 32 | } 33 | 34 | /// Blend two colors together. 35 | pub fn blend(&self, other: Color) -> Self { 36 | match other.a { 37 | a if a >= 1.0 => other, 38 | a if a <= 0.0 => *self, 39 | a => Color { 40 | r: self.r * (1.0 - a) + other.r * a, 41 | g: self.g * (1.0 - a) + other.g * a, 42 | b: self.b * (1.0 - a) + other.b * a, 43 | a: self.a, 44 | }, 45 | } 46 | } 47 | } 48 | 49 | /// Convert a tuple of four floats to a Color struct. 50 | impl From<(f32, f32, f32, f32)> for Color { 51 | fn from(tuple: (f32, f32, f32, f32)) -> Self { 52 | Color::new(tuple.0, tuple.1, tuple.2, tuple.3) 53 | } 54 | } 55 | 56 | /// Convert a hexadecimal color value to a Color struct. 57 | impl From for Color { 58 | fn from(hex: u32) -> Self { 59 | let r = (((hex >> 24) & 0xff) as f32) / 255.0; 60 | let g = (((hex >> 16) & 0xff) as f32) / 255.0; 61 | let b = (((hex >> 8) & 0xff) as f32) / 255.0; 62 | let a = ((hex & 0xff) as f32) / 255.0; 63 | Color { r, g, b, a } 64 | } 65 | } 66 | 67 | /// Convert a Color struct to a hexadecimal color value. 68 | impl From for u32 { 69 | fn from(color: Color) -> Self { 70 | let r = (color.r * 255.0) as u32; 71 | let g = (color.g * 255.0) as u32; 72 | let b = (color.b * 255.0) as u32; 73 | let a = (color.a * 255.0) as u32; 74 | (r << 24) | (g << 16) | (b << 8) | a 75 | } 76 | } 77 | 78 | impl std::fmt::Debug for Color { 79 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 80 | write!(f, "rgba({:#010x})", u32::from(*self)) 81 | } 82 | } 83 | 84 | #[cfg(test)] 85 | mod tests { 86 | use super::Color; 87 | 88 | #[test] 89 | fn test_color_new() { 90 | let color = Color::new(0.1, 0.2, 0.3, 0.4); 91 | assert_eq!(color.r, 0.1); 92 | assert_eq!(color.g, 0.2); 93 | assert_eq!(color.b, 0.3); 94 | assert_eq!(color.a, 0.4); 95 | } 96 | 97 | #[test] 98 | fn test_color_lerp() { 99 | let color1 = Color::new(0.0, 0.0, 0.0, 0.0); 100 | let color2 = Color::new(1.0, 1.0, 1.0, 1.0); 101 | let result = color1.lerp(color2, 0.5); 102 | assert_eq!(result, Color::new(0.5, 0.5, 0.5, 0.5)); 103 | } 104 | 105 | #[test] 106 | fn test_color_blend() { 107 | let color1 = Color::new(0.0, 0.0, 0.0, 1.0); 108 | let color2 = Color::new(1.0, 1.0, 1.0, 0.5); 109 | let result = color1.blend(color2); 110 | assert_eq!(result, Color::new(0.5, 0.5, 0.5, 1.0)); 111 | } 112 | 113 | #[test] 114 | fn test_color_from_tuple() { 115 | let color: Color = (0.1, 0.2, 0.3, 0.4).into(); 116 | assert_eq!(color, Color::new(0.1, 0.2, 0.3, 0.4)); 117 | } 118 | 119 | #[test] 120 | fn test_color_from_u32() { 121 | let color: Color = (0xff00ff00).into(); 122 | assert_eq!(color, Color::new(1.0, 0.0, 1.0, 0.0)); 123 | } 124 | 125 | #[test] 126 | fn test_color_into_u32() { 127 | let color = Color::new(1.0, 0.0, 1.0, 0.0); 128 | let hex: u32 = color.into(); 129 | assert_eq!(hex, 0xff00ff00); 130 | } 131 | 132 | #[test] 133 | fn test_color_debug() { 134 | let color = Color::new(1.0, 0.0, 1.0, 0.0); 135 | assert_eq!(format!("{:?}", color), "rgba(0xff00ff00)"); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /crates/vui/src/ui/primitives/dimension_list/mod.rs: -------------------------------------------------------------------------------- 1 | mod axis; 2 | 3 | pub use self::axis::Axis; 4 | use crate::{builder_field, ui::primitives::Dimensions, vec2, Vec2}; 5 | 6 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 7 | pub enum Justify { 8 | Begin, 9 | Center, 10 | End, 11 | } 12 | 13 | #[derive(Debug, Copy, Clone, PartialEq)] 14 | pub enum SpaceBetween { 15 | Fixed(f32), 16 | 17 | EvenSpaceBetween, 18 | 19 | EvenSpaceAround, 20 | } 21 | 22 | pub struct DimensionList { 23 | main_axis: Axis, 24 | off_axis: Axis, 25 | children: Vec<(Dimensions, Justify)>, 26 | total_children_size: Dimensions, 27 | max_size: Dimensions, 28 | space_between: SpaceBetween, 29 | } 30 | 31 | impl DimensionList { 32 | pub fn new(main_axis: Axis, off_axis: Axis) -> Self { 33 | Self { 34 | main_axis, 35 | off_axis, 36 | total_children_size: Dimensions::new(0.0, 0.0), 37 | max_size: Dimensions::new(0.0, 0.0), 38 | children: Vec::new(), 39 | space_between: SpaceBetween::Fixed(0.0), 40 | } 41 | } 42 | 43 | pub fn horizontal() -> Self { 44 | Self::new(Axis::Horizontal, Axis::Vertical) 45 | } 46 | 47 | pub fn vertical() -> Self { 48 | Self::new(Axis::Vertical, Axis::Horizontal) 49 | } 50 | 51 | builder_field!(space_between, SpaceBetween); 52 | 53 | pub fn set_max_size(&mut self, max_size: &Dimensions) { 54 | self.max_size = *max_size; 55 | } 56 | 57 | pub fn dimensions(&self) -> Dimensions { 58 | match self.space_between { 59 | SpaceBetween::Fixed(_) => self.total_children_size, 60 | _ => self.off_axis.min(&self.max_size, &self.total_children_size), 61 | } 62 | } 63 | 64 | pub fn add_child_dimensions( 65 | &mut self, 66 | child_dimensions: Dimensions, 67 | justify: Justify, 68 | ) -> Dimensions { 69 | self.children.push((child_dimensions, justify)); 70 | 71 | self.total_children_size = self.main_axis.sum(&self.total_children_size, &child_dimensions); 72 | 73 | if let SpaceBetween::Fixed(size) = self.space_between { 74 | if self.children.len() > 1 { 75 | self.total_children_size = 76 | self.main_axis.add_scalar(&self.total_children_size, size); 77 | } 78 | } 79 | 80 | self.total_children_size = self.off_axis.max(&self.total_children_size, &child_dimensions); 81 | 82 | self.main_axis.sub(&self.max_size, &self.total_children_size) 83 | } 84 | 85 | pub fn compute_child_positions(&self) -> Vec { 86 | let main_axis_remaining_size = 87 | self.main_axis.get(&self.max_size) - self.main_axis.get(&self.total_children_size); 88 | let main_axis_offset = match self.space_between { 89 | SpaceBetween::Fixed(size) => self.main_axis.vec2(size), 90 | SpaceBetween::EvenSpaceBetween => { 91 | let space_count = (self.children.len() - 1).max(1) as f32; 92 | let offset = main_axis_remaining_size / space_count; 93 | self.main_axis.vec2(offset) 94 | } 95 | SpaceBetween::EvenSpaceAround => { 96 | let space_count = (self.children.len() + 1) as f32; 97 | let offset = main_axis_remaining_size / space_count; 98 | self.main_axis.vec2(offset) 99 | } 100 | }; 101 | 102 | let mut position = match self.space_between { 103 | SpaceBetween::EvenSpaceAround => main_axis_offset, 104 | _ => vec2(0.0, 0.0), 105 | }; 106 | 107 | let mut child_positions = Vec::with_capacity(self.children.len()); 108 | for (child, justify) in &self.children { 109 | let off_axis_remaining_size = 110 | self.off_axis.get(&self.total_children_size) - self.off_axis.get(child); 111 | let off_axis_offset = match *justify { 112 | Justify::Begin => self.off_axis.vec2(0.0), 113 | Justify::End => self.off_axis.vec2(off_axis_remaining_size), 114 | Justify::Center => self.off_axis.vec2(0.5 * off_axis_remaining_size), 115 | }; 116 | 117 | child_positions.push(position + off_axis_offset); 118 | 119 | position += main_axis_offset + self.main_axis.vec2(self.main_axis.get(child)); 120 | } 121 | 122 | child_positions 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /crates/vui/src/ui/primitives/rect.rs: -------------------------------------------------------------------------------- 1 | use crate::{ui::primitives::Dimensions, vec2, Vec2}; 2 | 3 | #[derive(Debug, Copy, Clone, PartialEq)] 4 | pub struct Rect { 5 | pub top_left: Vec2, 6 | pub bottom_right: Vec2, 7 | } 8 | 9 | impl Rect { 10 | pub fn new(top: f32, left: f32, bottom: f32, right: f32) -> Self { 11 | Self { top_left: vec2(left, top), bottom_right: vec2(right, bottom) } 12 | } 13 | 14 | pub fn centered_at(x: f32, y: f32, width: f32, height: f32) -> Self { 15 | let half_height = 0.5 * height; 16 | let half_width = 0.5 * width; 17 | Self::new(y - half_height, x - half_width, y + half_height, x + half_width) 18 | } 19 | 20 | #[inline] 21 | pub fn left(&self) -> f32 { 22 | self.top_left.x 23 | } 24 | 25 | #[inline] 26 | pub fn right(&self) -> f32 { 27 | self.bottom_right.x 28 | } 29 | 30 | #[inline] 31 | pub fn top(&self) -> f32 { 32 | self.top_left.y 33 | } 34 | 35 | #[inline] 36 | pub fn bottom(&self) -> f32 { 37 | self.bottom_right.y 38 | } 39 | 40 | pub fn width(&self) -> f32 { 41 | (self.left() - self.right()).abs() 42 | } 43 | 44 | pub fn height(&self) -> f32 { 45 | (self.top() - self.bottom()).abs() 46 | } 47 | 48 | pub fn dimensions(&self) -> Dimensions { 49 | (self.width(), self.height()).into() 50 | } 51 | 52 | pub fn translate(&self, offset: Vec2) -> Self { 53 | Self { top_left: self.top_left + offset, bottom_right: self.bottom_right + offset } 54 | } 55 | 56 | pub fn set_top_left_position(&self, position: Vec2) -> Self { 57 | let offset = position - self.top_left; 58 | self.translate(offset) 59 | } 60 | 61 | pub fn top_left(&self) -> Vec2 { 62 | self.top_left 63 | } 64 | 65 | pub fn contains(&self, point: Vec2) -> bool { 66 | let horizontal = self.left() <= point.x && point.x <= self.right(); 67 | let vertical = self.top() <= point.y && point.y <= self.bottom(); 68 | horizontal && vertical 69 | } 70 | 71 | pub fn expand(&self, other: Rect) -> Self { 72 | Self { 73 | top_left: vec2(self.left().min(other.left()), self.top().min(other.top())), 74 | bottom_right: vec2(self.right().max(other.right()), self.bottom().max(other.bottom())), 75 | } 76 | } 77 | } 78 | 79 | #[cfg(test)] 80 | mod test { 81 | use super::*; 82 | 83 | #[test] 84 | fn test_new() { 85 | let rect = Rect::new(10.0, -9.0, -10.0, 9.0); 86 | 87 | assert_eq!(rect.left(), rect.top_left.x); 88 | assert_eq!(rect.right(), rect.bottom_right.x); 89 | assert_eq!(rect.top(), rect.top_left.y); 90 | assert_eq!(rect.bottom(), rect.bottom_right.y); 91 | } 92 | 93 | #[test] 94 | fn test_width_and_height() { 95 | let rect = Rect::new(10.0, -9.0, -10.0, 9.0); 96 | assert_eq!(rect.width(), 18.0); 97 | assert_eq!(rect.height(), 20.0); 98 | } 99 | 100 | #[test] 101 | fn test_width_and_height_abs() { 102 | let rect = Rect::new(-10.0, 9.0, 10.0, -9.0); 103 | assert_eq!(rect.width(), 18.0); 104 | assert_eq!(rect.height(), 20.0); 105 | } 106 | 107 | #[test] 108 | fn test_translate() { 109 | let rect = Rect::new(10.0, -9.0, -10.0, 9.0).translate(vec2(9.0, 10.0)); 110 | 111 | assert_eq!(rect.left(), 0.0); 112 | assert_eq!(rect.right(), 18.0); 113 | assert_eq!(rect.top(), 20.0); 114 | assert_eq!(rect.bottom(), 0.0); 115 | 116 | assert_eq!(rect.width(), 18.0); 117 | assert_eq!(rect.height(), 20.0); 118 | } 119 | 120 | #[test] 121 | fn test_contains() { 122 | let rect = Rect::centered_at(0.0, 0.0, 10.0, 10.0); 123 | 124 | assert!(rect.contains(vec2(0.0, 0.0))); 125 | 126 | assert!(rect.contains(vec2(-5.0, -5.0)), "msg"); 127 | assert!(rect.contains(vec2(5.0, -5.0))); 128 | assert!(rect.contains(vec2(-5.0, 5.0))); 129 | assert!(rect.contains(vec2(5.0, 5.0))); 130 | 131 | assert!(!rect.contains(vec2(-6.0, 0.0))); 132 | assert!(!rect.contains(vec2(6.0, 0.0))); 133 | assert!(!rect.contains(vec2(0.0, 6.0))); 134 | assert!(!rect.contains(vec2(0.0, -6.0))); 135 | } 136 | 137 | #[test] 138 | fn test_expand() { 139 | let rect = Rect::new(0.0, 0.0, 10.0, 10.0); 140 | let other = Rect::new(20.0, 2.0, 23.0, 5.0); 141 | let expanded = rect.expand(other); 142 | 143 | assert_eq!(expanded.top(), 0.0); 144 | assert_eq!(expanded.left(), 0.0); 145 | assert_eq!(expanded.right(), 10.0); 146 | assert_eq!(expanded.bottom(), 23.0); 147 | } 148 | } 149 | --------------------------------------------------------------------------------