// Copyright (C) 2013 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 #include #include namespace { struct ParamsHash { std::size_t operator()(const NzShaderProgramManagerParams& params) const { static_assert(nzShaderTarget_Max < 0x4, "Maximum shader target takes more than 2 bits"); std::size_t h = (params.target << 0) | // 2 bits (params.flags << 2); // 8 bits switch (params.target) { case nzShaderTarget_FullscreenQuad: h |= (params.fullscreenQuad.alphaMapping << 10) | // 1 bit (params.fullscreenQuad.alphaTest << 11) | // 1 bit (params.fullscreenQuad.diffuseMapping << 12); // 1 bit break; case nzShaderTarget_Model: h |= (params.model.alphaMapping << 10) | // 1 bit (params.model.alphaTest << 11) | // 1 bit (params.model.diffuseMapping << 12) | // 1 bit (params.model.emissiveMapping << 13) | // 1 bit (params.model.lighting << 14) | // 1 bit (params.model.normalMapping << 15) | // 1 bit (params.model.parallaxMapping << 16) | // 1 bit (params.model.specularMapping << 17); // 1 bit break; case nzShaderTarget_None: break; case nzShaderTarget_Sprite: h |= (params.sprite.alphaMapping << 10) | // 1 bit (params.sprite.alphaTest << 11) | // 1 bit (params.sprite.diffuseMapping << 12); // 1 bit break; } return h; } }; struct ParamsEquality { bool operator()(const NzShaderProgramManagerParams& first, const NzShaderProgramManagerParams& second) const { if (first.target != second.target || first.flags != second.flags) return false; switch (first.target) { case nzShaderTarget_FullscreenQuad: return std::memcmp(&first.fullscreenQuad, &second.fullscreenQuad, sizeof(NzShaderProgramManagerParams::FullscreenQuad)) == 0; case nzShaderTarget_Model: return std::memcmp(&first.model, &second.model, sizeof(NzShaderProgramManagerParams::Model)) == 0; case nzShaderTarget_None: return true; case nzShaderTarget_Sprite: return std::memcmp(&first.sprite, &second.sprite, sizeof(NzShaderProgramManagerParams::Sprite)) == 0; } return false; } }; std::unordered_map s_programs; NzString s_cacheDirectory("shaderCache"); bool s_cacheEnabled = false; bool s_earlyFragmentTest; bool s_glsl140; //bool s_loadCachedPrograms = true; unsigned int s_glslVersion; } const NzShaderProgram* NzShaderProgramManager::Get(const NzShaderProgramManagerParams& params) { auto it = s_programs.find(params); if (it == s_programs.end()) { // Alors nous générons le programme std::unique_ptr program; if (s_cacheEnabled) { NzString programFileName = NzNumberToString(ParamsHash()(params), 36) + ".nsb"; // Nazara Shader Binary, très original, je sais NazaraDebug("Checking cache for program file \"" + programFileName + "\"..."); NzFile shaderFile(s_cacheDirectory + NAZARA_DIRECTORY_SEPARATOR + programFileName); if (shaderFile.Open(NzFile::ReadOnly)) { NazaraDebug("File found"); unsigned int size = shaderFile.GetSize(); NzByteArray binary; binary.Resize(size); if (shaderFile.Read(&binary[0], size) != size) { NazaraError("Failed to read program binary"); return false; } shaderFile.Close(); program.reset(new NzShaderProgram); if (!program->LoadFromBinary(binary)) { NazaraWarning("Program \"" + programFileName + "\" loading failed, this is mostly due to a driver/video card " "update or a file corruption, regenerating program..."); program.reset(GenerateProgram(params)); } } else { if (shaderFile.Exists()) NazaraWarning("Program file exists but couldn't be opened"); program.reset(GenerateProgram(params)); if (program) { if (program->IsBinaryRetrievable()) { NazaraDebug("Program \"" + programFileName + "\" (re)generated, saving it into program cache directory..."); NzByteArray programBinary = program->GetBinary(); if (!programBinary.IsEmpty()) { if (!shaderFile.Open(NzFile::Truncate | NzFile::WriteOnly) || !shaderFile.Write(programBinary)) NazaraWarning("Failed to save program binary to file \"" + programFileName + "\": " + NzGetLastSystemError()); } else NazaraWarning("Failed to retrieve shader program binary"); } } } } else program.reset(GenerateProgram(params)); if (program) s_programs[params] = program.get(); else { NazaraWarning("Failed to build program, using default one..."); NzShaderProgramManagerParams defaultParams; defaultParams.flags = params.flags; defaultParams.target = nzShaderTarget_None; program.reset(s_programs[defaultParams]); // Shader par défaut } return program.release(); } else return it->second; } NzString NzShaderProgramManager::BuildFragmentCode(const NzShaderProgramManagerParams& params) { #ifdef NAZARA_DEBUG if (params.target > nzShaderTarget_Max) { NazaraError("Shader target out of enum"); return NzString(); } #endif NzString source; /********************Header********************/ source.Reserve(14 + 24 + 24 + 26 + 1); source += "#version "; source += NzString::Number(s_glslVersion); source += "\n\n"; source += "#define FLAG_DEFERRED "; source += (params.flags & nzShaderFlags_Deferred) ? '1' : '0'; source += '\n'; source += "#define FLAG_FLIP_UVS "; source += (params.flags & nzShaderFlags_FlipUVs) ? '1' : '0'; source += '\n'; source += "#define FLAG_INSTANCING "; source += (params.flags & nzShaderFlags_Instancing) ? '1' : '0'; source += '\n'; source += '\n'; switch (params.target) { case nzShaderTarget_FullscreenQuad: { char coreFragmentShader[] = { #include }; char compatibilityFragmentShader[] = { #include }; const char* shaderSource = (s_glsl140) ? coreFragmentShader : compatibilityFragmentShader; unsigned int shaderSourceSize = (s_glsl140) ? sizeof(coreFragmentShader) : sizeof(compatibilityFragmentShader); source.Reserve(source.GetCapacity() + 34 + 24 + 21 + 26 + 1 + shaderSourceSize); // "discard" ne s'entend pas bien avec les early fragment tests if (s_earlyFragmentTest && !params.fullscreenQuad.alphaMapping) source += "layout(early_fragment_tests) in;\n\n"; source += "#define ALPHA_MAPPING "; source += (params.fullscreenQuad.alphaMapping) ? '1' : '0'; source += '\n'; source += "#define ALPHA_TEST "; source += (params.fullscreenQuad.alphaTest) ? '1' : '0'; source += '\n'; source += "#define DIFFUSE_MAPPING "; source += (params.fullscreenQuad.diffuseMapping) ? '1' : '0'; source += '\n'; source += '\n'; source.Append(shaderSource, shaderSourceSize); break; } case nzShaderTarget_Model: { char coreFragmentShader[] = { #include }; char compatibilityFragmentShader[] = { #include }; const char* shaderSource = (s_glsl140) ? coreFragmentShader : compatibilityFragmentShader; unsigned int shaderSourceSize = (s_glsl140) ? sizeof(coreFragmentShader) : sizeof(compatibilityFragmentShader); source.Reserve(source.GetCapacity() + 34 + 24 + 21 + 26 + 27 + 19 + 25 + 27 + 27 + 1 + shaderSourceSize); if (s_earlyFragmentTest && !params.model.alphaMapping) source += "layout(early_fragment_tests) in;\n\n"; source += "#define ALPHA_MAPPING "; source += (params.model.alphaMapping) ? '1' : '0'; source += '\n'; source += "#define ALPHA_TEST "; source += (params.model.alphaTest) ? '1' : '0'; source += '\n'; source += "#define DIFFUSE_MAPPING "; source += (params.model.diffuseMapping) ? '1' : '0'; source += '\n'; source += "#define EMISSIVE_MAPPING "; source += (params.model.emissiveMapping) ? '1' : '0'; source += '\n'; source += "#define LIGHTING "; source += (params.model.lighting) ? '1' : '0'; source += '\n'; source += "#define NORMAL_MAPPING "; source += (params.model.normalMapping) ? '1' : '0'; source += '\n'; source += "#define PARALLAX_MAPPING "; source += (params.model.parallaxMapping) ? '1' : '0'; source += '\n'; source += "#define SPECULAR_MAPPING "; source += (params.model.specularMapping) ? '1' : '0'; source += '\n'; source += '\n'; source.Append(shaderSource, shaderSourceSize); break; } case nzShaderTarget_None: { char coreFragmentShader[] = { #include }; char compatibilityFragmentShader[] = { #include }; const char* shaderSource = (s_glsl140) ? coreFragmentShader : compatibilityFragmentShader; unsigned int shaderSourceSize = (s_glsl140) ? sizeof(coreFragmentShader) : sizeof(compatibilityFragmentShader); source.Reserve(source.GetCapacity() + 34 + shaderSourceSize); if (s_earlyFragmentTest) source += "layout(early_fragment_tests) in;\n\n"; source.Append(shaderSource, shaderSourceSize); break; } case nzShaderTarget_Sprite: { char coreFragmentShader[] = { #include }; char compatibilityFragmentShader[] = { #include }; const char* shaderSource = (s_glsl140) ? coreFragmentShader : compatibilityFragmentShader; unsigned int shaderSourceSize = (s_glsl140) ? sizeof(coreFragmentShader) : sizeof(compatibilityFragmentShader); source.Reserve(source.GetCapacity() + 34 + 24 + 21 + 26 + 1 + shaderSourceSize); // "discard" ne s'entend pas bien avec les early fragment tests if (s_earlyFragmentTest && !params.sprite.alphaMapping) source += "layout(early_fragment_tests) in;\n\n"; source += "#define ALPHA_MAPPING "; source += (params.sprite.alphaMapping) ? '1' : '0'; source += '\n'; source += "#define ALPHA_TEST "; source += (params.sprite.alphaTest) ? '1' : '0'; source += '\n'; source += "#define DIFFUSE_MAPPING "; source += (params.sprite.diffuseMapping) ? '1' : '0'; source += '\n'; source += '\n'; source.Append(shaderSource, shaderSourceSize); break; } } return source; } NzString NzShaderProgramManager::BuildVertexCode(const NzShaderProgramManagerParams& params) { #ifdef NAZARA_DEBUG if (params.target > nzShaderTarget_Max) { NazaraError("Shader target out of enum"); return NzString(); } #endif NzString source; /********************Header********************/ source.Reserve(14 + 24 + 24 + 26 + 1); source += "#version "; source += NzString::Number(s_glslVersion); source += "\n\n"; source += "#define FLAG_DEFERRED "; source += (params.flags & nzShaderFlags_Deferred) ? '1' : '0'; source += '\n'; source += "#define FLAG_FLIP_UVS "; source += (params.flags & nzShaderFlags_FlipUVs) ? '1' : '0'; source += '\n'; source += "#define FLAG_INSTANCING "; source += (params.flags & nzShaderFlags_Instancing) ? '1' : '0'; source += '\n'; source += '\n'; switch (params.target) { case nzShaderTarget_FullscreenQuad: { char coreVertexShader[] = { #include }; char compatibilityVertexShader[] = { #include }; const char* shaderSource = (s_glsl140) ? coreVertexShader : compatibilityVertexShader; unsigned int shaderSourceSize = (s_glsl140) ? sizeof(coreVertexShader) : sizeof(compatibilityVertexShader); source.Reserve(source.GetCapacity() + 24 + 21 + 26 + 1 + shaderSourceSize); source += "#define ALPHA_MAPPING "; source += (params.fullscreenQuad.alphaMapping) ? '1' : '0'; source += '\n'; source += "#define ALPHA_TEST "; source += (params.fullscreenQuad.alphaTest) ? '1' : '0'; source += '\n'; source += "#define DIFFUSE_MAPPING "; source += (params.fullscreenQuad.diffuseMapping) ? '1' : '0'; source += '\n'; source += '\n'; source.Append(shaderSource, shaderSourceSize); break; } case nzShaderTarget_Model: { char coreVertexShader[] = { #include }; char compatibilityVertexShader[] = { #include }; const char* shaderSource = (s_glsl140) ? coreVertexShader : compatibilityVertexShader; unsigned int shaderSourceSize = (s_glsl140) ? sizeof(coreVertexShader) : sizeof(compatibilityVertexShader); source.Reserve(source.GetCapacity() + 24 + 21 + 26 + 27 + 19 + 25 + 27 + 27 + 1 + shaderSourceSize); source += "#define ALPHA_MAPPING "; source += (params.model.alphaMapping) ? '1' : '0'; source += '\n'; source += "#define ALPHA_TEST "; source += (params.model.alphaTest) ? '1' : '0'; source += '\n'; source += "#define DIFFUSE_MAPPING "; source += (params.model.diffuseMapping) ? '1' : '0'; source += '\n'; source += "#define EMISSIVE_MAPPING "; source += (params.model.emissiveMapping) ? '1' : '0'; source += '\n'; source += "#define LIGHTING "; source += (params.model.lighting) ? '1' : '0'; source += '\n'; source += "#define NORMAL_MAPPING "; source += (params.model.normalMapping) ? '1' : '0'; source += '\n'; source += "#define PARALLAX_MAPPING "; source += (params.model.parallaxMapping) ? '1' : '0'; source += '\n'; source += "#define SPECULAR_MAPPING "; source += (params.model.specularMapping) ? '1' : '0'; source += '\n'; source += '\n'; source.Append(shaderSource, shaderSourceSize); break; } case nzShaderTarget_None: { char coreVertexShader[] = { #include }; char compatibilityVertexShader[] = { #include }; const char* shaderSource = (s_glsl140) ? coreVertexShader : compatibilityVertexShader; unsigned int shaderSourceSize = (s_glsl140) ? sizeof(coreVertexShader) : sizeof(compatibilityVertexShader); source.Append(shaderSource, shaderSourceSize); break; } case nzShaderTarget_Sprite: { char coreVertexShader[] = { #include }; char compatibilityVertexShader[] = { #include }; const char* shaderSource = (s_glsl140) ? coreVertexShader : compatibilityVertexShader; unsigned int shaderSourceSize = (s_glsl140) ? sizeof(coreVertexShader) : sizeof(compatibilityVertexShader); source.Reserve(source.GetCapacity() + 24 + 21 + 26 + 1 + shaderSourceSize); source += "#define ALPHA_MAPPING "; source += (params.fullscreenQuad.alphaMapping) ? '1' : '0'; source += '\n'; source += "#define ALPHA_TEST "; source += (params.fullscreenQuad.alphaTest) ? '1' : '0'; source += '\n'; source += "#define DIFFUSE_MAPPING "; source += (params.fullscreenQuad.diffuseMapping) ? '1' : '0'; source += '\n'; source += '\n'; source.Append(shaderSource, shaderSourceSize); break; } } return source; } NzShaderProgram* NzShaderProgramManager::GenerateProgram(const NzShaderProgramManagerParams& params) { std::unique_ptr program(new NzShaderProgram); program->SetPersistent(false); if (!program->Create(nzShaderLanguage_GLSL)) { NazaraError("Failed to create program"); return nullptr; } NzString fragmentSource = BuildFragmentCode(params); if (!program->LoadShader(nzShaderType_Fragment, fragmentSource)) { NazaraError("Failed to load fragment shader: " + program->GetLog()); NazaraNotice("Source:\n" + fragmentSource); return nullptr; } NzString vertexSource = BuildVertexCode(params); if (!program->LoadShader(nzShaderType_Vertex, vertexSource)) { NazaraError("Failed to load vertex shader: " + program->GetLog()); NazaraNotice("Source:\n" + vertexSource); return nullptr; } if (!program->Compile()) { NazaraError("Failed to compile program: " + program->GetLog()); return nullptr; } return program.release(); } bool NzShaderProgramManager::Initialize() { s_glslVersion = NzOpenGL::GetGLSLVersion(); s_earlyFragmentTest = (s_glslVersion >= 420 || NzOpenGL::IsSupported(nzOpenGLExtension_Shader_ImageLoadStore)); s_glsl140 = (s_glslVersion >= 140); NzShaderProgramManagerParams params; params.target = nzShaderTarget_None; for (unsigned int i = 0; i <= nzShaderFlags_Max; ++i) { params.flags = i; NzShaderProgram* program = GenerateProgram(params); if (!program) { NazaraError("Failed to generate default program (flags: 0x" + NzString::Number(i, 16) + ')'); Uninitialize(); return false; } s_programs[params] = program; } /*if (s_loadCachedPrograms) { NzDirectory cacheDirectory(s_cacheDirectory); cacheDirectory.SetPattern("*.nsb"); if (cacheDirectory.Open()) { while (cacheDirectory.NextResult(true)) { long long hash; if (cacheDirectory.GetResultName().SubStringTo(".nsb", -1, true, false).ToInteger(&hash, 32)) { std::size_t hashCode = static_cast(hash); if (s_programs.find(hashCode) == s_programs.end()) { NzFile shaderFile(cacheDirectory.GetResultPath()); if (shaderFile.Open(NzFile::ReadOnly)) { unsigned int size = cacheDirectory.GetResultSize(); NzByteArray binary; binary.Resize(size); if (shaderFile.Read(&binary[0], size) != size) { NazaraError("Failed to read program binary"); return false; } shaderFile.Close(); std::unique_ptr program(new NzShaderProgram); if (program->LoadFromBinary(binary)) s_programs[hashCode] = binary.release(); else NazaraWarning("Program binary \"" + cacheDirectory.GetResultName() + "\" loading failed, this is mostly due to a driver/video card " "update or a file corruption, regenerating program..."); } } } else NazaraWarning("Failed to parse program file name (" + cacheDirectory.GetResultName() + ')'); } } else if (cacheDirectory.Exists()) NazaraWarning("Failed to open shader cache directory"); }*/ return true; } void NzShaderProgramManager::Uninitialize() { s_programs.clear(); }