コード例 #1
0
ファイル: GLMesh.cpp プロジェクト: AzCopey/ChilliSource
 //------------------------------------------------------------------------------
 void GLMesh::Bind(GLShader* glShader) noexcept
 {
     auto vertexFormat = m_renderMesh->GetVertexFormat();
     
     //TODO: This should be pre-calculated.
     GLint maxVertexAttributes = 0;
     glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxVertexAttributes);
     CS_ASSERT(u32(maxVertexAttributes) >= vertexFormat.GetNumElements(), "Too many vertex elements.");
     
     glBindBuffer(GL_ARRAY_BUFFER, m_vertexBufferHandle);
     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBufferHandle);
     
     CS_ASSERT_NOGLERROR("An OpenGL error occurred while binding GLMesh.");
     
     for (u32 i = 0; i < vertexFormat.GetNumElements(); ++i)
     {
         glEnableVertexAttribArray(i);
         
         auto elementType = vertexFormat.GetElement(i);
         auto name = GLMeshUtils::GetAttributeName(elementType);
         auto numComponents = ChilliSource::VertexFormat::GetNumComponents(elementType);
         auto type = GLMeshUtils::GetGLType(ChilliSource::VertexFormat::GetDataType(elementType));
         auto normalised = GLMeshUtils::IsNormalised(elementType);
         auto offset = reinterpret_cast<const GLvoid*>(u64(vertexFormat.GetElementOffset(i)));
         
         glShader->SetAttribute(name, numComponents, type, normalised, vertexFormat.GetSize(), offset);
     }
     
     for (s32 i = vertexFormat.GetNumElements(); i < maxVertexAttributes; ++i)
     {
         glDisableVertexAttribArray(i);
     }
 }
コード例 #2
0
        //---------------------------------------------------
        //---------------------------------------------------
        void TextureUnitSystem::Bind(GLenum in_type, const void* in_object, GLint in_handle, u32 in_unit)
        {
            if((s32)in_unit != m_currentTextureUnit)
            {
                m_currentTextureUnit = (s32)in_unit;
                glActiveTexture(GL_TEXTURE0 + in_unit);
            }
            
            if(m_texUnits.empty() == true)
			{
                CSRendering::RenderCapabilities* renderCapabilities = CSCore::Application::Get()->GetSystem<CSRendering::RenderCapabilities>();
                CS_ASSERT(renderCapabilities, "Cannot find required system: Render Capabilities.");
                
                //Create the available texture unit slots
                m_texUnits.resize(renderCapabilities->GetNumTextureUnits());
                std::fill(m_texUnits.begin(), m_texUnits.end(), nullptr);
			}
            
            //Check to make sure this texture is not already bound to this unit
            if(m_texUnits[in_unit] != in_object)
			{
                glBindTexture(in_type, in_handle);
                m_texUnits[in_unit] = in_object;
            }
            
            CS_ASSERT_NOGLERROR("An OpenGL error occurred while binding texture to unit.");
        }
コード例 #3
0
ファイル: Texture.cpp プロジェクト: DNSMorgan/ChilliSource
        //--------------------------------------------------
        //--------------------------------------------------
        void Texture::Destroy()
        {
            m_width = 0;
            m_height = 0;
            
            m_hasFilterModeChanged = true;
            m_hasWrapModeChanged = true;
            m_hasMipMaps = false;
            
            m_filterMode = FilterMode::k_bilinear;
            m_sWrapMode = WrapMode::k_clamp;
            m_tWrapMode = WrapMode::k_clamp;
            
            //If the context has already been destroyed then the cubemap has already been destroyed
            bool hasContext = static_cast<RenderSystem*>(CSCore::Application::Get()->GetRenderSystem())->HasContext();
            if(hasContext == true && m_texHandle > 0)
            {
                Unbind();
                glDeleteTextures(1, &m_texHandle);
            }
            
            m_texHandle = 0;
            
#ifdef CS_TARGETPLATFORM_ANDROID
            m_restoreTextureDataEnabled = false;
            m_restorationDataSize = 0;
            m_restorationData.reset();
#endif
            
            CS_ASSERT_NOGLERROR("An OpenGL error occurred while destroying texture.");
        }
