Initial compute/vertex/fragment render system

This commit is contained in:
2025-01-20 18:54:24 -05:00
commit 203435ca72
13 changed files with 3837 additions and 0 deletions

View File

@ -0,0 +1,17 @@
[package]
name = "flare-shader"
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
[lib]
crate-type = ["dylib", "lib"]
[dependencies]
bytemuck.workspace = true
glam.workspace = true
spirv-std.workspace = true
[lints]
workspace = true

View File

@ -0,0 +1,141 @@
#![cfg_attr(target_arch = "spirv", no_std)]
use glam::{UVec2, Vec4, Vec4Swizzles, uvec2, vec2, vec4};
use spirv_std::spirv;
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
#[repr(C)]
pub struct IfsConstants {
accum_width: u32,
accum_height: u32,
viewport_width: u32,
viewport_height: u32,
background_color: Vec4,
}
impl IfsConstants {
pub fn new(
accum_width: u32,
accum_height: u32,
viewport_width: u32,
viewport_height: u32,
background_color: Vec4,
) -> Self {
Self {
accum_width,
accum_height,
viewport_width,
viewport_height,
background_color,
}
}
pub fn viewport_dimensions(&self) -> UVec2 {
uvec2(self.viewport_width, self.viewport_height)
}
pub fn with_accumulate(&mut self, width: u32, height: u32) {
self.accum_width = width;
self.accum_height = height;
}
pub fn with_viewport(&mut self, width: u32, height: u32) {
self.viewport_width = width;
self.viewport_height = height;
}
}
pub trait Color {
type Element;
const BLACK: Self;
const WHITE: Self;
}
impl Color for Vec4 {
type Element = f32;
const BLACK: Self = vec4(0., 0., 0., 1.);
const WHITE: Self = vec4(1., 1., 1., 1.);
}
pub(crate) fn image_index(pixel_x: usize, pixel_y: usize, image_width: u32) -> usize {
pixel_x + pixel_y * image_width as usize
}
#[spirv(compute(threads(1)))]
pub fn main_cs(
#[spirv(push_constant)] constants: &IfsConstants,
#[spirv(storage_buffer, descriptor_set = 0, binding = 0)] accum_image: &mut [Vec4],
) {
let block_size = 64;
for i_width in 0..constants.accum_width as usize {
for i_height in 0..constants.accum_height as usize {
let color = if (i_width / block_size % 2 == 1) != (i_height / block_size % 2 == 1) {
Vec4::BLACK
} else {
Vec4::WHITE
};
accum_image[image_index(i_width, i_height, constants.accum_width)] = color;
}
}
}
#[spirv(vertex)]
pub fn main_vs(
#[spirv(vertex_index)] vert_id: u32,
#[spirv(position, invariant)] position: &mut Vec4,
) {
// Create a "quad" that fills the viewport for the fragment shader.
// The `draw` call issued by the main application will be for three vertex ID's (0, 1, 2).
// This code maps them to the points (-1, -1), (3, -1), and (-1, 3) respectively.
// Because the interior of that triangle covers the entire viewport,
// the GPU clips to the viewport and invokes the fragment shader for each pixel.
// https://stackoverflow.com/a/59739538
// https://www.saschawillems.de/blog/2016/08/13/vulkan-tutorial-on-rendering-a-fullscreen-quad-without-buffers/
let output_uv = vec2(((vert_id << 1) & 2) as f32, (vert_id & 2) as f32);
*position = (output_uv * 2.0 - 1.0, 0.0, 1.0).into();
}
#[spirv(fragment)]
pub fn main_fs(
#[spirv(frag_coord)] frag_coord: Vec4,
#[spirv(push_constant)] ifs_constants: &IfsConstants,
#[spirv(storage_buffer, descriptor_set = 0, binding = 0)] accum_image: &[Vec4],
output: &mut Vec4,
) {
// Bootleg texture sampling; map from viewport image pixel coordinates to accumulator image
// pixel coordinates
let viewport_coordinate = frag_coord.xy().as_uvec2();
let a_width = ifs_constants.accum_width as f32;
let a_height = ifs_constants.accum_height as f32;
let v_width = ifs_constants.viewport_width as f32;
let v_height = ifs_constants.viewport_height as f32;
// Scale both width and height by the same factor; preserves aspect ratio
let scale_width = a_width / v_width;
let scale_height = a_height / v_height;
let scale = scale_width.max(scale_height);
// Re-center the image in the viewport after scale
let offset_x = (v_width * scale - a_width) / 2.0;
let offset_y = (v_height * scale - a_height) / 2.0;
let accum_coordinate = viewport_coordinate.as_vec2() * scale - vec2(offset_x, offset_y);
if accum_coordinate.x < 0.0
|| accum_coordinate.x >= ifs_constants.accum_width as f32
|| accum_coordinate.y < 0.0
|| accum_coordinate.y >= ifs_constants.accum_height as f32
{
*output = ifs_constants.background_color;
} else {
*output = accum_image[image_index(
accum_coordinate.x as usize,
accum_coordinate.y as usize,
ifs_constants.accum_width,
)];
}
}