// Copyright (C) 2017 Jérôme Leclercq // This file is part of the "Nazara Engine - Renderer module" // For conditions of distribution and use, see copyright notice in Config.hpp #include #include #include #include #include #include #include #include namespace Nz { UberShaderPreprocessor::~UberShaderPreprocessor() { OnUberShaderPreprocessorRelease(this); } UberShaderInstance* UberShaderPreprocessor::Get(const ParameterList& parameters) const { // Première étape, transformer les paramètres en un flag UInt32 flags = 0; for (auto it = m_flags.begin(); it != m_flags.end(); ++it) { if (parameters.HasParameter(it->first)) { bool value; if (parameters.GetBooleanParameter(it->first, &value) && value) flags |= it->second; } } // Le shader fait-il partie du cache ? auto shaderIt = m_cache.find(flags); // Si non, il nous faut le construire if (shaderIt == m_cache.end()) { try { // Une exception sera lancée à la moindre erreur et celle-ci ne sera pas enregistrée dans le log (car traitée dans le bloc catch) ErrorFlags errFlags(ErrorFlag_Silent | ErrorFlag_ThrowException, true); ShaderRef shader = Shader::New(); shader->Create(); for (unsigned int i = 0; i <= ShaderStageType_Max; ++i) { const CachedShader& shaderStage = m_shaders[i]; // Le shader stage est-il activé dans cette version du shader ? if (shaderStage.present && (flags & shaderStage.requiredFlags) == shaderStage.requiredFlags) { UInt32 stageFlags = 0; for (auto it = shaderStage.flags.begin(); it != shaderStage.flags.end(); ++it) { if (parameters.HasParameter(it->first)) { bool value; if (parameters.GetBooleanParameter(it->first, &value) && value) stageFlags |= it->second; } } auto stageIt = shaderStage.cache.find(stageFlags); if (stageIt == shaderStage.cache.end()) { ShaderStage stage; stage.Create(static_cast(i)); unsigned int glslVersion = OpenGL::GetGLSLVersion(); StringStream code; code << "#version " << glslVersion << "\n\n"; code << "#define GLSL_VERSION " << glslVersion << "\n\n"; code << "#define EARLY_FRAGMENT_TESTS " << ((glslVersion >= 420 || OpenGL::IsSupported(OpenGLExtension_Shader_ImageLoadStore)) ? '1' : '0') << "\n\n"; for (auto it = shaderStage.flags.begin(); it != shaderStage.flags.end(); ++it) code << "#define " << it->first << ' ' << ((stageFlags & it->second) ? '1' : '0') << '\n'; code << "\n#line 1\n"; // Pour que les éventuelles erreurs du shader se réfèrent à la bonne ligne code << shaderStage.source; stage.SetSource(code); try { stage.Compile(); } catch (const std::exception&) { ErrorFlags errFlags2(ErrorFlag_ThrowExceptionDisabled); NazaraError("Shader code failed to compile (" + stage.GetLog() + ")\n" + code.ToString()); throw; } stageIt = shaderStage.cache.emplace(flags, std::move(stage)).first; } shader->AttachStage(static_cast(i), stageIt->second); } } shader->Link(); // On construit l'instant shaderIt = m_cache.emplace(flags, shader.Get()).first; } catch (const std::exception&) { ErrorFlags errFlags(ErrorFlag_ThrowExceptionDisabled); NazaraError("Failed to build UberShader instance: " + Error::GetLastError()); throw; } } return &shaderIt->second; } void UberShaderPreprocessor::SetShader(ShaderStageType stage, const String& source, const String& shaderFlags, const String& requiredFlags) { CachedShader& shader = m_shaders[stage]; shader.present = true; shader.source = source; // On extrait les flags de la chaîne std::vector flags; shaderFlags.Split(flags, ' '); for (String& flag : flags) { auto it = m_flags.find(flag); if (it == m_flags.end()) m_flags[flag] = 1U << m_flags.size(); auto it2 = shader.flags.find(flag); if (it2 == shader.flags.end()) shader.flags[flag] = 1U << shader.flags.size(); } // On construit les flags requis pour l'activation du shader shader.requiredFlags = 0; flags.clear(); requiredFlags.Split(flags, ' '); for (String& flag : flags) { UInt32 flagVal; auto it = m_flags.find(flag); if (it == m_flags.end()) { flagVal = 1U << m_flags.size(); m_flags[flag] = flagVal; } else flagVal = it->second; shader.requiredFlags |= flagVal; } } bool UberShaderPreprocessor::SetShaderFromFile(ShaderStageType stage, const String& filePath, const String& shaderFlags, const String& requiredFlags) { File file(filePath); if (!file.Open(OpenMode_ReadOnly | OpenMode_Text)) { NazaraError("Failed to open \"" + filePath + '"'); return false; } unsigned int length = static_cast(file.GetSize()); String source(length, '\0'); if (file.Read(&source[0], length) != length) { NazaraError("Failed to read program file"); return false; } file.Close(); SetShader(stage, source, shaderFlags, requiredFlags); return true; } bool UberShaderPreprocessor::IsSupported() { return true; // Forcément supporté } }