using System.Text; namespace Veldrid.SPIRV { /// /// Contains extension methods for loading modules from SPIR-V bytecode. /// public static class ResourceFactoryExtensions { /// /// Creates a vertex and fragment shader pair from the given pair containing SPIR-V /// bytecode or GLSL source code. /// /// The used to compile the translated shader code. /// The vertex shader's description. /// should contain SPIR-V bytecode or Vulkan-style GLSL source code which can be compiled to SPIR-V. /// The fragment shader's description. /// should contain SPIR-V bytecode or Vulkan-style GLSL source code which /// can be compiled to SPIR-V. /// A two-element array, containing the vertex shader (element 0) and the fragment shader (element 1). public static Shader[] CreateFromSpirv( this ResourceFactory factory, ShaderDescription vertexShaderDescription, ShaderDescription fragmentShaderDescription) { return CreateFromSpirv(factory, vertexShaderDescription, fragmentShaderDescription, new CrossCompileOptions()); } /// /// Creates a vertex and fragment shader pair from the given pair containing SPIR-V /// bytecode or GLSL source code. /// /// The used to compile the translated shader code. /// The vertex shader's description. /// should contain SPIR-V bytecode or Vulkan-style GLSL source code which can be compiled to SPIR-V. /// The fragment shader's description. /// should contain SPIR-V bytecode or Vulkan-style GLSL source code which /// can be compiled to SPIR-V. /// The which will control the parameters used to translate the /// shaders from SPIR-V to the target language. /// A two-element array, containing the vertex shader (element 0) and the fragment shader (element 1). public static Shader[] CreateFromSpirv( this ResourceFactory factory, ShaderDescription vertexShaderDescription, ShaderDescription fragmentShaderDescription, CrossCompileOptions options) { GraphicsBackend backend = factory.BackendType; if (backend == GraphicsBackend.Vulkan) { vertexShaderDescription.ShaderBytes = EnsureSpirv(vertexShaderDescription); fragmentShaderDescription.ShaderBytes = EnsureSpirv(fragmentShaderDescription); return new Shader[] { factory.CreateShader(ref vertexShaderDescription), factory.CreateShader(ref fragmentShaderDescription) }; } CrossCompileTarget target = GetCompilationTarget(factory.BackendType); VertexFragmentCompilationResult compilationResult = SpirvCompilation.CompileVertexFragment( vertexShaderDescription.ShaderBytes, fragmentShaderDescription.ShaderBytes, target, options); string vertexEntryPoint = (backend == GraphicsBackend.Metal && vertexShaderDescription.EntryPoint == "main") ? "main0" : vertexShaderDescription.EntryPoint; byte[] vertexBytes = GetBytes(backend, compilationResult.VertexShader); Shader vertexShader = factory.CreateShader(new ShaderDescription( vertexShaderDescription.Stage, vertexBytes, vertexEntryPoint)); string fragmentEntryPoint = (backend == GraphicsBackend.Metal && fragmentShaderDescription.EntryPoint == "main") ? "main0" : fragmentShaderDescription.EntryPoint; byte[] fragmentBytes = GetBytes(backend, compilationResult.FragmentShader); Shader fragmentShader = factory.CreateShader(new ShaderDescription( fragmentShaderDescription.Stage, fragmentBytes, fragmentEntryPoint)); return new Shader[] { vertexShader, fragmentShader }; } /// /// /// /// /// /// public static void CompileFromSpirv(ref ShaderDescription vertex,ref ShaderDescription fragment,GraphicsBackend backend = GraphicsBackend.Vulkan) { CompileFromSpirv(ref vertex, ref fragment, new CrossCompileOptions(), backend); } /// /// /// /// /// /// /// public static void CompileFromSpirv(ref ShaderDescription vertex, ref ShaderDescription fragment,CrossCompileOptions options, GraphicsBackend backend = GraphicsBackend.Vulkan) { if (backend == GraphicsBackend.Vulkan) { vertex.ShaderBytes = EnsureSpirv(vertex); fragment.ShaderBytes = EnsureSpirv(fragment); return; } CrossCompileTarget target = GetCompilationTarget(backend); VertexFragmentCompilationResult compilationResult = SpirvCompilation.CompileVertexFragment( vertex.ShaderBytes, fragment.ShaderBytes, target, options); vertex.EntryPoint = (backend == GraphicsBackend.Metal && vertex.EntryPoint == "main") ? "main0" : vertex.EntryPoint; vertex.ShaderBytes = GetBytes(backend, compilationResult.VertexShader); fragment.EntryPoint = (backend == GraphicsBackend.Metal && fragment.EntryPoint == "main") ? "main0" : fragment.EntryPoint; fragment.ShaderBytes = GetBytes(backend, compilationResult.FragmentShader); } /// /// Creates a compute shader from the given containing SPIR-V bytecode or GLSL source /// code. /// /// The used to compile the translated shader code. /// The compute shader's description. /// should contain SPIR-V bytecode or Vulkan-style GLSL source code which /// can be compiled to SPIR-V. /// The compiled compute . public static Shader CreateFromSpirv( this ResourceFactory factory, ShaderDescription computeShaderDescription) { return CreateFromSpirv(factory, computeShaderDescription, new CrossCompileOptions()); } /// /// Creates a compute shader from the given containing SPIR-V bytecode or GLSL source /// code. /// /// The used to compile the translated shader code. /// The compute shader's description. /// should contain SPIR-V bytecode or Vulkan-style GLSL source code which /// can be compiled to SPIR-V. /// The which will control the parameters used to translate the /// shaders from SPIR-V to the target language. /// The compiled compute . public static Shader CreateFromSpirv( this ResourceFactory factory, ShaderDescription computeShaderDescription, CrossCompileOptions options) { GraphicsBackend backend = factory.BackendType; if (backend == GraphicsBackend.Vulkan) { computeShaderDescription.ShaderBytes = EnsureSpirv(computeShaderDescription); return factory.CreateShader(ref computeShaderDescription); } CrossCompileTarget target = GetCompilationTarget(factory.BackendType); ComputeCompilationResult compilationResult = SpirvCompilation.CompileCompute( computeShaderDescription.ShaderBytes, target, options); string computeEntryPoint = (backend == GraphicsBackend.Metal && computeShaderDescription.EntryPoint == "main") ? "main0" : computeShaderDescription.EntryPoint; byte[] computeBytes = GetBytes(backend, compilationResult.ComputeShader); return factory.CreateShader(new ShaderDescription( computeShaderDescription.Stage, computeBytes, computeEntryPoint)); } private static unsafe byte[] EnsureSpirv(ShaderDescription description) { if (Util.HasSpirvHeader(description.ShaderBytes)) { return description.ShaderBytes; } else { fixed (byte* sourceAsciiPtr = description.ShaderBytes) { SpirvCompilationResult glslCompileResult = SpirvCompilation.CompileGlslToSpirv( (uint)description.ShaderBytes.Length, sourceAsciiPtr, null, description.Stage, description.Debug, 0, null); return glslCompileResult.SpirvBytes; } } } private static byte[] GetBytes(GraphicsBackend backend, string code) { switch (backend) { case GraphicsBackend.Direct3D11: case GraphicsBackend.OpenGL: case GraphicsBackend.OpenGLES: return Encoding.ASCII.GetBytes(code); case GraphicsBackend.Metal: return Encoding.UTF8.GetBytes(code); default: throw new SpirvCompilationException($"Invalid GraphicsBackend: {backend}"); } } private static CrossCompileTarget GetCompilationTarget(GraphicsBackend backend) { switch (backend) { case GraphicsBackend.Direct3D11: return CrossCompileTarget.HLSL; case GraphicsBackend.OpenGL: return CrossCompileTarget.GLSL; case GraphicsBackend.Metal: return CrossCompileTarget.MSL; case GraphicsBackend.OpenGLES: return CrossCompileTarget.ESSL; default: throw new SpirvCompilationException($"Invalid GraphicsBackend: {backend}"); } } } }