Browse Source

Add support for multiple stages per shader module, WGSL compilation.

master
Alison Watson 11 months ago
parent
commit
a1ea8b33d8
  1. 113
      Cargo.lock
  2. 22
      Cargo.toml
  3. 15
      bl/main.rs
  4. 32
      fw/data/read.rs
  5. 75
      fw/data/shader.rs
  6. 3
      fw/hal/ctx.rs
  7. 72
      fw/hal/sdl.rs
  8. 7
      fw/hal/win.rs
  9. 19
      fw/render.rs
  10. 26
      fw/render/shader.rs

113
Cargo.lock generated

@ -17,24 +17,39 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "bit-set"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de"
dependencies = [
"bit-vec",
]
[[package]]
name = "bit-vec"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
[[package]]
name = "bitflags"
version = "1.2.1"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "blonkus"
version = "0.1.0"
dependencies = [
"ash",
"bitflags",
"blonkus-ma",
"cc",
"easy-cast",
"flagset",
"glam",
"half",
"seahash",
"naga",
"serde",
"smallvec",
"smol_str",
@ -51,6 +66,12 @@ dependencies = [
"syn",
]
[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "cc"
version = "1.0.70"
@ -63,6 +84,16 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "codespan-reporting"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
dependencies = [
"termcolor",
"unicode-width",
]
[[package]]
name = "easy-cast"
version = "0.4.4"
@ -70,10 +101,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bd102ee8c418348759919b83b81cdbdc933ffe29740b903df448b4bafaa348e"
[[package]]
name = "flagset"
version = "0.4.2"
name = "fxhash"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1207393e01e20804589a3fc9781c9df2a70687cd81362ca58e33b2a726ec83cf"
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
dependencies = [
"byteorder",
]
[[package]]
name = "glam"
@ -115,6 +149,31 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a"
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
]
[[package]]
name = "naga"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c5859e55c51da10b98e7a73068e0a0c5da7bbcae4fc38f86043d0c6d1b917cf"
dependencies = [
"bit-set",
"bitflags",
"codespan-reporting",
"fxhash",
"log",
"num-traits",
"spirv",
"thiserror",
]
[[package]]
name = "num-traits"
version = "0.2.14"
@ -143,12 +202,6 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "seahash"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
[[package]]
name = "serde"
version = "1.0.130"
@ -181,6 +234,16 @@ version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b203e79e90905594272c1c97c7af701533d42adaab0beb3859018e477d54a3b0"
[[package]]
name = "spirv"
version = "0.2.0+1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "246bfa38fe3db3f1dfc8ca5a2cdeb7348c78be2112740cc0ec8ef18b6d94f830"
dependencies = [
"bitflags",
"num-traits",
]
[[package]]
name = "spirv-std"
version = "0.4.0-alpha.8"
@ -223,6 +286,15 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "termcolor"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
dependencies = [
"winapi-util",
]
[[package]]
name = "thiserror"
version = "1.0.29"
@ -258,6 +330,12 @@ version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
[[package]]
name = "unicode-width"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
[[package]]
name = "unicode-xid"
version = "0.2.2"
@ -280,6 +358,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"

22
Cargo.toml

@ -20,14 +20,16 @@ build = "fw/build.rs"
blonkus-ma = { path = "ma" }
# types:
# - flagset for FFI usage
# - bitflags for FFI usage
# - smallvec for potentially small dynamic arrays
# - smol_str for potentially small immutable strings
# - thiserror for implementing error types
flagset = "~0.4"
# - intaglio for symbol interning?
bitflags = "~1.3"
smallvec = { version = "~1.6", features = ["const_generics", "union"] }
smol_str = "~0.1"
thiserror = "~1.0"
#intaglio = "~1.2"
# i/o:
# - serde for config / description files
@ -38,22 +40,18 @@ toml = "~0.5"
# math:
# - easy-cast for safer casting
# - glam for linear algebra
# - half for half-floats in certain areas
# - half for half-floats
# - seahash for general hashing?
easy-cast = "~0.4"
glam = "~0.13"
half = "~1.7"
#seahash = { version = "~4.1" }
# hashing:
# - intaglio for symbol interning?
# - rustc-hash for things like mesh names?
# - seahash for file deduplication
#intaglio = "~1.2"
#rustc-hash = { version = "~1.1", default-features = false }
seahash = { version = "~4.1" }
# bindings:
# rendering:
# - ash for the renderer implementation
# - naga for compiling shaders
ash = "~0.32"
naga = { version = "~0.6", features = ["wgsl-in", "spv-out"] }
[build-dependencies]
cc = "1.0"

15
bl/main.rs

@ -8,7 +8,14 @@ fn entry(conf: &fw::conf::Conf) -> Result<(), Box<dyn std::error::Error>> {
let hal = fw::hal::ctx::Context::new()?;
let win = fw::hal::win::Window::new(&hal, fw::c_str!("BLONKUS"), 640, 480)?;
let ren = fw::render::Renderer::new(&win, &conf.render);
let mut ren = fw::render::Renderer::new(
&win,
&conf.render,
&[fw::data::shader::Module::compile(
vfs.get("main.wgsl").unwrap().text()?,
"main",
)?],
)?;
'main_loop: loop {
for event in fw::hal::evt::EventIterator::new(&hal) {
@ -20,7 +27,7 @@ fn entry(conf: &fw::conf::Conf) -> Result<(), Box<dyn std::error::Error>> {
}
}
//ren.draw_frame()?;
ren.draw_frame()?;
}
Ok(())
@ -38,9 +45,7 @@ fn main() {
fw::conf::Conf::default()
});
if let Err(e) = entry(&conf) {
eprintln!("Uncaught error: {}", e);
}
entry(&conf).unwrap();
}
// EOF

