using System; using System.Text; namespace Veldrid.SPIRV { /// /// Static functions for cross-compiling SPIR-V bytecode to various shader languages, and for compiling GLSL to SPIR-V. /// public static class SpirvCompilation { /// /// Cross-compiles the given vertex-fragment pair into some target language. /// /// The vertex shader's SPIR-V bytecode or ASCII-encoded GLSL source code. /// The fragment shader's SPIR-V bytecode or ASCII-encoded GLSL source code. /// The target language. /// A containing the compiled output. public static unsafe VertexFragmentCompilationResult CompileVertexFragment( byte[] vsBytes, byte[] fsBytes, CrossCompileTarget target) => CompileVertexFragment(vsBytes, fsBytes, target, new CrossCompileOptions()); /// /// Cross-compiles the given vertex-fragment pair into some target language. /// /// The vertex shader's SPIR-V bytecode or ASCII-encoded GLSL source code. /// The fragment shader's SPIR-V bytecode or ASCII-encoded GLSL source code. /// The target language. /// The options for shader translation. /// A containing the compiled output. public static unsafe VertexFragmentCompilationResult CompileVertexFragment( byte[] vsBytes, byte[] fsBytes, CrossCompileTarget target, CrossCompileOptions options) { int size1 = sizeof(CrossCompileInfo); int size2 = sizeof(InteropArray); byte[] vsSpirvBytes; byte[] fsSpirvBytes; if (Util.HasSpirvHeader(vsBytes)) { vsSpirvBytes = vsBytes; } else { fixed (byte* sourceTextPtr = vsBytes) { SpirvCompilationResult vsCompileResult = CompileGlslToSpirv( (uint)vsBytes.Length, sourceTextPtr, string.Empty, ShaderStages.Vertex, target == CrossCompileTarget.GLSL || target == CrossCompileTarget.ESSL, 0, null); vsSpirvBytes = vsCompileResult.SpirvBytes; } } if (Util.HasSpirvHeader(fsBytes)) { fsSpirvBytes = fsBytes; } else { fixed (byte* sourceTextPtr = fsBytes) { SpirvCompilationResult fsCompileResult = CompileGlslToSpirv( (uint)fsBytes.Length, sourceTextPtr, string.Empty, ShaderStages.Fragment, target == CrossCompileTarget.GLSL || target == CrossCompileTarget.ESSL, 0, null); fsSpirvBytes = fsCompileResult.SpirvBytes; } } int specConstantsCount = options.Specializations.Length; NativeSpecializationConstant* nativeSpecConstants = stackalloc NativeSpecializationConstant[specConstantsCount]; for (int i = 0; i < specConstantsCount; i++) { nativeSpecConstants[i].ID = options.Specializations[i].ID; nativeSpecConstants[i].Constant = options.Specializations[i].Data; } CrossCompileInfo info; info.Target = target; info.FixClipSpaceZ = options.FixClipSpaceZ; info.InvertY = options.InvertVertexOutputY; info.NormalizeResourceNames = options.NormalizeResourceNames; fixed (byte* vsBytesPtr = vsSpirvBytes) fixed (byte* fsBytesPtr = fsSpirvBytes) { info.VertexShader = new InteropArray((uint)vsSpirvBytes.Length / 4, vsBytesPtr); info.FragmentShader = new InteropArray((uint)fsSpirvBytes.Length / 4, fsBytesPtr); info.Specializations = new InteropArray((uint)specConstantsCount, nativeSpecConstants); CompilationResult* result = null; try { result = VeldridSpirvNative.Default.CrossCompile(&info); if (!result->Succeeded) { throw new SpirvCompilationException( "Compilation failed: " + Util.GetString((byte*)result->GetData(0), result->GetLength(0))); } string vsCode = Util.GetString((byte*)result->GetData(0), result->GetLength(0)); string fsCode = Util.GetString((byte*)result->GetData(1), result->GetLength(1)); ReflectionInfo* reflInfo = &result->ReflectionInfo; VertexElementDescription[] vertexElements = new VertexElementDescription[reflInfo->VertexElements.Count]; for (uint i = 0; i < reflInfo->VertexElements.Count; i++) { ref NativeVertexElementDescription nativeDesc = ref reflInfo->VertexElements.Ref(i); vertexElements[i] = new VertexElementDescription( Util.GetString((byte*)nativeDesc.Name.Data, nativeDesc.Name.Count), nativeDesc.Semantic, nativeDesc.Format, nativeDesc.Offset); } ResourceLayoutDescription[] layouts = new ResourceLayoutDescription[reflInfo->ResourceLayouts.Count]; for (uint i = 0; i < reflInfo->ResourceLayouts.Count; i++) { ref NativeResourceLayoutDescription nativeDesc = ref reflInfo->ResourceLayouts.Ref(i); layouts[i].Elements = new ResourceLayoutElementDescription[nativeDesc.ResourceElements.Count]; for (uint j = 0; j < nativeDesc.ResourceElements.Count; j++) { ref NativeResourceElementDescription elemDesc = ref nativeDesc.ResourceElements.Ref(j); layouts[i].Elements[j] = new ResourceLayoutElementDescription( Util.GetString((byte*)elemDesc.Name.Data, elemDesc.Name.Count), elemDesc.Kind, elemDesc.Stages, elemDesc.Options); } } SpirvReflection reflection = new SpirvReflection( vertexElements, layouts); return new VertexFragmentCompilationResult(vsCode, fsCode, reflection); } finally { if (result != null) { VeldridSpirvNative.Default.FreeResult(result); } } } } /// /// Cross-compiles the given vertex-fragment pair into some target language. /// /// The compute shader's SPIR-V bytecode or ASCII-encoded GLSL source code. /// The target language. /// A containing the compiled output. public static unsafe ComputeCompilationResult CompileCompute( byte[] csBytes, CrossCompileTarget target) => CompileCompute(csBytes, target, new CrossCompileOptions()); /// /// Cross-compiles the given vertex-fragment pair into some target language. /// /// The compute shader's SPIR-V bytecode or ASCII-encoded GLSL source code. /// The target language. /// The options for shader translation. /// A containing the compiled output. public static unsafe ComputeCompilationResult CompileCompute( byte[] csBytes, CrossCompileTarget target, CrossCompileOptions options) { byte[] csSpirvBytes; if (Util.HasSpirvHeader(csBytes)) { csSpirvBytes = csBytes; } else { fixed (byte* sourceTextPtr = csBytes) { SpirvCompilationResult vsCompileResult = CompileGlslToSpirv( (uint)csBytes.Length, sourceTextPtr, string.Empty, ShaderStages.Compute, target == CrossCompileTarget.GLSL || target == CrossCompileTarget.ESSL, 0, null); csSpirvBytes = vsCompileResult.SpirvBytes; } } CrossCompileInfo info; info.Target = target; info.FixClipSpaceZ = options.FixClipSpaceZ; info.InvertY = options.InvertVertexOutputY; info.NormalizeResourceNames = options.NormalizeResourceNames; fixed (byte* csBytesPtr = csSpirvBytes) fixed (SpecializationConstant* specConstants = options.Specializations) { info.ComputeShader = new InteropArray((uint)csSpirvBytes.Length / 4, csBytesPtr); info.Specializations = new InteropArray((uint)options.Specializations.Length, specConstants); CompilationResult* result = null; try { result = VeldridSpirvNative.Default.CrossCompile(&info); if (!result->Succeeded) { throw new SpirvCompilationException( "Compilation failed: " + Util.GetString((byte*)result->GetData(0), result->GetLength(0))); } string csCode = Util.GetString((byte*)result->GetData(0), result->GetLength(0)); ReflectionInfo* reflInfo = &result->ReflectionInfo; ResourceLayoutDescription[] layouts = new ResourceLayoutDescription[reflInfo->ResourceLayouts.Count]; for (uint i = 0; i < reflInfo->ResourceLayouts.Count; i++) { ref NativeResourceLayoutDescription nativeDesc = ref reflInfo->ResourceLayouts.Ref(i); layouts[i].Elements = new ResourceLayoutElementDescription[nativeDesc.ResourceElements.Count]; for (uint j = 0; j < nativeDesc.ResourceElements.Count; j++) { ref NativeResourceElementDescription elemDesc = ref nativeDesc.ResourceElements.Ref(j); layouts[i].Elements[j] = new ResourceLayoutElementDescription( Util.GetString((byte*)elemDesc.Name.Data, elemDesc.Name.Count), elemDesc.Kind, elemDesc.Stages, elemDesc.Options); } } SpirvReflection reflection = new SpirvReflection( Array.Empty(), layouts); return new ComputeCompilationResult(csCode, reflection); } finally { if (result != null) { VeldridSpirvNative.Default.FreeResult(result); } } } } /// /// Compiles the given GLSL source code into SPIR-V. /// /// The shader source code. /// A descriptive name for the shader. May be null. /// The which the shader is used in. /// Parameters for the GLSL compiler. /// A containing the compiled SPIR-V bytecode. public static unsafe SpirvCompilationResult CompileGlslToSpirv( string sourceText, string fileName, ShaderStages stage, GlslCompileOptions options) { int sourceAsciiCount = Encoding.ASCII.GetByteCount(sourceText); byte* sourceAsciiPtr = stackalloc byte[sourceAsciiCount]; fixed (char* sourceTextPtr = sourceText) { Encoding.ASCII.GetBytes(sourceTextPtr, sourceText.Length, sourceAsciiPtr, sourceAsciiCount); } int macroCount = options.Macros.Length; NativeMacroDefinition* macros = stackalloc NativeMacroDefinition[(int)macroCount]; for (int i = 0; i < macroCount; i++) { macros[i] = new NativeMacroDefinition(options.Macros[i]); } return CompileGlslToSpirv( (uint)sourceAsciiCount, sourceAsciiPtr, fileName, stage, options.Debug, (uint)macroCount, macros); } internal static unsafe SpirvCompilationResult CompileGlslToSpirv( uint sourceLength, byte* sourceTextPtr, string fileName, ShaderStages stage, bool debug, uint macroCount, NativeMacroDefinition* macros) { GlslCompileInfo info; info.Kind = GetShadercKind(stage); info.SourceText = new InteropArray(sourceLength, sourceTextPtr); info.Debug = debug; info.Macros = new InteropArray(macroCount, macros); if (string.IsNullOrEmpty(fileName)) { fileName = ""; } int fileNameAsciiCount = Encoding.ASCII.GetByteCount(fileName); byte* fileNameAsciiPtr = stackalloc byte[fileNameAsciiCount]; if (fileNameAsciiCount > 0) { fixed (char* fileNameTextPtr = fileName) { Encoding.ASCII.GetBytes(fileNameTextPtr, fileName.Length, fileNameAsciiPtr, fileNameAsciiCount); } } info.FileName = new InteropArray((uint)fileNameAsciiCount, fileNameAsciiPtr); CompilationResult* result = null; try { result = VeldridSpirvNative.Default.CompileGlslToSpirv(&info); if (!result->Succeeded) { throw new SpirvCompilationException( "Compilation failed: " + Util.GetString((byte*)result->GetData(0), result->GetLength(0))); } uint length = result->GetLength(0); byte[] spirvBytes = new byte[(int)length]; fixed (byte* spirvBytesPtr = &spirvBytes[0]) { Buffer.MemoryCopy(result->GetData(0), spirvBytesPtr, length, length); } return new SpirvCompilationResult(spirvBytes); } finally { if (result != null) { VeldridSpirvNative.Default.FreeResult(result); } } } private static ShadercShaderKind GetShadercKind(ShaderStages stage) { switch (stage) { case ShaderStages.Vertex: return ShadercShaderKind.Vertex; case ShaderStages.Geometry: return ShadercShaderKind.Geometry; case ShaderStages.TessellationControl: return ShadercShaderKind.TessellationControl; case ShaderStages.TessellationEvaluation: return ShadercShaderKind.TessellationEvaluation; case ShaderStages.Fragment: return ShadercShaderKind.Fragment; case ShaderStages.Compute: return ShadercShaderKind.Compute; default: throw new SpirvCompilationException($"Invalid shader stage: {stage}"); } } } }