/// <summary> /// Converts the hlsl code into glsl and stores the result as plain text /// </summary> /// <param name="shaderSource">the hlsl shader</param> /// <param name="entryPoint">the entrypoint function name</param> /// <param name="stage">the shader pipeline stage</param> /// <param name="compilerParameters"></param> /// <param name="reflection">the reflection gathered from the hlsl analysis</param> /// <param name="sourceFilename">the name of the source file</param> /// <returns></returns> public ShaderBytecodeResult Compile(string shaderSource, string entryPoint, ShaderStage stage, ShaderMixinParameters compilerParameters, EffectReflection reflection, string sourceFilename = null) { var isOpenGLES = compilerParameters.Get(CompilerParameters.GraphicsPlatformKey) == GraphicsPlatform.OpenGLES; var isOpenGLES3 = compilerParameters.Get(CompilerParameters.GraphicsProfileKey) >= GraphicsProfile.Level_10_0; var shaderBytecodeResult = new ShaderBytecodeResult(); byte[] rawData; var shader = Compile(shaderSource, entryPoint, stage, isOpenGLES, isOpenGLES3, shaderBytecodeResult, sourceFilename); if (shader == null) return shaderBytecodeResult; if (isOpenGLES) { // store both ES 2 and ES 3 on OpenGL ES platforms var shaderBytecodes = new ShaderLevelBytecode(); if (isOpenGLES3) { shaderBytecodes.DataES3 = shader; shaderBytecodes.DataES2 = null; } else { shaderBytecodes.DataES2 = shader; shaderBytecodes.DataES3 = Compile(shaderSource, entryPoint, stage, true, true, shaderBytecodeResult, sourceFilename); } using (var stream = new MemoryStream()) { BinarySerialization.Write(stream, shaderBytecodes); #if !SILICONSTUDIO_RUNTIME_CORECLR rawData = stream.GetBuffer(); #else // FIXME: Manu: The call to "ToArray()" might be slower than "GetBuffer()" rawData = stream.ToArray(); #endif } } else { // store string on OpenGL platforms rawData = Encoding.ASCII.GetBytes(shader); } var bytecodeId = ObjectId.FromBytes(rawData); var bytecode = new ShaderBytecode(bytecodeId, rawData); bytecode.Stage = stage; shaderBytecodeResult.Bytecode = bytecode; return shaderBytecodeResult; }
private void UpdateReflection(ShaderBytecode shaderBytecode, EffectReflection effectReflection, LoggerResult log) { var shaderReflectionRaw = new SharpDX.D3DCompiler.ShaderReflection(shaderBytecode); var shaderReflectionRawDesc = shaderReflectionRaw.Description; // Constant Buffers for (int i = 0; i < shaderReflectionRawDesc.ConstantBuffers; ++i) { var constantBufferRaw = shaderReflectionRaw.GetConstantBuffer(i); var constantBufferRawDesc = constantBufferRaw.Description; var linkBuffer = effectReflection.ConstantBuffers.FirstOrDefault(buffer => buffer.Name == constantBufferRawDesc.Name && buffer.Stage == ShaderStage.None); var constantBuffer = GetConstantBufferReflection(constantBufferRaw, ref constantBufferRawDesc, linkBuffer, log); constantBuffer.Stage = shaderBytecode.Stage; effectReflection.ConstantBuffers.Add(constantBuffer); } // BoundResources for (int i = 0; i < shaderReflectionRawDesc.BoundResources; ++i) { var boundResourceDesc = shaderReflectionRaw.GetResourceBindingDescription(i); string linkKeyName = null; foreach (var linkResource in effectReflection.ResourceBindings) { if (linkResource.Param.RawName == boundResourceDesc.Name && linkResource.Stage == ShaderStage.None) { linkKeyName = linkResource.Param.KeyName; break; } } if (linkKeyName == null) { log.Error("Resource [{0}] has no link", boundResourceDesc.Name); } else { var binding = GetResourceBinding(boundResourceDesc, linkKeyName, log); binding.Stage = shaderBytecode.Stage; effectReflection.ResourceBindings.Add(binding); } } }
public ShaderBytecodeResult Compile(string shaderSource, string entryPoint, ShaderStage stage, ShaderMixinParameters compilerParameters, EffectReflection reflection, string sourceFilename = null) { var isDebug = compilerParameters.Get(CompilerParameters.DebugKey); var profile = compilerParameters.Get(CompilerParameters.GraphicsProfileKey); var shaderModel = ShaderStageToString(stage) + "_" + ShaderProfileFromGraphicsProfile(profile); var shaderFlags = ShaderFlags.None; if (isDebug) { shaderFlags = ShaderFlags.OptimizationLevel0 | ShaderFlags.Debug; } SharpDX.Configuration.ThrowOnShaderCompileError = false; // Compile using D3DCompiler var compilationResult = SharpDX.D3DCompiler.ShaderBytecode.Compile(shaderSource, entryPoint, shaderModel, shaderFlags, EffectFlags.None, null, null, sourceFilename); var byteCodeResult = new ShaderBytecodeResult(); if (compilationResult.HasErrors) { // Log compilation errors byteCodeResult.Error(compilationResult.Message); } else { // As effect bytecode binary can changed when having debug infos (with d3dcompiler_47), we are calculating a bytecodeId on the stripped version var rawData = compilationResult.Bytecode.Strip(StripFlags.CompilerStripDebugInformation | StripFlags.CompilerStripReflectionData); var bytecodeId = ObjectId.FromBytes(rawData); byteCodeResult.Bytecode = new ShaderBytecode(bytecodeId, compilationResult.Bytecode.Data) { Stage = stage }; // If compilation succeed, then we can update reflection. UpdateReflection(byteCodeResult.Bytecode, reflection, byteCodeResult); if (!string.IsNullOrEmpty(compilationResult.Message)) { byteCodeResult.Warning(compilationResult.Message); } } return byteCodeResult; }
/// <summary> /// Converts the hlsl code into glsl and stores the result as plain text /// </summary> /// <param name="shaderSource">the hlsl shader</param> /// <param name="entryPoint">the entrypoint function name</param> /// <param name="stage">the shader pipeline stage</param> /// <param name="compilerParameters"></param> /// <param name="reflection">the reflection gathered from the hlsl analysis</param> /// <param name="sourceFilename">the name of the source file</param> /// <returns></returns> public ShaderBytecodeResult Compile(string shaderSource, string entryPoint, ShaderStage stage, ShaderMixinParameters compilerParameters, EffectReflection reflection, string sourceFilename = null) { var isOpenGLES = compilerParameters.Get(CompilerParameters.GraphicsPlatformKey) == GraphicsPlatform.OpenGLES; var shaderBytecodeResult = new ShaderBytecodeResult(); PipelineStage pipelineStage = PipelineStage.None; switch (stage) { case ShaderStage.Vertex: pipelineStage = PipelineStage.Vertex; break; case ShaderStage.Pixel: pipelineStage = PipelineStage.Pixel; break; case ShaderStage.Geometry: shaderBytecodeResult.Error("Geometry stage can't be converted to OpenGL. Only Vertex and Pixel shaders are supported"); break; case ShaderStage.Hull: shaderBytecodeResult.Error("Hull stage can't be converted to OpenGL. Only Vertex and Pixel shaders are supported"); break; case ShaderStage.Domain: shaderBytecodeResult.Error("Domain stage can't be converted to OpenGL. Only Vertex and Pixel shaders are supported"); break; case ShaderStage.Compute: shaderBytecodeResult.Error("Compute stage can't be converted to OpenGL. Only Vertex and Pixel shaders are supported"); break; default: shaderBytecodeResult.Error("Unknown shader profile."); break; } if (shaderBytecodeResult.HasErrors) { return(shaderBytecodeResult); } // Convert from HLSL to GLSL // Note that for now we parse from shader as a string, but we could simply clone effectPass.Shader to avoid multiple parsing. var glslConvertor = new ShaderConverter(isOpenGLES); var glslShader = glslConvertor.Convert(shaderSource, entryPoint, pipelineStage, sourceFilename, shaderBytecodeResult); // Add std140 layout foreach (var constantBuffer in glslShader.Declarations.OfType <ConstantBuffer>()) { constantBuffer.Qualifiers |= new LayoutQualifier(new LayoutKeyValue("std140")); } // Output the result var glslShaderWriter = new HlslToGlslWriter(); if (isOpenGLES) { glslShaderWriter.TrimFloatSuffix = true; glslShaderWriter.GenerateUniformBlocks = false; foreach (var variable in glslShader.Declarations.OfType <Variable>()) { if (variable.Qualifiers.Contains(ParameterQualifier.In)) { variable.Qualifiers.Values.Remove(ParameterQualifier.In); // "in" becomes "attribute" in VS, "varying" in other stages variable.Qualifiers.Values.Add( pipelineStage == PipelineStage.Vertex ? global::SiliconStudio.Shaders.Ast.Glsl.ParameterQualifier.Attribute : global::SiliconStudio.Shaders.Ast.Glsl.ParameterQualifier.Varying); } if (variable.Qualifiers.Contains(ParameterQualifier.Out)) { variable.Qualifiers.Values.Remove(ParameterQualifier.Out); variable.Qualifiers.Values.Add(global::SiliconStudio.Shaders.Ast.Glsl.ParameterQualifier.Varying); } } } // Write shader glslShaderWriter.Visit(glslShader); // Build shader source var glslShaderCode = new StringBuilder(); // Append some header depending on target if (!isOpenGLES) { glslShaderCode .AppendLine("#version 420") .AppendLine(); if (pipelineStage == PipelineStage.Pixel) { glslShaderCode .AppendLine("out vec4 gl_FragData[1];") .AppendLine(); } } if (isOpenGLES) { if (pipelineStage == PipelineStage.Pixel) { glslShaderCode .AppendLine("precision highp float;") .AppendLine(); } } glslShaderCode.Append(glslShaderWriter.Text); var realShaderSource = glslShaderCode.ToString(); // optimize shader var optShaderSource = RunOptimizer(realShaderSource, isOpenGLES, false, pipelineStage == PipelineStage.Vertex); if (!String.IsNullOrEmpty(optShaderSource)) { realShaderSource = optShaderSource; } var rawData = Encoding.ASCII.GetBytes(realShaderSource); var bytecodeId = ObjectId.FromBytes(rawData); var bytecode = new ShaderBytecode(bytecodeId, rawData); bytecode.Stage = stage; shaderBytecodeResult.Bytecode = bytecode; return(shaderBytecodeResult); }
/// <summary> /// Create or updates the reflection for this shader /// </summary> /// <param name="effectReflection">the reflection from the hlsl</param> /// <param name="stage">the shader pipeline stage</param> private void CreateReflection(EffectReflection effectReflection, ShaderStage stage) { int currentProgram; GL.GetInteger(GetPName.CurrentProgram, out currentProgram); GL.UseProgram(resourceId); int uniformBlockCount; GL.GetProgram(resourceId, PdxActiveUniformBlocks, out uniformBlockCount); for (int uniformBlockIndex = 0; uniformBlockIndex < uniformBlockCount; ++uniformBlockIndex) { // TODO: get previous name to find te actual constant buffer in the reflexion #if SILICONSTUDIO_PARADOX_GRAPHICS_API_OPENGLES const int sbCapacity = 128; int length; var sb = new StringBuilder(sbCapacity); GL.GetActiveUniformBlockName(resourceId, uniformBlockIndex, sbCapacity, out length, sb); var constantBufferName = sb.ToString(); #else var constantBufferName = GL.GetActiveUniformBlockName(resourceId, uniformBlockIndex); #endif var constantBufferDescriptionIndex = effectReflection.ConstantBuffers.FindIndex(x => x.Name == constantBufferName); if (constantBufferDescriptionIndex == -1) { reflectionResult.Error("Unable to find the constant buffer description [{0}]", constantBufferName); return; } var constantBufferIndex = effectReflection.ResourceBindings.FindIndex(x => x.Param.RawName == constantBufferName); if (constantBufferIndex == -1) { reflectionResult.Error("Unable to find the constant buffer [{0}]", constantBufferName); return; } var constantBufferDescription = effectReflection.ConstantBuffers[constantBufferDescriptionIndex]; var constantBuffer = effectReflection.ResourceBindings[constantBufferIndex]; GL.GetActiveUniformBlock(resourceId, uniformBlockIndex, ActiveUniformBlockParameter.UniformBlockDataSize, out constantBufferDescription.Size); int uniformCount; GL.GetActiveUniformBlock(resourceId, uniformBlockIndex, ActiveUniformBlockParameter.UniformBlockActiveUniforms, out uniformCount); // set the binding GL.UniformBlockBinding(resourceId, uniformBlockIndex, uniformBlockIndex); // Read uniforms desc var uniformIndices = new int[uniformCount]; var uniformOffsets = new int[uniformCount]; var uniformTypes = new int[uniformCount]; var uniformNames = new string[uniformCount]; GL.GetActiveUniformBlock(resourceId, uniformBlockIndex, ActiveUniformBlockParameter.UniformBlockActiveUniformIndices, uniformIndices); GL.GetActiveUniforms(resourceId, uniformIndices.Length, uniformIndices, ActiveUniformParameter.UniformOffset, uniformOffsets); GL.GetActiveUniforms(resourceId, uniformIndices.Length, uniformIndices, ActiveUniformParameter.UniformType, uniformTypes); for (int uniformIndex = 0; uniformIndex < uniformIndices.Length; ++uniformIndex) { #if SILICONSTUDIO_PARADOX_GRAPHICS_API_OPENGLES int size; ActiveUniformType aut; GL.GetActiveUniform(resourceId, uniformIndices[uniformIndex], sbCapacity, out length, out size, out aut, sb); uniformNames[uniformIndex] = sb.ToString(); #else uniformNames[uniformIndex] = GL.GetActiveUniformName(resourceId, uniformIndices[uniformIndex]); #endif } // Reoder by offset var indexMapping = uniformIndices.Select((x, i) => new UniformMergeInfo { Offset = uniformOffsets[i], Type = (ActiveUniformType)uniformTypes[i], Name = uniformNames[i], NextOffset = 0 }).OrderBy(x => x.Offset).ToArray(); indexMapping.Last().NextOffset = constantBufferDescription.Size; // Fill next offsets for (int i = 1; i < indexMapping.Length; ++i) { indexMapping[i - 1].NextOffset = indexMapping[i].Offset; } // Group arrays/structures into one variable (std140 layout is enough for offset determinism inside arrays/structures) indexMapping = indexMapping.GroupBy(x => { // Use only first part of name (ignore structure/array part) var name = x.Name; if (name.Contains(".")) { name = name.Substring(0, name.IndexOf('.')); } if (name.Contains("[")) { name = name.Substring(0, name.IndexOf('[')); } return(name); }) .Select(x => { var result = x.First(); result.NextOffset = x.Last().NextOffset; // Check weither it's an array or a struct int dotIndex = result.Name.IndexOf('.'); int arrayIndex = result.Name.IndexOf('['); if (x.Count() > 1 && arrayIndex == -1 && dotIndex == -1) { throw new InvalidOperationException(); } // TODO: Type processing result.Name = x.Key; return(result); }).ToArray(); foreach (var variableIndexGroup in indexMapping) { var variableIndex = -1; for (var tentativeIndex = 0; tentativeIndex < constantBufferDescription.Members.Length; ++tentativeIndex) { if (constantBufferDescription.Members[tentativeIndex].Param.RawName == variableIndexGroup.Name) { variableIndex = tentativeIndex; break; } } if (variableIndex == -1) { reflectionResult.Error("Unable to find uniform [{0}] in constant buffer [{1}]", variableIndexGroup.Name, constantBufferName); continue; } var variable = constantBufferDescription.Members[variableIndex]; variable.Param.Type = GetTypeFromActiveUniformType(variableIndexGroup.Type); variable.Offset = variableIndexGroup.Offset; variable.Size = variableIndexGroup.NextOffset - variableIndexGroup.Offset; constantBufferDescription.Members[variableIndex] = variable; } constantBufferDescription.Stage = stage; constantBufferDescription.Type = ConstantBufferType.ConstantBuffer; constantBuffer.SlotCount = 1; // constant buffers are not arrays constantBuffer.SlotStart = uniformBlockIndex; constantBuffer.Stage = stage; // store the new values effectReflection.ConstantBuffers[constantBufferDescriptionIndex] = constantBufferDescription; effectReflection.ResourceBindings[constantBufferIndex] = constantBuffer; } //#endif // Register textures, samplers, etc... //TODO: (?) non texture/buffer uniform outside of a block { // Register "NoSampler", required by HLSL=>GLSL translation to support HLSL such as texture.Load(). var noSampler = new EffectParameterResourceData { Param = { RawName = "NoSampler", KeyName = "NoSampler", Class = EffectParameterClass.Sampler }, SlotStart = -1 }; Reflection.ResourceBindings.Add(noSampler); bool usingSamplerNoSampler = false; int activeUniformCount; GL.GetProgram(resourceId, ProgramParameter.ActiveUniforms, out activeUniformCount); #if !SILICONSTUDIO_PARADOX_GRAPHICS_API_OPENGLES var uniformTypes = new int[activeUniformCount]; GL.GetActiveUniforms(resourceId, activeUniformCount, Enumerable.Range(0, activeUniformCount).ToArray(), ActiveUniformParameter.UniformType, uniformTypes); #endif #if SILICONSTUDIO_PARADOX_GRAPHICS_API_OPENGLES if (GraphicsDevice.IsOpenGLES2) { // Register global "fake" cbuffer //var constantBuffer = new ShaderReflectionConstantBuffer // { // Name = "$Globals", // Variables = new List<ShaderReflectionVariable>(), // Type = ConstantBufferType.ConstantBuffer // }; //shaderReflection.ConstantBuffers.Add(constantBuffer); //shaderReflection.BoundResources.Add(new InputBindingDescription { BindPoint = 0, BindCount = 1, Name = constantBuffer.Name, Type = ShaderInputType.ConstantBuffer }); // reset the size of the constant buffers foreach (var constantBuffer in effectReflection.ConstantBuffers) { constantBuffer.Size = 0; } // set the state of the constant buffers foreach (var constantBuffer in effectReflection.ConstantBuffers) { constantBuffer.Stage = stage; } for (int i = 0; i < effectReflection.ResourceBindings.Count; i++) { if (effectReflection.ResourceBindings[i].Param.Class != EffectParameterClass.ConstantBuffer) { continue; } var globalConstantBufferCopy = effectReflection.ResourceBindings[i]; globalConstantBufferCopy.Stage = stage; effectReflection.ResourceBindings[i] = globalConstantBufferCopy; } //Create a Globals constant buffer if necessary var globalConstantBufferDescriptionIndex = effectReflection.ConstantBuffers.FindIndex(x => x.Name == "Globals"); var globalConstantBufferIndex = effectReflection.ResourceBindings.FindIndex(x => x.Param.RawName == "Globals"); if (globalConstantBufferDescriptionIndex == -1 && globalConstantBufferIndex == -1) { var newConstantBufferDescription = new ShaderConstantBufferDescription { Name = "Globals", Stage = stage, Type = ConstantBufferType.ConstantBuffer, Size = 0, Members = new EffectParameterValueData[0], }; var newConstantBuffer = new EffectParameterResourceData { Stage = stage, SlotStart = 0, SlotCount = 1, Param = { RawName = "Globals", KeyName = "Globals", Type = EffectParameterType.ConstantBuffer, Class = EffectParameterClass.ConstantBuffer } }; effectReflection.ConstantBuffers.Add(newConstantBufferDescription); effectReflection.ResourceBindings.Add(newConstantBuffer); globalConstantBufferDescriptionIndex = effectReflection.ConstantBuffers.Count - 1; globalConstantBufferIndex = effectReflection.ResourceBindings.Count - 1; } // Merge all the variables in the Globals constant buffer if (globalConstantBufferDescriptionIndex != -1 && globalConstantBufferIndex != -1) { var globalConstantBufferDescription = effectReflection.ConstantBuffers[globalConstantBufferDescriptionIndex]; for (int cstDescrIndex = 0; cstDescrIndex < effectReflection.ConstantBuffers.Count; ++cstDescrIndex) { if (cstDescrIndex == globalConstantBufferDescriptionIndex) { continue; } var currentConstantBufferDescription = effectReflection.ConstantBuffers[cstDescrIndex]; globalConstantBufferDescription.Members = ArrayExtensions.Concat( globalConstantBufferDescription.Members, currentConstantBufferDescription.Members); effectReflection.ResourceBindings.RemoveAll(x => x.Param.RawName == currentConstantBufferDescription.Name); } // only keep the active uniforms globalConstantBufferDescription.Members = globalConstantBufferDescription.Members.Where(x => GL.GetUniformLocation(resourceId, #if SILICONSTUDIO_PLATFORM_ANDROID new StringBuilder(x.Param.RawName) #else x.Param.RawName #endif ) >= 0).ToArray(); // remove all the constant buffers and their resource bindings except the Globals one effectReflection.ConstantBuffers.Clear(); effectReflection.ConstantBuffers.Add(globalConstantBufferDescription); } else if (globalConstantBufferDescriptionIndex != -1 && globalConstantBufferIndex == -1) { reflectionResult.Error("Globals constant buffer has a description and no resource binding"); } else if (globalConstantBufferDescriptionIndex == -1 && globalConstantBufferIndex != -1) { reflectionResult.Error("Globals constant buffer has a description and no resource binding"); } } #endif int textureUnitCount = 0; const int sbCapacity = 128; int length; var sb = new StringBuilder(sbCapacity); for (int activeUniformIndex = 0; activeUniformIndex < activeUniformCount; ++activeUniformIndex) { #if !SILICONSTUDIO_PARADOX_GRAPHICS_API_OPENGLES var uniformType = (ActiveUniformType)uniformTypes[activeUniformIndex]; var uniformName = GL.GetActiveUniformName(resourceId, activeUniformIndex); #else ActiveUniformType uniformType; int uniformCount; GL.GetActiveUniform(resourceId, activeUniformIndex, sbCapacity, out length, out uniformCount, out uniformType, sb); var uniformName = sb.ToString(); //int uniformSize; //GL.GetActiveUniform(resourceId, activeUniformIndex, out uniformSize, ActiveUniformType.Float); #endif switch (uniformType) { #if SILICONSTUDIO_PARADOX_GRAPHICS_API_OPENGLES case ActiveUniformType.Bool: case ActiveUniformType.Int: AddUniform(effectReflection, sizeof(int) * 1, uniformCount, uniformName, uniformType); break; case ActiveUniformType.BoolVec2: case ActiveUniformType.IntVec2: AddUniform(effectReflection, sizeof(int) * 2, uniformCount, uniformName, uniformType); break; case ActiveUniformType.BoolVec3: case ActiveUniformType.IntVec3: AddUniform(effectReflection, sizeof(int) * 3, uniformCount, uniformName, uniformType); break; case ActiveUniformType.BoolVec4: case ActiveUniformType.IntVec4: AddUniform(effectReflection, sizeof(int) * 4, uniformCount, uniformName, uniformType); break; case ActiveUniformType.Float: AddUniform(effectReflection, sizeof(float) * 1, uniformCount, uniformName, uniformType); break; case ActiveUniformType.FloatVec2: AddUniform(effectReflection, sizeof(float) * 2, uniformCount, uniformName, uniformType); break; case ActiveUniformType.FloatVec3: AddUniform(effectReflection, sizeof(float) * 3, uniformCount, uniformName, uniformType); break; case ActiveUniformType.FloatVec4: AddUniform(effectReflection, sizeof(float) * 4, uniformCount, uniformName, uniformType); break; case ActiveUniformType.FloatMat4: AddUniform(effectReflection, sizeof(float) * 4 * 4, uniformCount, uniformName, uniformType); break; case ActiveUniformType.FloatMat2: case ActiveUniformType.FloatMat3: throw new NotImplementedException(); #endif #if !SILICONSTUDIO_PARADOX_GRAPHICS_API_OPENGLES case ActiveUniformType.Sampler1D: case ActiveUniformType.Sampler1DShadow: #endif case ActiveUniformType.Sampler2D: case ActiveUniformType.Sampler3D: // TODO: remove Texture3D that is not available in OpenGL ES 2 case ActiveUniformType.SamplerCube: case ActiveUniformType.Sampler2DShadow: #if SILICONSTUDIO_PLATFORM_ANDROID var uniformIndex = GL.GetUniformLocation(resourceId, new StringBuilder(uniformName)); #else var uniformIndex = GL.GetUniformLocation(resourceId, uniformName); #endif // Temporary way to scan which texture and sampler created this texture_sampler variable (to fix with new HLSL2GLSL converter) var startIndex = -1; var textureReflectionIndex = -1; var samplerReflectionIndex = -1; do { int middlePart = uniformName.IndexOf('_', startIndex + 1); var textureName = middlePart != -1 ? uniformName.Substring(0, middlePart) : uniformName; var samplerName = middlePart != -1 ? uniformName.Substring(middlePart + 1) : null; textureReflectionIndex = effectReflection.ResourceBindings.FindIndex(x => x.Param.RawName == textureName); samplerReflectionIndex = effectReflection.ResourceBindings.FindIndex(x => x.Param.RawName == samplerName); if (textureReflectionIndex != -1 && samplerReflectionIndex != -1) { break; } startIndex = middlePart; } while (startIndex != -1); if (startIndex == -1 || textureReflectionIndex == -1 || samplerReflectionIndex == -1) { reflectionResult.Error("Unable to find sampler and texture corresponding to [{0}]", uniformName); continue; // Error } var textureReflection = effectReflection.ResourceBindings[textureReflectionIndex]; var samplerReflection = effectReflection.ResourceBindings[samplerReflectionIndex]; // Contrary to Direct3D, samplers and textures are part of the same object in OpenGL // Since we are exposing the Direct3D representation, a single sampler parameter key can be used for several textures, a single texture can be used with several samplers. // When such a case is detected, we need to duplicate the resource binding. textureReflectionIndex = GetReflexionIndex(textureReflection, textureReflectionIndex, effectReflection.ResourceBindings); samplerReflectionIndex = GetReflexionIndex(samplerReflection, samplerReflectionIndex, effectReflection.ResourceBindings); // Update texture uniform mapping GL.Uniform1(uniformIndex, textureUnitCount); textureReflection.Stage = stage; //textureReflection.Param.RawName = uniformName; textureReflection.Param.Type = GetTypeFromActiveUniformType(uniformType); textureReflection.SlotStart = textureUnitCount; textureReflection.SlotCount = 1; // TODO: texture arrays textureReflection.Param.Class = EffectParameterClass.ShaderResourceView; samplerReflection.Stage = stage; samplerReflection.SlotStart = textureUnitCount; samplerReflection.SlotCount = 1; // TODO: texture arrays samplerReflection.Param.Class = EffectParameterClass.Sampler; effectReflection.ResourceBindings[textureReflectionIndex] = textureReflection; effectReflection.ResourceBindings[samplerReflectionIndex] = samplerReflection; Textures.Add(new Texture(textureUnitCount)); textureUnitCount++; break; } } // Remove any optimized resource binding effectReflection.ResourceBindings.RemoveAll(x => x.SlotStart == -1); } GL.UseProgram(currentProgram); }
private static void LinkVariable(EffectReflection reflection, string variableName, LocalParameterKey parameterKey) { var binding = new EffectParameterResourceData { Param = { KeyName = parameterKey.Name, Class = parameterKey.Class, Type = parameterKey.Type, RawName = variableName }, SlotStart = -1 }; reflection.ResourceBindings.Add(binding); }
private static void CleanupReflection(EffectReflection reflection) { // TODO GRAPHICS REFACTOR we hardcode several resource group we want to preserve or optimize completly // Somehow this should be handled some other place (or probably we shouldn't cleanup reflection at all?) bool hasMaterialGroup = false; bool hasLightingGroup = false; foreach (var resourceBinding in reflection.ResourceBindings) { if (resourceBinding.Stage != ShaderStage.None) { if (!hasLightingGroup && resourceBinding.ResourceGroup == "PerLighting") hasLightingGroup = true; else if (!hasMaterialGroup && resourceBinding.ResourceGroup == "PerMaterial") hasMaterialGroup = true; } } var usedConstantBuffers = new HashSet<string>(); for (int i = reflection.ResourceBindings.Count - 1; i >= 0; i--) { var resourceBinding = reflection.ResourceBindings[i]; // Do not touch anything if there is logical groups // TODO: We can do better than that: remove only if the full group can be optimized away if (resourceBinding.LogicalGroup != null) continue; if (resourceBinding.Stage == ShaderStage.None && !(hasMaterialGroup && resourceBinding.ResourceGroup == "PerMaterial") && !(hasLightingGroup && resourceBinding.ResourceGroup == "PerLighting")) { reflection.ResourceBindings.RemoveAt(i); } else if (resourceBinding.Class == EffectParameterClass.ConstantBuffer || resourceBinding.Class == EffectParameterClass.TextureBuffer) { // Mark associated cbuffer/tbuffer as used usedConstantBuffers.Add(resourceBinding.KeyInfo.KeyName); } } // Remove unused cbuffer for (int i = reflection.ConstantBuffers.Count - 1; i >= 0; i--) { var cbuffer = reflection.ConstantBuffers[i]; // Do not touch anything if there is logical groups // TODO: We can do better than that: remove only if the full group can be optimized away var hasLogicalGroup = false; foreach (var member in cbuffer.Members) { if (member.LogicalGroup != null) { hasLogicalGroup = true; break; } } if (hasLogicalGroup) continue; if (!usedConstantBuffers.Contains(cbuffer.Name)) { reflection.ConstantBuffers.RemoveAt(i); } } }
/// <summary> /// Initializes a new instance of the <see cref="ShaderLinker" /> class. /// </summary> /// <param name="parsingResult">The parsing result.</param> public ShaderLinker(ShaderMixinParsingResult parsingResult) : base(true, false) { this.parsingResult = parsingResult; this.effectReflection = parsingResult.Reflection; }
/// <summary> /// Converts the hlsl code into glsl and stores the result as plain text /// </summary> /// <param name="shaderSource">the hlsl shader</param> /// <param name="entryPoint">the entrypoint function name</param> /// <param name="stage">the shader pipeline stage</param> /// <param name="effectParameters"></param> /// <param name="reflection">the reflection gathered from the hlsl analysis</param> /// <param name="sourceFilename">the name of the source file</param> /// <returns></returns> public ShaderBytecodeResult Compile(string shaderSource, string entryPoint, ShaderStage stage, EffectCompilerParameters effectParameters, EffectReflection reflection, string sourceFilename = null) { var shaderBytecodeResult = new ShaderBytecodeResult(); byte[] rawData; GlslShaderPlatform shaderPlatform; int shaderVersion; switch (effectParameters.Platform) { case GraphicsPlatform.OpenGL: shaderPlatform = GlslShaderPlatform.OpenGL; shaderVersion = 420; break; case GraphicsPlatform.OpenGLES: shaderPlatform = GlslShaderPlatform.OpenGLES; shaderVersion = effectParameters.Profile >= GraphicsProfile.Level_10_0 ? 300 : 100; break; default: throw new ArgumentOutOfRangeException("effectParameters.Platform"); } var shader = Compile(shaderSource, entryPoint, stage, shaderPlatform, shaderVersion, shaderBytecodeResult, reflection, sourceFilename); if (shader == null) return shaderBytecodeResult; if (effectParameters.Platform == GraphicsPlatform.OpenGLES) { // store both ES 2 and ES 3 on OpenGL ES platforms var shaderBytecodes = new ShaderLevelBytecode(); if (effectParameters.Profile >= GraphicsProfile.Level_10_0) { shaderBytecodes.DataES3 = shader; shaderBytecodes.DataES2 = null; } else { shaderBytecodes.DataES2 = shader; shaderBytecodes.DataES3 = Compile(shaderSource, entryPoint, stage, GlslShaderPlatform.OpenGLES, 300, shaderBytecodeResult, reflection, sourceFilename); } using (var stream = new MemoryStream()) { BinarySerialization.Write(stream, shaderBytecodes); #if !SILICONSTUDIO_RUNTIME_CORECLR rawData = stream.GetBuffer(); #else // FIXME: Manu: The call to "ToArray()" might be slower than "GetBuffer()" rawData = stream.ToArray(); #endif } } else { // store string on OpenGL platforms rawData = Encoding.ASCII.GetBytes(shader); } var bytecodeId = ObjectId.FromBytes(rawData); var bytecode = new ShaderBytecode(bytecodeId, rawData); bytecode.Stage = stage; shaderBytecodeResult.Bytecode = bytecode; return shaderBytecodeResult; }
private static void MarkResourceBindingAsUsed(EffectReflection reflection, int resourceBindingIndex, ShaderStage stage) { var resourceBinding = reflection.ResourceBindings[resourceBindingIndex]; if (resourceBinding.Stage == ShaderStage.None) { resourceBinding.Stage = stage; reflection.ResourceBindings[resourceBindingIndex] = resourceBinding; } }
private string Compile(string shaderSource, string entryPoint, ShaderStage stage, GlslShaderPlatform shaderPlatform, int shaderVersion, ShaderBytecodeResult shaderBytecodeResult, EffectReflection reflection, string sourceFilename = null) { if (shaderPlatform == GlslShaderPlatform.OpenGLES && shaderVersion < 300 && renderTargetCount > 1) shaderBytecodeResult.Error("OpenGL ES 2 does not support multiple render targets."); PipelineStage pipelineStage = PipelineStage.None; switch (stage) { case ShaderStage.Vertex: pipelineStage = PipelineStage.Vertex; break; case ShaderStage.Pixel: pipelineStage = PipelineStage.Pixel; break; case ShaderStage.Geometry: shaderBytecodeResult.Error("Geometry stage can't be converted to OpenGL. Only Vertex and Pixel shaders are supported"); break; case ShaderStage.Hull: shaderBytecodeResult.Error("Hull stage can't be converted to OpenGL. Only Vertex and Pixel shaders are supported"); break; case ShaderStage.Domain: shaderBytecodeResult.Error("Domain stage can't be converted to OpenGL. Only Vertex and Pixel shaders are supported"); break; case ShaderStage.Compute: shaderBytecodeResult.Error("Compute stage can't be converted to OpenGL. Only Vertex and Pixel shaders are supported"); break; default: shaderBytecodeResult.Error("Unknown shader profile."); break; } if (shaderBytecodeResult.HasErrors) return null; Shader glslShader; // null entry point means no shader. In that case, we return a default function in HlslToGlslWriter // TODO: support that directly in HlslToGlslConvertor? if (entryPoint == null) { glslShader = null; } else { // Convert from HLSL to GLSL // Note that for now we parse from shader as a string, but we could simply clone effectPass.Shader to avoid multiple parsing. var glslConvertor = new ShaderConverter(shaderPlatform, shaderVersion); glslShader = glslConvertor.Convert(shaderSource, entryPoint, pipelineStage, sourceFilename, shaderBytecodeResult); if (glslShader == null || shaderBytecodeResult.HasErrors) return null; foreach (var constantBuffer in glslShader.Declarations.OfType<ConstantBuffer>()) { // Update constant buffer itself (first time only) var reflectionConstantBuffer = reflection.ConstantBuffers.FirstOrDefault(x => x.Name == constantBuffer.Name && x.Size == 0); if (reflectionConstantBuffer != null) { // Used to compute constant buffer size and member offsets (std140 rule) int constantBufferOffset = 0; // Fill members for (int index = 0; index < reflectionConstantBuffer.Members.Length; index++) { var member = reflectionConstantBuffer.Members[index]; // Properly compute size and offset according to std140 rules var memberSize = ComputeMemberSize(ref member.Type, ref constantBufferOffset); // Store size/offset info member.Offset = constantBufferOffset; member.Size = memberSize; // Adjust offset for next item constantBufferOffset += memberSize; reflectionConstantBuffer.Members[index] = member; } reflectionConstantBuffer.Size = constantBufferOffset; } // Find binding var resourceBindingIndex = reflection.ResourceBindings.IndexOf(x => x.RawName == constantBuffer.Name); if (resourceBindingIndex != -1) MarkResourceBindingAsUsed(reflection, resourceBindingIndex, stage); } foreach (var variable in glslShader.Declarations.OfType<Variable>().Where(x => (x.Qualifiers.Contains(StorageQualifier.Uniform)))) { // Check if we have a variable that starts or ends with this name (in case of samplers) // TODO: Have real AST support for all the list in Keywords.glsl if (variable.Type.Name.Text.Contains("sampler1D") || variable.Type.Name.Text.Contains("sampler2D") || variable.Type.Name.Text.Contains("sampler3D") || variable.Type.Name.Text.Contains("samplerCube")) { // TODO: Make more robust var textureBindingIndex = reflection.ResourceBindings.IndexOf(x => variable.Name.ToString().StartsWith(x.RawName)); var samplerBindingIndex = reflection.ResourceBindings.IndexOf(x => variable.Name.ToString().EndsWith(x.RawName)); if (textureBindingIndex != -1) MarkResourceBindingAsUsed(reflection, textureBindingIndex, stage); if (samplerBindingIndex != -1) MarkResourceBindingAsUsed(reflection, samplerBindingIndex, stage); } else { var resourceBindingIndex = reflection.ResourceBindings.IndexOf(x => x.RawName == variable.Name); if (resourceBindingIndex != -1) MarkResourceBindingAsUsed(reflection, resourceBindingIndex, stage); } } } // Output the result var glslShaderWriter = new HlslToGlslWriter(shaderPlatform, shaderVersion, pipelineStage); if (shaderPlatform == GlslShaderPlatform.OpenGLES && shaderVersion < 320) { glslShaderWriter.ExtraHeaders = "#define texelFetchBufferPlaceholder"; } // Write shader glslShaderWriter.Visit(glslShader); var shaderString = glslShaderWriter.Text; // Build shader source var glslShaderCode = new StringBuilder(); // Append some header depending on target //if (isOpenGLES) //{ // if (isOpenGLES3) // { // glslShaderCode // .AppendLine("#version 300 es") // TODO: 310 version? // .AppendLine(); // } // // if (pipelineStage == PipelineStage.Pixel) // glslShaderCode // .AppendLine("precision highp float;") // .AppendLine(); //} //else //{ // glslShaderCode // .AppendLine("#version 420") // .AppendLine() // .AppendLine("#define samplerBuffer sampler2D") // .AppendLine("#define isamplerBuffer isampler2D") // .AppendLine("#define usamplerBuffer usampler2D") // .AppendLine("#define texelFetchBuffer(sampler, P) texelFetch(sampler, ivec2((P) & 0xFFF, (P) >> 12), 0)"); // //.AppendLine("#define texelFetchBuffer(sampler, P) texelFetch(sampler, P)"); //} glslShaderCode.Append(shaderString); var realShaderSource = glslShaderCode.ToString(); #if SILICONSTUDIO_PLATFORM_WINDOWS_DESKTOP // optimize shader try { var optShaderSource = RunOptimizer(shaderBytecodeResult, realShaderSource, shaderPlatform, shaderVersion, pipelineStage == PipelineStage.Vertex); if (!String.IsNullOrEmpty(optShaderSource)) realShaderSource = optShaderSource; } catch (Exception e) { shaderBytecodeResult.Warning("Could not run GLSL optimizer:\n{0}", e.Message); } #else shaderBytecodeResult.Warning("GLSL optimized has not been executed because it is currently not supported on this platform."); #endif return realShaderSource; }
private void UpdateReflection(ShaderBytecode shaderBytecode, EffectReflection effectReflection, LoggerResult log) { var shaderReflectionRaw = new SharpDX.D3DCompiler.ShaderReflection(shaderBytecode); var shaderReflectionRawDesc = shaderReflectionRaw.Description; foreach (var constantBuffer in effectReflection.ConstantBuffers) { UpdateConstantBufferReflection(constantBuffer); } // Constant Buffers for (int i = 0; i < shaderReflectionRawDesc.ConstantBuffers; ++i) { var constantBufferRaw = shaderReflectionRaw.GetConstantBuffer(i); var constantBufferRawDesc = constantBufferRaw.Description; if (constantBufferRawDesc.Type == SharpDX.D3DCompiler.ConstantBufferType.ResourceBindInformation) { continue; } try { var linkBuffer = effectReflection.ConstantBuffers.First(buffer => buffer.Name == constantBufferRawDesc.Name); ValidateConstantBufferReflection(constantBufferRaw, ref constantBufferRawDesc, linkBuffer, log); } catch (Exception) { // couldn't find a match for this resource, skip } } // BoundResources for (int i = 0; i < shaderReflectionRawDesc.BoundResources; ++i) { var boundResourceDesc = shaderReflectionRaw.GetResourceBindingDescription(i); string linkKeyName = null; string resourceGroup = null; string logicalGroup = null; foreach (var linkResource in effectReflection.ResourceBindings) { if (linkResource.RawName == boundResourceDesc.Name && linkResource.Stage == ShaderStage.None) { linkKeyName = linkResource.KeyInfo.KeyName; resourceGroup = linkResource.ResourceGroup; logicalGroup = linkResource.LogicalGroup; break; } } if (linkKeyName == null) { log.Error($"Resource [{boundResourceDesc.Name}] has no link"); } else { var binding = GetResourceBinding(boundResourceDesc, linkKeyName, log); binding.Stage = shaderBytecode.Stage; binding.ResourceGroup = resourceGroup; binding.LogicalGroup = logicalGroup; effectReflection.ResourceBindings.Add(binding); } } }
public ShaderBytecodeResult Compile(string shaderSource, string entryPoint, ShaderStage stage, EffectCompilerParameters effectParameters, EffectReflection reflection, string sourceFilename = null) { var isDebug = effectParameters.Debug; var optimLevel = effectParameters.OptimizationLevel; var profile = effectParameters.Profile; var shaderModel = ShaderStageToString(stage) + "_" + ShaderProfileFromGraphicsProfile(profile); var shaderFlags = ShaderFlags.None; if (isDebug) { shaderFlags = ShaderFlags.Debug; } switch (optimLevel) { case 0: shaderFlags |= ShaderFlags.OptimizationLevel0; break; case 1: shaderFlags |= ShaderFlags.OptimizationLevel1; break; case 2: shaderFlags |= ShaderFlags.OptimizationLevel2; break; case 3: shaderFlags |= ShaderFlags.OptimizationLevel3; break; } SharpDX.Configuration.ThrowOnShaderCompileError = false; // Compile using D3DCompiler var compilationResult = SharpDX.D3DCompiler.ShaderBytecode.Compile(shaderSource, entryPoint, shaderModel, shaderFlags, EffectFlags.None, null, null, sourceFilename); var byteCodeResult = new ShaderBytecodeResult(); if (compilationResult.HasErrors || compilationResult.Bytecode == null) { // Log compilation errors byteCodeResult.Error(compilationResult.Message); } else { // TODO: Make this optional try { byteCodeResult.DisassembleText = compilationResult.Bytecode.Disassemble(); } catch (SharpDXException) { } // As effect bytecode binary can changed when having debug infos (with d3dcompiler_47), we are calculating a bytecodeId on the stripped version var rawData = compilationResult.Bytecode.Strip(StripFlags.CompilerStripDebugInformation | StripFlags.CompilerStripReflectionData); var bytecodeId = ObjectId.FromBytes(rawData); byteCodeResult.Bytecode = new ShaderBytecode(bytecodeId, compilationResult.Bytecode.Data) { Stage = stage }; // If compilation succeed, then we can update reflection. UpdateReflection(byteCodeResult.Bytecode, reflection, byteCodeResult); if (!string.IsNullOrEmpty(compilationResult.Message)) { byteCodeResult.Warning(compilationResult.Message); } } return(byteCodeResult); }
private void AddUniform(EffectReflection effectReflection, int uniformSize, int uniformCount, string uniformName, ActiveUniformType uniformType) { // clean the name if (uniformName.Contains(".")) { uniformName = uniformName.Substring(0, uniformName.IndexOf('.')); } if (uniformName.Contains("[")) { uniformName = uniformName.Substring(0, uniformName.IndexOf('[')); } if (GraphicsDevice.IsOpenGLES2) { var indexOfConstantBufferDescription = effectReflection.ConstantBuffers.FindIndex(x => x.Name == "Globals"); var indexOfConstantBuffer = effectReflection.ResourceBindings.FindIndex(x => x.Param.RawName == "Globals"); if (indexOfConstantBufferDescription == -1 || indexOfConstantBuffer == -1) { reflectionResult.Error("Unable to find uniform [{0}] in any constant buffer", uniformName); return; } var constantBufferDescription = effectReflection.ConstantBuffers[indexOfConstantBufferDescription]; var constantBuffer = effectReflection.ResourceBindings[indexOfConstantBuffer]; var elementSize = uniformSize; // For array, each element is rounded to register size if (uniformSize % 16 != 0 && uniformCount > 1) { constantBufferDescription.Size = (constantBufferDescription.Size + 15) / 16 * 16; uniformSize = (uniformSize + 15) / 16 * 16; } // Check if it can fits in the same register, otherwise starts at the next one if (uniformCount == 1 && constantBufferDescription.Size / 16 != (constantBufferDescription.Size + uniformSize - 1) / 16) { constantBufferDescription.Size = (constantBufferDescription.Size + 15) / 16 * 16; } var indexOfUniform = -1; for (var tentativeIndex = 0; tentativeIndex < constantBufferDescription.Members.Length; ++tentativeIndex) { if (constantBufferDescription.Members[tentativeIndex].Param.RawName == uniformName) { indexOfUniform = tentativeIndex; break; } } var variable = constantBufferDescription.Members[indexOfUniform]; variable.Param.Type = GetTypeFromActiveUniformType(uniformType); //variable.SourceOffset = variableIndexGroup.Offset; variable.Offset = constantBufferDescription.Size; variable.Count = uniformCount; variable.Size = uniformSize * uniformCount; constantBufferDescription.Type = ConstantBufferType.ConstantBuffer; constantBuffer.SlotStart = 0; constantBufferDescription.Members[indexOfUniform] = variable; effectReflection.ResourceBindings[indexOfConstantBuffer] = constantBuffer; // No need to compare last element padding. // TODO: In case of float1/float2 arrays (rare) it is quite non-optimal to do a CompareMemory var compareSize = uniformSize * (uniformCount - 1) + elementSize; Uniforms.Add(new Uniform { Type = uniformType, Count = uniformCount, CompareSize = compareSize, Offset = constantBufferDescription.Size, #if SILICONSTUDIO_PLATFORM_ANDROID UniformIndex = GL.GetUniformLocation(resourceId, new StringBuilder(uniformName)) #else UniformIndex = GL.GetUniformLocation(resourceId, uniformName) #endif });
private string Compile(string shaderSource, string entryPoint, ShaderStage stage, GlslShaderPlatform shaderPlatform, int shaderVersion, ShaderBytecodeResult shaderBytecodeResult, EffectReflection reflection, IDictionary <int, string> inputAttributeNames, Dictionary <string, int> resourceBindings, string sourceFilename = null) { if (shaderPlatform == GlslShaderPlatform.OpenGLES && shaderVersion < 300 && renderTargetCount > 1) { shaderBytecodeResult.Error("OpenGL ES 2 does not support multiple render targets."); } PipelineStage pipelineStage = PipelineStage.None; switch (stage) { case ShaderStage.Vertex: pipelineStage = PipelineStage.Vertex; break; case ShaderStage.Pixel: pipelineStage = PipelineStage.Pixel; break; case ShaderStage.Geometry: shaderBytecodeResult.Error("Geometry stage can't be converted to OpenGL. Only Vertex and Pixel shaders are supported"); break; case ShaderStage.Hull: shaderBytecodeResult.Error("Hull stage can't be converted to OpenGL. Only Vertex and Pixel shaders are supported"); break; case ShaderStage.Domain: shaderBytecodeResult.Error("Domain stage can't be converted to OpenGL. Only Vertex and Pixel shaders are supported"); break; case ShaderStage.Compute: shaderBytecodeResult.Error("Compute stage can't be converted to OpenGL. Only Vertex and Pixel shaders are supported"); break; default: shaderBytecodeResult.Error("Unknown shader profile."); break; } if (shaderBytecodeResult.HasErrors) { return(null); } Shader glslShader; // null entry point means no shader. In that case, we return a default function in HlslToGlslWriter // TODO: support that directly in HlslToGlslConvertor? if (entryPoint == null) { glslShader = null; } else { // Convert from HLSL to GLSL // Note that for now we parse from shader as a string, but we could simply clone effectPass.Shader to avoid multiple parsing. var glslConvertor = new ShaderConverter(shaderPlatform, shaderVersion); glslShader = glslConvertor.Convert(shaderSource, entryPoint, pipelineStage, sourceFilename, inputAttributeNames, shaderBytecodeResult); if (glslShader == null || shaderBytecodeResult.HasErrors) { return(null); } foreach (var constantBuffer in glslShader.Declarations.OfType <ConstantBuffer>()) { // Update constant buffer itself (first time only) var reflectionConstantBuffer = reflection.ConstantBuffers.FirstOrDefault(x => x.Name == constantBuffer.Name && x.Size == 0); if (reflectionConstantBuffer != null) { // Used to compute constant buffer size and member offsets (std140 rule) int constantBufferOffset = 0; // Fill members for (int index = 0; index < reflectionConstantBuffer.Members.Length; index++) { var member = reflectionConstantBuffer.Members[index]; // Properly compute size and offset according to std140 rules var memberSize = ComputeMemberSize(ref member.Type, ref constantBufferOffset); // Store size/offset info member.Offset = constantBufferOffset; member.Size = memberSize; // Adjust offset for next item constantBufferOffset += memberSize; reflectionConstantBuffer.Members[index] = member; } reflectionConstantBuffer.Size = constantBufferOffset; } // Find binding var resourceBindingIndex = reflection.ResourceBindings.IndexOf(x => x.RawName == constantBuffer.Name); if (resourceBindingIndex != -1) { MarkResourceBindingAsUsed(reflection, resourceBindingIndex, stage); } } foreach (var variable in glslShader.Declarations.OfType <Variable>().Where(x => (x.Qualifiers.Contains(StorageQualifier.Uniform)))) { // Check if we have a variable that starts or ends with this name (in case of samplers) // TODO: Have real AST support for all the list in Keywords.glsl if (variable.Type.Name.Text.Contains("sampler1D") || variable.Type.Name.Text.Contains("sampler2D") || variable.Type.Name.Text.Contains("sampler3D") || variable.Type.Name.Text.Contains("samplerCube") || variable.Type.Name.Text.Contains("samplerBuffer")) { // TODO: Make more robust var textureBindingIndex = reflection.ResourceBindings.IndexOf(x => variable.Name.ToString().StartsWith(x.RawName)); var samplerBindingIndex = reflection.ResourceBindings.IndexOf(x => variable.Name.ToString().EndsWith(x.RawName)); if (textureBindingIndex != -1) { MarkResourceBindingAsUsed(reflection, textureBindingIndex, stage); } if (samplerBindingIndex != -1) { MarkResourceBindingAsUsed(reflection, samplerBindingIndex, stage); } } else { var resourceBindingIndex = reflection.ResourceBindings.IndexOf(x => x.RawName == variable.Name); if (resourceBindingIndex != -1) { MarkResourceBindingAsUsed(reflection, resourceBindingIndex, stage); } } } if (shaderPlatform == GlslShaderPlatform.Vulkan) { // Defines the ordering of resource groups in Vulkan. This is mirrored in the PipelineState var resourceGroups = reflection.ResourceBindings.Select(x => x.ResourceGroup ?? "Globals").Distinct().ToList(); var bindings = resourceGroups.SelectMany(resourceGroup => reflection.ResourceBindings .Where(x => x.ResourceGroup == resourceGroup || (x.ResourceGroup == null && resourceGroup == "Globals")) .GroupBy(x => new { KeyName = x.KeyInfo.KeyName, RawName = x.RawName, Class = x.Class, Type = x.Type, SlotCount = x.SlotCount, LogicalGroup = x.LogicalGroup }) .OrderBy(x => x.Key.Class == EffectParameterClass.ConstantBuffer ? 0 : 1)) .ToList(); // Add layout(set, bindings) qualifier to all constant buffers foreach (var constantBuffer in glslShader.Declarations.OfType <ConstantBuffer>()) { var layoutBindingIndex = bindings.IndexOf(x => x.Key.RawName == constantBuffer.Name); if (layoutBindingIndex != -1) { var layoutQualifier = constantBuffer.Qualifiers.OfType <SiliconStudio.Shaders.Ast.Glsl.LayoutQualifier>().FirstOrDefault(); if (layoutQualifier == null) { layoutQualifier = new SiliconStudio.Shaders.Ast.Glsl.LayoutQualifier(); constantBuffer.Qualifiers |= layoutQualifier; } //layoutQualifier.Layouts.Add(new LayoutKeyValue("set", resourceGroups.IndexOf(resourceGroup))); layoutQualifier.Layouts.Add(new LayoutKeyValue("set", 0)); layoutQualifier.Layouts.Add(new LayoutKeyValue("binding", layoutBindingIndex + 1)); resourceBindings.Add(bindings[layoutBindingIndex].Key.KeyName, layoutBindingIndex + 1); } } // Add layout(set, bindings) qualifier to all other uniforms foreach (var variable in glslShader.Declarations.OfType <Variable>().Where(x => (x.Qualifiers.Contains(StorageQualifier.Uniform)))) { var layoutBindingIndex = bindings.IndexOf(x => variable.Name.Text.StartsWith(x.Key.RawName)); if (layoutBindingIndex != -1) { var layoutQualifier = variable.Qualifiers.OfType <SiliconStudio.Shaders.Ast.Glsl.LayoutQualifier>().FirstOrDefault(); if (layoutQualifier == null) { layoutQualifier = new SiliconStudio.Shaders.Ast.Glsl.LayoutQualifier(); variable.Qualifiers |= layoutQualifier; } //layoutQualifier.Layouts.Add(new LayoutKeyValue("set", resourceGroups.IndexOf(resourceGroup))); layoutQualifier.Layouts.Add(new LayoutKeyValue("set", 0)); layoutQualifier.Layouts.Add(new LayoutKeyValue("binding", layoutBindingIndex + 1)); resourceBindings.Add(bindings[layoutBindingIndex].Key.KeyName, layoutBindingIndex + 1); } } } } // Output the result var glslShaderWriter = new HlslToGlslWriter(shaderPlatform, shaderVersion, pipelineStage); if (shaderPlatform == GlslShaderPlatform.OpenGLES && shaderVersion < 320) { glslShaderWriter.ExtraHeaders = "#define texelFetchBufferPlaceholder"; } // Write shader glslShaderWriter.Visit(glslShader); var shaderString = glslShaderWriter.Text; // Build shader source var glslShaderCode = new StringBuilder(); // Append some header depending on target //if (isOpenGLES) //{ // if (isOpenGLES3) // { // glslShaderCode // .AppendLine("#version 300 es") // TODO: 310 version? // .AppendLine(); // } // // if (pipelineStage == PipelineStage.Pixel) // glslShaderCode // .AppendLine("precision highp float;") // .AppendLine(); //} //else //{ // glslShaderCode // .AppendLine("#version 420") // .AppendLine() // .AppendLine("#define samplerBuffer sampler2D") // .AppendLine("#define isamplerBuffer isampler2D") // .AppendLine("#define usamplerBuffer usampler2D") // .AppendLine("#define texelFetchBuffer(sampler, P) texelFetch(sampler, ivec2((P) & 0xFFF, (P) >> 12), 0)"); // //.AppendLine("#define texelFetchBuffer(sampler, P) texelFetch(sampler, P)"); //} glslShaderCode.Append(shaderString); var realShaderSource = glslShaderCode.ToString(); // TODO: Disabled because glsl optimizer don't properly put lowp/highp qualifier inside struct (which make shader compilation fails since we use struct for lighting) #if SILICONSTUDIO_PLATFORM_WINDOWS_DESKTOP // optimize shader try { var optShaderSource = RunOptimizer(shaderBytecodeResult, realShaderSource, shaderPlatform, shaderVersion, pipelineStage == PipelineStage.Vertex); if (!String.IsNullOrEmpty(optShaderSource)) { realShaderSource = optShaderSource; } } catch (Exception e) { shaderBytecodeResult.Warning("Could not run GLSL optimizer}", e); } #else shaderBytecodeResult.Warning("GLSL optimized has not been executed because it is currently not supported on this platform."); #endif return(realShaderSource); }
/// <summary> /// Converts the hlsl code into glsl and stores the result as plain text /// </summary> /// <param name="shaderSource">the hlsl shader</param> /// <param name="entryPoint">the entrypoint function name</param> /// <param name="stage">the shader pipeline stage</param> /// <param name="effectParameters"></param> /// <param name="reflection">the reflection gathered from the hlsl analysis</param> /// <param name="sourceFilename">the name of the source file</param> /// <returns></returns> public ShaderBytecodeResult Compile(string shaderSource, string entryPoint, ShaderStage stage, EffectCompilerParameters effectParameters, EffectReflection reflection, string sourceFilename = null) { var shaderBytecodeResult = new ShaderBytecodeResult(); byte[] rawData; var inputAttributeNames = new Dictionary <int, string>(); var resourceBindings = new Dictionary <string, int>(); GlslShaderPlatform shaderPlatform; int shaderVersion; switch (effectParameters.Platform) { case GraphicsPlatform.OpenGL: shaderPlatform = GlslShaderPlatform.OpenGL; shaderVersion = 410; break; case GraphicsPlatform.OpenGLES: shaderPlatform = GlslShaderPlatform.OpenGLES; shaderVersion = effectParameters.Profile >= GraphicsProfile.Level_10_0 ? 300 : 100; break; case GraphicsPlatform.Vulkan: shaderPlatform = GlslShaderPlatform.Vulkan; shaderVersion = 450; break; default: throw new ArgumentOutOfRangeException("effectParameters.Platform"); } var shader = Compile(shaderSource, entryPoint, stage, shaderPlatform, shaderVersion, shaderBytecodeResult, reflection, inputAttributeNames, resourceBindings, sourceFilename); if (shader == null) { return(shaderBytecodeResult); } if (effectParameters.Platform == GraphicsPlatform.OpenGLES) { // store both ES 2 and ES 3 on OpenGL ES platforms var shaderBytecodes = new ShaderLevelBytecode(); if (effectParameters.Profile >= GraphicsProfile.Level_10_0) { shaderBytecodes.DataES3 = shader; shaderBytecodes.DataES2 = null; } else { shaderBytecodes.DataES2 = shader; shaderBytecodes.DataES3 = Compile(shaderSource, entryPoint, stage, GlslShaderPlatform.OpenGLES, 300, shaderBytecodeResult, reflection, inputAttributeNames, resourceBindings, sourceFilename); } using (var stream = new MemoryStream()) { BinarySerialization.Write(stream, shaderBytecodes); #if !SILICONSTUDIO_RUNTIME_CORECLR && !SILICONSTUDIO_PLATFORM_UWP rawData = stream.GetBuffer(); #else // FIXME: Manu: The call to "ToArray()" might be slower than "GetBuffer()" rawData = stream.ToArray(); #endif } } #if !SILICONSTUDIO_RUNTIME_CORECLR && !SILICONSTUDIO_PLATFORM_UWP else if (effectParameters.Platform == GraphicsPlatform.Vulkan) { string inputFileExtension; switch (stage) { case ShaderStage.Vertex: inputFileExtension = ".vert"; break; case ShaderStage.Pixel: inputFileExtension = ".frag"; break; case ShaderStage.Geometry: inputFileExtension = ".geom"; break; case ShaderStage.Domain: inputFileExtension = ".tese"; break; case ShaderStage.Hull: inputFileExtension = ".tesc"; break; case ShaderStage.Compute: inputFileExtension = ".comp"; break; default: shaderBytecodeResult.Error("Unknown shader profile"); return(shaderBytecodeResult); } var inputFileName = Path.ChangeExtension(Path.GetTempFileName(), inputFileExtension); var outputFileName = Path.ChangeExtension(inputFileName, ".spv"); // Write shader source to disk File.WriteAllBytes(inputFileName, Encoding.ASCII.GetBytes(shader)); // Run shader compiler var filename = Platform.Type == PlatformType.Windows ? "glslangValidator.exe" : "glslangValidator"; ShellHelper.RunProcessAndRedirectToLogger(filename, $"-V -o {outputFileName} {inputFileName}", null, shaderBytecodeResult); if (!File.Exists(outputFileName)) { shaderBytecodeResult.Error("Failed to generate SPIR-V from GLSL"); return(shaderBytecodeResult); } // Read compiled shader var shaderBytecodes = new ShaderInputBytecode { InputAttributeNames = inputAttributeNames, ResourceBindings = resourceBindings, Data = File.ReadAllBytes(outputFileName), }; using (var stream = new MemoryStream()) { BinarySerialization.Write(stream, shaderBytecodes); rawData = stream.ToArray(); } // Cleanup temp files File.Delete(inputFileName); File.Delete(outputFileName); } #endif else { // store string on OpenGL platforms rawData = Encoding.UTF8.GetBytes(shader); } var bytecodeId = ObjectId.FromBytes(rawData); var bytecode = new ShaderBytecode(bytecodeId, rawData); bytecode.Stage = stage; shaderBytecodeResult.Bytecode = bytecode; return(shaderBytecodeResult); }
/// <summary> /// Converts the hlsl code into glsl and stores the result as plain text /// </summary> /// <param name="shaderSource">the hlsl shader</param> /// <param name="entryPoint">the entrypoint function name</param> /// <param name="stage">the shader pipeline stage</param> /// <param name="compilerParameters"></param> /// <param name="reflection">the reflection gathered from the hlsl analysis</param> /// <param name="sourceFilename">the name of the source file</param> /// <returns></returns> public ShaderBytecodeResult Compile(string shaderSource, string entryPoint, ShaderStage stage, ShaderMixinParameters compilerParameters, EffectReflection reflection, string sourceFilename = null) { var isOpenGLES = compilerParameters.Get(CompilerParameters.GraphicsPlatformKey) == GraphicsPlatform.OpenGLES; var isOpenGLES3 = compilerParameters.Get(CompilerParameters.GraphicsProfileKey) >= GraphicsProfile.Level_10_0; var shaderBytecodeResult = new ShaderBytecodeResult(); byte[] rawData; var shader = Compile(shaderSource, entryPoint, stage, isOpenGLES, isOpenGLES3, shaderBytecodeResult, sourceFilename); if (shader == null) { return(shaderBytecodeResult); } if (isOpenGLES) { // store both ES 2 and ES 3 on OpenGL ES platforms var shaderBytecodes = new ShaderLevelBytecode(); if (isOpenGLES3) { shaderBytecodes.DataES3 = shader; shaderBytecodes.DataES2 = null; } else { shaderBytecodes.DataES2 = shader; shaderBytecodes.DataES3 = Compile(shaderSource, entryPoint, stage, true, true, shaderBytecodeResult, sourceFilename); } using (var stream = new MemoryStream()) { BinarySerialization.Write(stream, shaderBytecodes); rawData = stream.GetBuffer(); } } else { // store string on OpenGL platforms rawData = Encoding.ASCII.GetBytes(shader); } var bytecodeId = ObjectId.FromBytes(rawData); var bytecode = new ShaderBytecode(bytecodeId, rawData); bytecode.Stage = stage; shaderBytecodeResult.Bytecode = bytecode; return(shaderBytecodeResult); }
public ShaderBytecodeResult Compile(string shaderSource, string entryPoint, ShaderStage stage, EffectCompilerParameters effectParameters, EffectReflection reflection, string sourceFilename = null) { var isDebug = effectParameters.Debug; var optimLevel = effectParameters.OptimizationLevel; var profile = effectParameters.Profile; var shaderModel = ShaderStageToString(stage) + "_" + ShaderProfileFromGraphicsProfile(profile); var shaderFlags = ShaderFlags.None; if (isDebug) { shaderFlags = ShaderFlags.Debug; } switch (optimLevel) { case 0: shaderFlags |= ShaderFlags.OptimizationLevel0; break; case 1: shaderFlags |= ShaderFlags.OptimizationLevel1; break; case 2: shaderFlags |= ShaderFlags.OptimizationLevel2; break; case 3: shaderFlags |= ShaderFlags.OptimizationLevel3; break; } SharpDX.Configuration.ThrowOnShaderCompileError = false; // Compile using D3DCompiler var compilationResult = SharpDX.D3DCompiler.ShaderBytecode.Compile(shaderSource, entryPoint, shaderModel, shaderFlags, EffectFlags.None, null, null, sourceFilename); var byteCodeResult = new ShaderBytecodeResult(); if (compilationResult.HasErrors || compilationResult.Bytecode == null) { // Log compilation errors byteCodeResult.Error(compilationResult.Message); } else { // TODO: Make this optional try { byteCodeResult.DisassembleText = compilationResult.Bytecode.Disassemble(); } catch (SharpDXException) { } // As effect bytecode binary can changed when having debug infos (with d3dcompiler_47), we are calculating a bytecodeId on the stripped version var rawData = compilationResult.Bytecode.Strip(StripFlags.CompilerStripDebugInformation | StripFlags.CompilerStripReflectionData); var bytecodeId = ObjectId.FromBytes(rawData); byteCodeResult.Bytecode = new ShaderBytecode(bytecodeId, compilationResult.Bytecode.Data) { Stage = stage }; // If compilation succeed, then we can update reflection. UpdateReflection(byteCodeResult.Bytecode, reflection, byteCodeResult); if (!string.IsNullOrEmpty(compilationResult.Message)) { byteCodeResult.Warning(compilationResult.Message); } } return byteCodeResult; }
public ShaderBytecodeResult Compile(string shaderSource, string entryPoint, ShaderStage stage, ShaderMixinParameters compilerParameters, EffectReflection reflection, string sourceFilename = null) { var isDebug = compilerParameters.Get(CompilerParameters.DebugKey); var profile = compilerParameters.Get(CompilerParameters.GraphicsProfileKey); var shaderModel = ShaderStageToString(stage) + "_" + ShaderProfileFromGraphicsProfile(profile); var shaderFlags = ShaderFlags.None; if (isDebug) { shaderFlags = ShaderFlags.OptimizationLevel0 | ShaderFlags.Debug; } SharpDX.Configuration.ThrowOnShaderCompileError = false; // Compile using D3DCompiler var compilationResult = SharpDX.D3DCompiler.ShaderBytecode.Compile(shaderSource, entryPoint, shaderModel, shaderFlags, EffectFlags.None, null, null, sourceFilename); var byteCodeResult = new ShaderBytecodeResult(); if (compilationResult.HasErrors) { // Log compilation errors byteCodeResult.Error(compilationResult.Message); } else { // As effect bytecode binary can changed when having debug infos (with d3dcompiler_47), we are calculating a bytecodeId on the stripped version var rawData = compilationResult.Bytecode.Strip(StripFlags.CompilerStripDebugInformation | StripFlags.CompilerStripReflectionData); var bytecodeId = ObjectId.FromBytes(rawData); byteCodeResult.Bytecode = new ShaderBytecode(bytecodeId, compilationResult.Bytecode.Data) { Stage = stage }; // If compilation succeed, then we can update reflection. UpdateReflection(byteCodeResult.Bytecode, reflection, byteCodeResult); if (!string.IsNullOrEmpty(compilationResult.Message)) { byteCodeResult.Warning(compilationResult.Message); } } return(byteCodeResult); }
private static void LinkVariable(EffectReflection reflection, string variableName, LocalParameterKey parameterKey, int slotCount) { var binding = new EffectResourceBindingDescription { KeyInfo = { KeyName = parameterKey.Name }, Class = parameterKey.Type.Class, Type = parameterKey.Type.Type, RawName = variableName, SlotStart = -1, SlotCount = slotCount > 0 ? slotCount : 1, ResourceGroup = parameterKey.ResourceGroup, LogicalGroup = parameterKey.LogicalGroup }; reflection.ResourceBindings.Add(binding); }
/// <summary> /// Create or updates the reflection for this shader /// </summary> /// <param name="effectReflection">the reflection from the hlsl</param> /// <param name="stage">the shader pipeline stage</param> private void CreateReflection(EffectReflection effectReflection, ShaderStage stage) { int currentProgram; GL.GetInteger(GetPName.CurrentProgram, out currentProgram); GL.UseProgram(ProgramId); int uniformBlockCount; GL.GetProgram(ProgramId, XkActiveUniformBlocks, out uniformBlockCount); var validConstantBuffers = new bool[effectReflection.ConstantBuffers.Count]; for (int uniformBlockIndex = 0; uniformBlockIndex < uniformBlockCount; ++uniformBlockIndex) { // TODO: get previous name to find te actual constant buffer in the reflexion #if SILICONSTUDIO_XENKO_GRAPHICS_API_OPENGLES const int sbCapacity = 128; int length; var sb = new StringBuilder(sbCapacity); GL.GetActiveUniformBlockName(ProgramId, uniformBlockIndex, sbCapacity, out length, sb); var constantBufferName = sb.ToString(); #else var constantBufferName = GL.GetActiveUniformBlockName(ProgramId, uniformBlockIndex); #endif var constantBufferDescriptionIndex = effectReflection.ConstantBuffers.FindIndex(x => x.Name == constantBufferName); if (constantBufferDescriptionIndex == -1) { reflectionResult.Error("Unable to find the constant buffer description [{0}]", constantBufferName); return; } var constantBufferIndex = effectReflection.ResourceBindings.FindIndex(x => x.RawName == constantBufferName); if (constantBufferIndex == -1) { reflectionResult.Error("Unable to find the constant buffer [{0}]", constantBufferName); return; } var constantBufferDescription = effectReflection.ConstantBuffers[constantBufferDescriptionIndex]; var constantBuffer = effectReflection.ResourceBindings[constantBufferIndex]; GL.GetActiveUniformBlock(ProgramId, uniformBlockIndex, ActiveUniformBlockParameter.UniformBlockDataSize, out constantBufferDescription.Size); int uniformCount; GL.GetActiveUniformBlock(ProgramId, uniformBlockIndex, ActiveUniformBlockParameter.UniformBlockActiveUniforms, out uniformCount); // set the binding GL.UniformBlockBinding(ProgramId, uniformBlockIndex, uniformBlockIndex); // Read uniforms desc var uniformIndices = new int[uniformCount]; var uniformOffsets = new int[uniformCount]; var uniformTypes = new int[uniformCount]; var uniformNames = new string[uniformCount]; GL.GetActiveUniformBlock(ProgramId, uniformBlockIndex, ActiveUniformBlockParameter.UniformBlockActiveUniformIndices, uniformIndices); GL.GetActiveUniforms(ProgramId, uniformIndices.Length, uniformIndices, ActiveUniformParameter.UniformOffset, uniformOffsets); GL.GetActiveUniforms(ProgramId, uniformIndices.Length, uniformIndices, ActiveUniformParameter.UniformType, uniformTypes); for (int uniformIndex = 0; uniformIndex < uniformIndices.Length; ++uniformIndex) { #if SILICONSTUDIO_XENKO_GRAPHICS_API_OPENGLES int size; ActiveUniformType aut; GL.GetActiveUniform(ProgramId, uniformIndices[uniformIndex], sbCapacity, out length, out size, out aut, sb); uniformNames[uniformIndex] = sb.ToString(); #else uniformNames[uniformIndex] = GL.GetActiveUniformName(ProgramId, uniformIndices[uniformIndex]); #endif } // Reoder by offset var indexMapping = uniformIndices.Select((x, i) => new UniformMergeInfo { Offset = uniformOffsets[i], Type = (ActiveUniformType)uniformTypes[i], Name = uniformNames[i], NextOffset = 0 }).OrderBy(x => x.Offset).ToArray(); indexMapping.Last().NextOffset = constantBufferDescription.Size; // Fill next offsets for (int i = 1; i < indexMapping.Length; ++i) { indexMapping[i - 1].NextOffset = indexMapping[i].Offset; } // Group arrays/structures into one variable (std140 layout is enough for offset determinism inside arrays/structures) indexMapping = indexMapping.GroupBy(x => { // Use only first part of name (ignore structure/array part) var name = x.Name; if (name.Contains(".")) { name = name.Substring(0, name.IndexOf('.')); } if (name.Contains("[")) { name = name.Substring(0, name.IndexOf('[')); } return(name); }) .Select(x => { var result = x.First(); result.NextOffset = x.Last().NextOffset; // Check weither it's an array or a struct int dotIndex = result.Name.IndexOf('.'); int arrayIndex = result.Name.IndexOf('['); if (x.Count() > 1 && arrayIndex == -1 && dotIndex == -1) { throw new InvalidOperationException(); } // TODO: Type processing result.Name = x.Key; return(result); }).ToArray(); foreach (var variableIndexGroup in indexMapping) { var variableIndex = -1; for (var tentativeIndex = 0; tentativeIndex < constantBufferDescription.Members.Length; ++tentativeIndex) { if (constantBufferDescription.Members[tentativeIndex].RawName == variableIndexGroup.Name) { variableIndex = tentativeIndex; break; } } if (variableIndex == -1) { reflectionResult.Error("Unable to find uniform [{0}] in constant buffer [{1}]", variableIndexGroup.Name, constantBufferName); continue; } var variable = constantBufferDescription.Members[variableIndex]; variable.Type.Type = GetTypeFromActiveUniformType(variableIndexGroup.Type); variable.Offset = variableIndexGroup.Offset; variable.Size = variableIndexGroup.NextOffset - variableIndexGroup.Offset; constantBufferDescription.Members[variableIndex] = variable; } constantBufferDescription.Type = ConstantBufferType.ConstantBuffer; constantBuffer.SlotCount = 1; // constant buffers are not arrays constantBuffer.SlotStart = uniformBlockIndex; constantBuffer.Stage = stage; // store the new values validConstantBuffers[constantBufferDescriptionIndex] = true; effectReflection.ConstantBuffers[constantBufferDescriptionIndex] = constantBufferDescription; effectReflection.ResourceBindings[constantBufferIndex] = constantBuffer; } //#endif // Remove unecessary cbuffer and resource bindings // Register textures, samplers, etc... //TODO: (?) non texture/buffer uniform outside of a block { // Register "NoSampler", required by HLSL=>GLSL translation to support HLSL such as texture.Load(). var noSampler = new EffectResourceBindingDescription { KeyInfo = { KeyName = "NoSampler" }, RawName = "NoSampler", Class = EffectParameterClass.Sampler, SlotStart = -1, SlotCount = 1 }; Reflection.ResourceBindings.Add(noSampler); int activeUniformCount; GL.GetProgram(ProgramId, GetProgramParameterName.ActiveUniforms, out activeUniformCount); #if !SILICONSTUDIO_XENKO_GRAPHICS_API_OPENGLES var uniformTypes = new int[activeUniformCount]; GL.GetActiveUniforms(ProgramId, activeUniformCount, Enumerable.Range(0, activeUniformCount).ToArray(), ActiveUniformParameter.UniformType, uniformTypes); #endif int textureUnitCount = 0; const int sbCapacity = 128; var sb = new StringBuilder(sbCapacity); for (int activeUniformIndex = 0; activeUniformIndex < activeUniformCount; ++activeUniformIndex) { #if !SILICONSTUDIO_XENKO_GRAPHICS_API_OPENGLES var uniformType = (ActiveUniformType)uniformTypes[activeUniformIndex]; var uniformName = GL.GetActiveUniformName(ProgramId, activeUniformIndex); #else ActiveUniformType uniformType; int uniformCount; int length; GL.GetActiveUniform(ProgramId, activeUniformIndex, sbCapacity, out length, out uniformCount, out uniformType, sb); var uniformName = sb.ToString(); //this is a special OpenglES case , it is declared as built in uniform, and the driver will take care of it, we just need to ignore it here if (uniformName.StartsWith("gl_DepthRange")) { continue; } #endif #if SILICONSTUDIO_XENKO_GRAPHICS_API_OPENGLES // Process uniforms if (GraphicsDevice.IsOpenGLES2) { switch (uniformType) { case ActiveUniformType.Bool: case ActiveUniformType.Int: AddUniform(effectReflection, validConstantBuffers, sizeof(int) * 1, uniformCount, uniformName, uniformType); break; case ActiveUniformType.BoolVec2: case ActiveUniformType.IntVec2: AddUniform(effectReflection, validConstantBuffers, sizeof(int) * 2, uniformCount, uniformName, uniformType); break; case ActiveUniformType.BoolVec3: case ActiveUniformType.IntVec3: AddUniform(effectReflection, validConstantBuffers, sizeof(int) * 3, uniformCount, uniformName, uniformType); break; case ActiveUniformType.BoolVec4: case ActiveUniformType.IntVec4: AddUniform(effectReflection, validConstantBuffers, sizeof(int) * 4, uniformCount, uniformName, uniformType); break; case ActiveUniformType.Float: AddUniform(effectReflection, validConstantBuffers, sizeof(float) * 1, uniformCount, uniformName, uniformType); break; case ActiveUniformType.FloatVec2: AddUniform(effectReflection, validConstantBuffers, sizeof(float) * 2, uniformCount, uniformName, uniformType); break; case ActiveUniformType.FloatVec3: AddUniform(effectReflection, validConstantBuffers, sizeof(float) * 3, uniformCount, uniformName, uniformType); break; case ActiveUniformType.FloatVec4: AddUniform(effectReflection, validConstantBuffers, sizeof(float) * 4, uniformCount, uniformName, uniformType); break; case ActiveUniformType.FloatMat4: AddUniform(effectReflection, validConstantBuffers, sizeof(float) * 4 * 4, uniformCount, uniformName, uniformType); break; case ActiveUniformType.FloatMat2: case ActiveUniformType.FloatMat3: throw new NotImplementedException(); } } #endif switch (uniformType) { #if !SILICONSTUDIO_XENKO_GRAPHICS_API_OPENGLES case ActiveUniformType.Sampler1D: case ActiveUniformType.Sampler1DShadow: case ActiveUniformType.IntSampler1D: case ActiveUniformType.UnsignedIntSampler1D: case ActiveUniformType.SamplerBuffer: case ActiveUniformType.UnsignedIntSamplerBuffer: case ActiveUniformType.IntSamplerBuffer: #endif case ActiveUniformType.Sampler2D: case ActiveUniformType.Sampler2DShadow: case ActiveUniformType.Sampler3D: // TODO: remove Texture3D that is not available in OpenGL ES 2 case ActiveUniformType.SamplerCube: case ActiveUniformType.IntSampler2D: case ActiveUniformType.IntSampler3D: case ActiveUniformType.IntSamplerCube: case ActiveUniformType.UnsignedIntSampler2D: case ActiveUniformType.UnsignedIntSampler3D: case ActiveUniformType.UnsignedIntSamplerCube: var uniformIndex = GL.GetUniformLocation(ProgramId, uniformName); // Temporary way to scan which texture and sampler created this texture_sampler variable (to fix with new HLSL2GLSL converter) var startIndex = -1; var textureReflectionIndex = -1; var samplerReflectionIndex = -1; do { int middlePart = uniformName.IndexOf('_', startIndex + 1); var textureName = middlePart != -1 ? uniformName.Substring(0, middlePart) : uniformName; var samplerName = middlePart != -1 ? uniformName.Substring(middlePart + 1) : null; textureReflectionIndex = effectReflection.ResourceBindings.FindIndex(x => x.RawName == textureName); samplerReflectionIndex = effectReflection.ResourceBindings.FindIndex(x => x.RawName == samplerName); if (textureReflectionIndex != -1 && samplerReflectionIndex != -1) { break; } startIndex = middlePart; } while (startIndex != -1); if (startIndex == -1 || textureReflectionIndex == -1 || samplerReflectionIndex == -1) { reflectionResult.Error("Unable to find sampler and texture corresponding to [{0}]", uniformName); continue; // Error } var textureReflection = effectReflection.ResourceBindings[textureReflectionIndex]; var samplerReflection = effectReflection.ResourceBindings[samplerReflectionIndex]; // Contrary to Direct3D, samplers and textures are part of the same object in OpenGL // Since we are exposing the Direct3D representation, a single sampler parameter key can be used for several textures, a single texture can be used with several samplers. // When such a case is detected, we need to duplicate the resource binding. textureReflectionIndex = GetReflexionIndex(textureReflection, textureReflectionIndex, effectReflection.ResourceBindings); samplerReflectionIndex = GetReflexionIndex(samplerReflection, samplerReflectionIndex, effectReflection.ResourceBindings); // Update texture uniform mapping GL.Uniform1(uniformIndex, textureUnitCount); textureReflection.Stage = stage; //textureReflection.Param.RawName = uniformName; textureReflection.Type = GetTypeFromActiveUniformType(uniformType); textureReflection.Class = EffectParameterClass.ShaderResourceView; textureReflection.SlotStart = textureUnitCount; textureReflection.SlotCount = 1; // TODO: texture arrays samplerReflection.Stage = stage; samplerReflection.Class = EffectParameterClass.Sampler; samplerReflection.SlotStart = textureUnitCount; samplerReflection.SlotCount = 1; // TODO: texture arrays effectReflection.ResourceBindings[textureReflectionIndex] = textureReflection; effectReflection.ResourceBindings[samplerReflectionIndex] = samplerReflection; Textures.Add(new Texture(textureUnitCount)); textureUnitCount++; break; } } // Remove any optimized resource binding effectReflection.ResourceBindings.RemoveAll(x => x.SlotStart == -1); effectReflection.ConstantBuffers = effectReflection.ConstantBuffers.Where((cb, i) => validConstantBuffers[i]).ToList(); } GL.UseProgram(currentProgram); }
private void AddUniform(EffectReflection effectReflection, bool[] validConstantBuffers, int uniformSize, int uniformCount, string uniformName, ActiveUniformType uniformType) { // OpenGL ES 2 is adding uniform for each cbuffer member, so we need to remove array and struct indexers so that we can identify it in cbuffer and find offset var uniformParts = new List <UniformPart>(8); var uniformLastStart = 0; for (var index = 0; index <= uniformName.Length; index++) { char c = index == uniformName.Length ? '.' : uniformName[index]; // Treat string end same as '.' if (c == '.' || c == '[') { var uniformPart = new UniformPart { Start = uniformLastStart, Count = index - uniformLastStart, Indexer = -1 }; // Read array index (if any) if (c == '[') { var indexerStart = ++index; while (uniformName[index] != ']') { index++; } // TODO: Avoid substring uniformPart.Indexer = int.Parse(uniformName.Substring(indexerStart, index - indexerStart)); index++; } uniformParts.Add(uniformPart); uniformLastStart = index + 1; } } var variableName = uniformName.Substring(0, uniformParts[0].Count); // check that this uniform is in a constant buffer int indexOfConstantBuffer = -1; int indexOfMember = -1; EffectConstantBufferDescription constantBufferDescription = null; for (int cbIndex = 0; cbIndex < effectReflection.ConstantBuffers.Count; cbIndex++) { var currentConstantBuffer = effectReflection.ConstantBuffers[cbIndex]; for (int index = 0; index < currentConstantBuffer.Members.Length; index++) { var member = currentConstantBuffer.Members[index]; if (member.RawName.Equals(variableName)) { indexOfConstantBuffer = cbIndex; indexOfMember = index; constantBufferDescription = currentConstantBuffer; break; } } if (constantBufferDescription != null) { break; } } if (constantBufferDescription == null) { throw new Exception("The uniform value " + variableName + " is defined outside of a uniform block, which is not supported by the engine."); } var indexOfResource = effectReflection.ResourceBindings.FindIndex(x => x.RawName == constantBufferDescription.Name); if (indexOfResource == -1) { reflectionResult.Error("Unable to find uniform [{0}] in any constant buffer", uniformName); return; } //var constantBufferDescription = effectReflection.ConstantBuffers[indexOfConstantBufferDescription]; var constantBuffer = effectReflection.ResourceBindings[indexOfResource]; // First time we encounter this cbuffer? if (!validConstantBuffers[indexOfConstantBuffer]) { constantBuffer.SlotStart = ConstantBufferOffsets.Length - 1; // Find next cbuffer slot Array.Resize(ref ConstantBufferOffsets, ConstantBufferOffsets.Length + 1); effectReflection.ResourceBindings[indexOfResource] = constantBuffer; ConstantBufferOffsets[constantBuffer.SlotStart + 1] = ConstantBufferOffsets[constantBuffer.SlotStart] + constantBufferDescription.Size; validConstantBuffers[indexOfConstantBuffer] = true; } //var elementSize = uniformSize; // For array, each element is rounded to register size //if (uniformSize%16 != 0 && uniformCount > 1) //{ // constantBufferDescription.Size = (constantBufferDescription.Size + 15)/16*16; // uniformSize = (uniformSize + 15)/16*16; //} // Check if it can fits in the same register, otherwise starts at the next one //if (uniformCount == 1 && constantBufferDescription.Size/16 != (constantBufferDescription.Size + uniformSize - 1)/16) // constantBufferDescription.Size = (constantBufferDescription.Size + 15)/16*16; var variable = constantBufferDescription.Members[indexOfMember]; // Resolve array/member var offset = variable.Offset; var type = variable.Type; for (int i = 0; i < uniformParts.Count; ++i) { var uniformPart = uniformParts[i]; // Apply member if (i > 0) { if (type.Members == null) { throw new InvalidOperationException($"Tried to find member \"{uniformName.Substring(uniformPart.Start, uniformPart.Count)}\" on a non-struct type when processing \"{uniformName}\""); } bool memberFound = false; for (int memberIndex = 0; memberIndex < type.Members.Length; ++memberIndex) { var member = type.Members[memberIndex]; if (string.Compare(member.Name, 0, uniformName, uniformPart.Start, uniformPart.Count) == 0) { // Adjust offset and set new type offset += member.Offset; type = member.Type; memberFound = true; break; } } if (!memberFound) { throw new InvalidOperationException($"Couldn't find member \"{uniformName.Substring(uniformPart.Start, uniformPart.Count)}\" on struct type \"{type.Name}\" when processing \"{uniformName}\""); } } // Apply indexer for arrays if (uniformPart.Indexer != -1) { offset += (type.ElementSize + 15) / 16 * 16 * uniformPart.Indexer; } } // Check type if (type.Type != GetTypeFromActiveUniformType(uniformType)) { throw new InvalidOperationException($"Uniform [{uniformName}] of type [{variable.Type.Type}] doesn't match OpenGL shader expected type [{GetTypeFromActiveUniformType(uniformType)}]"); } // No need to compare last element padding. // TODO: In case of float1/float2 arrays (rare) it is quite non-optimal to do a CompareMemory //variable.Size = uniformSize * (uniformCount - 1) + elementSize; //constantBufferDescription.Members[indexOfUniform] = variable; Uniforms.Add(new Uniform { Type = uniformType, Count = uniformCount, CompareSize = uniformSize + (uniformSize + 15) / 16 * 16 * (uniformCount - 1), ConstantBufferSlot = constantBuffer.SlotStart, Offset = offset, UniformIndex = GL.GetUniformLocation(ProgramId, uniformName) }); }
/// <summary> /// Converts the hlsl code into glsl and stores the result as plain text /// </summary> /// <param name="shaderSource">the hlsl shader</param> /// <param name="entryPoint">the entrypoint function name</param> /// <param name="stage">the shader pipeline stage</param> /// <param name="effectParameters"></param> /// <param name="reflection">the reflection gathered from the hlsl analysis</param> /// <param name="sourceFilename">the name of the source file</param> /// <returns></returns> public ShaderBytecodeResult Compile(string shaderSource, string entryPoint, ShaderStage stage, EffectCompilerParameters effectParameters, EffectReflection reflection, string sourceFilename = null) { var shaderBytecodeResult = new ShaderBytecodeResult(); byte[] rawData; var inputAttributeNames = new Dictionary <int, string>(); var resourceBindings = new Dictionary <string, int>(); GlslShaderPlatform shaderPlatform; int shaderVersion; switch (effectParameters.Platform) { case GraphicsPlatform.OpenGL: shaderPlatform = GlslShaderPlatform.OpenGL; shaderVersion = 410; break; case GraphicsPlatform.OpenGLES: shaderPlatform = GlslShaderPlatform.OpenGLES; shaderVersion = 300; break; case GraphicsPlatform.Vulkan: shaderPlatform = GlslShaderPlatform.Vulkan; shaderVersion = 450; break; default: throw new ArgumentOutOfRangeException("effectParameters.Platform"); } var shader = Compile(shaderSource, entryPoint, stage, shaderPlatform, shaderVersion, shaderBytecodeResult, reflection, inputAttributeNames, resourceBindings, sourceFilename); if (shader == null) { return(shaderBytecodeResult); } if (effectParameters.Platform == GraphicsPlatform.OpenGLES) // TODO: Add check to run on android only. The current version breaks OpenGL ES on windows. { //TODO: Remove this ugly hack! if (shaderSource.Contains($"Texture2D XenkoInternal_TextureExt0") && shader.Contains("uniform sampler2D")) { if (shaderPlatform != GlslShaderPlatform.OpenGLES || shaderVersion != 300) { throw new Exception("Invalid GLES platform or version: require OpenGLES 300"); } shader = shader.Replace("uniform sampler2D", "uniform samplerExternalOES"); shader = shader.Replace("#version 300 es", "#version 300 es\n#extension GL_OES_EGL_image_external_essl3 : require"); } } if (effectParameters.Platform == GraphicsPlatform.Vulkan) { string inputFileExtension; switch (stage) { case ShaderStage.Vertex: inputFileExtension = ".vert"; break; case ShaderStage.Pixel: inputFileExtension = ".frag"; break; case ShaderStage.Geometry: inputFileExtension = ".geom"; break; case ShaderStage.Domain: inputFileExtension = ".tese"; break; case ShaderStage.Hull: inputFileExtension = ".tesc"; break; case ShaderStage.Compute: inputFileExtension = ".comp"; break; default: shaderBytecodeResult.Error("Unknown shader profile"); return(shaderBytecodeResult); } var inputFileName = Path.ChangeExtension(Path.GetTempFileName(), inputFileExtension); var outputFileName = Path.ChangeExtension(inputFileName, ".spv"); // Write shader source to disk File.WriteAllBytes(inputFileName, Encoding.ASCII.GetBytes(shader)); // Run shader compiler var filename = Platform.Type == PlatformType.Windows ? "glslangValidator.exe" : "glslangValidator"; ShellHelper.RunProcessAndRedirectToLogger(filename, $"-V -o {outputFileName} {inputFileName}", null, shaderBytecodeResult); if (!File.Exists(outputFileName)) { shaderBytecodeResult.Error("Failed to generate SPIR-V from GLSL"); return(shaderBytecodeResult); } // Read compiled shader var shaderBytecodes = new ShaderInputBytecode { InputAttributeNames = inputAttributeNames, ResourceBindings = resourceBindings, Data = File.ReadAllBytes(outputFileName), }; using (var stream = new MemoryStream()) { BinarySerialization.Write(stream, shaderBytecodes); rawData = stream.ToArray(); } // Cleanup temp files File.Delete(inputFileName); File.Delete(outputFileName); } else { // store string on OpenGL platforms rawData = Encoding.UTF8.GetBytes(shader); } var bytecodeId = ObjectId.FromBytes(rawData); var bytecode = new ShaderBytecode(bytecodeId, rawData); bytecode.Stage = stage; shaderBytecodeResult.Bytecode = bytecode; return(shaderBytecodeResult); }
private static void CleanupReflection(EffectReflection reflection) { // TODO GRAPHICS REFACTOR we hardcode several resource group we want to preserve or optimize completly // Somehow this should be handled some other place (or probably we shouldn't cleanup reflection at all?) bool hasMaterialGroup = false; bool hasLightingGroup = false; foreach (var resourceBinding in reflection.ResourceBindings) { if (resourceBinding.Stage != ShaderStage.None) { if (!hasLightingGroup && resourceBinding.ResourceGroup == "PerLighting") { hasLightingGroup = true; } else if (!hasMaterialGroup && resourceBinding.ResourceGroup == "PerMaterial") { hasMaterialGroup = true; } } } var usedConstantBuffers = new HashSet <string>(); for (int i = reflection.ResourceBindings.Count - 1; i >= 0; i--) { var resourceBinding = reflection.ResourceBindings[i]; // Do not touch anything if there is logical groups // TODO: We can do better than that: remove only if the full group can be optimized away if (resourceBinding.LogicalGroup != null) { continue; } if (resourceBinding.Stage == ShaderStage.None && !(hasMaterialGroup && resourceBinding.ResourceGroup == "PerMaterial") && !(hasLightingGroup && resourceBinding.ResourceGroup == "PerLighting")) { reflection.ResourceBindings.RemoveAt(i); } else if (resourceBinding.Class == EffectParameterClass.ConstantBuffer || resourceBinding.Class == EffectParameterClass.TextureBuffer) { // Mark associated cbuffer/tbuffer as used usedConstantBuffers.Add(resourceBinding.KeyInfo.KeyName); } } // Remove unused cbuffer for (int i = reflection.ConstantBuffers.Count - 1; i >= 0; i--) { var cbuffer = reflection.ConstantBuffers[i]; // Do not touch anything if there is logical groups // TODO: We can do better than that: remove only if the full group can be optimized away var hasLogicalGroup = false; foreach (var member in cbuffer.Members) { if (member.LogicalGroup != null) { hasLogicalGroup = true; break; } } if (hasLogicalGroup) { continue; } if (!usedConstantBuffers.Contains(cbuffer.Name)) { reflection.ConstantBuffers.RemoveAt(i); } } }
private static void CleanupReflection(EffectReflection reflection) { for (int i = reflection.ConstantBuffers.Count - 1; i >= 0; i--) { var cBuffer = reflection.ConstantBuffers[i]; if (cBuffer.Stage == ShaderStage.None) { reflection.ConstantBuffers.RemoveAt(i); } } for (int i = reflection.ResourceBindings.Count - 1; i >= 0; i--) { var resourceBinding = reflection.ResourceBindings[i]; if (resourceBinding.Stage == ShaderStage.None) { reflection.ResourceBindings.RemoveAt(i); } } }
/// <summary> /// Converts the hlsl code into glsl and stores the result as plain text /// </summary> /// <param name="shaderSource">the hlsl shader</param> /// <param name="entryPoint">the entrypoint function name</param> /// <param name="stage">the shader pipeline stage</param> /// <param name="effectParameters"></param> /// <param name="reflection">the reflection gathered from the hlsl analysis</param> /// <param name="sourceFilename">the name of the source file</param> /// <returns></returns> public ShaderBytecodeResult Compile(string shaderSource, string entryPoint, ShaderStage stage, EffectCompilerParameters effectParameters, EffectReflection reflection, string sourceFilename = null) { var shaderBytecodeResult = new ShaderBytecodeResult(); byte[] rawData; var inputAttributeNames = new Dictionary<int, string>(); var resourceBindings = new Dictionary<string, int>(); GlslShaderPlatform shaderPlatform; int shaderVersion; switch (effectParameters.Platform) { case GraphicsPlatform.OpenGL: shaderPlatform = GlslShaderPlatform.OpenGL; shaderVersion = 410; break; case GraphicsPlatform.OpenGLES: shaderPlatform = GlslShaderPlatform.OpenGLES; shaderVersion = effectParameters.Profile >= GraphicsProfile.Level_10_0 ? 300 : 100; break; case GraphicsPlatform.Vulkan: shaderPlatform = GlslShaderPlatform.Vulkan; shaderVersion = 450; break; default: throw new ArgumentOutOfRangeException("effectParameters.Platform"); } var shader = Compile(shaderSource, entryPoint, stage, shaderPlatform, shaderVersion, shaderBytecodeResult, reflection, inputAttributeNames, resourceBindings, sourceFilename); if (shader == null) return shaderBytecodeResult; if (effectParameters.Platform == GraphicsPlatform.OpenGLES) { // store both ES 2 and ES 3 on OpenGL ES platforms var shaderBytecodes = new ShaderLevelBytecode(); if (effectParameters.Profile >= GraphicsProfile.Level_10_0) { shaderBytecodes.DataES3 = shader; shaderBytecodes.DataES2 = null; } else { shaderBytecodes.DataES2 = shader; shaderBytecodes.DataES3 = Compile(shaderSource, entryPoint, stage, GlslShaderPlatform.OpenGLES, 300, shaderBytecodeResult, reflection, inputAttributeNames, resourceBindings, sourceFilename); } using (var stream = new MemoryStream()) { BinarySerialization.Write(stream, shaderBytecodes); #if !SILICONSTUDIO_RUNTIME_CORECLR && !SILICONSTUDIO_PLATFORM_UWP rawData = stream.GetBuffer(); #else // FIXME: Manu: The call to "ToArray()" might be slower than "GetBuffer()" rawData = stream.ToArray(); #endif } } #if !SILICONSTUDIO_RUNTIME_CORECLR && !SILICONSTUDIO_PLATFORM_UWP else if (effectParameters.Platform == GraphicsPlatform.Vulkan) { string inputFileExtension; switch (stage) { case ShaderStage.Vertex: inputFileExtension = ".vert"; break; case ShaderStage.Pixel: inputFileExtension = ".frag"; break; case ShaderStage.Geometry: inputFileExtension = ".geom"; break; case ShaderStage.Domain: inputFileExtension = ".tese"; break; case ShaderStage.Hull: inputFileExtension = ".tesc"; break; case ShaderStage.Compute: inputFileExtension = ".comp"; break; default: shaderBytecodeResult.Error("Unknown shader profile"); return shaderBytecodeResult; } var inputFileName = Path.ChangeExtension(Path.GetTempFileName(), inputFileExtension); var outputFileName = Path.ChangeExtension(inputFileName, ".spv"); // Write shader source to disk File.WriteAllBytes(inputFileName, Encoding.ASCII.GetBytes(shader)); // Run shader compiler var filename = Platform.Type == PlatformType.Windows ? "glslangValidator.exe" : "glslangValidator"; ShellHelper.RunProcessAndRedirectToLogger(filename, $"-V -o {outputFileName} {inputFileName}", null, shaderBytecodeResult); if (!File.Exists(outputFileName)) { shaderBytecodeResult.Error("Failed to generate SPIR-V from GLSL"); return shaderBytecodeResult; } // Read compiled shader var shaderBytecodes = new ShaderInputBytecode { InputAttributeNames = inputAttributeNames, ResourceBindings = resourceBindings, Data = File.ReadAllBytes(outputFileName), }; using (var stream = new MemoryStream()) { BinarySerialization.Write(stream, shaderBytecodes); rawData = stream.ToArray(); } // Cleanup temp files File.Delete(inputFileName); File.Delete(outputFileName); } #endif else { // store string on OpenGL platforms rawData = Encoding.UTF8.GetBytes(shader); } var bytecodeId = ObjectId.FromBytes(rawData); var bytecode = new ShaderBytecode(bytecodeId, rawData); bytecode.Stage = stage; shaderBytecodeResult.Bytecode = bytecode; return shaderBytecodeResult; }