コード例 #4
0
ファイル: GLShader.cpp プロジェクト: AzCopey/ChilliSource
 //------------------------------------------------------------------------------
 void GLShader::SetUniform(const std::string& name, const ChilliSource::Matrix4& value, FailurePolicy failurePolicy) noexcept
 {
     GLint uniformHandle = GetUniformHandle(name, failurePolicy);
     
     if(uniformHandle >= 0)
     {
         glUniformMatrix4fv(uniformHandle, 1, GL_FALSE, reinterpret_cast<const GLfloat*>(&value.m));
         CS_ASSERT_NOGLERROR("An OpenGL error occurred while setting uniform.");
     }
 }
コード例 #5
0
ファイル: GLShader.cpp プロジェクト: AzCopey/ChilliSource
 //------------------------------------------------------------------------------
 void GLShader::SetUniform(const std::string& name, const ChilliSource::Vector4* values, u32 numValues, FailurePolicy failurePolicy) noexcept
 {
     GLint uniformHandle = GetUniformHandle(name, failurePolicy);
     
     if(uniformHandle >= 0)
     {
         glUniform4fv(uniformHandle, numValues, reinterpret_cast<const GLfloat*>(values));
         CS_ASSERT_NOGLERROR("An OpenGL error occurred while setting uniform.");
     }
 }
コード例 #6
0
ファイル: GLShader.cpp プロジェクト: AzCopey/ChilliSource
 //------------------------------------------------------------------------------
 void GLShader::SetUniform(const std::string& name, f32 value, FailurePolicy failurePolicy) noexcept
 {
     GLint uniformHandle = GetUniformHandle(name, failurePolicy);
     
     if(uniformHandle >= 0)
     {
         glUniform1f(uniformHandle, value);
         CS_ASSERT_NOGLERROR("An OpenGL error occurred while setting uniform.");
     }
 }
コード例 #7
0
ファイル: GLShader.cpp プロジェクト: AzCopey/ChilliSource
        //------------------------------------------------------------------------------
        void GLShader::SetAttribute(const std::string& name, GLint size, GLenum type, GLboolean isNormalised, GLsizei stride, const GLvoid* offset) noexcept
        {
            auto it = m_attributeHandles.find(name);
            if(it == m_attributeHandles.end())
            {
                return;
            }

            glVertexAttribPointer(it->second, size, type, isNormalised, stride, offset);

            CS_ASSERT_NOGLERROR("An OpenGL error occurred while setting attribute.");
        }
コード例 #8
0
 //------------------------------------------------------------------------------
 GLTextureUnitManager::GLTextureUnitManager() noexcept
 {
     s32 numTextureUnits = 0;
     glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &numTextureUnits);
     
     m_boundTextures.reserve(numTextureUnits);
     for (s32 i = 0; i < numTextureUnits; ++i)
     {
         m_boundTextures.push_back(nullptr);
     }
     
     CS_ASSERT_NOGLERROR("An OpenGL error occurred while setting up the texture unit manager.");
 }
コード例 #9
0
 //------------------------------------------------------------------------------
 GLDynamicMesh::~GLDynamicMesh() noexcept
 {
     if(!m_invalidData)
     {
         glDeleteBuffers(1, &m_vertexBufferHandle);
         if(m_indexBufferHandle != 0)
         {
             glDeleteBuffers(1, &m_indexBufferHandle);
         }
         
         CS_ASSERT_NOGLERROR("An OpenGL error occurred while deleting GLDynamicMesh.");
     }
 }
