Open mitchmindtree opened 5 years ago
Wouldn't it just be a matter of us somehow invoking the glslangValidator during runtime to convert the .frag shader for example to a .spv file and then load the resulting spir-v files using the techniques in this example?
Yeah that's probably what vulkano-shaders is currently doing at compile time, I reckon we just want to contribute a patch so that it also offers a runtime version of what it's currently doing at compile time.
There's an interesting issue here at vulkano-rs/vulkano#910 on dropping vulkano-shaders
in favour of using rspirv for generating rust code from shaderc.
I wonder if it's worth just using shaderc directly (and not touching vulkano-shaders
for now), or if rspirv might be useful for validation.
Ok I've got this to atleast load a fragment shader at run time. Todo:
examples/vulkan/shaders/
hotload_vert.glsl
and hotload_frag.glsl
in the above directoryVertInput
that has fields to match the possible inputs to the vertex shader and can be set at runtimeSo there is some code that needs to be specific to each shader. This is the what I need for the example:
impl Iterator for FragInputIter {
type Item = ShaderInterfaceDefEntry;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
None
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let len = (0 - self.0) as usize;
(len, Some(len))
}
}
impl Iterator for FragOutputIter {
type Item = ShaderInterfaceDefEntry;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
// Note that color fragment color entry will be determined
// automatically by Vulkano.
if self.0 == 0 {
self.0 += 1;
return Some(ShaderInterfaceDefEntry {
location: 0..1,
format: vk::Format::R32G32B32A32Sfloat,
name: Some(Cow::Borrowed("f_color"))
})
}
None
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let len = (1 - self.0) as usize;
(len, Some(len))
}
}
We can't dynamically link types but we can probably generate the impl Iterator
or maybe just get next()
and size_hint()
to call some dynamically linked functions so that we can update them to reflect the correct inputs and outputs to each shader at runtime.
The more I look into this the more I think we should probably just be using vulkan_shaders
to compile a dynamic lib at runtime. As there is so many checks that it is already doing.
But this isn't really possible because we can't have runtime types
I'm not sure what the best way to move forward here is.
glslangValidator
and don't allow changes to shader input / outputs at runtime. Easiestvulkano_shaders
so that there is a way to just produce runtime function tokens and not the types. (We'd have to get this to land in vulkano) Mediumvulkano_shaders
and sort through the tokens and just use the ones we need. (It could be hard to find the tokens we need but we don't need to modify vulkano) Medium
- shaderc ...It also makes compiling slow
Oh true, have you run some tests that have confirmed this? I would have imagined it would be just as fast as glslangValidator
at least, as the compilation process would get compiled into our exe rather than I/Oing out to glslangValidator
. Both shaderc
and glslangValidator
have to type check etc before they can compile to SPIR-V anyways, so it shouldn't be much more expensive at all to get some runtime checks from either (not sure if you can just request type info from glslangValidator
, but you should be able to from shaderc).
I think I'd be more inclined to go with shaderc
as it seems like a more portable solution (a user doesn't have to also separately install glslangValidator
, or we don't have to work out how to ship an instance compatible with each OS). That said, shaderc
does need some deps like python
and cmake
, though we already need those for the vulkano-shaders
crate to work anyways. I guess the other issue is that while shaderc
is a more easily portable option for us, it's still not at all pure-rust (hence you're hitting segfaults... :mask: ). Ideally, something like rspirv
will gain a GLSL->SPIR-V front-end for a pure-rust SPIR-V stack eventually, but I'm not sure what progress is like on this yet.
- Modify vulkano_shaders so that there is a way to just produce runtime function tokens and not the types. (We'd have to get this to land in vulkano)
There's actually already a bit of discussion about refactoring vulkano-shaders
- vulkano-rs/vulkano#945 is worth a read. I think there was also another issue there about eventually moving to use a pure-rust solution like rspirv (not sure if it was that issue or another) so I'm not sure how easy it would be to land a run-time alternative in vulkano-shaders
where there's already talk of breaking it up and switching from shaderc
some day.
The most promising options in my mind are:
2.
where we use shaderc to do compilation and all the checks. We could use vulkano-shaders
' compile time checks as a reference on how to do this, however we'd obviously be applying these at run-time instead.glslangValidator
for now and require that users have it installed until a pure rust GLSL->SPIR-V solution like rspirv
becomes usable. It's hard to say when this will happen if ever, but I've defs seen others also interested in it and getting rid of shaderc
is pretty good motivation.Hope I'm not making this more confusing haha. What are your thoughts?
Oh yeah this is the more general follow up on refactoring vulkano-shaders by the current maintainer vulkano-rs/vulkano#1039.
Whoops should have clarified. It makes the rust example slower to compile not the shader but I haven't tested this to confirm.
You're option 2. where we use shaderc to do compilation and all the checks. We could use vulkano-shaders' compile time checks as a reference on how to do this, however we'd obviously be applying these at run-time instead.
I think if we start writing test like what is in vulkan_shaders then we will just end up with vulkan_shaders with a runtime option.
I'm not sure I was being super clear above. The main issue is that we need to generate rust code at runtime if any inputs or outputs change to any shaders. vulkan_shaders
does this. I was thinking of doing something similar to gantz where we compile a little rust crate at runtime with these functions in it. The problem is vulkan_shaders currently outputs the whole types and not just the impl blocks (which could be made just functions).
I was thinking of just adding another function to vulkan_shaders that only outputs the tokens for these functions.
But you're right I don't think they would land it easily because they probably want to eventually modify vulkano so that this runtime codegen isn't required.
I sort of feel like the best option is just glslvalidator or shaderc without any checks.
The main issue is that we need to generate rust code at runtime if any inputs or outputs change to any shaders.
What do you mean by inputs or outputs here? It's not clear to me why we need to generate rust code at runtime. I'm fairly sure compiling rust at runtime shouldn't be necessary, but for a run-time solution to work we might need to implement some unsafe vulkano traits for a custom "run-time" checked version (where Result
s are returned) of some existing vulkano types that are normally compile-time checked.
Basically to do this:
let vert_main = unsafe { vs2.graphics_entry_point(
CStr::from_bytes_with_nul_unchecked(b"main\0"),
VertInput,
VertOutput,
VertLayout(vk::ShaderStages { vertex: true, ..vk::ShaderStages::none() }),
vk::pipeline::shader::GraphicsShaderType::Vertex
) };
You need VertInput
etc.
which looks like this:
// This structure will tell Vulkan how input entries of our vertex shader look like
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
struct VertInput;
unsafe impl ShaderInterfaceDef for VertInput {
type Iter = VertInputIter;
fn elements(&self) -> VertInputIter {
VertInputIter(0)
}
}
#[derive(Debug, Copy, Clone)]
struct VertInputIter(u16);
impl Iterator for VertInputIter {
type Item = ShaderInterfaceDefEntry;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
// There are things to consider when giving out entries:
// * There must be only one entry per one location, you can't have
// `color' and `position' entries both at 0..1 locations. They also
// should not overlap.
// * Format of each element must be no larger than 128 bits.
if self.0 == 0 {
self.0 += 1;
return Some(ShaderInterfaceDefEntry {
location: 0..1,
format: vk::Format::R32G32Sfloat,
name: Some(Cow::Borrowed("position"))
})
}
None
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
// We must return exact number of entries left in iterator.
let len = (1 - self.0) as usize;
(len, Some(len))
}
}
impl ExactSizeIterator for VertInputIter { }
Where the next()
returns a ShaderInterfaceDefEntry
which matches the uniforms or constants etc. Like it an input would need to match this:
layout(location = 0) in vec2 position;
we might need to implement some unsafe vulkano traits
That would be cool if we could avoid the above by doing this
It should be possible to change the VertInput
type to store fields for whatever inputs might change at runtime and then update these fields each time the shader changes at runtime. Then the ShaderInterfaceDef
and Iterator
method implementations use these fields to construct the necessary ShaderInterfaceDefEntry
s to be returned.
I think the only reason VertInput
has no fields in this example is because it's a simple-as-possible demonstration of how to run-time load a SPIR-V shader.
This is the trait that the pipeline builder needs. Perhaps it could be implemented. We would still need a way to say what the inputs / outputs are. But this might be about to be done without codegen. I think it would just be a matter of making the iterator.
Might need to use notify. (Do we care about this? Would someone be saving so often that the response should be < 2secs?)
I have used notify
for my gfx
+ gl
shader reloading system. It's pretty much instantaneous and I haven't observed any performance issues at all.
I've hit another road block with this.
I'm trying to use the vulkan_shaders
crate to parse the spirv and find the values for the interfaces between each shader.
However everything in vulkan_shaders
is private except the macro.
I tried to pull out just what I needed to do this from the crate but pretty much ended up with the whole crate.
Options:
vulkano_shaders
and make another pub function just for parsing spirv and getting the interfaces.What do people think is a good option here? In the meantime I'm going to search for other ways to solve this.
I just found spirv-reflect-rs which looks like it can do just this. I'm going to try and implement the example using this crate now.
This is a super useful technique for getting near-realtime feedback while prototyping graphics applications or sketching.
Currently vulkano only provides provides a way for loading SPIR-V shader code at runtime via the
ShaderModule::new
function. Originally I thought we might want to add an alternativeShaderModule::from_glsl
constructor, but it looks like keeping a clear boundary and only handling SPIR-V is a conscious choice for vulkano itself.Instead, it is the role of the
vulkano-shaders
crate for compiling GLSL to SPIR-V, however it currently only exposes a way to do this at compile time via a macro. It would be worth investigating how thevulkano-shaders
crate does this compilation and exposing it in a way so that it can be shared between both the compile-time macro and a runtime function.