bluenoise-raytracer/raytracer/nvpro_core/nvgl/programmanager_gl.cpp
2024-05-25 11:53:25 +02:00

463 lines
13 KiB
C++

/*
* Copyright (c) 2014-2021, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2021 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#include "programmanager_gl.hpp"
#include <algorithm>
#include <assert.h>
#include <fstream>
#include <iostream>
#include <sstream>
#include <stdarg.h>
#include <stdio.h>
#include <nvh/fileoperations.hpp>
#include <nvh/nvprint.hpp>
namespace nvgl {
static bool checkProgram(GLuint program)
{
if(!program)
return false;
GLint result = GL_FALSE;
glGetProgramiv(program, GL_LINK_STATUS, &result);
int infoLogLength;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength);
if(infoLogLength > 1
#ifdef NDEBUG
&& result == GL_FALSE
#endif
)
{
std::vector<char> buffer(infoLogLength);
glGetProgramInfoLog(program, infoLogLength, NULL, &buffer[0]);
LOGW("%s\n", &buffer[0]);
}
return result == GL_TRUE;
}
static bool checkShader(GLuint shader, std::string const& filename)
{
if(!shader)
return false;
GLint result = GL_FALSE;
glGetShaderiv(shader, GL_COMPILE_STATUS, &result);
LOGI("%s ...\n", filename.c_str());
int infoLogLength;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength);
if(infoLogLength > 1
#ifdef NDEBUG
&& result == GL_FALSE
#endif
)
{
std::vector<char> buffer(infoLogLength);
glGetShaderInfoLog(shader, infoLogLength, NULL, &buffer[0]);
LOGW("%s\n", &buffer[0]);
}
return result == GL_TRUE;
}
bool ProgramManager::setupProgram(Program& prog)
{
prog.program = 0;
if(prog.definitions.empty())
return false;
m_supportsExtendedInclude = has_GL_ARB_shading_language_include != 0;
std::string combinedPrepend = m_prepend;
std::string combinedFilenames;
for(size_t i = 0; i < prog.definitions.size(); i++)
{
combinedPrepend += prog.definitions[i].prepend;
combinedFilenames += prog.definitions[i].filename;
}
bool allFound = true;
for(size_t i = 0; i < prog.definitions.size(); i++)
{
Definition& definition = prog.definitions[i];
if(definition.filetype == FILETYPE_DEFAULT)
{
definition.filetype = m_filetype;
}
if(m_rawOnly)
{
definition.content = getContent(definition.filename, definition.filenameFound);
}
else
{
char const* strDefine = "";
switch(definition.type)
{
case GL_VERTEX_SHADER:
strDefine = "#define _VERTEX_SHADER_ 1\n";
break;
case GL_FRAGMENT_SHADER:
strDefine = "#define _FRAGMENT_SHADER_ 1\n";
break;
case GL_COMPUTE_SHADER:
strDefine = "#define _COMPUTE_SHADER_ 1\n";
break;
case GL_GEOMETRY_SHADER:
strDefine = "#define _GEOMETRY_SHADER_ 1\n";
break;
case GL_TESS_CONTROL_SHADER:
strDefine = "#define _TESS_CONTROL_SHADER_ 1\n";
break;
case GL_TESS_EVALUATION_SHADER:
strDefine = "#define _TESS_EVALUATION_SHADER_ 1\n";
break;
#if GL_NV_mesh_shader
case GL_MESH_SHADER_NV:
strDefine = "#define _MESH_SHADER_ 1\n";
break;
case GL_TASK_SHADER_NV:
strDefine = "#define _TASK_SHADER_ 1\n";
break;
#endif
}
definition.content = manualInclude(definition.filename, definition.filenameFound,
m_prepend + definition.prepend + std::string(strDefine), false);
}
allFound = allFound && !definition.content.empty();
}
if(m_preprocessOnly)
{
prog.program = PREPROCESS_ONLY_PROGRAM;
return true;
}
else
{
prog.program = glCreateProgram();
if(!m_useCacheFile.empty() && has_GL_VERSION_4_1)
{
glProgramParameteri(prog.program, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE);
}
}
bool loadedCache = false;
if(!m_useCacheFile.empty() && (!allFound || m_preferCache) && has_GL_VERSION_4_1)
{
// try cache
loadedCache = loadBinary(prog.program, combinedPrepend, combinedFilenames);
}
if(!loadedCache)
{
for(size_t i = 0; i < prog.definitions.size(); i++)
{
Definition& definition = prog.definitions[i];
GLuint shader = 0;
if(!definition.content.empty())
{
char const* sourcePointer = definition.content.c_str();
shader = glCreateShader(definition.type);
glShaderSource(shader, 1, &sourcePointer, NULL);
glCompileShader(shader);
}
if(!shader || !checkShader(shader, definition.filename))
{
glDeleteShader(shader);
glDeleteProgram(prog.program);
prog.program = 0;
return false;
}
glAttachShader(prog.program, shader);
glDeleteShader(shader);
}
glLinkProgram(prog.program);
}
if(checkProgram(prog.program))
{
if(!m_useCacheFile.empty() && !loadedCache && has_GL_VERSION_4_1)
{
saveBinary(prog.program, combinedPrepend, combinedFilenames);
}
return true;
}
glDeleteProgram(prog.program);
prog.program = 0;
return false;
}
ProgramID ProgramManager::createProgram(const Definition& def0,
const Definition& def1 /*= ShaderDefinition()*/,
const Definition& def2 /*= ShaderDefinition()*/,
const Definition& def3 /*= ShaderDefinition()*/,
const Definition& def4 /*= ShaderDefinition()*/)
{
std::vector<ProgramManager::Definition> defs;
defs.push_back(def0);
if(def1.type)
defs.push_back(def1);
if(def2.type)
defs.push_back(def2);
if(def3.type)
defs.push_back(def3);
if(def4.type)
defs.push_back(def4);
return createProgram(defs);
}
ProgramID ProgramManager::createProgram(const std::vector<ProgramManager::Definition>& definitions)
{
Program prog;
prog.definitions = definitions;
setupProgram(prog);
for(size_t i = 0; i < m_programs.size(); i++)
{
if(m_programs[i].definitions.empty())
{
m_programs[i] = prog;
return i;
}
}
m_programs.push_back(prog);
return m_programs.size() - 1;
}
bool ProgramManager::areProgramsValid()
{
bool valid = true;
for(size_t i = 0; i < m_programs.size(); i++)
{
valid = valid && isValid((ProgramID)i);
}
return valid;
}
void ProgramManager::deletePrograms()
{
for(size_t i = 0; i < m_programs.size(); i++)
{
if(m_programs[i].program && m_programs[i].program != PREPROCESS_ONLY_PROGRAM)
{
glDeleteProgram(m_programs[i].program);
}
m_programs[i].program = 0;
}
}
void ProgramManager::reloadProgram(ProgramID i)
{
if(!isValid(i))
return;
bool old = m_preprocessOnly;
if(m_programs[i].program && m_programs[i].program != PREPROCESS_ONLY_PROGRAM)
{
glDeleteProgram(m_programs[i].program);
}
m_preprocessOnly = m_programs[i].program == PREPROCESS_ONLY_PROGRAM;
m_programs[i].program = 0;
if(!m_programs[i].definitions.empty())
{
setupProgram(m_programs[i]);
}
m_preprocessOnly = old;
}
void ProgramManager::reloadPrograms()
{
LOGI("Reloading programs...\n");
for(size_t i = 0; i < m_programs.size(); i++)
{
reloadProgram((ProgramID)i);
}
LOGI("done\n");
}
bool ProgramManager::isValid(ProgramID idx) const
{
return idx.isValid() && (m_programs[idx].definitions.empty() || m_programs[idx].program != 0);
}
unsigned int ProgramManager::get(ProgramID idx) const
{
assert(m_programs[idx].program != PREPROCESS_ONLY_PROGRAM);
return m_programs[idx].program;
}
void ProgramManager::destroyProgram(ProgramID idx)
{
if(m_programs[idx].program && m_programs[idx].program != PREPROCESS_ONLY_PROGRAM)
{
glDeleteProgram(m_programs[idx].program);
}
m_programs[idx].program = 0;
m_programs[idx].definitions.clear();
}
//-----------------------------------------------------------------------------
// MurmurHash2A, by Austin Appleby
// This is a variant of MurmurHash2 modified to use the Merkle-Damgard
// construction. Bulk speed should be identical to Murmur2, small-key speed
// will be 10%-20% slower due to the added overhead at the end of the hash.
// This variant fixes a minor issue where null keys were more likely to
// collide with each other than expected, and also makes the algorithm
// more amenable to incremental implementations. All other caveats from
// MurmurHash2 still apply.
#define mmix(h, k) \
{ \
k *= m; \
k ^= k >> r; \
k *= m; \
h *= m; \
h ^= k; \
}
static unsigned int strMurmurHash2A(const void* key, size_t len, unsigned int seed)
{
const unsigned int m = 0x5bd1e995;
const int r = 24;
unsigned int l = (unsigned int)len;
const unsigned char* data = (const unsigned char*)key;
unsigned int h = seed;
unsigned int t = 0;
while(len >= 4)
{
unsigned int k = *(unsigned int*)data;
mmix(h, k);
data += 4;
len -= 4;
}
switch(len)
{
case 3:
t ^= data[2] << 16;
case 2:
t ^= data[1] << 8;
case 1:
t ^= data[0];
};
mmix(h, t);
mmix(h, l);
h ^= h >> 13;
h *= m;
h ^= h >> 15;
return h;
}
#undef mmix
static size_t strHexFromByte(char* buffer, size_t bufferlen, const void* data, size_t len)
{
const char tostr[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
const unsigned char* d = (const unsigned char*)data;
char* out = buffer;
size_t i = 0;
for(; i < len && (i * 2) + 1 < bufferlen; i++, d++, out += 2)
{
unsigned int val = *d;
unsigned int hi = val / 16;
unsigned int lo = val % 16;
out[0] = tostr[hi];
out[1] = tostr[lo];
}
return i * 2;
}
std::string ProgramManager::binaryName(const std::string& combinedPrepend, const std::string& combinedFilenames)
{
unsigned int hashCombine = combinedPrepend.empty() ? 0 : strMurmurHash2A(&combinedPrepend[0], combinedPrepend.size(), 127);
unsigned int hashFilenames = strMurmurHash2A(&combinedFilenames[0], combinedFilenames.size(), 129);
std::string hexCombine;
std::string hexFilenames;
hexCombine.resize(8);
hexFilenames.resize(8);
strHexFromByte(&hexCombine[0], 8, &hashCombine, 4);
strHexFromByte(&hexFilenames[0], 8, &hashFilenames, 4);
return m_useCacheFile + "_" + hexCombine + "_" + hexFilenames + ".glp";
}
bool ProgramManager::loadBinary(GLuint program, const std::string& combinedPrepend, const std::string& combinedFilenames)
{
std::string filename = binaryName(combinedPrepend, combinedFilenames);
std::string filenameFound;
std::string binraw = nvh::loadFile(filename, true, m_directories, filenameFound);
if(!binraw.empty())
{
const char* bindata = &binraw[0];
glProgramBinary(program, *(GLenum*)bindata, bindata + 4, GLsizei(binraw.size() - 4));
return checkProgram(program);
}
return false;
}
void ProgramManager::saveBinary(GLuint program, const std::string& combinedPrepend, const std::string& combinedFilenames)
{
std::string filename = binaryName(combinedPrepend, combinedFilenames);
GLint datasize;
GLint datasize2;
glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH, &datasize);
std::string binraw;
binraw.resize(datasize + 4ULL);
char* bindata = &binraw[0];
glGetProgramBinary(program, datasize, &datasize2, (GLenum*)bindata, bindata + 4);
std::ofstream binfile;
binfile.open(filename.c_str(), std::ios::binary | std::ios::out);
if(binfile.is_open())
{
binfile.write(bindata, datasize + 4ULL);
}
}
} // namespace nvgl