コード例 #10
0
ファイル: Texture.cpp プロジェクト: DNSMorgan/ChilliSource
        //--------------------------------------------------
        /// GL makes a copy of the data so we can just
        /// let the incoming data delete itself
        //--------------------------------------------------
        void Texture::Build(const Descriptor& in_desc, TextureDataUPtr in_data, bool in_mipMap, bool in_restoreTextureDataEnabled)
        {
            Destroy();
            
            m_width = in_desc.m_width;
            m_height = in_desc.m_height;
            m_format = in_desc.m_format;
            m_compression = in_desc.m_compression;
            
            CS_ASSERT(m_width <= m_renderCapabilities->GetMaxTextureSize() && m_height <= m_renderCapabilities->GetMaxTextureSize(),
                      "OpenGL does not support textures of this size on this device (" + CSCore::ToString(m_width) + ", " + CSCore::ToString(m_height) + ")");
            
            glGenTextures(1, &m_texHandle);
            Bind();
            
            u8* data = in_data.get();
            
			switch(m_compression)
			{
				case CSCore::ImageCompression::k_none:
                    UploadImageDataNoCompression(m_format, m_width, m_height, data);
					break;
				case CSCore::ImageCompression::k_ETC1:
                    UploadImageDataETC1(m_format, m_width, m_height, data, in_desc.m_dataSize);
					break;
				case CSCore::ImageCompression::k_PVR2Bpp:
                    UploadImageDataPVR2(m_format, m_width, m_height, data, in_desc.m_dataSize);
					break;
				case CSCore::ImageCompression::k_PVR4Bpp:
                    UploadImageDataPVR4(m_format, m_width, m_height, data, in_desc.m_dataSize);
					break;
			};
            
            if(in_mipMap == true)
            {
                glGenerateMipmap(GL_TEXTURE_2D);
            }
            
            m_hasMipMaps = in_mipMap;
            
#ifdef CS_TARGETPLATFORM_ANDROID
            if (GetStorageLocation() == CSCore::StorageLocation::k_none && in_restoreTextureDataEnabled == true)
            {
            	m_restoreTextureDataEnabled = true;
                m_restorationDataSize = in_desc.m_dataSize;
                m_restorationData = std::move(in_data);
            }
#endif
            
            CS_ASSERT_NOGLERROR("An OpenGL error occurred while building texture.");
        }
コード例 #11
0
        //-------------------------------------------------------
        ChilliSource::RenderInfo RenderInfoFactory::CreateRenderInfo() noexcept
        {
            bool areHighPrecFragmentsSupported = true;
            bool areMapBuffersSupported = true;
            bool areVAOsSupported = true;
            bool areDepthTexturesSupported = false;
            bool areShadowMapsSupported = false;
            
            u32 maxTextureSize = 0;
            u32 maxTextureUnits = 0;
            u32 maxVertexAttribs = 0;
            
#ifdef CS_OPENGLVERSION_ES
            s32 fragmentHighRanges[2];
            s32 fragmentHighPrecision;
            
            glGetShaderPrecisionFormat(GL_FRAGMENT_SHADER, GL_HIGH_FLOAT, fragmentHighRanges, &fragmentHighPrecision);
            areHighPrecFragmentsSupported = fragmentHighPrecision != 0 && fragmentHighRanges[0] != 0 && fragmentHighRanges[1] != 0;
            
#ifdef CS_TARGETPLATFORM_ANDROID
            //Check for map buffer support
            areMapBuffersSupported = CheckForOpenGLExtension("GL_OES_mapbuffer");
            areVAOsSupported = CheckForOpenGLExtension("GL_OES_vertex_array_object");
#endif
            
#ifdef CS_TARGETPLATFORM_RPI
            areVAOsSupported = false;
#endif
            
#endif
            
#ifdef CS_OPENGLVERSION_STANDARD
            areDepthTexturesSupported = CheckForOpenGLExtension("GL_ARB_depth_texture");
#elif defined(CS_OPENGLVERSION_ES)
            areDepthTexturesSupported = CheckForOpenGLExtension("GL_OES_depth_texture");
#endif
            areShadowMapsSupported = (areDepthTexturesSupported && areHighPrecFragmentsSupported);
            glGetIntegerv(GL_MAX_TEXTURE_SIZE, (GLint*)&maxTextureSize);
            glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, (GLint*)&maxTextureUnits);
            glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, (GLint*)&maxVertexAttribs);
            
            CS_ASSERT_NOGLERROR("An OpenGL error occurred while getting render capabilities.");
            
            ChilliSource::RenderInfo renderInfo(areShadowMapsSupported, areDepthTexturesSupported, areMapBuffersSupported, areVAOsSupported, areHighPrecFragmentsSupported, maxTextureSize, maxTextureUnits, maxVertexAttribs);
            
            return renderInfo;
        }
