johnno1962 / InjectionIII

Re-write of Injection for Xcode in (mostly) Swift
MIT License
4.09k stars 320 forks source link

Q: SwiftUI Metal shader reload #516

Closed gonchar closed 2 months ago

gonchar commented 2 months ago

Hey! First, I want to say that I love InjectionIII! thank you for your hard work, and for keeping improving and supporting this project! I use it all the time in my development process. Currently, I’m working on some shader effects in SwiftUI, and it would be amazing if pressing the save button on my Metal file could automatically hot-reload it into the app.

With regard that I have 3 questions: 1) Is it smth that InjectionIII can do? 2) Or any some "easy" changes can be made to make it work? 3) If neither is possible, how would you approach this task?

Compilation process

Every metal file inside the project is getting compiled into .ir file (.air is archive of .ir files)

CompileMetalFile projectPath/playground/glowLayerShader.metal (in target 'playground' from project 'App')
    cd projectPath
    /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/metal 
    -c 
    -target air64-apple-ios17.0-simulator 
    -gline-tables-only 
    -frecord-sources -IDerivedDataPath/Build/Products/Debug-iphonesimulator/include 
    -FDerivedDataPath/Build/Products/Debug-iphonesimulator -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator17.5.sdk 
    -ffast-math 
    -serialize-diagnostics DerivedDataPath/Build/Intermediates.noindex/App.build/Debug-iphonesimulator/playground.build/Metal/glowLayerShader.dia 
    -o DerivedDataPath/Build/Intermediates.noindex/App.build/Debug-iphonesimulator/playground.build/Metal/glowLayerShader.air 
    -index-store-path DerivedDataPath/Index.noindex/DataStore 
    -MMD 
    -MT dependencies 
    -MF DerivedDataPath/Build/Intermediates.noindex/App.build/Debug-iphonesimulator/playground.build/Metal/glowLayerShader.dat 
    projectPath/playground/glowLayerShader.metal

and then it packs it into default.metallib which is then in runtime loaded as default ShaderLibrary object (my best guess) and makes it available as ShaderLibrary.default.glowLayerShader

MetalLink DerivedDataPath/Build/Products/Debug-iphonesimulator/playground.app/default.metallib (in target 'playground' from project 'App')
    cd projectPath
    /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/metal -target air64-apple-ios17.0-simulator -frecord-sources -o DerivedDataPath/Build/Products/Debug-iphonesimulator/playground.app/default.metallib DerivedDataPath/Build/Intermediates.noindex/App.build/Debug-iphonesimulator/playground.build/Metal/glowLayerShader.air

also it is possible to load metallib in runtime using this API to precompile sources

let libraryURL = Bundle.main.url(forResource: "glowLayerShader",
                                 withExtension: "metallib")
guard let libraryURL else {
  print("Couldn't find library file: glowLayerShader");
  return;
}
myshaderlib = ShaderLibrary.init(url: libraryURL)
shaderFunction = .init(library: myshaderlib!, name: "glowLayerShader")

and then use in SwiftUI as

.layerEffect(shaderFunction(.boundingRect, .float(0.0)), maxSampleOffset: .zero)

Curious to hear your thoughts. I believe this could be a game changer for building amazing wow-effects.

Thank you!

johnno1962 commented 2 months ago

Hi @gonchar, This could be relatively easy or not possible at all - I really can't say as I know nothing about metal or shaders. What do you think @oryonatan?

oryonatan commented 2 months ago

Hi @gonchar I didn't use Injection in the past with metal as the built-in Metal debugger already supports shader hot reloading which worked for me most of the times, I'm not sure though if this is applicable to shaders that are used in SwiftUI, as this requires capturing the command buffer and controlling the rendering.

I think that compiling the shader in runtime from source seems like the easier and it should probably work with Injection without changes - I suggest looikng into the the MTLDevice.makeLibrary(source:options:) method, which should allow you to create a new shader library from a string, if you can change the string in runtime using injection, and cause the code to recompile it upon Injection and redraw the view, you should be good I think.

gonchar commented 2 months ago

Do you know a way how to convert MTLLibrary protocol to ShaderLibrary(which is used in SwiftUI)?

I will close the issue, as it was just a suggestion.