public Shader LoadShader(string name, AssetSourceEnum assetTypes, ShaderStages stage, bool useSpirvCompile = false) { //Input path should be '/' folder delimited. It is changed to '.' delimited for embedded resources var shaderFileInfo = GetShaderFileInfo(name, _systemComponents.Device, useSpirvCompile); var shaderBytes = new byte[] { }; switch (assetTypes) { case AssetSourceEnum.File: shaderBytes = TryLoadShaderBytesFromFile(shaderFileInfo); break; case AssetSourceEnum.Embedded: shaderFileInfo.Directory = shaderFileInfo.Directory.Replace('/', '.'); shaderBytes = TryLoadShaderBytesFromEmbeddedResource(shaderFileInfo); break; } if (shaderBytes.Length == 0) { _frameworkMessenger.Report(string.Concat("Unable to load shader file: ", shaderFileInfo.Directory, "| ", shaderFileInfo.Name, " | ", shaderFileInfo.Extension, ", of type --> ", assetTypes.ToString())); return(null); } return(useSpirvCompile ? _systemComponents.Factory.CreateShaderCompileFromSpirv(new ShaderDescription(stage, shaderBytes, shaderFileInfo.EntryPointMethod, true)): _systemComponents.Factory.CreateShader(new ShaderDescription(stage, shaderBytes, shaderFileInfo.EntryPointMethod, true))); }
public void FontManager_LoadUserFontCheckValidFlow_MakesCorrectCalls(AssetSourceEnum source) { var id = Substitute.For <IIdGenerator>(); var properties = Substitute.For <IStartupPropertiesCache>(); var collection = Substitute.For <IFontCollection>(); var loader = Substitute.For <IFontLoader>(); id.New().Returns(0UL); collection.Add(Arg.Any <ulong>(), Arg.Any <IFontModel>()).Returns(true); properties.User.Returns(new StartupConfig { FontFolder = "folder" }); loader.FindDotFntFileNamePartialMatchesFromEmbeddedResource(Arg.Any <bool>(), Arg.Any <string>()).Returns(new List <string> { "random" }); loader.FindDotFntFileNamePartialMatchesFromFileResource(Arg.Any <string>()).Returns(new List <string> { "random" }); loader.LoadEmbeddedStream(Arg.Any <bool>(), Arg.Any <string>()).Returns(default(Stream)); loader.LoadFileStream(Arg.Any <string>()).Returns(default(Stream)); loader.ReadStreamToStringList(Arg.Any <Stream>()).Returns(new List <string> { "single line" }); loader.TryToLoadSubFontDescription(Arg.Any <string>(), Arg.Any <bool>(), Arg.Any <AssetSourceEnum>(), Arg.Any <ImageFormat>(), Arg.Any <List <string> >()).Returns(new CandidateSubFontDesc()); loader.GenerateFontFromDescriptionInfo(Arg.Any <CandidateFontDesc>()) .Returns(new Yak2D.Font.FontModel(new List <SubFont> { new SubFont(1, 1, null, null, false, null) })); IFontManager manager = new FontManager(id, properties, collection, loader); loader.ClearReceivedCalls(); var result = manager.LoadUserFont("path", source, ImageFormat.PNG); Assert.NotNull(result); switch (source) { case AssetSourceEnum.Embedded: loader.Received(1).FindDotFntFileNamePartialMatchesFromEmbeddedResource(false, Arg.Any <string>()); loader.Received(1).LoadEmbeddedStream(false, Arg.Any <string>()); break; case AssetSourceEnum.File: loader.Received(1).FindDotFntFileNamePartialMatchesFromFileResource(Arg.Any <string>()); loader.Received(1).LoadFileStream(Arg.Any <string>()); break; } loader.Received(1).ReadStreamToStringList(Arg.Any <Stream>()); loader.Received(1).TryToLoadSubFontDescription(Arg.Any <string>(), false, source, ImageFormat.PNG, Arg.Any <List <string> >()); loader.Received(1).GenerateFontFromDescriptionInfo(Arg.Any <CandidateFontDesc>()); }
public ICustomShaderStage CreateCustomShaderStage(string fragmentShaderPathName, AssetSourceEnum assetType, ShaderUniformDescription[] uniformDescriptions, BlendState blendState, bool useSpirvCompile = false) { return(_renderStageManager.CreateCustomShaderStage(fragmentShaderPathName, assetType, uniformDescriptions, blendState, useSpirvCompile)); }
public TextureData LoadTextureColourData(string path, AssetSourceEnum assetType, ImageFormat imageFormat) { switch (assetType) { case AssetSourceEnum.File: return(_surfaceManager.LoadTextureColourDataFromFile(path, imageFormat)); case AssetSourceEnum.Embedded: return(_surfaceManager.LoadTextureColourDataFromEmbeddedResourceInUserApplication(path, imageFormat)); } return(default(TextureData)); }
public ICustomShaderStage CreateCustomShaderStage(string fragmentShaderFilename, AssetSourceEnum assetType, ShaderUniformDescription[] uniformDescriptions, BlendState blendState, bool useSpirvCompile) { var id = _idGenerator.New(); var model = _renderStageModelFactory.CreateCustomStageModel(fragmentShaderFilename, assetType, uniformDescriptions, blendState, useSpirvCompile); var userReference = new CustomShaderStage(id); return(_renderStageCollection.Add(id, model) ? userReference : null); }
public IFont LoadFont(string path, AssetSourceEnum assetType, ImageFormat imageFormat = ImageFormat.PNG) { if (string.IsNullOrWhiteSpace(path)) { throw new Yak2DException("Fonts -> Load font passed null or empty path", new ArgumentNullException("path")); } var trimmedPath = path.Trim(); if (trimmedPath.Any(char.IsWhiteSpace)) { throw new Yak2DException("Fonts -> Asset path name cannot contain any whitespace", new ArgumentNullException("path")); } return(_fontManager.LoadUserFont(trimmedPath, assetType, imageFormat)); }
public IFont LoadUserFont(string fontPathWithoutExtension, AssetSourceEnum assetType, ImageFormat imageFormat) { //Guards and exceptions for null or white space strings already exist at the IFont Level if (string.IsNullOrWhiteSpace(fontPathWithoutExtension) || fontPathWithoutExtension.Trim().Any(char.IsWhiteSpace)) { throw new Yak2DException(string.Concat("Unable to load user font as path is null or contains whitespace")); } fontPathWithoutExtension = fontPathWithoutExtension.Trim(); var font = LoadFont(false, fontPathWithoutExtension, assetType, imageFormat); var id = _idGenerator.New(); return(_userFontCollection.Add(id, font) ? new FontReference(id) : null); }
public ICustomShaderStageModel CreateCustomStageModel(string fragmentShaderFilename, AssetSourceEnum assetType, ShaderUniformDescription[] uniformDescriptions, BlendState blendState, bool useSpirvCompile) { return(new CustomShaderStageModel(_frameworkMessenger, _systemComponents, _shaderTools, _pipelineFactory, _blendStateConverter, fragmentShaderFilename, assetType, uniformDescriptions, blendState, useSpirvCompile)); }
public ITexture LoadTexture(string path, AssetSourceEnum assetType, ImageFormat imageFormat = ImageFormat.PNG, SamplerType samplerType = SamplerType.Anisotropic, bool generateMipMaps = true) { switch (assetType) { case AssetSourceEnum.File: return(_surfaceManager.CreateTextureFromFile(path, imageFormat, samplerType, generateMipMaps)); case AssetSourceEnum.Embedded: return(_surfaceManager.CreateTextureFromEmbeddedResourceInUserApplication(path, imageFormat, samplerType, generateMipMaps)); } return(null); }
public CustomShaderStageModel(IFrameworkMessenger frameworkMessenger, ISystemComponents systemComponents, IShaderLoader shaderLoader, IPipelineFactory pipelineFactory, IBlendStateConverter blendStateConverter, string fragmentShaderFilename, AssetSourceEnum shaderFileAssetType, ShaderUniformDescription[] userUniformDescriptions, BlendState blendState, bool userSpirvCompile) { _frameworkMessenger = frameworkMessenger; _systemComponents = systemComponents; _shaderLoader = shaderLoader; _pipelineFactory = pipelineFactory; _blendStateConverter = blendStateConverter; _fragmentShaderFilename = fragmentShaderFilename; _shaderFileAssetType = shaderFileAssetType; _userUniformDescriptions = userUniformDescriptions; _blendState = blendState; _useSpirvCompile = userSpirvCompile; Initialise(); }
/* * Shader File Configuration Notes * * A small number of naming, file extension and entry point restrictions are mandated to enable easy use * of shaders across each backend. * * Shader Files * - One file should be used for each Vertex and Fragment Shader * - Each Vertex shader code file MUST have "Vertex" in the name * - Each Fragment shader code file MUST have "Fragment" in the name * - Shaders for each backend sit in their respective sub-folders (Direct3D, Metal, OpenGL and Vulkan) * * - All HLSL shader source files should have extension .hlsl * - The HLSL compiling script uses the presence of Vertex/Fragment in the source file name to choose the right compiling profile * - Compiled HLSL code files loaded by the framework have the extension .hlsl.bytes * * - Vulkan Shaders must use the extensions .vert and .frag respectively for Vertex and Fragment shaders * - Vulkan shader code is compiled to .spv * * - OpenGL Shaders must all use the extension .glsl * - OpenGL shader code is not compiled, and loaded as text byte strings straight from shader source during runtime * * - Metal shader source files have the extension .metal * - Metal shaders are compiled to .metallib * * Shader Code * - The entry point for all Vertex and Pixel Shader functions across backends is main() * - EXCEPT metal, where main() is not permitted. For metal shaders the function shader() is used * * SPIR-V Additions * - The main framework avoids relying on SPIR-V compilation from single source due to inconsistencies that can crop up * either due to user error, SPIR-V issue or backend unique properties. This caution maybe unwarranted, but there was * a preference to code each backend shader individually * - HOWEVER - for custom shaders stages, a SPIR-V .glsl can be used, enabling runtime cross platform compilation * - The rules for SPIR-V shaders are 1. paths are rooted in Shaders/ base directory, .glsl extension and main() entiry point * * Notes: * You may note significant padding in shader uniforms, and the avoidance of some vector types, particularly float/vec3s * - Most of this is overly defensive and ignores potential reliance that could be had on explicit byte layout guides provided in c# structs * - The aim here was to avoid the stride differences found in some data types across backends, reducing/removing uniform data problems * Compiling HLSL with DXC.exe (the latest compiler) causes issues with SharpDX that are quite opague. Compiling with FXC.exe * - works for included shaders and is therefore the preferred method. In the future I would like to find out why this issue exists (semantics?) * I avoided using Veldrid-SPIRV cross for shader translation across backends to avoid edge case problems, * - remove another layer of potential debugging, and also to better learn other shader languages * - With the shader set relatively fixed, it could be ported to single source, cross translation in the future * * Shader Compilation * Scripts are provided for compiling all shaders in a directory: .bat file for windows HLSL/Vulkan and .sh for Linux Vulkan and OSX metal * .bat files must be given one argument, which is the filepath of the compiler .exe to use * .sh shell scripts relay on compiler paths being present on the system * NOTE: Currently, the .bat files are able to hand recursive directories of shaders to compile * .sh shell scripts currently only compile shaders in same directory as the script (to be modified in the future) * * Compilers * HLSL: Use FXC.exe (I experience issues with DXC.exe) * Vulkan: Use glslc from the Vulkan SDK * Metal: Use xcrun (will require installation of XCode and associated tools) */ public ShaderPackage CreateShaderPackage(string vertexShaderName, AssetSourceEnum vertexShaderAssetType, string fragmentShaderName, AssetSourceEnum fragmentShaderAssetType, VertexLayoutDescription layoutDescription, ResourceLayoutElementDescription[][] uniformDescriptions, bool useSpirvCompileVertexShader = false, bool useSpirvCompileFragmentShader = false) { if (uniformDescriptions == null) { throw new Yak2DException("Error loading shader, null uniform resource layout descriptions array provided"); } for (var n = 0; n < uniformDescriptions.Length; n++) { if (uniformDescriptions[n] == null) { throw new Yak2DException("Error loading shader, null uniform resource layout descriptions sub-array provided"); } } if (vertexShaderName == null || vertexShaderName.Trim().Any(char.IsWhiteSpace)) { throw new Yak2DException("Error loading shader, Vertex Shader name cannot be null or contain whitespace"); } vertexShaderName = vertexShaderName.Trim(); if (fragmentShaderName == null || fragmentShaderName.Trim().Any(char.IsWhiteSpace)) { throw new Yak2DException("Error loading shader, Fragment Shader name cannot be null or contain whitespace"); } fragmentShaderName = fragmentShaderName.Trim(); var uniformResourceLayout = _loadFunctions.CreateUniformResourceLayouts(uniformDescriptions); var vertexShader = _loadFunctions.LoadShader(vertexShaderName, vertexShaderAssetType, ShaderStages.Vertex, useSpirvCompileVertexShader); if (vertexShader == null) { throw new Yak2DException(string.Concat("Vertex Shader: ", vertexShaderName, " failed to Load")); } var fragmentShader = _loadFunctions.LoadShader(fragmentShaderName, fragmentShaderAssetType, ShaderStages.Fragment, useSpirvCompileFragmentShader); if (fragmentShader == null) { throw new Yak2DException(string.Concat("Fragment Shader: ", fragmentShaderName, " failed to Load")); } return(new ShaderPackage { Description = new ShaderSetDescription( new[] { layoutDescription }, new[] { vertexShader, fragmentShader } ), UniformResourceLayout = uniformResourceLayout }); }
private IFontModel LoadFont(bool isFrameworkInternal, string pathToFontNameNoExtension, AssetSourceEnum assetType, ImageFormat imageFormat) { //There is a .fnt file for each size of a font. The .fnt file contains information on spacing and importantly, //what the texture files are called for that font size. When loading a font, we attempt to load each font size //as an additional subfont. Hence here, we seach for font name partial/leading matches. i.e. //"notosans" would find font sizes such as "notosans_22.fnt", "notosans_24.fnt", etc. We then try and load each //of those font files as a sub font. The seach path may include additional folders, such as "/mono/notosans" etc //We always match the entire search string with the leading part of the font name var pathWithoutAssemblyOrExtension = string.Concat(isFrameworkInternal ? _systemFontFolder : _startUpProperties.FontFolder, isFrameworkInternal ? "." : "/", pathToFontNameNoExtension); pathWithoutAssemblyOrExtension = assetType == AssetSourceEnum.Embedded ? pathWithoutAssemblyOrExtension.Replace("/", ".") : pathWithoutAssemblyOrExtension.Replace(".", "/"); var fontBaseFolderWithoutAssemblyDoesNotEndInDivisor = assetType == AssetSourceEnum.Embedded ? pathWithoutAssemblyOrExtension.Substring(0, pathWithoutAssemblyOrExtension.LastIndexOf(".")) : pathWithoutAssemblyOrExtension.Substring(0, pathWithoutAssemblyOrExtension.LastIndexOf("/")); var candidateSubFontDotFntResourceFileNames = new List <string> { }; switch (assetType) { case AssetSourceEnum.Embedded: candidateSubFontDotFntResourceFileNames = _fontLoader.FindDotFntFileNamePartialMatchesFromEmbeddedResource(isFrameworkInternal, pathWithoutAssemblyOrExtension); break; case AssetSourceEnum.File: candidateSubFontDotFntResourceFileNames = _fontLoader.FindDotFntFileNamePartialMatchesFromFileResource(pathWithoutAssemblyOrExtension); break; } if (candidateSubFontDotFntResourceFileNames.Count == 0) { throw new Yak2DException(string.Concat("Unable to complete Load Font request, no potential .fnt resources were found: ", pathWithoutAssemblyOrExtension, " , ", assetType.ToString(), ",", " Framework Internal?== ", isFrameworkInternal)); } candidateSubFontDotFntResourceFileNames.ForEach(x => { if (x.Any(char.IsWhiteSpace)) { throw new Yak2DException(string.Concat("Unable to complete Load Font request, resource names must not contain whitespace: ", x)); } ; }); //These LINQs could be collapsed, but split out for now to aid reading var candidateSubFontDotFntFileStreams = candidateSubFontDotFntResourceFileNames.Select(resourcePathName => { Stream stream = null; //Switch rather than one line conditional in case future requires other content types switch (assetType) { case AssetSourceEnum.Embedded: stream = _fontLoader.LoadEmbeddedStream(isFrameworkInternal, resourcePathName); break; case AssetSourceEnum.File: stream = _fontLoader.LoadFileStream(resourcePathName); break; } return(stream); }).ToList(); var streamStrings = candidateSubFontDotFntFileStreams.Select(stream => { return(_fontLoader.ReadStreamToStringList(stream)); }).Where(lines => lines.Count > 0).ToList(); var candidateFontDesc = new CandidateFontDesc { Name = pathToFontNameNoExtension, CandidateSubFonts = streamStrings.Select(lines => _fontLoader.TryToLoadSubFontDescription( fontBaseFolderWithoutAssemblyDoesNotEndInDivisor, isFrameworkInternal, assetType, imageFormat, lines)).Where(desc => desc != null).ToList() }; if (candidateFontDesc.CandidateSubFonts.Count == 0) { throw new Yak2DException(string.Concat(string.Concat("Unable to complete font request, failed to load any sub font descriptors: ", pathToFontNameNoExtension, " , ", assetType.ToString(), ",", " Framework Internal?== ", isFrameworkInternal))); } return(_fontLoader.GenerateFontFromDescriptionInfo(candidateFontDesc)); }
public CandidateSubFontDesc TryToLoadSubFontDescription(string fontBaseFolderWithoutAssemblyDoesNotEndInDivisor, bool isFrameworkInternal, AssetSourceEnum assetType, ImageFormat imageFormat, List <string> fntFileLines) { var resourceFolder = fontBaseFolderWithoutAssemblyDoesNotEndInDivisor; var extractedTexturePathsWithoutBasePath = _subFontGenerator.ExtractPngFilePathsFromDotFntLines(fntFileLines); if (extractedTexturePathsWithoutBasePath.Count == 0) { return(null); //Fail Message already displayed in earlier method } var texturePathsWithoutBasePathsWithoutFileExtension = extractedTexturePathsWithoutBasePath.Select(x => { if (!x.EndsWith(".png")) { _frameworkMessenger.Report("Warning -> expected texture path name does not end with .png"); } return(x.Remove(x.Length - 4, 4)); }).ToList(); var textures = new List <ITexture>(); texturePathsWithoutBasePathsWithoutFileExtension.ForEach(texturePath => { ITexture texture = null; var texturePathWithoutExtension = string.Concat(resourceFolder, assetType == AssetSourceEnum.Embedded ? "." : "/", texturePath); switch (assetType) { case AssetSourceEnum.Embedded: texture = _gpuSurfaceManager.CreateFontTextureFromEmbeddedResource(isFrameworkInternal, texturePathWithoutExtension, imageFormat, SamplerType.Anisotropic); break; case AssetSourceEnum.File: texture = _gpuSurfaceManager.CreateFontTextureFromFile(texturePathWithoutExtension, imageFormat, SamplerType.Anisotropic); break; } if (texture != null) { textures.Add(texture); } }); if (textures.Count == 0) { _frameworkMessenger.Report(string.Concat("Texture loads for font failed: ", resourceFolder, ",", " Framework Internal?== ", isFrameworkInternal)); } return(new CandidateSubFontDesc { DotFntLines = fntFileLines, TexturePaths = texturePathsWithoutBasePathsWithoutFileExtension, Textures = textures }); }