vulkano icon indicating copy to clipboard operation
vulkano copied to clipboard

Compile, load and run GLSL at runtime

Open Limeth opened this issue 8 years ago • 30 comments

I can currently transpile GLSL to SPIR-V at runtime via:

    let vs_spirv = glsl_to_spirv::compile(SHADER_VERTEX, ShaderType::Vertex)
        .expect("failed to transpile the vertex shader");
    let fs_spirv = glsl_to_spirv::compile(SHADER_FRAGMENT, ShaderType::Fragment)
        .expect("failed to transpile the fragment shader");

Then, I edited vulkano-shaders so I could do this:

    let vs = vulkano_shaders::load_spirv(vs_spirv, &device);
    let fs = vulkano_shaders::load_spirv(fs_spirv, &device);

The last thing I need, is to get the main_entry_point to run the shader, if I understand this correctly. I am willing to implement it myself, so it complies with the current code. Would you mind guiding me?

Limeth avatar Sep 26 '16 06:09 Limeth

Getting the entry point is the trickiest aspect, as it's at this moment that you must specify the input/output interfaces, the specialization constants and the pipeline layout.

For example if you call vertex_shader_entry_point, the arguments you must pass are the name of the entry point (usually main), then two objects that must implement the ShaderInterfaceDef trait, then one object that must implement PipelineLayoutDesc. Also, the S parameter must implement SpecializationConstants.

If you're looking for a quick solution, you can implement them in a dummy way (returning empty sets). Doing it properly would require a rework of vulkano_shaders in order to separate the part that introspects the SPIR-V from the part that emits Rust code.

tomaka avatar Sep 26 '16 06:09 tomaka

To elaborate a bit more on this.

I guess it should also be possible to write wrappers around ComputePipeline and GraphicsPipeline that automatically build ShaderModule objects at runtime and reload the modules whenever their corresponding files are modified. The reload can fail obviously if the source code of the shader doesn't compile, but also if the shader's interface no longer matches the pipeline layout or subpass passed used to build the pipeline.

You would essentially create either a ComputePipeline or a RuntimeComputePipeline for example.

The drawback of the RuntimeComputePipeline is of course that it needs to compare the pipeline layout or subpass with values loaded at runtime, rather than optimizing out these comparisons at compile-time.

tomaka avatar Sep 27 '16 16:09 tomaka

I wouldn't see it as a drawback. Which situations require you to recompile the shaders? It would be tools! As for tools, it does not matter if they stutter a bit when reloading the shader, so imho it's ok :)

I would love to see this feature in vulkano!

minecrawler avatar Sep 28 '16 12:09 minecrawler

More plan:

  • [ ] Merge VertexShaderEntryPoint, TessControlShaderEntryPoint, etc. into a single struct named ShaderEntryPoint. One of the members will need to contain details specific to each entry point.
  • [ ] Add a new trait ShaderEntryPointLoad that has a method named load() that returns a ShaderEntryPoint. The various template parameters of ShaderEntryPoint are associated types of ShaderEntryPointLoad. ShaderEntryPoint itself implements the trait.
  • [ ] Add a trait ShaderEntryPointRuntimeLoad that requires ShaderEntryPointLoad and that adds a method named reload_if_changed().
  • [ ] Make GraphicsPipelineParams and ComputePipelineParams generic over the entry point loader. In other words, instead of having a member of type ShaderEntryPoint<...>, have it be of type Vs where Vs must implement ShaderEntryPointLoad.
  • [ ] Add AutoReloadGraphicsPipeline and AutoReloadComputePipeline that wrap around GraphicsPipeline and ComputePipeline.
  • [ ] Add a struct named SpirvFileShaderLoader that can be created from a Path and that implements ShaderEntryPointRuntimeLoad. The struct will check whether the file has changed from time to time, and if so re-parse it.
  • [ ] Add a feature to vulkano named glsl, which corresponds to runtime compilation of glsl. Vulkano depends on glsl-to-spirv if that feature is enabled, and wraps around glsl-to-spirv's capabilities.
  • [ ] Move the parsing and analyzing capabilities, plus the glsl compilation, from vulkano-shaders to vulkano. Make the SPIR-V analyzer return enums. Make vulkano-shaders depend on vulkano to parse and analyze its SPIR-V files and to compile GLSL. The only role of vulkano-shaders will be to generate Rust code at compile-time using the output of vulkano's SPIR-V analyzer.
  • [ ] Add another struct named GlslFileShaderLoader behind the feature glsl. The struct does the same as SpirvFileShaderLoader but compiles the file from GLSL first.

The remaining question is: should the shaders be reloaded automatically, or does the user need to manually call a method? Another small problem is that if you record a command buffer that uses a pipeline, it can't be reloaded (unless you rebuild the command buffer).

tomaka avatar Sep 28 '16 16:09 tomaka