コード例 #12
0
 //------------------------------------------------------------------------------
 GLuint GLTextureUnitManager::BindAdditional(const ChilliSource::RenderTexture* texture) noexcept
 {
     GLuint textureIndex = GetNextAvailableUnit();
     
     m_boundTextures.push_back(texture);
     
     auto glTexture = static_cast<GLTexture*>(texture->GetExtraData());
     CS_ASSERT(glTexture, "Cannot bind a texture which hasn't been loaded.");
     
     glActiveTexture(GL_TEXTURE0 + textureIndex);
     glBindTexture(GL_TEXTURE_2D, glTexture->GetHandle());
     glActiveTexture(GL_TEXTURE0);
     
     CS_ASSERT_NOGLERROR("An OpenGL error occurred while binding an additional texture.");
     
     return textureIndex;
 }
コード例 #13
0
 //-------------------------------------------------------
 //-------------------------------------------------------
 void TextureUnitSystem::Unbind(const void* in_object)
 {
     for(u32 i=0; i<m_texUnits.size(); ++i)
     {
         if(m_texUnits[i] == in_object)
         {
             //Unbind the texture from this slot by binding zero.
             if((s32)i != m_currentTextureUnit)
             {
                 m_currentTextureUnit = (s32)i;
                 glActiveTexture(GL_TEXTURE0 + i);
             }
             glBindTexture(GL_TEXTURE_2D, 0);
             m_texUnits[i] = nullptr;
         }
     }
     
     CS_ASSERT_NOGLERROR("An OpenGL error occurred while unbinding texture.");
 }
コード例 #14
0
 //------------------------------------------------------------------------------
 void GLDynamicMesh::Bind(GLShader* glShader, ChilliSource::PolygonType polygonType, const ChilliSource::VertexFormat& vertexFormat, ChilliSource::IndexFormat indexFormat, u32 numVertices, u32 numIndices,
                          const u8* vertexData, u32 vertexDataSize, const u8* indexData, u32 indexDataSize) noexcept
 {
     m_polygonType = polygonType;
     m_vertexFormat = vertexFormat;
     m_indexFormat = indexFormat;
     m_numVertices = numVertices;
     m_numIndices = numIndices;
     
     glBindBuffer(GL_ARRAY_BUFFER, m_vertexBufferHandle);
     glBufferSubData(GL_ARRAY_BUFFER, 0, vertexDataSize, vertexData);
     
     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBufferHandle);
     if (m_indexBufferHandle != 0)
     {
         glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, indexDataSize, indexData);
     }
     
     CS_ASSERT_NOGLERROR("An OpenGL error occurred while binding GLDynamicMesh.");
     
     ApplyVertexAttributes(glShader);
 }
コード例 #15
0
        //------------------------------------------------------------------------------
        void GLTextureUnitManager::Bind(const std::vector<const ChilliSource::RenderTexture*>& textures) noexcept
        {
            for (u32 textureUnitIndex = 0; textureUnitIndex < u32(textures.size()); ++textureUnitIndex)
            {
                if (m_boundTextures[textureUnitIndex] != textures[textureUnitIndex])
                {
                    m_boundTextures[textureUnitIndex] = textures[textureUnitIndex];
                    
                    auto glTexture = static_cast<GLTexture*>(m_boundTextures[textureUnitIndex]->GetExtraData());

                    CS_ASSERT(glTexture, "Cannot bind a texture which hasn't been loaded.");
                    CS_ASSERT(!glTexture->IsDataInvalid(), "GLTextureUnitManager::Bind(): Failed to bind texture, its context is invalid!");
                    
                    glActiveTexture(GL_TEXTURE0 + textureUnitIndex);
                    glBindTexture(GL_TEXTURE_2D, glTexture->GetHandle());
                }
            }
            
            glActiveTexture(GL_TEXTURE0);
            
            CS_ASSERT_NOGLERROR("An OpenGL error occurred while binding textures.");
        }
