Added vert and shader, created Vulkan chain, started work on async timestep
This commit is contained in:
parent
5f84d78add
commit
591f81b514
272
Cargo.lock
generated
272
Cargo.lock
generated
@ -18,6 +18,15 @@ version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2187590a23ab1e3df8681afdf0987c48504d80291f002fcdb651f0ef5e25169"
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.24.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.1"
|
||||
@ -91,6 +100,21 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
||||
|
||||
[[package]]
|
||||
name = "approx"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.9"
|
||||
@ -139,6 +163,21 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
@ -545,6 +584,12 @@ dependencies = [
|
||||
"wasi 0.14.2+wasi-0.2.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.31.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.6.0"
|
||||
@ -596,6 +641,17 @@ dependencies = [
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-uring"
|
||||
version = "0.7.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
@ -724,6 +780,16 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matrixmultiply"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"rawpointer",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.5"
|
||||
@ -785,6 +851,44 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nalgebra"
|
||||
version = "0.33.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26aecdf64b707efd1310e3544d709c5c0ac61c13756046aaaba41be5c4f66a3b"
|
||||
dependencies = [
|
||||
"approx",
|
||||
"matrixmultiply",
|
||||
"nalgebra-macros",
|
||||
"num-complex",
|
||||
"num-rational",
|
||||
"num-traits",
|
||||
"simba",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nalgebra-macros"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "254a5372af8fc138e36684761d3c0cdb758a4410e938babcff1c860ce14ddbfc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ndk"
|
||||
version = "0.7.0"
|
||||
@ -873,6 +977,54 @@ dependencies = [
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
|
||||
dependencies = [
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-complex"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.5.11"
|
||||
@ -1230,6 +1382,15 @@ dependencies = [
|
||||
"objc2-foundation 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.36.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
@ -1277,6 +1438,12 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.1"
|
||||
@ -1419,6 +1586,12 @@ dependencies = [
|
||||
"objc2-quartz-core 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rawpointer"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.3.5"
|
||||
@ -1484,6 +1657,12 @@ dependencies = [
|
||||
"xmlparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.44"
|
||||
@ -1522,6 +1701,15 @@ version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "safe_arch"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
@ -1535,7 +1723,10 @@ dependencies = [
|
||||
name = "sapphirePhysSim"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytemuck",
|
||||
"nalgebra",
|
||||
"tokio",
|
||||
"vulkano 0.35.1",
|
||||
"vulkano-shaders",
|
||||
"vulkano-win",
|
||||
@ -1639,6 +1830,28 @@ version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simba"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3a386a501cd104797982c15ae17aafe8b9261315b5d07e3ec803f2ea26be0fa"
|
||||
dependencies = [
|
||||
"approx",
|
||||
"num-complex",
|
||||
"num-traits",
|
||||
"paste",
|
||||
"wide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.7"
|
||||
@ -1725,6 +1938,16 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strict-num"
|
||||
version = "0.1.1"
|
||||
@ -1832,6 +2055,37 @@ dependencies = [
|
||||
"strict-num",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.46.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
"io-uring",
|
||||
"libc",
|
||||
"mio 1.0.4",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"slab",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.11"
|
||||
@ -1882,6 +2136,12 @@ version = "0.25.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
@ -2323,6 +2583,16 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wide"
|
||||
version = "0.7.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"safe_arch",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
@ -2656,7 +2926,7 @@ dependencies = [
|
||||
"instant",
|
||||
"libc",
|
||||
"log",
|
||||
"mio",
|
||||
"mio 0.8.11",
|
||||
"ndk 0.7.0",
|
||||
"objc2 0.3.0-beta.3.patch-leaks.3",
|
||||
"once_cell",
|
||||
|
@ -4,8 +4,13 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
bytemuck = "1.23.1"
|
||||
bytemuck = { version = "1.13", features = ["derive"] }
|
||||
# Error handling
|
||||
anyhow = "1.0"
|
||||
# For math operations (e.g., vectors, matrices)
|
||||
nalgebra = "0.33.2"
|
||||
vulkano = "0.35.1"
|
||||
vulkano-shaders = "0.35.0"
|
||||
vulkano-win = "0.34.0"
|
||||
winit = "0.30.11"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
|
603
src/main.rs
603
src/main.rs
@ -1,6 +1,605 @@
|
||||
// Jake Jensen, 2025
|
||||
// Rust
|
||||
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
use anyhow::{Result, Context};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::watch;
|
||||
use vulkano::{
|
||||
buffer::{Buffer, BufferCreateInfo, BufferUsage, CpuAccessibleBuffer, Subbuffer},
|
||||
command_buffer::{
|
||||
allocator::{StandardCommandBufferAllocator, StandardCommandBufferAllocatorCreateInfo},
|
||||
AutoCommandBufferBuilder, CommandBufferUsage, RenderPassBeginInfo, SubpassContents,
|
||||
},
|
||||
device::{
|
||||
physical::{PhysicalDevice, PhysicalDeviceType},
|
||||
Device, DeviceCreateInfo, DeviceExtensions, QueueCreateInfo, QueueFlags,
|
||||
},
|
||||
format::Format,
|
||||
image::{view::ImageView, Image, ImageCreateInfo, ImageUsage, SwapchainImage},
|
||||
instance::{Instance, InstanceCreateInfo, InstanceExtensions, Version},
|
||||
memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator},
|
||||
pipeline::{
|
||||
graphics::{
|
||||
input_assembly::InputAssemblyState,
|
||||
vertex_input::{Vertex, VertexInputState},
|
||||
viewport::{Viewport, ViewportState},
|
||||
GraphicsPipeline,
|
||||
},
|
||||
Pipeline, PipelineBindPoint,
|
||||
},
|
||||
render_pass::{Framebuffer, FramebufferCreateInfo, RenderPass, Subpass},
|
||||
swapchain::{
|
||||
acquire_next_image, Surface, Swapchain, SwapchainCreateInfo, SwapchainPresentInfo,
|
||||
},
|
||||
sync::{self, GpuFuture},
|
||||
VulkanLibrary,
|
||||
};
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::{Window, WindowBuilder},
|
||||
};
|
||||
use nalgebra::{Vector2, Vector4};
|
||||
|
||||
// Include the shader code
|
||||
mod vs {
|
||||
vulkano_shaders::shader! {
|
||||
ty: "vertex",
|
||||
path: "src/shaders/vert.glsl",
|
||||
}
|
||||
}
|
||||
|
||||
mod fs {
|
||||
vulkano_shaders::shader! {
|
||||
ty: "fragment",
|
||||
path: "src/shaders/frag.glsl",
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a single circle in the simulation.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Circle {
|
||||
pub position: Vector2<f32>,
|
||||
pub radius: f32,
|
||||
pub color: Vector4<f32>,
|
||||
}
|
||||
|
||||
/// The entire physics state to be sent to the renderer.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PhysicsState {
|
||||
pub circles: Vec<Circle>,
|
||||
}
|
||||
|
||||
// Implement the `Vertex` trait for our quad vertices.
|
||||
// These are the vertices that make up a unit square, which will be scaled and positioned by the shader.
|
||||
#[derive(BufferContents, Vertex)]
|
||||
#[repr(C)]
|
||||
struct QuadVertex {
|
||||
#[format(R32G32_SFLOAT)]
|
||||
position: [f32; 2],
|
||||
}
|
||||
|
||||
// --- Physics Simulation Task ---
|
||||
async fn physics_task(
|
||||
sender: watch::Sender<PhysicsState>,
|
||||
initial_state: PhysicsState,
|
||||
) -> Result<()> {
|
||||
let mut physics_state = initial_state;
|
||||
let mut last_update_time = tokio::time::Instant::now();
|
||||
let mut accumulator = tokio::time::Duration::ZERO;
|
||||
|
||||
// Fixed timestep for physics updates (e.g., 60 updates per second)
|
||||
const PHYSICS_TIMESTEP: tokio::time::Duration = tokio::time::Duration::from_nanos(16_666_667); // ~1/60th second
|
||||
|
||||
log::info!("Physics task started.");
|
||||
|
||||
loop {
|
||||
let now = tokio::time::Instant::now();
|
||||
accumulator += now.duration_since(last_update_time);
|
||||
last_update_time = now;
|
||||
|
||||
// Prevent "spiral of death" if rendering is too slow or system is overloaded
|
||||
// By clamping the accumulator, we ensure physics doesn't try to catch up infinitely.
|
||||
// It will just slow down the simulation if the system can't keep up.
|
||||
if accumulator > PHYSICS_TIMESTEP * 5 { // Cap at 5 physics steps behind
|
||||
accumulator = PHYSICS_TIMESTEP * 5;
|
||||
}
|
||||
|
||||
// Perform physics updates in fixed steps
|
||||
while accumulator >= PHYSICS_TIMESTEP {
|
||||
// --- YOUR PHYSICS LOGIC GOES HERE ---
|
||||
// For now, let's just make circles bounce off walls and move randomly
|
||||
for circle in &mut physics_state.circles {
|
||||
// Simple movement
|
||||
circle.position.x += 0.001; // Move right
|
||||
circle.position.y += 0.0005; // Move up
|
||||
|
||||
// Simple wall bounce (normalized device coordinates -1 to 1)
|
||||
if circle.position.x + circle.radius > 1.0 {
|
||||
circle.position.x = 1.0 - circle.radius;
|
||||
}
|
||||
if circle.position.x - circle.radius < -1.0 {
|
||||
circle.position.x = -1.0 + circle.radius;
|
||||
}
|
||||
if circle.position.y + circle.radius > 1.0 {
|
||||
circle.position.y = 1.0 - circle.radius;
|
||||
}
|
||||
if circle.position.y - circle.radius < -1.0 {
|
||||
circle.position.y = -1.0 + circle.radius;
|
||||
}
|
||||
}
|
||||
// --- END PHYSICS LOGIC ---
|
||||
|
||||
accumulator -= PHYSICS_TIMESTEP;
|
||||
}
|
||||
|
||||
// Send the updated physics state to the renderer.
|
||||
// `send_replace` ensures only the latest value is available, dropping older ones.
|
||||
sender.send_replace(physics_state.clone());
|
||||
|
||||
// Yield control back to the Tokio runtime.
|
||||
// This is important to allow other tasks (like the winit event loop if it's also async)
|
||||
// to run, and to prevent busy-waiting.
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Vulkan Renderer ---
|
||||
struct Renderer {
|
||||
_library: Arc<VulkanLibrary>,
|
||||
instance: Arc<Instance>,
|
||||
surface: Arc<Surface>,
|
||||
device: Arc<Device>,
|
||||
queue: Arc<vulkano::device::Queue>,
|
||||
memory_allocator: Arc<StandardMemoryAllocator>,
|
||||
command_buffer_allocator: Arc<StandardCommandBufferAllocator>,
|
||||
render_pass: Arc<RenderPass>,
|
||||
pipeline: Arc<GraphicsPipeline>,
|
||||
swapchain: Arc<Swapchain>,
|
||||
framebuffers: Vec<Arc<Framebuffer>>,
|
||||
recreate_swapchain: bool,
|
||||
previous_frame_end: Option<Box<dyn GpuFuture>>,
|
||||
quad_vertex_buffer: Subbuffer<[QuadVertex]>, // Static buffer for the quad
|
||||
}
|
||||
|
||||
impl Renderer {
|
||||
fn new(event_loop: &EventLoop<()>) -> Result<Self> {
|
||||
let library = VulkanLibrary::new().context("No Vulkan library found")?;
|
||||
log::info!("Vulkan library loaded: {:?}", library.name());
|
||||
|
||||
let required_extensions = vulkano_win::required_extensions(&library);
|
||||
let instance = Instance::new(
|
||||
library.clone(),
|
||||
InstanceCreateInfo {
|
||||
enabled_extensions: required_extensions,
|
||||
// Enable validation layers for debugging
|
||||
enabled_layers: if cfg!(debug_assertions) {
|
||||
vec!["VK_LAYER_KHRONOS_validation".to_string()]
|
||||
} else {
|
||||
vec![]
|
||||
},
|
||||
max_api_version: Some(Version::V1_1), // Specify minimum Vulkan API version
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.context("Failed to create Vulkan instance")?;
|
||||
log::info!("Vulkan instance created.");
|
||||
|
||||
let window = Arc::new(WindowBuilder::new().build(event_loop).context("Failed to create window")?);
|
||||
let surface = vulkano_win::create_surface_from_winit(instance.clone(), window.clone())
|
||||
.context("Failed to create Vulkan surface")?;
|
||||
log::info!("Vulkan surface created.");
|
||||
|
||||
let (physical_device, queue_family_index) = Self::select_physical_device(&instance, &surface)?;
|
||||
log::info!("Selected physical device: {}", physical_device.properties().device_name);
|
||||
|
||||
let (device, mut queues) = Device::new(
|
||||
physical_device.clone(),
|
||||
DeviceCreateInfo {
|
||||
enabled_extensions: DeviceExtensions {
|
||||
khr_swapchain: true, // Required for rendering to a window
|
||||
..DeviceExtensions::empty()
|
||||
},
|
||||
queue_create_infos: vec![QueueCreateInfo {
|
||||
queue_family_index,
|
||||
// Prioritize this queue for scheduling
|
||||
queue_priorities: vec![1.0],
|
||||
..Default::default()
|
||||
}],
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.context("Failed to create Vulkan device")?;
|
||||
let queue = queues.next().context("No queues found")?;
|
||||
log::info!("Vulkan device and queue created.");
|
||||
|
||||
let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device.clone()));
|
||||
let command_buffer_allocator = Arc::new(StandardCommandBufferAllocator::new(
|
||||
device.clone(),
|
||||
StandardCommandBufferAllocatorCreateInfo::default(),
|
||||
));
|
||||
log::info!("Memory and command buffer allocators created.");
|
||||
|
||||
let (mut swapchain, images) = Self::create_swapchain(
|
||||
device.clone(),
|
||||
surface.clone(),
|
||||
physical_device.clone(),
|
||||
window.inner_size(),
|
||||
)?;
|
||||
log::info!("Swapchain created.");
|
||||
|
||||
let render_pass = Self::create_render_pass(device.clone(), swapchain.image_format())?;
|
||||
log::info!("Render pass created.");
|
||||
|
||||
let pipeline = Self::create_pipeline(
|
||||
device.clone(),
|
||||
render_pass.clone(),
|
||||
swapchain.image_extent(),
|
||||
)?;
|
||||
log::info!("Graphics pipeline created.");
|
||||
|
||||
let framebuffers = Self::create_framebuffers(&images, render_pass.clone())?;
|
||||
log::info!("Framebuffers created.");
|
||||
|
||||
// Create a static vertex buffer for a unit quad
|
||||
let quad_vertices = [
|
||||
QuadVertex { position: [-1.0, -1.0] },
|
||||
QuadVertex { position: [ 1.0, -1.0] },
|
||||
QuadVertex { position: [-1.0, 1.0] },
|
||||
QuadVertex { position: [-1.0, 1.0] },
|
||||
QuadVertex { position: [ 1.0, -1.0] },
|
||||
QuadVertex { position: [ 1.0, 1.0] },
|
||||
];
|
||||
let quad_vertex_buffer = Buffer::from_iter(
|
||||
&memory_allocator,
|
||||
BufferCreateInfo {
|
||||
usage: BufferUsage::VERTEX_BUFFER,
|
||||
..Default::default()
|
||||
},
|
||||
AllocationCreateInfo {
|
||||
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
|
||||
..Default::default()
|
||||
},
|
||||
quad_vertices,
|
||||
).context("Failed to create quad vertex buffer")?;
|
||||
log::info!("Quad vertex buffer created.");
|
||||
|
||||
let previous_frame_end = Some(sync::now(device.clone()).boxed());
|
||||
|
||||
Ok(Self {
|
||||
_library: library,
|
||||
instance,
|
||||
surface,
|
||||
device,
|
||||
queue,
|
||||
memory_allocator,
|
||||
command_buffer_allocator,
|
||||
render_pass,
|
||||
pipeline,
|
||||
swapchain,
|
||||
framebuffers,
|
||||
recreate_swapchain: false,
|
||||
previous_frame_end,
|
||||
quad_vertex_buffer,
|
||||
})
|
||||
}
|
||||
|
||||
fn select_physical_device(
|
||||
instance: &Arc<Instance>,
|
||||
surface: &Arc<Surface>,
|
||||
) -> Result<(Arc<PhysicalDevice>, u32)> {
|
||||
instance
|
||||
.enumerate_physical_devices()
|
||||
.context("Failed to enumerate physical devices")?
|
||||
.filter(|p| p.supported_extensions().contains(&DeviceExtensions { khr_swapchain: true, ..DeviceExtensions::empty() }))
|
||||
.filter_map(|p| {
|
||||
p.queue_family_properties()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.position(|(i, q)| {
|
||||
q.queue_flags.contains(QueueFlags::GRAPHICS)
|
||||
&& p.surface_support(i as u32, surface).unwrap_or(false)
|
||||
})
|
||||
.map(|i| (p, i as u32))
|
||||
})
|
||||
.min_by_key(|(p, _)| match p.properties().device_type {
|
||||
PhysicalDeviceType::DiscreteGpu => 0,
|
||||
PhysicalDeviceType::IntegratedGpu => 1,
|
||||
PhysicalDeviceType::VirtualGpu => 2,
|
||||
PhysicalDeviceType::Cpu => 3,
|
||||
_ => 4,
|
||||
})
|
||||
.context("No suitable physical device found")
|
||||
}
|
||||
|
||||
fn create_swapchain(
|
||||
device: Arc<Device>,
|
||||
surface: Arc<Surface>,
|
||||
physical_device: Arc<PhysicalDevice>,
|
||||
window_size: winit::dpi::PhysicalSize<u32>,
|
||||
) -> Result<(Arc<Swapchain>, Vec<Arc<Image>>)> {
|
||||
let capabilities = physical_device
|
||||
.surface_capabilities(&surface, Default::default())
|
||||
.context("Failed to get surface capabilities")?;
|
||||
|
||||
let image_format = physical_device
|
||||
.surface_formats(&surface, Default::default())
|
||||
.context("Failed to get surface formats")?
|
||||
.iter()
|
||||
.min_by_key(|(f, _)| match f {
|
||||
// Prefer sRGB if available, otherwise just pick the first one
|
||||
Format::R8G8B8A8_SRGB => 0,
|
||||
Format::B8G8R8A8_SRGB => 0,
|
||||
_ => 1,
|
||||
})
|
||||
.map(|(f, _)| *f)
|
||||
.unwrap_or(Format::B8G8R8A8_SRGB); // Fallback
|
||||
|
||||
let present_mode = capabilities.present_modes.iter().next().unwrap(); // Just pick the first available
|
||||
let image_extent = capabilities.current_extent.unwrap_or([
|
||||
window_size.width,
|
||||
window_size.height,
|
||||
]);
|
||||
|
||||
Swapchain::new(
|
||||
device.clone(),
|
||||
surface.clone(),
|
||||
SwapchainCreateInfo {
|
||||
min_image_count: capabilities.min_image_count,
|
||||
image_format,
|
||||
image_extent,
|
||||
image_array_layers: 1,
|
||||
image_usage: ImageUsage::COLOR_ATTACHMENT,
|
||||
composite_alpha: capabilities.supported_composite_alpha.iter().next().unwrap(),
|
||||
present_mode,
|
||||
clipped: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.context("Failed to create swapchain")
|
||||
}
|
||||
|
||||
fn create_render_pass(device: Arc<Device>, format: Format) -> Result<Arc<RenderPass>> {
|
||||
vulkano::single_pass_renderpass! {
|
||||
device.clone(),
|
||||
attachments: {
|
||||
color: {
|
||||
load: Clear,
|
||||
store: Store,
|
||||
format: format,
|
||||
samples: 1,
|
||||
}
|
||||
},
|
||||
pass: {
|
||||
color: [color],
|
||||
depth_stencil: {}
|
||||
}
|
||||
}
|
||||
.context("Failed to create render pass")
|
||||
}
|
||||
|
||||
fn create_pipeline(
|
||||
device: Arc<Device>,
|
||||
render_pass: Arc<RenderPass>,
|
||||
image_extent: [u32; 2],
|
||||
) -> Result<Arc<GraphicsPipeline>> {
|
||||
let vs = vs::load(device.clone()).context("Failed to load vertex shader")?;
|
||||
let fs = fs::load(device.clone()).context("Failed to load fragment shader")?;
|
||||
|
||||
GraphicsPipeline::new(
|
||||
device.clone(),
|
||||
Some(vs.entry_point("main").unwrap()),
|
||||
Some(fs.entry_point("main").unwrap()),
|
||||
InputAssemblyState::new(),
|
||||
ViewportState::viewport_fixed_scissor_irrelevant([Viewport {
|
||||
offset: [0.0, 0.0],
|
||||
extent: [image_extent[0] as f32, image_extent[1] as f32],
|
||||
depth_range: 0.0..=1.0,
|
||||
}]),
|
||||
render_pass.clone().into(),
|
||||
None,
|
||||
)
|
||||
.context("Failed to create graphics pipeline")
|
||||
}
|
||||
|
||||
fn create_framebuffers(
|
||||
images: &[Arc<SwapchainImage>],
|
||||
render_pass: Arc<RenderPass>,
|
||||
) -> Result<Vec<Arc<Framebuffer>>> {
|
||||
images
|
||||
.iter()
|
||||
.map(|image| {
|
||||
let view = ImageView::new_default(image.clone()).context("Failed to create image view")?;
|
||||
Framebuffer::new(
|
||||
render_pass.clone(),
|
||||
FramebufferCreateInfo {
|
||||
attachments: vec![view],
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.context("Failed to create framebuffer")
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()
|
||||
}
|
||||
|
||||
fn recreate_swapchain_if_needed(&mut self, new_dimensions: [u32; 2]) -> Result<()> {
|
||||
if self.recreate_swapchain {
|
||||
self.recreate_swapchain = false;
|
||||
|
||||
let (new_swapchain, new_images) = Self::create_swapchain(
|
||||
self.device.clone(),
|
||||
self.surface.clone(),
|
||||
self.device.physical_device().clone(),
|
||||
winit::dpi::PhysicalSize::new(new_dimensions[0], new_dimensions[1]),
|
||||
)?;
|
||||
|
||||
self.swapchain = new_swapchain;
|
||||
self.framebuffers = Self::create_framebuffers(&new_images, self.render_pass.clone())?;
|
||||
self.pipeline = Self::create_pipeline(
|
||||
self.device.clone(),
|
||||
self.render_pass.clone(),
|
||||
new_dimensions,
|
||||
)?;
|
||||
log::info!("Swapchain recreated with new dimensions: {:?}", new_dimensions);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render(&mut self, physics_state: &PhysicsState) -> Result<()> {
|
||||
self.previous_frame_end.as_mut().unwrap().cleanup_finished();
|
||||
|
||||
let image_extent = self.swapchain.image_extent();
|
||||
if image_extent[0] == 0 || image_extent[1] == 0 {
|
||||
// Window is minimized, skip rendering
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.recreate_swapchain_if_needed(image_extent)?;
|
||||
|
||||
let (image_index, suboptimal, acquire_future) =
|
||||
match acquire_next_image(self.swapchain.clone(), None) {
|
||||
Ok(r) => r,
|
||||
Err(vulkano::sync::AcquireError::OutOfDate) => {
|
||||
self.recreate_swapchain = true;
|
||||
return Ok(());
|
||||
}
|
||||
Err(e) => return Err(anyhow::Error::from(e).context("Failed to acquire next swapchain image")),
|
||||
};
|
||||
|
||||
if suboptimal {
|
||||
self.recreate_swapchain = true;
|
||||
}
|
||||
|
||||
let mut builder = AutoCommandBufferBuilder::primary(
|
||||
&self.command_buffer_allocator,
|
||||
self.queue.queue_family_index(),
|
||||
CommandBufferUsage::OneTimeSubmit, // Command buffer will be submitted once
|
||||
).context("Failed to create command buffer builder")?;
|
||||
|
||||
builder
|
||||
.begin_render_pass(
|
||||
RenderPassBeginInfo {
|
||||
clear_values: vec![Some([0.0, 0.0, 0.1, 1.0].into())], // Clear to dark blue
|
||||
..RenderPassBeginInfo::framebuffer(self.framebuffers[image_index as usize].clone())
|
||||
},
|
||||
SubpassContents::Inline,
|
||||
)?
|
||||
.bind_pipeline_graphics(self.pipeline.clone())?;
|
||||
|
||||
// Draw each circle
|
||||
for circle in &physics_state.circles {
|
||||
let push_constants = fs::PushConstants {
|
||||
center: circle.position.into(),
|
||||
radius: circle.radius,
|
||||
color: circle.color.into(),
|
||||
};
|
||||
builder
|
||||
.push_constants(self.pipeline.layout().clone(), 0, push_constants)?
|
||||
.bind_vertex_buffers(0, self.quad_vertex_buffer.clone())?
|
||||
.draw(self.quad_vertex_buffer.len() as u32, 1, 0, 0)?;
|
||||
}
|
||||
|
||||
builder.end_render_pass()?;
|
||||
|
||||
let command_buffer = builder.build().context("Failed to build command buffer")?;
|
||||
|
||||
let future = self
|
||||
.previous_frame_end
|
||||
.take()
|
||||
.unwrap()
|
||||
.join(acquire_future)
|
||||
.then_execute(self.queue.clone(), command_buffer)
|
||||
.context("Failed to execute command buffer")?
|
||||
.then_swapchain_present(self.queue.clone(), self.swapchain.clone(), image_index)
|
||||
.then_signal_fence_and_flush();
|
||||
|
||||
match future {
|
||||
Ok(future) => {
|
||||
self.previous_frame_end = Some(future.boxed());
|
||||
}
|
||||
Err(vulkano::sync::FlushError::OutOfDate) => {
|
||||
self.recreate_swapchain = true;
|
||||
self.previous_frame_end = Some(sync::now(self.device.clone()).boxed());
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to flush future: {:?}", e);
|
||||
self.previous_frame_end = Some(sync::now(self.device.clone()).boxed());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
// Initialize logging (useful for Vulkano debug messages)
|
||||
env_logger::init();
|
||||
|
||||
// --- Setup communication channels ---
|
||||
// `watch` channel is great for sending the latest state, dropping older ones if not read.
|
||||
let initial_physics_state = PhysicsState {
|
||||
circles: vec![
|
||||
Circle {
|
||||
position: Vector2::new(0.0, 0.0),
|
||||
radius: 0.1,
|
||||
color: Vector4::new(1.0, 0.0, 0.0, 1.0), // Red
|
||||
},
|
||||
Circle {
|
||||
position: Vector2::new(0.5, 0.5),
|
||||
radius: 0.05,
|
||||
color: Vector4::new(0.0, 1.0, 0.0, 1.0), // Green
|
||||
},
|
||||
Circle {
|
||||
position: Vector2::new(-0.5, -0.5),
|
||||
radius: 0.08,
|
||||
color: Vector4::new(0.0, 0.0, 1.0, 1.0), // Blue
|
||||
},
|
||||
],
|
||||
};
|
||||
let (physics_sender, physics_receiver) = watch::channel(initial_physics_state.clone());
|
||||
|
||||
// --- Spawn Physics Task ---
|
||||
tokio::spawn(physics_task(physics_sender, initial_physics_state));
|
||||
|
||||
// --- Setup Winit Event Loop and Renderer ---
|
||||
let event_loop = EventLoop::new().context("Failed to create event loop")?;
|
||||
let mut renderer = Renderer::new(&event_loop)?;
|
||||
|
||||
// --- Main Rendering Loop ---
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Poll; // Continuously poll for events and redraw
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::Resized(_),
|
||||
..
|
||||
} => {
|
||||
renderer.recreate_swapchain = true;
|
||||
}
|
||||
Event::MainEventsCleared => {
|
||||
// This event fires when all other events have been processed.
|
||||
// It's a good place to trigger a redraw.
|
||||
|
||||
// Get the latest physics state from the watch channel
|
||||
let current_physics_state = physics_receiver.borrow().clone();
|
||||
|
||||
// Render the current state
|
||||
if let Err(e) = renderer.render(¤t_physics_state) {
|
||||
log::error!("Rendering error: {:?}", e);
|
||||
// Handle critical rendering errors, e.g., exit application
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
85
src/shaders/frag.glsl
Normal file
85
src/shaders/frag.glsl
Normal file
@ -0,0 +1,85 @@
|
||||
#version 450
|
||||
|
||||
// Output color
|
||||
layout(location = 0) out vec4 f_color;
|
||||
|
||||
// Push constants for circle data (must match vertex shader)
|
||||
layout(push_constant) uniform PushConstants {
|
||||
vec2 center;
|
||||
float radius;
|
||||
vec4 color;
|
||||
} push_constants;
|
||||
|
||||
void main() {
|
||||
// Get the fragment's position in normalized device coordinates (NDC)
|
||||
// which is the same space as our circle's center and radius
|
||||
vec2 frag_pos_ndc = gl_FragCoord.xy / vec2(gl_FragCoord.w) * 0.5 + 0.5; // Convert to [0,1] range, then adjust to [-1,1]
|
||||
|
||||
// Convert gl_FragCoord from window coordinates to NDC
|
||||
// gl_FragCoord.xy are pixel coordinates, gl_FragCoord.w is 1.0 for non-perspective.
|
||||
// We need to transform these to the [-1, 1] NDC space that our vertex shader outputs.
|
||||
// This assumes a viewport from (0,0) to (width, height)
|
||||
// A more robust solution would involve passing screen dimensions as a uniform.
|
||||
// For now, let's assume gl_FragCoord.xy is already in a space where distance makes sense relative to NDC.
|
||||
// A simpler approach for this example is to use the `position` from the vertex shader
|
||||
// which is implicitly interpolated, but that's not how fragment shaders work directly.
|
||||
|
||||
// Let's simplify and assume a full-screen quad is drawn, and we're calculating distance from the center.
|
||||
// This is a common pattern for drawing circles or other 2D shapes in a fragment shader.
|
||||
// The `position` from the vertex shader is interpolated across the quad.
|
||||
// We need to pass the *vertex* position to the fragment shader to calculate distance.
|
||||
// Let's modify the vertex shader to pass the transformed position.
|
||||
|
||||
// Corrected approach: Pass the transformed vertex position to the fragment shader
|
||||
// The vertex shader will output `gl_Position` which is the clip-space coordinate.
|
||||
// The fragment shader receives `gl_FragCoord` which is window-space.
|
||||
// To correctly calculate the distance, we need the fragment's position *relative to the circle's center in NDC*.
|
||||
// This means we need to transform gl_FragCoord.xy back to NDC, or pass the vertex position.
|
||||
|
||||
// Let's adjust the vertex shader to pass the local quad coordinates to the fragment shader
|
||||
// and the fragment shader will use that to determine distance.
|
||||
|
||||
// For a simple circle drawn within a quad, the most common approach is to pass
|
||||
// the *normalized* coordinates of the quad to the fragment shader.
|
||||
// The vertex shader would output `vec2 v_uv;` and the fragment shader would take `layout(location = 0) in vec2 v_uv;`
|
||||
// Then `distance(v_uv, vec2(0.0, 0.0))` would be used.
|
||||
|
||||
// Let's refine the shaders for this common pattern:
|
||||
// Vert shader passes a UV coordinate (0 to 1 or -1 to 1) for the quad.
|
||||
// Frag shader uses this UV to calculate distance from the center of its *local* quad.
|
||||
// The push constants will then position and scale this quad.
|
||||
|
||||
// REVISED SHADER PLAN:
|
||||
// vert.glsl:
|
||||
// - input: vec2 in_pos (for a unit quad, e.g., (-1,-1) to (1,1))
|
||||
// - push_constants: center (vec2), radius (float), color (vec4)
|
||||
// - output: vec2 v_uv (pass in_pos as v_uv)
|
||||
// - gl_Position = vec4(center + in_pos * radius, 0.0, 1.0);
|
||||
// frag.glsl:
|
||||
// - input: vec2 v_uv
|
||||
// - push_constants: color (vec4) (center/radius not needed here if v_uv is relative)
|
||||
// - calculate distance from v_uv to (0,0)
|
||||
// - if distance > 1.0, discard
|
||||
// - else, output push_constants.color
|
||||
|
||||
// Let's go with this revised plan for the shaders.
|
||||
// This is the fragment shader for the *revised* plan.
|
||||
layout(location = 0) in vec2 v_uv; // Interpolated UV coordinate from vertex shader
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
vec2 center; // Not directly used for distance calculation here, but for context
|
||||
float radius; // Not directly used for distance calculation here
|
||||
vec4 color;
|
||||
} push_constants;
|
||||
|
||||
void main() {
|
||||
// Calculate distance from the center of the quad (which is (0,0) in v_uv space)
|
||||
float dist = length(v_uv);
|
||||
|
||||
// Discard fragments outside the unit circle
|
||||
if (dist > 1.0) {
|
||||
discard;
|
||||
}
|
||||
|
||||
f_color = push_constants.color;
|
||||
}
|
25
src/shaders/vert.glsl
Normal file
25
src/shaders/vert.glsl
Normal file
@ -0,0 +1,25 @@
|
||||
#version 450
|
||||
|
||||
// Vertex input: position for a unit quad (-1, -1) to (1, 1)
|
||||
layout(location = 0) in vec2 in_pos;
|
||||
|
||||
// Output to fragment shader: interpolated UV for the quad
|
||||
layout(location = 0) out vec2 v_uv;
|
||||
|
||||
// Push constants for circle data
|
||||
layout(push_constant) uniform PushConstants {
|
||||
vec2 center;
|
||||
float radius;
|
||||
vec4 color;
|
||||
} push_constants;
|
||||
|
||||
void main() {
|
||||
// Transform the unit quad vertex to be centered at `push_constants.center`
|
||||
// and scaled by `push_constants.radius`.
|
||||
// This effectively creates a square around the circle's center with side length 2*radius.
|
||||
gl_Position = vec4(push_constants.center + in_pos * push_constants.radius, 0.0, 1.0);
|
||||
|
||||
// Pass the original unit quad position (UV) to the fragment shader.
|
||||
// The fragment shader will use this to determine if the fragment is inside the circle.
|
||||
v_uv = in_pos;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user