Merge VertexShaderEntryPoint, TessControlShaderEntryPoint, etc. into a single struct named ShaderEntryPoint. One of the members will need to contain details specific to each entry point.

How about something like this? Am I heading the right direction?

Limeth avatar Oct 03 '16 09:10 Limeth

@Limeth Yes I suggest adding getters in ShaderEntryPoint, for example fn input(&self) -> Option<&I> or fn geometry_execution_mode(&self) -> Option<GeometryShaderExecutionMode>.

tomaka avatar Oct 03 '16 09:10 tomaka

What exactly should happen, when a file change is detected by SpirvFileShaderLoader?

Limeth avatar Oct 04 '16 17:10 Limeth

The reload_if_changed() method implemented on SpirvFileShaderLoader should check for file changes and if so reload the file, reparse it, and build a new ShaderEntryPoint.

tomaka avatar Oct 04 '16 18:10 tomaka

So SpirvFileShaderLoader itself will have to insert it into GraphicsPipeline or GraphicsPipelineParams, meaning that it will have to hold a reference to them, correct?

Limeth avatar Oct 04 '16 18:10 Limeth

No, the idea is this:

pub struct GraphicsPipelineParams<Vs: ShaderEntryPointLoad> {
    pub vertex_shader: Vs,
    ...
}

// GraphicsPipeline doesn't get any change except in its constructor

pub struct RuntimeGraphicsPipeline<...> {
    vs_loader: Box<ShaderEntryPointRuntimeLoad>,
    fs_loader: ...,
    ...
    pipeline: GraphicsPipeline<...>,
}

impl RuntimeGraphicsPipeline<...> {
    pub fn new<Vs>(device: &Arc<Device>, params: GraphicsPipelineParams<Vs>)
        where Vs: ShaderEntryPointRuntimeLoad
    {
        ...
    }
}

It's only RuntimeGraphicsPipeline that knows about stuff related to reloading.

tomaka avatar Oct 04 '16 18:10 tomaka

You will probably face ownership issues with the fact that you'll need the ShaderEntryPointLoad for both GraphicsPipeline::new() and to store in RuntimeGraphicsPipeline.

To solve that you can add impl<'a, T> ShaderEntryPointLoad for &'a T where T: 'a + ShaderEntryPointLoad { ... } and pass the loader by reference instead of by value within RuntimeGraphicsPipeline::new().

tomaka avatar Oct 04 '16 18:10 tomaka

Would you mind giving me some feedback? I am not too sure about all this. :D

Limeth avatar Oct 10 '16 15:10 Limeth

Hmm you have a different approach than what I was thinking of.

In my mind, the module member of ShaderEntryPoint would become a Cow, so that the ShaderEntryPointLoad trait can choose whether to return a module+an entry point (by returning an entry point that has ownership of its module), or just an entry point of an existing module.

With your approach you're supposing that you're assuming that the implementation of ShaderEntryPointLoad holds a ShaderModule by reference. This is problematic because if, for example, the RuntimeComputePipeline struct needs to hold the implementation ShaderEntryPointLoad, then that means the RuntimeComputePipeline will also need a lifetime and the user won't be able to manipulate a standalone Arc<RuntimeComputePipeline>.

tomaka avatar Oct 10 '16 20:10 tomaka

Note that any change you make to vulkano-shaders will likely heavily conflict with some changes I'm going to make in the incoming branch. Please tell me if you start anything so that we get in sync.

tomaka avatar Oct 15 '16 15:10 tomaka

Has there been any update on this? I am currently trying to load glsl shaders from a file on runtime as well but didn't find a way till now.

SiebenCorgie avatar Jul 30 '17 20:07 SiebenCorgie

Is this issue still open? I have recently started adopting Vulkan on Rust. Currently trying to evaluate which wrapper to use, between vulkano, ash and gfx-ll (Vulkan backend). Started with Vulkano because it's safer and easier to get off the ground but I found this brick wall on compiling and loading shaders at runtime - which is a non-negotiable requirement.

Before I plan to switch to one of the (more painful) alternatives, is this going to be supported any time soon? I need to know sooner than later as switching later would just be even more painful.

What does @rukai say/suggest? Does https://github.com/vulkano-rs/vulkano/blob/master/examples/src/bin/runtime-shader/main.rs solve the problem?

norru avatar Apr 29 '19 10:04 norru

@norru you might be interested in @freesig's shade_runner crate (you can find a nannou hot-loading example that uses it here). It's still brand new, but at least demonstrates how this is possible with current vulkano.

Vulkano already allows for run-time SPIR-V shader loading as you pointed out with that example - the real trick is finding the nicest way of going from GLSL -> SPIR-V.