コード例 #16
0
ファイル: GLShader.cpp プロジェクト: AzCopey/ChilliSource
 //------------------------------------------------------------------------------
 GLShader::~GLShader() noexcept
 {
     if(!m_invalidData)
     {
         if(m_vertexShaderId > 0)
         {
             glDetachShader(m_programId, m_vertexShaderId);
             glDeleteShader(m_vertexShaderId);
         }
         if(m_fragmentShaderId > 0)
         {
             glDetachShader(m_programId, m_fragmentShaderId);
             glDeleteShader(m_fragmentShaderId);
         }
         if(m_programId > 0)
         {
             glDeleteProgram(m_programId);
         }
         
         CS_ASSERT_NOGLERROR("An OpenGL error occurred while destroying shader.");
     }
 }
コード例 #17
0
 //------------------------------------------------------------------------------
 GLDynamicMesh::GLDynamicMesh(u32 vertexDataSize, u32 indexDataSize) noexcept
    : m_allocator(k_allocatorSize), m_maxVertexDataSize(vertexDataSize), m_maxIndexDataSize(indexDataSize)
 {
     glGenBuffers(1, &m_vertexBufferHandle);
     CS_ASSERT(m_vertexBufferHandle != 0, "Invalid vertex buffer.");
     
     if(indexDataSize > 0)
     {
         glGenBuffers(1, &m_indexBufferHandle);
         CS_ASSERT(m_indexBufferHandle != 0, "Invalid index buffer.");
     }
     
     glBindBuffer(GL_ARRAY_BUFFER, m_vertexBufferHandle);
     glBufferData(GL_ARRAY_BUFFER, m_maxVertexDataSize, nullptr, GL_DYNAMIC_DRAW);
     
     if(indexDataSize > 0)
     {
         glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBufferHandle);
         glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_maxIndexDataSize, nullptr, GL_DYNAMIC_DRAW);
     }
     
     CS_ASSERT_NOGLERROR("An OpenGL error occurred while creating GLDynamicMesh.");
 }
コード例 #18
0
ファイル: GLMesh.cpp プロジェクト: AzCopey/ChilliSource
 //------------------------------------------------------------------------------
 void GLMesh::BuildMesh(const u8* vertexData, u32 vertexDataSize, const u8* indexData, u32 indexDataSize) noexcept
 {
     CS_ASSERT(vertexDataSize > 0 && vertexData, "Cannot build mesh with empty data");
     
     glGenBuffers(1, &m_vertexBufferHandle);
     CS_ASSERT(m_vertexBufferHandle != 0, "Invalid vertex buffer.");
     
     if(indexData)
     {
         glGenBuffers(1, &m_indexBufferHandle);
         CS_ASSERT(m_indexBufferHandle != 0, "Invalid index buffer.");
     }
     
     glBindBuffer(GL_ARRAY_BUFFER, m_vertexBufferHandle);
     glBufferData(GL_ARRAY_BUFFER, vertexDataSize, vertexData, GL_STATIC_DRAW);
     
     if(indexData)
     {
         glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBufferHandle);
         glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexDataSize, indexData, GL_STATIC_DRAW);
     }
     
     CS_ASSERT_NOGLERROR("An OpenGL error occurred while creating GLMesh.");
 }
コード例 #19
0
ファイル: GLShader.cpp プロジェクト: AzCopey/ChilliSource
        //------------------------------------------------------------------------------
        GLint GLShader::GetUniformHandle(const std::string& name, FailurePolicy failurePolicy) noexcept
        {
            GLint uniformHandle = -1;
            
            auto it = m_uniformHandles.find(name);
            if(it != m_uniformHandles.end())
            {
                uniformHandle = it->second;
            }
            else
            {
                uniformHandle = glGetUniformLocation(m_programId, name.c_str());
                CS_ASSERT_NOGLERROR("An OpenGL error occurred while getting uniform handle.");
                
                m_uniformHandles.insert(std::make_pair(name, uniformHandle));
            }

            if (uniformHandle < 0 && failurePolicy == FailurePolicy::k_hard)
            {
                CS_LOG_FATAL("Cannot find shader uniform: " + name);
            }
            
            return uniformHandle;
        }
