Exemple #1
0
/// @copydoc ResourceHandler::CacheResource()
bool FontResourceHandler::CacheResource(
    ObjectPreprocessor* pObjectPreprocessor,
    Resource* pResource,
    const String& rSourceFilePath )
{
    HELIUM_ASSERT( pObjectPreprocessor );
    HELIUM_ASSERT( pResource );

    Font* pFont = Reflect::AssertCast< Font >( pResource );

    // Load the font into memory ourselves in order to make sure we properly support Unicode file names.
    FileStream* pFileStream = File::Open( rSourceFilePath, FileStream::MODE_READ );
    if( !pFileStream )
    {
        HELIUM_TRACE(
            TRACE_ERROR,
            TXT( "FontResourceHandler: Source file for font resource \"%s\" failed to open properly.\n" ),
            *rSourceFilePath );

        return false;
    }

    uint64_t fileSize64 = static_cast< uint64_t >( pFileStream->GetSize() );
    if( fileSize64 > SIZE_MAX )
    {
        HELIUM_TRACE(
            TRACE_ERROR,
            ( TXT( "FontResourceHandler: Font file \"%s\" exceeds the maximum addressable size of data in memory for " )
              TXT( "this platform and will not be cached.\n" ) ),
            *rSourceFilePath );

        delete pFileStream;

        return false;
    }

    size_t fileSize = static_cast< size_t >( fileSize64 );

    uint8_t* pFileData = new uint8_t [ fileSize ];
    if( !pFileData )
    {
        HELIUM_TRACE(
            TRACE_ERROR,
            ( TXT( "FontResourceHandler: Failed to allocate %" ) TPRIuSZ TXT( " bytes for resource data for font " )
              TXT( "\"%s\".\n" ) ),
            fileSize,
            *rSourceFilePath );

        delete pFileStream;

        return false;
    }

    size_t bytesRead = pFileStream->Read( pFileData, 1, fileSize );
    delete pFileStream;

    if( bytesRead != fileSize )
    {
        HELIUM_TRACE(
            TRACE_WARNING,
            ( TXT( "FontResourceHandler: Attempted to read %" ) TPRIuSZ TXT( " bytes from font resource file \"%s\", " )
              TXT( "but only %" ) TPRIuSZ TXT( " bytes were read successfully.\n" ) ),
            fileSize,
            *rSourceFilePath,
            bytesRead );
    }

    // Create the font face.
    FT_Library pLibrary = GetStaticLibrary();
    HELIUM_ASSERT( pLibrary );

    FT_Face pFace = NULL;
    FT_Error error = FT_New_Memory_Face( pLibrary, pFileData, static_cast< FT_Long >( bytesRead ), 0, &pFace );
    if( error != 0 )
    {
        HELIUM_TRACE(
            TRACE_ERROR,
            TXT( "FontResourceHandler: Failed to create font face from resource file \"%s\".\n" ),
            *rSourceFilePath );

        delete [] pFileData;

        return false;
    }

    // Set the appropriate font size.
    int32_t pointSize = Font::Float32ToFixed26x6( pFont->GetPointSize() );
    uint32_t dpi = pFont->GetDpi();

    error = FT_Set_Char_Size( pFace, pointSize, pointSize, dpi, dpi );
    if( error != 0 )
    {
        HELIUM_TRACE(
            TRACE_ERROR,
            TXT( "FontResourceHandler: Failed to set size of font resource \"%s\".\n" ),
            *rSourceFilePath );

        FT_Done_Face( pFace );
        delete [] pFileData;

        return false;
    }

    // Get the general font size information.
    FT_Size pSize = pFace->size;
    HELIUM_ASSERT( pSize );

    int32_t ascender = pSize->metrics.ascender;
    int32_t descender = pSize->metrics.descender;
    int32_t height = pSize->metrics.height;
    int32_t maxAdvance = pSize->metrics.max_advance;

    // Make sure that all characters in the font will fit on a single texture sheet (note that we also need at least a
    // pixel on each side in order to pad each glyph).
    uint16_t textureSheetWidth = Max< uint16_t >( pFont->GetTextureSheetWidth(), 1 );
    uint16_t textureSheetHeight = Max< uint16_t >( pFont->GetTextureSheetHeight(), 1 );

    int32_t integerHeight = ( height + ( 1 << 6 ) - 1 ) >> 6;
    if( integerHeight + 2 > textureSheetHeight )
    {
        HELIUM_TRACE(
            TRACE_ERROR,
            ( TXT( "FontResourceHandler: Font height (%" ) TPRId32 TXT( ") exceeds the texture sheet height (%" )
              TPRIu16 TXT( ") for font resource \"%s\".\n" ) ),
            integerHeight,
            textureSheetHeight,
            *pResource->GetPath().ToString() );

        FT_Done_Face( pFace );
        delete [] pFileData;

        return false;
    }

    int32_t integerMaxAdvance = ( maxAdvance + ( 1 << 6 ) - 1 ) >> 6;
    if( integerMaxAdvance + 2 > textureSheetWidth )
    {
        HELIUM_TRACE(
            TRACE_ERROR,
            ( TXT( "FontResourceHandler: Maximum character advance (%" ) TPRId32 TXT( ") exceeds the texture sheet " )
              TXT( "width (%" ) TPRIu16 TXT( ") for font resource \"%s\".\n" ) ),
            integerMaxAdvance,
            textureSheetWidth,
            *pResource->GetPath().ToString() );

        FT_Done_Face( pFace );
        delete [] pFileData;

        return false;
    }

    // Allocate a buffer for building our texture sheets.
    uint_fast32_t texturePixelCount =
        static_cast< uint_fast32_t >( textureSheetWidth ) * static_cast< uint_fast32_t >( textureSheetHeight );
    uint8_t* pTextureBuffer = new uint8_t [ texturePixelCount ];
    HELIUM_ASSERT( pTextureBuffer );
    if( !pTextureBuffer )
    {
        HELIUM_TRACE(
            TRACE_ERROR,
            ( TXT( "FontResourceHandler: Failed to allocate %" ) TPRIuFAST32 TXT( " bytes for texture resource " )
              TXT( "buffer data while caching font resource \"%s\".\n" ) ),
            texturePixelCount,
            *pResource->GetPath().ToString() );

        FT_Done_Face( pFace );
        delete [] pFileData;

        return false;
    }

    MemoryZero( pTextureBuffer, texturePixelCount );

    // Build the texture sheets for our glyphs.
    Font::ECompression textureCompression = pFont->GetTextureCompression();
    bool bAntialiased = pFont->GetAntialiased();

    DynArray< DynArray< uint8_t > > textureSheets;
    DynArray< Font::Character > characters;

    uint16_t penX = 1;
    uint16_t penY = 1;
    uint16_t lineHeight = 0;

    FT_Int32 glyphLoadFlags = FT_LOAD_RENDER;
    if( !bAntialiased )
    {
        glyphLoadFlags |= FT_LOAD_TARGET_MONO;
    }

    for( uint_fast32_t codePoint = 0; codePoint <= UNICODE_CODE_POINT_MAX; ++codePoint )
    {
        // Check whether the current code point is contained within the font.
        FT_UInt characterIndex = FT_Get_Char_Index( pFace, static_cast< FT_ULong >( codePoint ) );
        if( characterIndex == 0 )
        {
            continue;
        }

        // Load and render the glyph for the current character.
        HELIUM_VERIFY( FT_Load_Glyph( pFace, characterIndex, glyphLoadFlags ) == 0 );

        FT_GlyphSlot pGlyph = pFace->glyph;
        HELIUM_ASSERT( pGlyph );

        // Proceed to the next line in the texture sheet or the next sheet itself if we don't have enough room in the
        // current line/sheet.
        HELIUM_ASSERT( pGlyph->bitmap.rows >= 0 );
        HELIUM_ASSERT( pGlyph->bitmap.width >= 0 );
        uint_fast32_t glyphRowCount = static_cast< uint32_t >( pGlyph->bitmap.rows );
        uint_fast32_t glyphWidth = static_cast< uint32_t >( pGlyph->bitmap.width );

        if( penX + glyphWidth + 1 >= textureSheetWidth )
        {
            penX = 1;

            if( penY + glyphRowCount + 1 >= textureSheetHeight )
            {
                CompressTexture(
                    pTextureBuffer,
                    textureSheetWidth,
                    textureSheetHeight,
                    textureCompression,
                    textureSheets );
                MemoryZero( pTextureBuffer, texturePixelCount );

                penY = 1;
            }
            else
            {
                penY += lineHeight + 1;
            }

            lineHeight = 0;
        }

        // Copy the character data from the glyph bitmap to the texture sheet.
        int_fast32_t glyphPitch = pGlyph->bitmap.pitch;

        const uint8_t* pGlyphBuffer = pGlyph->bitmap.buffer;
        HELIUM_ASSERT( pGlyphBuffer || glyphRowCount == 0 );

        uint8_t* pTexturePixel =
            pTextureBuffer + static_cast< size_t >( penY ) * static_cast< size_t >( textureSheetWidth ) + penX;

        if( bAntialiased )
        {
            // Anti-aliased fonts are rendered as 8-bit grayscale images, so just copy the data as-is.
            for( uint_fast32_t rowIndex = 0; rowIndex < glyphRowCount; ++rowIndex )
            {
                MemoryCopy( pTexturePixel, pGlyphBuffer, glyphWidth );
                pGlyphBuffer += glyphPitch;
                pTexturePixel += textureSheetWidth;
            }
        }
        else
        {
            // Fonts without anti-aliasing are rendered as 1-bit monochrome images, so we need to manually convert each
            // row to 8-bit grayscale.
            for( uint_fast32_t rowIndex = 0; rowIndex < glyphRowCount; ++rowIndex )
            {
                const uint8_t* pGlyphPixelBlock = pGlyphBuffer;
                pGlyphBuffer += glyphPitch;

                uint8_t* pCurrentTexturePixel = pTexturePixel;
                pTexturePixel += textureSheetWidth;

                uint_fast32_t remainingPixelCount = glyphWidth;
                while( remainingPixelCount >= 8 )
                {
                    remainingPixelCount -= 8;

                    uint8_t pixelBlock = *pGlyphPixelBlock;
                    ++pGlyphPixelBlock;

                    *( pCurrentTexturePixel++ ) = ( ( pixelBlock & ( 1 << 7 ) ) ? 255 : 0 );
                    *( pCurrentTexturePixel++ ) = ( ( pixelBlock & ( 1 << 6 ) ) ? 255 : 0 );
                    *( pCurrentTexturePixel++ ) = ( ( pixelBlock & ( 1 << 5 ) ) ? 255 : 0 );
                    *( pCurrentTexturePixel++ ) = ( ( pixelBlock & ( 1 << 4 ) ) ? 255 : 0 );
                    *( pCurrentTexturePixel++ ) = ( ( pixelBlock & ( 1 << 3 ) ) ? 255 : 0 );
                    *( pCurrentTexturePixel++ ) = ( ( pixelBlock & ( 1 << 2 ) ) ? 255 : 0 );
                    *( pCurrentTexturePixel++ ) = ( ( pixelBlock & ( 1 << 1 ) ) ? 255 : 0 );
                    *( pCurrentTexturePixel++ ) = ( ( pixelBlock & ( 1 << 0 ) ) ? 255 : 0 );
                }

                uint8_t pixelBlock = *pGlyphPixelBlock;
                uint8_t mask = ( 1 << 7 );
                while( remainingPixelCount != 0 )
                {
                    *( pCurrentTexturePixel++ ) = ( ( pixelBlock & mask ) ? 255 : 0 );
                    mask >>= 1;
                    --remainingPixelCount;
                }
            }
        }

        // Store the character information in our character array.
        Font::Character* pCharacter = characters.New();
        HELIUM_ASSERT( pCharacter );
        pCharacter->codePoint = static_cast< uint32_t >( codePoint );

        pCharacter->imageX = penX;
        pCharacter->imageY = penY;
        pCharacter->imageWidth = static_cast< uint16_t >( glyphWidth );
        pCharacter->imageHeight = static_cast< uint16_t >( glyphRowCount );

        pCharacter->width = pGlyph->metrics.width;
        pCharacter->height = pGlyph->metrics.height;
        pCharacter->bearingX = pGlyph->metrics.horiBearingX;
        pCharacter->bearingY = pGlyph->metrics.horiBearingY;
        pCharacter->advance = pGlyph->metrics.horiAdvance;

        HELIUM_ASSERT( textureSheets.GetSize() < UINT8_MAX );
        pCharacter->texture = static_cast< uint8_t >( static_cast< uint8_t >( textureSheets.GetSize() ) );

        // Update the pen location as well as the maximum line height as appropriate based on the current line height.
        penX += static_cast< uint16_t >( glyphWidth ) + 1;

        HELIUM_ASSERT( glyphRowCount <= UINT16_MAX );
        lineHeight = Max< uint16_t >( lineHeight, static_cast< uint16_t >( glyphRowCount ) );
    }

    // Compress and store the last texture in the sheet.
    if( !characters.IsEmpty() )
    {
        CompressTexture( pTextureBuffer, textureSheetWidth, textureSheetHeight, textureCompression, textureSheets );
    }

    // Done processing the font itself, so free some resources.
    delete [] pTextureBuffer;

    FT_Done_Face( pFace );
    delete [] pFileData;

    // Cache the font data.
    size_t characterCountActual = characters.GetSize();
    HELIUM_ASSERT( characterCountActual <= UINT32_MAX );
    uint32_t characterCount = static_cast< uint32_t >( characterCountActual );

    size_t textureCountActual = textureSheets.GetSize();
    HELIUM_ASSERT( textureCountActual < UINT8_MAX );
    uint8_t textureCount = static_cast< uint8_t >( textureCountActual );

    BinarySerializer persistentDataSerializer;
    for( size_t platformIndex = 0; platformIndex < static_cast< size_t >( Cache::PLATFORM_MAX ); ++platformIndex )
    {
        PlatformPreprocessor* pPreprocessor = pObjectPreprocessor->GetPlatformPreprocessor(
            static_cast< Cache::EPlatform >( platformIndex ) );
        if( !pPreprocessor )
        {
            continue;
        }

        persistentDataSerializer.SetByteSwapping( pPreprocessor->SwapBytes() );
        persistentDataSerializer.BeginSerialize();

        persistentDataSerializer << ascender;
        persistentDataSerializer << descender;
        persistentDataSerializer << height;
        persistentDataSerializer << maxAdvance;
        persistentDataSerializer << characterCount;
        persistentDataSerializer << textureCount;

        for( size_t characterIndex = 0; characterIndex < characterCountActual; ++characterIndex )
        {
            characters[ characterIndex ].Serialize( persistentDataSerializer );
        }

        persistentDataSerializer.EndSerialize();

        Resource::PreprocessedData& rPreprocessedData = pResource->GetPreprocessedData(
            static_cast< Cache::EPlatform >( platformIndex ) );
        rPreprocessedData.persistentDataBuffer = persistentDataSerializer.GetPropertyStreamBuffer();
        rPreprocessedData.subDataBuffers = textureSheets;
        rPreprocessedData.bLoaded = true;
    }

    return true;
}
/// @copydoc ResourceHandler::CacheResource()
bool MeshResourceHandler::CacheResource(
                                        ObjectPreprocessor* pObjectPreprocessor,
                                        Resource* pResource,
                                        const String& rSourceFilePath )
{
    HELIUM_ASSERT( pObjectPreprocessor );
    HELIUM_ASSERT( pResource );

    // Load and parse the mesh data.
    DynArray< StaticMeshVertex< 1 > > vertices;
    DynArray< uint16_t > indices;
    DynArray< uint16_t > sectionVertexCounts;
    DynArray< uint32_t > sectionTriangleCounts;
    DynArray< FbxSupport::BoneData > bones;
    DynArray< FbxSupport::BlendData > vertexBlendData;
    DynArray< uint8_t > skinningPaletteMap;
    bool bLoadSuccess = m_rFbxSupport.LoadMesh(
        rSourceFilePath,
        vertices,
        indices,
        sectionVertexCounts,
        sectionTriangleCounts,
        bones,
        vertexBlendData,
        skinningPaletteMap );
    if( !bLoadSuccess )
    {
        HELIUM_TRACE(
            TRACE_ERROR,
            TXT( "MeshResourceHandler::CacheResource(): Failed to build mesh from source file \"%s\".\n" ),
            *rSourceFilePath );

        return false;
    }

    size_t vertexCountActual = vertices.GetSize();
    HELIUM_ASSERT( vertexCountActual <= UINT32_MAX );
    uint32_t vertexCount = static_cast< uint32_t >( vertexCountActual );

    size_t indexCount = indices.GetSize();
    size_t triangleCountActual = indexCount;
    HELIUM_ASSERT( triangleCountActual % 3 == 0 );
    triangleCountActual /= 3;
    HELIUM_ASSERT( triangleCountActual <= UINT32_MAX );
    uint32_t triangleCount = static_cast< uint32_t >( triangleCountActual );

    size_t boneCountActual = bones.GetSize();
    HELIUM_ASSERT( boneCountActual <= UINT8_MAX );
#if !HELIUM_USE_GRANNY_ANIMATION
    uint8_t boneCount = static_cast< uint8_t >( boneCountActual );
#endif

    // Compute the mesh bounding box.
    Simd::AaBox bounds;
    if( vertexCountActual != 0 )
    {
        const float32_t* pPosition = vertices[ 0 ].position;
        Simd::Vector3 position( pPosition[ 0 ], pPosition[ 1 ], pPosition[ 2 ] );
        bounds.Set( position, position );
        for( size_t vertexIndex = 1; vertexIndex < vertexCountActual; ++vertexIndex )
        {
            pPosition = vertices[ vertexIndex ].position;
            bounds.Expand( Simd::Vector3( pPosition[ 0 ], pPosition[ 1 ], pPosition[ 2 ] ) );
        }
    }

#if HELIUM_USE_GRANNY_ANIMATION
    Granny::MeshCachingData grannyMeshCachingData;
    grannyMeshCachingData.BuildResourceData( bones );
#endif  // HELIUM_USE_GRANNY_ANIMATION

    // Cache the data for each supported platform.
    BinarySerializer serializer;
    for( size_t platformIndex = 0; platformIndex < static_cast< size_t >( Cache::PLATFORM_MAX ); ++platformIndex )
    {
        PlatformPreprocessor* pPreprocessor = pObjectPreprocessor->GetPlatformPreprocessor(
            static_cast< Cache::EPlatform >( platformIndex ) );
        if( !pPreprocessor )
        {
            continue;
        }

        Resource::PreprocessedData& rPreprocessedData = pResource->GetPreprocessedData(
            static_cast< Cache::EPlatform >( platformIndex ) );

        DynArray< DynArray< uint8_t > >& rSubDataBuffers = rPreprocessedData.subDataBuffers;
        rSubDataBuffers.Reserve( 2 );
        rSubDataBuffers.Resize( 2 );
        rSubDataBuffers.Trim();

        serializer.SetByteSwapping( pPreprocessor->SwapBytes() );

        // Serialize the buffer sizes and mesh bounds first.
        serializer.BeginSerialize();
        serializer << Serializer::WrapDynArray( sectionVertexCounts );
        serializer << Serializer::WrapDynArray( sectionTriangleCounts );
        serializer << Serializer::WrapDynArray( skinningPaletteMap );
        serializer << vertexCount;
        serializer << triangleCount;
        serializer << bounds;

#if HELIUM_USE_GRANNY_ANIMATION
        grannyMeshCachingData.CachePlatformResourceData( pPreprocessor, serializer );
#else
        serializer << boneCount;

        for( size_t boneIndex = 0; boneIndex < boneCount; ++boneIndex )
        {
            FbxSupport::BoneData& rBoneData = bones[ boneIndex ];
            serializer << rBoneData.name;
            serializer << rBoneData.parentIndex;
            serializer << rBoneData.referenceTransform;
        }
#endif

        serializer.EndSerialize();

        rPreprocessedData.persistentDataBuffer = serializer.GetPropertyStreamBuffer();

        // Serialize the vertex buffer.  If the mesh is a skinned mesh, the vertices will need to be converted to
        // and serialized as an array of SkinnedMeshVertex structs.
        serializer.BeginSerialize();

        if( boneCountActual == 0 )
        {
            for( size_t vertexIndex = 0; vertexIndex < vertexCountActual; ++vertexIndex )
            {
                vertices[ vertexIndex ].Serialize( serializer );
            }
        }
        else
        {
            HELIUM_ASSERT( vertexBlendData.GetSize() == vertexCountActual );

            SkinnedMeshVertex vertex;
            for( size_t vertexIndex = 0; vertexIndex < vertexCountActual; ++vertexIndex )
            {
                const StaticMeshVertex< 1 >& rStaticVertex = vertices[ vertexIndex ];
                const FbxSupport::BlendData& rBlendData = vertexBlendData[ vertexIndex ];

                MemoryCopy( vertex.position, rStaticVertex.position, sizeof( vertex.position ) );

                vertex.blendWeights[ 0 ] = static_cast< uint8_t >( Clamp(
                    rBlendData.weights[ 0 ] * 255.0f + 0.5f,
                    0.0f,
                    255.0f ) );
                vertex.blendWeights[ 1 ] = static_cast< uint8_t >( Clamp(
                    rBlendData.weights[ 1 ] * 255.0f + 0.5f,
                    0.0f,
                    255.0f ) );
                vertex.blendWeights[ 2 ] = static_cast< uint8_t >( Clamp(
                    rBlendData.weights[ 2 ] * 255.0f + 0.5f,
                    0.0f,
                    255.0f ) );
                vertex.blendWeights[ 3 ] = static_cast< uint8_t >( Clamp(
                    rBlendData.weights[ 3 ] * 255.0f + 0.5f,
                    0.0f,
                    255.0f ) );

                // Tweak the blend weights to ensure they still add up to 255 (1.0 when normalized by the GPU).
                size_t blendWeightTotal =
                    static_cast< size_t >( vertex.blendWeights[ 0 ] ) +
                    static_cast< size_t >( vertex.blendWeights[ 1 ] ) +
                    static_cast< size_t >( vertex.blendWeights[ 2 ] ) +
                    static_cast< size_t >( vertex.blendWeights[ 3 ] );
                if( blendWeightTotal != 0 && blendWeightTotal != 255 )
                {
                    if( blendWeightTotal > 255 )
                    {
                        // Total blend weight is too large, so decrease blend weights, starting from the lowest
                        // non-zero weight.
                        size_t weightAdjustIndex = 0;
                        do
                        {
                            do
                            {
                                weightAdjustIndex = ( weightAdjustIndex + 3 ) % 4;
                            } while( vertex.blendWeights[ weightAdjustIndex ] == 0 );

                            --vertex.blendWeights[ weightAdjustIndex ];
                            --blendWeightTotal;
                        } while( blendWeightTotal > 255 );
                    }
                    else
                    {
                        // Total blend weight is too small, so increase blend weights, starting from the highest
                        // non-zero blend weight.  Note that we should not have to check whether the blend weight is
                        // already at its max, as that would mean our total blend weight would have to already be at
                        // least 255.
                        size_t weightAdjustIndex = 3;
                        do
                        {
                            do
                            {
                                weightAdjustIndex = ( weightAdjustIndex + 1 ) % 4;
                            } while( vertex.blendWeights[ weightAdjustIndex ] == 0 );

                            HELIUM_ASSERT( vertex.blendWeights[ weightAdjustIndex ] != 255 );

                            ++vertex.blendWeights[ weightAdjustIndex ];
                            ++blendWeightTotal;
                        } while( blendWeightTotal < 255 );
                    }

                    HELIUM_ASSERT( blendWeightTotal == 255 );
                }

                MemoryCopy( vertex.blendIndices, rBlendData.indices, sizeof( vertex.blendIndices ) );

                MemoryCopy( vertex.normal, rStaticVertex.normal, sizeof( vertex.normal ) );
                MemoryCopy( vertex.tangent, rStaticVertex.tangent, sizeof( vertex.tangent ) );
                MemoryCopy( vertex.texCoords, rStaticVertex.texCoords[ 0 ], sizeof( vertex.texCoords ) );

                vertex.Serialize( serializer );
            }
        }

        serializer.EndSerialize();

        rSubDataBuffers[ 0 ] = serializer.GetPropertyStreamBuffer();

        // Serialize the index buffer.
        serializer.BeginSerialize();
        for( size_t indexIndex = 0; indexIndex < indexCount; ++indexIndex )
        {
            serializer << indices[ indexIndex ];
        }

        serializer.EndSerialize();

        rSubDataBuffers[ 1 ] = serializer.GetPropertyStreamBuffer();

        // Platform data is now loaded.
        rPreprocessedData.bLoaded = true;
    }

    return true;
}