32
fw/data/read.rs

@ -86,14 +86,14 @@ pub fn hunk(rd: &mut impl Read, size: usize) -> io::Result<Vec<u8>> {
macro_rules! bits_impl {
($t:ty, $be:ident, $le:ident) => {
#[doc = concat!(
"Reads `width` bits from `b` starting at bit `cr_bit` into one [`",
stringify!($t), "`] most significant bit first.\n",
"\n",
"# Errors\n",
"\n",
"This function will return `None` if there are not enough bits left ",
"in the buffer."
)]
"Reads `width` bits from `b` starting at bit `cr_bit` into one [`",
stringify!($t), "`] most significant bit first.\n",
"\n",
"# Errors\n",
"\n",
"This function will return `None` if there are not enough bits left ",
"in the buffer."
)]
#[inline]
pub const fn $be(
b: &[u8], cr_bit: usize, mut width: usize,
@ -133,14 +133,14 @@ macro_rules! bits_impl {
}
#[doc = concat!(
"The same as [`", stringify!($be), "`], but least-significant bit ",
"first.\n",
"\n",
"# Errors\n",
"\n",
"This function will return [`None`] if there are not enough bits ",
"left in the buffer."
)]
"The same as [`", stringify!($be), "`], but least-significant bit ",
"first.\n",
"\n",
"# Errors\n",
"\n",
"This function will return [`None`] if there are not enough bits ",
"left in the buffer."
)]
#[inline]
pub const fn $le(
b: &[u8], cr_bit: usize, mut width: usize,

75
fw/data/shader.rs

@ -1,30 +1,38 @@
//! SPIR-V shaders.
flagset::flags! {
use ash::vk;
bitflags::bitflags! {
/// A flag set of shader stages.
pub enum ShaderStage {
Compute,
Fragment,
Geometry,
TessControl,
TessEvaluation,
Vertex,
pub struct ShaderStage: u8 {
const COMPUTE = 1;
const FRAGMENT = 1 << 1;
const VERTEX = 1 << 2;
}
}
impl From<ShaderStage> for ash::vk::ShaderStageFlags {
}
/// A SPIR-V IR module.
pub struct Module {
pub code: Vec<u32>,
pub size: usize,
pub name: String,
pub stag: ShaderStage,
}
/// The error type which is returned from reading a shader program.
#[derive(thiserror::Error, Debug)]
pub enum Err {
#[error("{}", err.emit_to_string(&src))]
Parse { err: naga::front::wgsl::ParseError, src: String },
#[error(transparent)]
Validate(#[from] naga::valid::ValidationError),
#[error(transparent)]
Compile(#[from] naga::back::spv::Error),
}
impl Module {
/// Reads SPIR-V IR in from a buffer.
pub fn read(bytecode: &[u8], name: &str) -> Self {
pub fn read(bytecode: &[u8], name: &str, stag: ShaderStage) -> Self {
let mut code = Vec::with_capacity(bytecode.len() / 4);
for word in bytecode.chunks_exact(4) {
@ -33,7 +41,48 @@ impl Module {
let size = code.len() * 4;
Self { code, size, name: name.to_owned() }
Self { code, size, name: name.to_owned(), stag }
}
/// Compiles WGSL source to SPIR-V IR.
pub fn compile(source: &str, name: &str) -> Result<Self, Err> {
use naga::back::spv;
let modu = naga::front::wgsl::parse_str(source)
.map_err(|err| Err::Parse { err, src: source.to_owned() })?;
let mut stag = ShaderStage::empty();
for entry in &modu.entry_points {
stag |= match entry.stage {
| naga::ShaderStage::Vertex => ShaderStage::VERTEX,
| naga::ShaderStage::Fragment => ShaderStage::FRAGMENT,
| naga::ShaderStage::Compute => ShaderStage::COMPUTE,
};
}
let mut vali = naga::valid::Validator::new(
naga::valid::ValidationFlags::all(),
naga::valid::Capabilities::empty(),
);
let info = vali.validate(&modu)?;
let mut caps = naga::FastHashSet::default();
caps.insert(spv::Capability::Shader);
let opts = spv::Options {
lang_version: (1, 0),
flags: spv::WriterFlags::DEBUG,
capabilities: Some(caps),
index_bounds_check_policy:
naga::back::IndexBoundsCheckPolicy::ReadZeroSkipWrite,
};
let code = spv::write_vec(&modu, &info, &opts)?;
let size = code.len() * 4;
Ok(Self { code, size, name: name.to_owned(), stag })
}
}

3
fw/hal/ctx.rs

@ -1,5 +1,4 @@
use super::*;
use flagset::FlagSet;
#[non_exhaustive]
pub struct Context;
@ -8,7 +7,7 @@ impl Context {
pub fn new() -> Result<Self, Err> {
unsafe {
sdl::set_main_ready();
if sdl::init(FlagSet::from(sdl::InitFlags::Video).bits()) == 0 {
if sdl::init(sdl::InitFlags::VIDEO) == 0 {
Ok(Self)
} else {
Err(Err::new_sdl())

72
fw/hal/sdl.rs

@ -6,41 +6,43 @@ use std::os::raw;
pub const WINDOW_POS_UNDEF: raw::c_int = 0x1FFF0000;
flagset::flags! {
pub enum InitFlags: u32 {
Timer = 0x01,
Audio = 0x10,
Video = 0x20,
Joystick = 0x200,
Haptic = 0x1000,
GameController = 0x2000,
Events = 0x4000,
Sensor = 0x8000,
bitflags::bitflags! {
#[repr(transparent)]
pub struct InitFlags: u32 {
const TIMER = 0x01;
const AUDIO = 0x10;
const VIDEO = 0x20;
const JOYSTICK = 0x200;
const HAPTIC = 0x1000;
const GAME_CONTROLLER = 0x2000;
const EVENTS = 0x4000;
const SENSOR = 0x8000;
}
pub enum WindowFlags: u32 {
Fullscreen = 0x00000001,
OpenGl = 0x00000002,
Shown = 0x00000004,
Hidden = 0x00000008,
Borderless = 0x00000010,
Resizable = 0x00000020,
Minimized = 0x00000040,
Maximized = 0x00000080,
InputGrabbed = 0x00000100,
InputFocus = 0x00000200,
MouseFocus = 0x00000400,
Foreign = 0x00000800,
FullscreenDesktop = 0x00001000,
AllowHighDpi = 0x00002000,
MouseCapture = 0x00004000,
AlwaysOnTop = 0x00008000,
SkipTaskbar = 0x00010000,
Utility = 0x00020000,
Tooltip = 0x00040000,
PopupMenu = 0x00080000,
Vulkan = 0x10000000,
Metal = 0x20000000,
#[repr(transparent)]
pub struct WindowFlags: u32 {
const FULLSCREEN = 0x00000001;
const OPENGL = 0x00000002;
const SHOWN = 0x00000004;
const HIDDEN = 0x00000008;
const BORDERLESS = 0x00000010;
const RESIZABLE = 0x00000020;
const MINIMIZED = 0x00000040;
const MAXIMIZED = 0x00000080;
const INPUT_GRABBED = 0x00000100;
const INPUT_FOCUS = 0x00000200;
const MOUSE_FOCUS = 0x00000400;
const FOREIGN = 0x00000800;
const FULLSCREEN_DESKTOP = Self::FULLSCREEN.bits | 0x00001000;
const ALLOW_HIGH_DPI = 0x00002000;
const MOUSE_CAPTURE = 0x00004000;
const ALWAYS_ON_TOP = 0x00008000;
const SKIP_TASKBAR = 0x00010000;
const UTILITY = 0x00020000;
const TOOLTIP = 0x00040000;
const POPUP_MENU = 0x00080000;
const VULKAN = 0x10000000;
const METAL = 0x20000000;
}
}
@ -141,7 +143,7 @@ extern "C" {
pub fn set_main_ready();
#[link_name = "SDL_Init"]
pub fn init(flags: u32) -> raw::c_int;
pub fn init(flags: InitFlags) -> raw::c_int;
#[link_name = "SDL_Quit"]
pub fn quit();
@ -158,7 +160,7 @@ extern "C" {
#[link_name = "SDL_CreateWindow"]
pub fn create_window(
title: Nts, x: raw::c_int, y: raw::c_int, w: raw::c_int, h: raw::c_int,
flags: u32,
flags: WindowFlags,
) -> *mut Window;
#[link_name = "SDL_DestroyWindow"]

7
fw/hal/win.rs

@ -19,10 +19,9 @@ impl<'a> Window<'a> {
sdl::WINDOW_POS_UNDEF,
w.into(),
h.into(),
(sdl::WindowFlags::Shown
| sdl::WindowFlags::Vulkan
| sdl::WindowFlags::Resizable)
.bits(),
sdl::WindowFlags::SHOWN
| sdl::WindowFlags::VULKAN
| sdl::WindowFlags::RESIZABLE,
);
if !handle.is_null() {
Ok(Self { handle, hal: PhantomData })

19
fw/render.rs

@ -107,7 +107,7 @@ struct Buf {
struct Shader {
module: vk::ShaderModule,
name: CString,
stage: vk::ShaderStageFlags,
stages: Vec<vk::ShaderStageFlags>,
}
struct Model {
@ -164,8 +164,7 @@ pub enum Err {
impl StaticData {
unsafe fn new(
window: &Window, conf: &Conf,
shaders: &[(data::shader::Module, vk::ShaderStageFlags)],
window: &Window, conf: &Conf, shaders: &[data::shader::Module],
) -> Result<Self, Err> {
let ent = unsafe { ash::Entry::new()? };
@ -246,9 +245,8 @@ impl StaticData {
let queue_srf = unsafe { dev.get_device_queue(qf.srf, 0) };
// compile shader modules
let shader_modules = vec_r![shaders, |(shd, stg)| unsafe {
Shader::new(&dev, shd, *stg)
}]?;
let shader_modules =
vec_r![shaders, |shd| unsafe { Shader::new(&dev, shd) }]?;
// create the command pool
let create_info = vk::CommandPoolCreateInfo {
@ -458,7 +456,11 @@ impl StaticData {
unsafe { self.dev.create_render_pass(&create_info, None)? };
// create the pipeline
let shader_stages = vec_e![&self.shader_modules, |shd| shd.stage_info()];
let shader_stages = self
.shader_modules
.iter()
.flat_map(|shd| shd.stage_infos())
.collect::<Vec<_>>();
let depth_stencil_state = vk::PipelineDepthStencilStateCreateInfo {
depth_test_enable: vk::TRUE,
@ -831,8 +833,7 @@ impl StaticData {
impl Renderer {
pub fn new(
window: &Window, conf: &Conf,
shaders: &[(data::shader::Module, vk::ShaderStageFlags)],
window: &Window, conf: &Conf, shaders: &[data::shader::Module],
) -> Result<Self, Err> {
unsafe {
let st = StaticData::new(window, conf, shaders)?;

26
fw/render/shader.rs

@ -1,9 +1,23 @@
use super::*;
use crate::data::shader::ShaderStage;
fn read_stages(s: ShaderStage) -> Vec<vk::ShaderStageFlags> {
let mut r = Vec::new();
if s.contains(ShaderStage::COMPUTE) {
r.push(vk::ShaderStageFlags::COMPUTE);
}
if s.contains(ShaderStage::FRAGMENT) {
r.push(vk::ShaderStageFlags::FRAGMENT);
}
if s.contains(ShaderStage::VERTEX) {
r.push(vk::ShaderStageFlags::VERTEX);
}
r
}
impl Shader {
pub(super) unsafe fn new(
dev: &ash::Device, shader: &data::shader::Module,
stage: vk::ShaderStageFlags,
) -> Result<Self, Err> {
let name = CString::new(shader.name.as_str())?;
@ -15,7 +29,7 @@ impl Shader {
let module = unsafe { dev.create_shader_module(&create_info, None)? };
Ok(Self { module, name, stage })
Ok(Self { module, name, stages: read_stages(shader.stag) })
}
pub(super) unsafe fn destroy(&self, dev: &ash::Device) {
@ -24,13 +38,13 @@ impl Shader {
}
}
pub(super) fn stage_info(&self) -> vk::PipelineShaderStageCreateInfo {
vk::PipelineShaderStageCreateInfo {
stage: self.stage,
pub(super) fn stage_infos(&self) -> Vec<vk::PipelineShaderStageCreateInfo> {
vec_e![&self.stages, |&stage| vk::PipelineShaderStageCreateInfo {
stage,
module: self.module,
p_name: self.name.as_ptr(),
..Default::default()
}
}]
}
}

Loading…
Cancel
Save