// // Copyright Contributors to the MaterialX Project // SPDX-License-Identifier: Apache-2.0 // #include #include #include #include #include #include #include #include #include MATERIALX_NAMESPACE_BEGIN namespace { const float PI = std::acos(-1.0f); } // anonymous namespace // OpenGL Constants unsigned int GlslProgram::UNDEFINED_OPENGL_RESOURCE_ID = 0; int GlslProgram::UNDEFINED_OPENGL_PROGRAM_LOCATION = -1; int GlslProgram::Input::INVALID_OPENGL_TYPE = -1; // // GlslProgram methods // GlslProgram::GlslProgram() : _programId(UNDEFINED_OPENGL_RESOURCE_ID), _shader(nullptr), _vertexArray(UNDEFINED_OPENGL_RESOURCE_ID) { } GlslProgram::~GlslProgram() { clearBuiltData(); } void GlslProgram::setStages(ShaderPtr shader) { if (!shader) { throw ExceptionRenderError("Cannot set stages using null hardware shader"); } // Clear existing stages and built data _stages.clear(); clearBuiltData(); // Extract out the shader code per stage _shader = shader; for (size_t i = 0; i < shader->numStages(); ++i) { const ShaderStage& stage = shader->getStage(i); addStage(stage.getName(), stage.getSourceCode()); } } void GlslProgram::addStage(const string& stage, const string& sourceCode) { _stages[stage] = sourceCode; } const string& GlslProgram::getStageSourceCode(const string& stage) const { auto it = _stages.find(stage); if (it != _stages.end()) { return it->second; } return EMPTY_STRING; } void GlslProgram::build() { clearBuiltData(); GLint glStatus = GL_FALSE; int glInfoLogLength = 0; StringVec errors; unsigned int stagesBuilt = 0; unsigned int desiredStages = 0; for (const auto& it : _stages) { if (!it.second.empty()) { desiredStages++; } } // Compile vertex shader, if any GLuint vertexShaderId = UNDEFINED_OPENGL_RESOURCE_ID; const string& vertexShaderSource = _stages[Stage::VERTEX]; if (!vertexShaderSource.empty()) { vertexShaderId = glCreateShader(GL_VERTEX_SHADER); // Compile vertex shader const char* vertexChar = vertexShaderSource.c_str(); glShaderSource(vertexShaderId, 1, &vertexChar, nullptr); glCompileShader(vertexShaderId); // Check vertex shader glGetShaderiv(vertexShaderId, GL_COMPILE_STATUS, &glStatus); if (glStatus == GL_FALSE) { errors.push_back("Error in compiling vertex shader:"); glGetShaderiv(vertexShaderId, GL_INFO_LOG_LENGTH, &glInfoLogLength); if (glInfoLogLength > 0) { std::vector vsErrorMessage((size_t) glInfoLogLength + 1); glGetShaderInfoLog(vertexShaderId, glInfoLogLength, nullptr, &vsErrorMessage[0]); errors.push_back(&vsErrorMessage[0]); } } else { stagesBuilt++; } } // Compile fragment shader, if any GLuint fragmentShaderId = UNDEFINED_OPENGL_RESOURCE_ID; const string& fragmentShaderSource = _stages[Stage::PIXEL]; if (!fragmentShaderSource.empty()) { fragmentShaderId = glCreateShader(GL_FRAGMENT_SHADER); // Compile fragment shader const char* fragmentChar = fragmentShaderSource.c_str(); glShaderSource(fragmentShaderId, 1, &fragmentChar, nullptr); glCompileShader(fragmentShaderId); // Check fragment shader glGetShaderiv(fragmentShaderId, GL_COMPILE_STATUS, &glStatus); if (glStatus == GL_FALSE) { errors.push_back("Error in compiling fragment shader:"); glGetShaderiv(fragmentShaderId, GL_INFO_LOG_LENGTH, &glInfoLogLength); if (glInfoLogLength > 0) { std::vector fsErrorMessage((size_t) glInfoLogLength + 1); glGetShaderInfoLog(fragmentShaderId, glInfoLogLength, nullptr, &fsErrorMessage[0]); errors.push_back(&fsErrorMessage[0]); } } else { stagesBuilt++; } } // Link the shader program if (stagesBuilt == desiredStages) { _programId = glCreateProgram(); glAttachShader(_programId, vertexShaderId); glAttachShader(_programId, fragmentShaderId); glLinkProgram(_programId); // Check the program glGetProgramiv(_programId, GL_LINK_STATUS, &glStatus); if (glStatus == GL_FALSE) { errors.push_back("Error in linking program:"); glGetProgramiv(_programId, GL_INFO_LOG_LENGTH, &glInfoLogLength); if (glInfoLogLength > 0) { std::vector ProgramErrorMessage(glInfoLogLength + 1); glGetProgramInfoLog(_programId, glInfoLogLength, nullptr, &ProgramErrorMessage[0]); errors.push_back(&ProgramErrorMessage[0]); } } } // Cleanup if (vertexShaderId != UNDEFINED_OPENGL_RESOURCE_ID) { if (_programId != UNDEFINED_OPENGL_RESOURCE_ID) { glDetachShader(_programId, vertexShaderId); } glDeleteShader(vertexShaderId); } if (fragmentShaderId != UNDEFINED_OPENGL_RESOURCE_ID) { if (_programId != UNDEFINED_OPENGL_RESOURCE_ID) { glDetachShader(_programId, fragmentShaderId); } glDeleteShader(fragmentShaderId); } // If we encountered any errors while trying to create return list // of all errors. That is we collect all errors per stage plus any // errors during linking and throw one exception for them all so that // if there is a failure a complete set of issues is returned. We do // this after cleanup so keep GL state clean. if (!errors.empty() || stagesBuilt != desiredStages) { throw ExceptionRenderError("GLSL compilation error", errors); } } bool GlslProgram::hasBuiltData() { return _programId != UNDEFINED_OPENGL_RESOURCE_ID; } void GlslProgram::clearBuiltData() { if (_programId != UNDEFINED_OPENGL_RESOURCE_ID) { glDeleteProgram(_programId); _programId = UNDEFINED_OPENGL_RESOURCE_ID; } _uniformList.clear(); _attributeList.clear(); } bool GlslProgram::bind() { if (_programId == UNDEFINED_OPENGL_RESOURCE_ID) { return false; } glUseProgram(_programId); checkGlErrors("after program bind"); return true; } void GlslProgram::bindAttribute(const GlslProgram::InputMap& inputs, MeshPtr mesh) { if (!mesh) { throw ExceptionRenderError("No geometry set to bind"); } for (const auto& input : inputs) { int location = input.second->location; unsigned int index = input.second->value ? input.second->value->asA() : 0; unsigned int stride = 0; MeshStreamPtr stream = mesh->getStream(input.first); if (!stream) { throw ExceptionRenderError("Geometry buffer could not be retrieved for binding: " + input.first + ". Index: " + std::to_string(index)); } MeshFloatBuffer& attributeData = stream->getData(); stride = stream->getStride(); if (attributeData.empty() || (stride == 0)) { throw ExceptionRenderError("Geometry buffer could not be retrieved for binding: " + input.first + ". Index: " + std::to_string(index)); } if (_attributeBufferIds.find(input.first) == _attributeBufferIds.end()) { const float* bufferData = &attributeData[0]; size_t bufferSize = attributeData.size() * sizeof(float); // Create a buffer based on attribute type. unsigned int bufferId = GlslProgram::UNDEFINED_OPENGL_RESOURCE_ID; glGenBuffers(1, &bufferId); glBindBuffer(GL_ARRAY_BUFFER, bufferId); glBufferData(GL_ARRAY_BUFFER, bufferSize, bufferData, GL_STATIC_DRAW); _attributeBufferIds[input.first] = bufferId; } else { glBindBuffer(GL_ARRAY_BUFFER, _attributeBufferIds[input.first]); } glEnableVertexAttribArray(location); _enabledStreamLocations.insert(location); if (input.second->gltype != GL_INT) { glVertexAttribPointer(location, stride, GL_FLOAT, GL_FALSE, 0, nullptr); } else { glVertexAttribIPointer(location, stride, GL_INT, 0, nullptr); } } } void GlslProgram::bindPartition(MeshPartitionPtr part) { if (!part || part->getFaceCount() == 0) { throw ExceptionRenderError("Cannot bind geometry partition"); } if (_indexBufferIds.find(part) != _indexBufferIds.end()) { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBufferIds[part]); } else { MeshIndexBuffer& indexData = part->getIndices(); size_t indexBufferSize = indexData.size(); unsigned int indexBuffer = GlslProgram::UNDEFINED_OPENGL_RESOURCE_ID; glGenBuffers(1, &indexBuffer); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer); glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexBufferSize * sizeof(uint32_t), &indexData[0], GL_STATIC_DRAW); _indexBufferIds[part] = indexBuffer; } } void GlslProgram::bindMesh(MeshPtr mesh) { _enabledStreamLocations.clear(); if (_programId == UNDEFINED_OPENGL_RESOURCE_ID) { throw ExceptionRenderError("Cannot bind geometry without a valid program"); } if (!mesh) { throw ExceptionRenderError("No mesh to bind"); } if (mesh != _boundMesh) { unbindGeometry(); } GlslProgram::InputMap foundList; const GlslProgram::InputMap& attributeList = getAttributesList(); if (_vertexArray == UNDEFINED_OPENGL_RESOURCE_ID) { // Set up vertex arrays glGenVertexArrays(1, &_vertexArray); } glBindVertexArray(_vertexArray); // Bind positions findInputs(HW::IN_POSITION, attributeList, foundList, true); if (foundList.size()) { bindAttribute(foundList, mesh); } // Bind normals findInputs(HW::IN_NORMAL, attributeList, foundList, true); if (foundList.size()) { bindAttribute(foundList, mesh); } // Bind tangents findInputs(HW::IN_TANGENT, attributeList, foundList, true); if (foundList.size()) { bindAttribute(foundList, mesh); } // Bind bitangents findInputs(HW::IN_BITANGENT, attributeList, foundList, true); if (foundList.size()) { bindAttribute(foundList, mesh); } // Bind colors // Search for anything that starts with the color prefix findInputs(HW::IN_COLOR + "_", attributeList, foundList, false); if (foundList.size()) { bindAttribute(foundList, mesh); } // Bind texture coordinates // Search for anything that starts with the texcoord prefix findInputs(HW::IN_TEXCOORD + "_", attributeList, foundList, false); if (foundList.size()) { bindAttribute(foundList, mesh); } // Bind any named varying geometric property information findInputs(HW::IN_GEOMPROP + "_", attributeList, foundList, false); if (foundList.size()) { bindAttribute(foundList, mesh); } // Bind any named uniform geometric property information const GlslProgram::InputMap& uniformList = getUniformsList(); findInputs(HW::GEOMPROP + "_", uniformList, foundList, false); for (const auto& input : foundList) { // Only handle float1-4 types for now switch (input.second->gltype) { case GL_INT: glUniform1i(input.second->location, 1); break; case GL_FLOAT: glUniform1f(input.second->location, 0.0f); break; case GL_FLOAT_VEC2: glUniform2f(input.second->location, 0.0f, 0.0f); break; case GL_FLOAT_VEC3: glUniform3f(input.second->location, 0.0f, 0.0f, 0.0f); break; case GL_FLOAT_VEC4: glUniform4f(input.second->location, 0.0f, 0.0f, 0.0f, 1.0f); break; default: break; } } // Store the bound mesh. _boundMesh = mesh; checkGlErrors("after program bind mesh"); } void GlslProgram::unbindGeometry() { if (!_boundMesh) { return; } // Unbind all geometry buffers. glBindVertexArray(UNDEFINED_OPENGL_RESOURCE_ID); glBindBuffer(GL_ARRAY_BUFFER, UNDEFINED_OPENGL_RESOURCE_ID); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, UNDEFINED_OPENGL_RESOURCE_ID); _enabledStreamLocations.clear(); // Release attribute buffers. for (const auto& attributeBufferId : _attributeBufferIds) { unsigned int bufferId = attributeBufferId.second; if (bufferId > 0) { glDeleteBuffers(1, &bufferId); } } _attributeBufferIds.clear(); // Release vertex array. if (_vertexArray != UNDEFINED_OPENGL_RESOURCE_ID) { glDeleteVertexArrays(1, &_vertexArray); _vertexArray = UNDEFINED_OPENGL_RESOURCE_ID; } // Release index buffers. for (const auto& indexBufferId : _indexBufferIds) { unsigned int bufferId = indexBufferId.second; if (bufferId > 0) { glDeleteBuffers(1, &bufferId); } } _indexBufferIds.clear(); // Clear the bound mesh. _boundMesh = nullptr; checkGlErrors("after program unbind geometry"); } ImagePtr GlslProgram::bindTexture(unsigned int uniformType, int uniformLocation, const FilePath& filePath, ImageHandlerPtr imageHandler, const ImageSamplingProperties& samplingProperties) { if (uniformLocation >= 0 && uniformType >= GL_SAMPLER_1D && uniformType <= GL_SAMPLER_CUBE) { // Acquire the image. ImagePtr image = imageHandler->acquireImage(filePath, samplingProperties.defaultColor); if (imageHandler->bindImage(image, samplingProperties)) { GLTextureHandlerPtr textureHandler = std::static_pointer_cast(imageHandler); int textureLocation = textureHandler->getBoundTextureLocation(image->getResourceId()); if (textureLocation >= 0) { glUniform1i(uniformLocation, textureLocation); } } checkGlErrors("after program bind texture"); return image; } return nullptr; } MaterialX::ValuePtr GlslProgram::findUniformValue(const string& uniformName, const GlslProgram::InputMap& uniformList) { auto uniform = uniformList.find(uniformName); if (uniform != uniformList.end()) { int location = uniform->second->location; if (location >= 0) { return uniform->second->value; } } return nullptr; } void GlslProgram::bindTextures(ImageHandlerPtr imageHandler) { if (_programId == UNDEFINED_OPENGL_RESOURCE_ID) { throw ExceptionRenderError("Cannot bind textures without a valid program"); } if (!imageHandler) { throw ExceptionRenderError("Cannot bind textures without an image handler"); } // Bind textures based on uniforms found in the program const GlslProgram::InputMap& uniformList = getUniformsList(); const VariableBlock& publicUniforms = _shader->getStage(Stage::PIXEL).getUniformBlock(HW::PUBLIC_UNIFORMS); for (const auto& uniform : uniformList) { GLenum uniformType = uniform.second->gltype; GLint uniformLocation = uniform.second->location; if (uniformLocation >= 0 && uniformType >= GL_SAMPLER_1D && uniformType <= GL_SAMPLER_CUBE) { const string fileName(uniform.second->value ? uniform.second->value->getValueString() : ""); // Always bind a texture unless it is a lighting texture. // Lighting textures are handled in the bindLighting() call. // If no texture can be loaded then the default color defined in // "samplingProperties" will be used to create a fallback texture. if (fileName != HW::ENV_RADIANCE && fileName != HW::ENV_IRRADIANCE) { ImageSamplingProperties samplingProperties; samplingProperties.setProperties(uniform.first, publicUniforms); bindTexture(uniformType, uniformLocation, fileName, imageHandler, samplingProperties); } } } } void GlslProgram::bindLighting(LightHandlerPtr lightHandler, ImageHandlerPtr imageHandler) { if (!lightHandler) { // Nothing to bind if a light handler is not used. This is a valid condition // for shaders that don't need lighting, so just exit silently. return; } if (_programId == UNDEFINED_OPENGL_RESOURCE_ID) { throw ExceptionRenderError("Cannot bind without a valid program"); } // Bind environment lighting properties. Matrix44 envRotation = Matrix44::createRotationY(PI) * lightHandler->getLightTransform().getTranspose(); bindUniform(HW::ENV_MATRIX, Value::createValue(envRotation), false); bindUniform(HW::ENV_RADIANCE_SAMPLES, Value::createValue(lightHandler->getEnvSampleCount()), false); bindUniform(HW::ENV_LIGHT_INTENSITY, Value::createValue(lightHandler->getEnvLightIntensity()), false); ImagePtr envRadiance = nullptr; if (lightHandler->getIndirectLighting()) { envRadiance = lightHandler->getUsePrefilteredMap() ? lightHandler->getEnvPrefilteredMap() : lightHandler->getEnvRadianceMap(); } else { envRadiance = imageHandler->getZeroImage(); } ImageMap envImages = { { HW::ENV_RADIANCE, envRadiance }, { HW::ENV_IRRADIANCE, lightHandler->getIndirectLighting() ? lightHandler->getEnvIrradianceMap() : imageHandler->getZeroImage() } }; for (const auto& env : envImages) { std::string uniform = env.first; ImagePtr image = env.second; if (image && hasUniform(env.first)) { ImageSamplingProperties samplingProperties; samplingProperties.uaddressMode = ImageSamplingProperties::AddressMode::PERIODIC; samplingProperties.vaddressMode = ImageSamplingProperties::AddressMode::CLAMP; samplingProperties.filterType = ImageSamplingProperties::FilterType::LINEAR; // Bind the environment image. if (imageHandler->bindImage(image, samplingProperties)) { GLTextureHandlerPtr textureHandler = std::static_pointer_cast(imageHandler); int textureLocation = textureHandler->getBoundTextureLocation(image->getResourceId()); if (textureLocation >= 0) { bindUniform(uniform, Value::createValue(textureLocation)); } // Bind any associated uniforms. if (uniform == HW::ENV_RADIANCE) { bindUniform(HW::ENV_RADIANCE_MIPS, Value::createValue((int) image->getMaxMipCount()), false); } } } } bindUniform(HW::REFRACTION_TWO_SIDED, Value::createValue(lightHandler->getRefractionTwoSided()), false); // Bind direct lighting properties. if (hasUniform(HW::NUM_ACTIVE_LIGHT_SOURCES)) { int lightCount = lightHandler->getDirectLighting() ? (int) lightHandler->getLightSources().size() : 0; bindUniform(HW::NUM_ACTIVE_LIGHT_SOURCES, Value::createValue(lightCount)); LightIdMap idMap = lightHandler->computeLightIdMap(lightHandler->getLightSources()); size_t index = 0; for (NodePtr light : lightHandler->getLightSources()) { auto nodeDef = light->getNodeDef(); if (!nodeDef) { continue; } const std::string prefix = HW::LIGHT_DATA_INSTANCE + "[" + std::to_string(index) + "]"; // Set light type id std::string lightType(prefix + ".type"); if (hasUniform(lightType)) { unsigned int lightTypeValue = idMap[nodeDef->getName()]; bindUniform(lightType, Value::createValue((int) lightTypeValue)); } // Set all inputs for (const auto& input : light->getInputs()) { // Make sure we have a value to set if (input->hasValue()) { std::string inputName(prefix + "." + input->getName()); if (hasUniform(inputName)) { if (input->getName() == "direction" && input->hasValue() && input->getValue()->isA()) { Vector3 dir = input->getValue()->asA(); dir = lightHandler->getLightTransform().transformVector(dir); bindUniform(inputName, Value::createValue(dir)); } else { bindUniform(inputName, input->getValue()); } } } } ++index; } } // Bind the directional albedo table, if needed. ImagePtr albedoTable = lightHandler->getAlbedoTable(); if (albedoTable && hasUniform(HW::ALBEDO_TABLE)) { ImageSamplingProperties samplingProperties; samplingProperties.uaddressMode = ImageSamplingProperties::AddressMode::CLAMP; samplingProperties.vaddressMode = ImageSamplingProperties::AddressMode::CLAMP; samplingProperties.filterType = ImageSamplingProperties::FilterType::LINEAR; if (imageHandler->bindImage(albedoTable, samplingProperties)) { GLTextureHandlerPtr textureHandler = std::static_pointer_cast(imageHandler); int textureLocation = textureHandler->getBoundTextureLocation(albedoTable->getResourceId()); if (textureLocation >= 0) { bindUniform(HW::ALBEDO_TABLE, Value::createValue(textureLocation)); } } } } bool GlslProgram::hasUniform(const string& name) { const GlslProgram::InputMap& uniformList = getUniformsList(); return uniformList.find(name) != uniformList.end(); } void GlslProgram::bindUniform(const string& name, ConstValuePtr value, bool errorIfMissing) { const GlslProgram::InputMap& uniformList = getUniformsList(); auto input = uniformList.find(name); if (input != uniformList.end()) { int location = input->second->location; if (location < 0) { if (errorIfMissing) { throw ExceptionRenderError("Unknown uniform: " + name); } return; } bindUniformLocation(location, value); } } void GlslProgram::bindUniformLocation(int location, ConstValuePtr value) { if (_programId == UNDEFINED_OPENGL_RESOURCE_ID) { throw ExceptionRenderError("Cannot bind without a valid program"); } if (location >= 0 && value->getValueString() != EMPTY_STRING) { if (value->getTypeString() == "float") { float v = value->asA(); glUniform1f(location, v); } else if (value->getTypeString() == "integer") { int v = value->asA(); glUniform1i(location, v); } else if (value->getTypeString() == "boolean") { bool v = value->asA(); glUniform1i(location, v ? 1 : 0); } else if (value->getTypeString() == "color3") { Color3 v = value->asA(); glUniform3f(location, v[0], v[1], v[2]); } else if (value->getTypeString() == "color4") { Color4 v = value->asA(); glUniform4f(location, v[0], v[1], v[2], v[3]); } else if (value->getTypeString() == "vector2") { Vector2 v = value->asA(); glUniform2f(location, v[0], v[1]); } else if (value->getTypeString() == "vector3") { Vector3 v = value->asA(); glUniform3f(location, v[0], v[1], v[2]); } else if (value->getTypeString() == "vector4") { Vector4 v = value->asA(); glUniform4f(location, v[0], v[1], v[2], v[3]); } else if (value->getTypeString() == "matrix33") { Matrix33 m = value->asA(); glUniformMatrix3fv(location, 1, GL_FALSE, m.data()); } else if (value->getTypeString() == "matrix44") { Matrix44 m = value->asA(); glUniformMatrix4fv(location, 1, GL_FALSE, m.data()); } else { throw ExceptionRenderError("Unsupported data type when setting uniform value"); } } } void GlslProgram::bindViewInformation(CameraPtr camera) { if (_programId == UNDEFINED_OPENGL_RESOURCE_ID) { throw ExceptionRenderError("Cannot bind without a valid program"); } if (!camera) { throw ExceptionRenderError("Cannot bind without a camera"); } // View position and direction bindUniform(HW::VIEW_POSITION, Value::createValue(camera->getViewPosition()), false); bindUniform(HW::VIEW_DIRECTION, Value::createValue(camera->getViewDirection()), false); // World matrices Matrix44 worldInv = camera->getWorldMatrix().getInverse(); bindUniform(HW::WORLD_MATRIX, Value::createValue(camera->getWorldMatrix()), false); bindUniform(HW::WORLD_TRANSPOSE_MATRIX, Value::createValue(camera->getWorldMatrix().getTranspose()), false); bindUniform(HW::WORLD_INVERSE_MATRIX, Value::createValue(worldInv), false); bindUniform(HW::WORLD_INVERSE_TRANSPOSE_MATRIX, Value::createValue(worldInv.getTranspose()), false); // View matrices Matrix44 viewInv = camera->getViewMatrix().getInverse(); bindUniform(HW::VIEW_MATRIX, Value::createValue(camera->getViewMatrix()), false); bindUniform(HW::VIEW_TRANSPOSE_MATRIX, Value::createValue(camera->getViewMatrix().getTranspose()), false); bindUniform(HW::VIEW_INVERSE_MATRIX, Value::createValue(viewInv), false); bindUniform(HW::VIEW_INVERSE_TRANSPOSE_MATRIX, Value::createValue(viewInv.getTranspose()), false); // Projection matrices Matrix44 projInv = camera->getProjectionMatrix().getInverse(); bindUniform(HW::PROJ_MATRIX, Value::createValue(camera->getProjectionMatrix()), false); bindUniform(HW::PROJ_TRANSPOSE_MATRIX, Value::createValue(camera->getProjectionMatrix().getTranspose()), false); bindUniform(HW::PROJ_INVERSE_MATRIX, Value::createValue(projInv), false); bindUniform(HW::PROJ_INVERSE_TRANSPOSE_MATRIX, Value::createValue(projInv.getTranspose()), false); // View-projection matrix Matrix44 viewProj = camera->getViewMatrix() * camera->getProjectionMatrix(); bindUniform(HW::VIEW_PROJECTION_MATRIX, Value::createValue(viewProj), false); // View-projection-world matrix Matrix44 worldViewProj = camera->getWorldViewProjMatrix(); bindUniform(HW::WORLD_VIEW_PROJECTION_MATRIX, Value::createValue(worldViewProj), false); } void GlslProgram::bindTimeAndFrame(float time, float frame) { if (_programId == UNDEFINED_OPENGL_RESOURCE_ID) { throw ExceptionRenderError("Cannot bind time/frame without a valid program"); } bindUniform(HW::TIME, Value::createValue(time), false); bindUniform(HW::FRAME, Value::createValue(frame), false); } bool GlslProgram::hasActiveAttributes() const { GLint activeAttributeCount = 0; if (_programId != UNDEFINED_OPENGL_RESOURCE_ID) { glGetProgramiv(_programId, GL_ACTIVE_ATTRIBUTES, &activeAttributeCount); } return activeAttributeCount > 0; } void GlslProgram::unbind() const { glUseProgram(UNDEFINED_OPENGL_RESOURCE_ID); } const GlslProgram::InputMap& GlslProgram::getUniformsList() { return updateUniformsList(); } const GlslProgram::InputMap& GlslProgram::getAttributesList() { return updateAttributesList(); } const GlslProgram::InputMap& GlslProgram::updateUniformsList() { if (_uniformList.size() > 0) { return _uniformList; } if (_programId == UNDEFINED_OPENGL_RESOURCE_ID) { throw ExceptionRenderError("Cannot parse for uniforms without a valid program"); } // Scan for textures int uniformCount = -1; int uniformSize = -1; GLenum uniformType = 0; int maxNameLength = 0; glGetProgramiv(_programId, GL_ACTIVE_UNIFORMS, &uniformCount); glGetProgramiv(_programId, GL_ACTIVE_UNIFORM_MAX_LENGTH, &maxNameLength); char* uniformName = new char[maxNameLength]; for (int i = 0; i < uniformCount; i++) { glGetActiveUniform(_programId, GLuint(i), maxNameLength, nullptr, &uniformSize, &uniformType, uniformName); GLint uniformLocation = glGetUniformLocation(_programId, uniformName); if (uniformLocation >= 0) { InputPtr inputPtr = std::make_shared(uniformLocation, uniformType, uniformSize, EMPTY_STRING); _uniformList[string(uniformName)] = inputPtr; } } delete[] uniformName; if (_shader) { StringVec errors; // Check for any type mismatches between the program and the h/w shader. // i.e the type indicated by the HwShader does not match what was generated. bool uniformTypeMismatchFound = false; const ShaderStage& ps = _shader->getStage(Stage::PIXEL); const ShaderStage& vs = _shader->getStage(Stage::VERTEX); // Process constants const VariableBlock& constants = ps.getConstantBlock(); for (size_t i = 0; i < constants.size(); ++i) { const ShaderPort* v = constants[i]; // There is no way to match with an unnamed variable if (v->getVariable().empty()) { continue; } // TODO: Shoud we really create new ones here each update? InputPtr inputPtr = std::make_shared(-1, -1, int(v->getType()->getSize()), EMPTY_STRING); _uniformList[v->getVariable()] = inputPtr; inputPtr->isConstant = true; inputPtr->value = v->getValue(); inputPtr->typeString = v->getType()->getName(); inputPtr->path = v->getPath(); } // Process pixel stage uniforms for (const auto& uniformMap : ps.getUniformBlocks()) { const VariableBlock& uniforms = *uniformMap.second; if (uniforms.getName() == HW::LIGHT_DATA) { // Need to go through LightHandler to match with uniforms continue; } for (size_t i = 0; i < uniforms.size(); ++i) { const ShaderPort* v = uniforms[i]; int glType = mapTypeToOpenGLType(v->getType()); // There is no way to match with an unnamed variable if (v->getVariable().empty()) { continue; } // Ignore types which are unsupported in GLSL. if (glType == Input::INVALID_OPENGL_TYPE) { continue; } auto inputIt = _uniformList.find(v->getVariable()); if (inputIt != _uniformList.end()) { Input* input = inputIt->second.get(); input->path = v->getPath(); input->unit = v->getUnit(); input->colorspace = v->getColorSpace(); input->value = v->getValue(); if (input->gltype == glType) { input->typeString = v->getType()->getName(); } else { errors.push_back( "Pixel shader uniform block type mismatch [" + uniforms.getName() + "]. " + "Name: \"" + v->getVariable() + "\". Type: \"" + v->getType()->getName() + "\". Semantic: \"" + v->getSemantic() + "\". Value: \"" + (v->getValue() ? v->getValue()->getValueString() : "") + "\". Unit: \"" + (!v->getUnit().empty() ? v->getUnit() : "") + "\". Colorspace: \"" + (!v->getColorSpace().empty() ? v->getColorSpace() : "") + "\". GLType: " + std::to_string(mapTypeToOpenGLType(v->getType())) ); uniformTypeMismatchFound = true; } } } } // Process vertex stage uniforms for (const auto& uniformMap : vs.getUniformBlocks()) { const VariableBlock& uniforms = *uniformMap.second; for (size_t i = 0; i < uniforms.size(); ++i) { const ShaderPort* v = uniforms[i]; auto inputIt = _uniformList.find(v->getVariable()); if (inputIt != _uniformList.end()) { Input* input = inputIt->second.get(); if (input->gltype == mapTypeToOpenGLType(v->getType())) { input->typeString = v->getType()->getName(); input->value = v->getValue(); input->path = v->getPath(); input->unit = v->getUnit(); input->colorspace = v->getColorSpace(); } else { errors.push_back( "Vertex shader uniform block type mismatch [" + uniforms.getName() + "]. " + "Name: \"" + v->getVariable() + "\". Type: \"" + v->getType()->getName() + "\". Semantic: \"" + v->getSemantic() + "\". Value: \"" + (v->getValue() ? v->getValue()->getValueString() : "") + "\". Unit: \"" + (!v->getUnit().empty() ? v->getUnit() : "") + "\". Colorspace: \"" + (!v->getColorSpace().empty() ? v->getColorSpace() : "") + "\". GLType: " + std::to_string(mapTypeToOpenGLType(v->getType())) ); uniformTypeMismatchFound = true; } } } } // Throw an error if any type mismatches were found if (uniformTypeMismatchFound) { throw ExceptionRenderError("GLSL uniform parsing error", errors); } } return _uniformList; } int GlslProgram::mapTypeToOpenGLType(const TypeDesc* type) { if (*type == *Type::INTEGER) return GL_INT; else if (*type == *Type::BOOLEAN) return GL_BOOL; else if (*type == *Type::FLOAT) return GL_FLOAT; else if (type->isFloat2()) return GL_FLOAT_VEC2; else if (type->isFloat3()) return GL_FLOAT_VEC3; else if (type->isFloat4()) return GL_FLOAT_VEC4; else if (*type == *Type::MATRIX33) return GL_FLOAT_MAT3; else if (*type == *Type::MATRIX44) return GL_FLOAT_MAT4; else if (*type == *Type::FILENAME) { // A "filename" is not indicative of type, so just return a 2d sampler. return GL_SAMPLER_2D; } return GlslProgram::Input::INVALID_OPENGL_TYPE; } const GlslProgram::InputMap& GlslProgram::updateAttributesList() { if (_attributeList.size() > 0) { return _attributeList; } if (_programId == UNDEFINED_OPENGL_RESOURCE_ID) { throw ExceptionRenderError("Cannot parse for attributes without a valid program"); } GLint numAttributes = 0; GLint maxNameLength = 0; glGetProgramiv(_programId, GL_ACTIVE_ATTRIBUTES, &numAttributes); glGetProgramiv(_programId, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &maxNameLength); char* attributeName = new char[maxNameLength]; for (int i = 0; i < numAttributes; i++) { GLint attributeSize = 0; GLenum attributeType = 0; glGetActiveAttrib(_programId, GLuint(i), maxNameLength, nullptr, &attributeSize, &attributeType, attributeName); GLint attributeLocation = glGetAttribLocation(_programId, attributeName); if (attributeLocation >= 0) { InputPtr inputPtr = std::make_shared(attributeLocation, attributeType, attributeSize, EMPTY_STRING); // Attempt to pull out the set number for specific attributes // string sattributeName(attributeName); const string colorSet(HW::IN_COLOR + "_"); const string uvSet(HW::IN_TEXCOORD + "_"); if (string::npos != sattributeName.find(colorSet)) { string setNumber = sattributeName.substr(colorSet.size(), sattributeName.size()); inputPtr->value = Value::createValueFromStrings(setNumber, getTypeString()); } else if (string::npos != sattributeName.find(uvSet)) { string setNumber = sattributeName.substr(uvSet.size(), sattributeName.size()); inputPtr->value = Value::createValueFromStrings(setNumber, getTypeString()); } _attributeList[sattributeName] = inputPtr; } } delete[] attributeName; if (_shader) { const ShaderStage& vs = _shader->getStage(Stage::VERTEX); bool uniformTypeMismatchFound = false; StringVec errors; const VariableBlock& vertexInputs = vs.getInputBlock(HW::VERTEX_INPUTS); if (!vertexInputs.empty()) { for (size_t i = 0; i < vertexInputs.size(); ++i) { const ShaderPort* v = vertexInputs[i]; auto inputIt = _attributeList.find(v->getVariable()); if (inputIt != _attributeList.end()) { Input* input = inputIt->second.get(); input->value = v->getValue(); if (input->gltype == mapTypeToOpenGLType(v->getType())) { input->typeString = v->getType()->getName(); } else { errors.push_back( "Vertex shader attribute type mismatch in block. Name: \"" + v->getVariable() + "\". Type: \"" + v->getType()->getName() + "\". Semantic: \"" + v->getSemantic() + "\". Value: \"" + (v->getValue() ? v->getValue()->getValueString() : "") + "\". GLType: " + std::to_string(mapTypeToOpenGLType(v->getType())) ); uniformTypeMismatchFound = true; } } } } // Throw an error if any type mismatches were found if (uniformTypeMismatchFound) { throw ExceptionRenderError("GLSL attribute parsing error", errors); } } return _attributeList; } void GlslProgram::findInputs(const string& variable, const InputMap& variableList, InputMap& foundList, bool exactMatch) { foundList.clear(); // Scan all attributes which match the attribute identifier completely or as a prefix // int ilocation = UNDEFINED_OPENGL_PROGRAM_LOCATION; auto input = variableList.find(variable); if (input != variableList.end()) { ilocation = input->second->location; if (ilocation >= 0) { foundList[variable] = input->second; } } else if (!exactMatch) { for (input = variableList.begin(); input != variableList.end(); ++input) { const string& name = input->first; if (name.compare(0, variable.size(), variable) == 0) { ilocation = input->second->location; if (ilocation >= 0) { foundList[input->first] = input->second; } } } } } void GlslProgram::printUniforms(std::ostream& outputStream) { updateUniformsList(); for (const auto& input : _uniformList) { unsigned int gltype = input.second->gltype; int location = input.second->location; int size = input.second->size; string type = input.second->typeString; string value = input.second->value ? input.second->value->getValueString() : EMPTY_STRING; string unit = input.second->unit; string colorspace = input.second->colorspace; bool isConstant = input.second->isConstant; outputStream << "Program Uniform: \"" << input.first << "\". Location:" << location << ". GLtype: " << std::hex << gltype << ". Size: " << std::dec << size; if (!type.empty()) outputStream << ". TypeString: \"" << type << "\""; if (!value.empty()) { outputStream << ". Value: " << value; if (!unit.empty()) outputStream << ". Unit: " << unit; if (!colorspace.empty()) outputStream << ". Colorspace: " << colorspace; } outputStream << ". Is constant: " << isConstant; if (!input.second->path.empty()) outputStream << ". Element Path: \"" << input.second->path << "\""; outputStream << "." << std::endl; } } void GlslProgram::printAttributes(std::ostream& outputStream) { updateAttributesList(); for (const auto& input : _attributeList) { unsigned int gltype = input.second->gltype; int location = input.second->location; int size = input.second->size; string type = input.second->typeString; string value = input.second->value ? input.second->value->getValueString() : EMPTY_STRING; outputStream << "Program Attribute: \"" << input.first << "\". Location:" << location << ". GLtype: " << std::hex << gltype << ". Size: " << std::dec << size; if (!type.empty()) outputStream << ". TypeString: \"" << type << "\""; if (!value.empty()) outputStream << ". Value: " << value; outputStream << "." << std::endl; } } MATERIALX_NAMESPACE_END