コード例 #20
0
ファイル: GLShader.cpp プロジェクト: AzCopey/ChilliSource
 //------------------------------------------------------------------------------
 void GLShader::BuildAttributeHandleMap() noexcept
 {
     static const std::array<std::string, 6> attribNames =
     {{
         k_attributePosition,
         k_attributeNormal,
         k_attributeTexCoord,
         k_attributeColour,
         k_attributeWeights,
         k_attributeJointIndices
     }};
     
     for(const auto& name : attribNames)
     {
         GLint handle = glGetAttribLocation(m_programId, name.c_str());
         
         if(handle >= 0)
         {
             m_attributeHandles.insert(std::make_pair(name, handle));
         }
     }
     
     CS_ASSERT_NOGLERROR("An OpenGL error occurred while populating attribute handles.");
 }
コード例 #21
0
ファイル: Texture.cpp プロジェクト: DNSMorgan/ChilliSource
        //--------------------------------------------------
        //--------------------------------------------------
        void Texture::UpdateRestorationData()
        {
            CS_ASSERT(GetStorageLocation() == CSCore::StorageLocation::k_none, "Cannot update restoration data on texture that was loaded from file.");
            
            if (m_restoreTextureDataEnabled == true)
            {
            	Unbind();

            	//create an bind a new frame buffer.
            	GLuint frameBufferHandle = 0;
                glGenFramebuffers(1, &frameBufferHandle);
				glBindFramebuffer(GL_FRAMEBUFFER, frameBufferHandle);

				//attach the texture
				glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_texHandle, 0);
				GLuint glCheck = glCheckFramebufferStatus(GL_FRAMEBUFFER);
				if(glCheck != GL_FRAMEBUFFER_COMPLETE)
				{
					CS_LOG_FATAL("Framebuffer incomplete while updating texture restoration data.");
				}

				//read the data from the texture. This can only be read back in RGBA8888 format so it will need
				//to be converted back to the format of the texture.
				u32 unconvertedDataSize = GetWidth() * GetHeight() * 4;
				std::unique_ptr<u8[]> unconvertedData(new u8[unconvertedDataSize]);
				glReadPixels(0, 0, GetWidth(), GetHeight(), GL_RGBA, GL_UNSIGNED_BYTE, unconvertedData.get());

				//Convert to the format of this texture
				CSCore::ImageFormatConverter::ImageBuffer convertedData;
				switch (m_format)
				{
				case CSCore::ImageFormat::k_RGBA8888:
					convertedData.m_size = unconvertedDataSize;
					convertedData.m_data = std::move(unconvertedData);
					break;
				case CSCore::ImageFormat::k_RGB888:
					convertedData = CSCore::ImageFormatConverter::RGBA8888ToRGB888(unconvertedData.get(), unconvertedDataSize);
					unconvertedData.reset();
					break;
				case CSCore::ImageFormat::k_RGBA4444:
					convertedData = CSCore::ImageFormatConverter::RGBA8888ToRGBA4444(unconvertedData.get(), unconvertedDataSize);
					unconvertedData.reset();
					break;
				case CSCore::ImageFormat::k_RGB565:
					convertedData = CSCore::ImageFormatConverter::RGBA8888ToRGB565(unconvertedData.get(), unconvertedDataSize);
					unconvertedData.reset();
					break;
				default:
					CS_LOG_FATAL("Texture is not in a restorable format. The restorable texture data option must be disabled for this texture.");
					break;
				}

				m_restorationDataSize = convertedData.m_size;
				m_restorationData = std::move(convertedData.m_data);

				//clean up the frame buffer.
				glBindFramebuffer(GL_FRAMEBUFFER, 0);
				glDeleteFramebuffers(1, &frameBufferHandle);

				CS_ASSERT_NOGLERROR("An OpenGL error occurred while updating texture restoration data.");
            }
        }
コード例 #22
0
ファイル: GLShader.cpp プロジェクト: AzCopey/ChilliSource
 //------------------------------------------------------------------------------
 void GLShader::Bind() noexcept
 {
     glUseProgram(m_programId);
     CS_ASSERT_NOGLERROR("An OpenGL error occurred while binding shader.");
 }