To be honest, I totally understand why vulkano or any other vulkan lib would feel it is not their responsibility as, in theory, the SPIR-V you submit for your shader component of your vulkan pipeline could be from anywhere (e.g. RLSL, nim, haskell, any other language that might compile to SPIR-V in the future).

In the case of shade_runner, shaderc is used to compile the GLSL at runtime and spirv_reflect is used to produce the information necessary for integration with the vulkano pipeline.

In the future it might be nice for vulkano-shaders to provide a runtime compilation option alongside its existing compile-time option, but it looks like there's some inclination to wait for a pure-rust GLSL->SPIR-V front-end to appear before that goes ahead #910 #1039.

mitchmindtree avatar Apr 29 '19 11:04 mitchmindtree

@mitchmindtree Thanks for the suggestion!

Not having to depend on GLSL compiling at runtime is kind of the point of Vulkan in itself, but I'm ok with feeding it SPIR-V code instead if everything else fails.

shaderc + spirv_reflect or even glslValidator + spirv_reflectsounds reasonable. Also rspirvhttps://github.com/google/rspirv sounds really promising, I'll have a look at how difficult it is to implement a working combination.

norru avatar Apr 29 '19 16:04 norru

I also need to be in control on when and how a shader gets reloaded so the "autoreload" stuff needs to be skippable.

norru avatar Apr 29 '19 20:04 norru

shade_runner is basically just shaderc + spirv_reflect. Still some more work to make it complete though. I'll put some more examples up when I get a chance but you can use the load() and parse() functions directly if you don't want the auto reloaded.

Not having to depend on GLSL compiling at runtime is kind of the point of Vulkan in itself, but I'm ok with feeding it SPIR-V code instead if everything else fails.

What is your desired workflow here?

freesig avatar Apr 29 '19 23:04 freesig

I have got two use cases. Something of the ilk of:

myapp --shader_folder=../my_shaders

myappgui --shader_folder=../my_shaders

myapp is a Rust console binary which is built once and run multiple times (possibly concurrently) on different ../my_shaders_X folders.

myappgui is an application which, after loading the shaders dynamically once, reacts by some kind of changes in the ../my_shaders folder, including both shader code/configuration and data.

Shader compilation at compile time is not required in either case.

This is a very common use case for game engines (editor/tools and runtime).

norru avatar Apr 30 '19 09:04 norru

If the shaders in ../my_shaders are .glsl then you could do both use cases with shade_runner. Alternatively for use case 1 you could store the shaders as compiled spirv and then just use shade_runner to parse them and generate the Entry point.

There's still a little more work for shade_runner to be complete but I'm actively working on it. Feel free to open an issue if something isn't working.

Ultimately it would be nice to have this built into vulkano. Or atleast have vulkano be able to compile and generate the entry points at run time. Hopefully shade_runner can show one way of achieving this.

freesig avatar May 01 '19 00:05 freesig

@freesig For the time being, GLSL is fine. Looking forward to seeing the upcoming examples.

If it helps, my priority is on compute shaders. It would be great to see a variant of https://github.com/vulkano-rs/vulkano-examples/blob/master/src/bin/basic-compute-shader.rs with the compile-time shaders replaced by your runtime stuff.

norru avatar May 02 '19 10:05 norru

Would is be desireable to use Naga for shader compilation in the future? It seems like a pure rust solution usable at compile or runtime would resolve some headaches. And dropping the FFI dependency on shaderc would also ease project setup and portability.

Tastaturtaste avatar Sep 26 '23 08:09 Tastaturtaste

Would is be desireable to use Naga for shader compilation in the future? It seems like a pure rust solution usable at compile or runtime would resolve some headaches. And dropping the FFI dependency on shaderc would also ease project setup and portability.

It certainly would be preferred over shaderc, but how production-ready is it?

Rua avatar Sep 28 '23 12:09 Rua

Has glslc being considered in the meantime? I'm 99% sure its bundled with the Vulkan SDK so binary versions should be available on everyone's computer

hYdos avatar Sep 28 '23 12:09 hYdos

Would is be desireable to use Naga for shader compilation in the future? It seems like a pure rust solution usable at compile or runtime would resolve some headaches. And dropping the FFI dependency on shaderc would also ease project setup and portability.

It certainly would be preferred over shaderc, but how production-ready is it?

I am not certain on that. It has not yet reached a 1.0 major version, but that doesn't necessarily mean it's not production ready. The readme lists glsl as secondary support target, with support only for glsl versions 440+ and only vulkan semantics. The latter should be no problem. I don't now how much the former is, but I would guess not a big one. Overall, I couldn't find any statement regarding production readiness one way or the other.

Tastaturtaste avatar Sep 28 '23 13:09 Tastaturtaste

Has glslc being considered in the meantime? I'm 99% sure its bundled with the Vulkan SDK so binary versions should be available on everyone's computer

That's what we use currently.

marc0246 avatar Sep 28 '23 15:09 marc0246