//////////////////////////////////////////////////////////////////////////////
//
// Tools_PS2_DMAChainWriter.cpp
//
// Copyright (c) 2002-2004 Rebellion. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////////

// I N C L U D E S ///////////////////////////////////////////////////////////

#include "Core/Asura.h"

// This file's header.
#include "PS2/PS2Strip/Tools_PS2_DMAChainWriter.h"

// Stdlib independent.
#include <limits.h>

// Stdlib platform-specific.
#include <windows.h>

// Engine independent.
#include "Debugging/Asura_Assert.h"
#include "Core/Asura_Maths.h"
#include "Core/Asura_TextureManagement.h"

// Tools.
#include "PS2/PS2Strip/PS2Strip_Exporter.h"
#include "PS2/PS2Strip/PS2Strip_PS2Stuff.h"
#include "PS2/PS2Strip/PS2Strip_ToolsInterface.h"

// C O N S T A N T S /////////////////////////////////////////////////////////

static const u_int INITIAL_NUM_WORDS_FOR_INTERNAL_BFR = 8192; // 32Kb.
static const u_int MIN_WORDS_TO_GROW_INTERNAL_BFR_BY  = 256;
static const u_int DEFAULT_MAX_QWC_PER_TAG            = (1 << 16) - 1;

// C L A S S   I M P L E M E N T A T I O N S /////////////////////////////////

Tools_PS2_DMAChainWriter::Tools_PS2_DMAChainWriter( void )
{
    m_puInternalBfr = NULL;
}

Tools_PS2_DMAChainWriter::~Tools_PS2_DMAChainWriter( void )
{
    delete[] m_puInternalBfr;
}

//////////////////////////////////////////////////////////////////////////////
//
// FUNCTION     Tools_PS2_DMAChainWriter::AddDataWord().
// PARAMETERS   uDataWord - 
// RETURNS      True iff successful.
// DESCRIPTION  
// AUTHOR       MP - 18.01.2003.
//
//////////////////////////////////////////////////////////////////////////////
bool Tools_PS2_DMAChainWriter::AddDataWord( u_int uDataWord )
{
    Asura_Assert( m_uWordsUsed <= m_uCapacity, "" );
    if( m_uWordsUsed == m_uCapacity )
    {
        if( !GrowBuffer(1) ) { return false; }
    }

    m_puInternalBfr[m_uWordsUsed++] = uDataWord;
    return true;   
}
// End function Tools_PS2_DMAChainWriter::AddDataWord().

//////////////////////////////////////////////////////////////////////////////
//
// FUNCTION     Tools_PS2_DMAChainWriter::AddData().
// PARAMETERS   pvData          - Input data buffer.
//              uNumBytesOfData - Size of data to be copied in bytes. If this
//                                is not a multiple of 4 then the data will
//                                be padded (with 0s) to word alignment.
// RETURNS      True iff successful.
// DESCRIPTION  Adds the specified data into the internal data buffer.
// AUTHOR       MP - 18.01.2003.
//
//////////////////////////////////////////////////////////////////////////////
bool Tools_PS2_DMAChainWriter::AddData( const void* pvData, u_int uNumBytesOfData )
{
    Asura_Assert( m_uWordsUsed <= m_uCapacity, "" );
    
    u_int uNumWords     = ((uNumBytesOfData + 3) >> 2);
    u_int uReqdCapacity = m_uWordsUsed + uNumWords;

    // Grow buffer if necessary.
    if( uReqdCapacity > m_uCapacity )
    {
        if( !GrowBuffer(uReqdCapacity - m_uCapacity) ) { return false; }
    }
   
    // Ensure unused bytes in last word will be zeroed.
    m_puInternalBfr[uReqdCapacity - 1] = 0;

    // And just memcpy.
    memcpy( &m_puInternalBfr[m_uWordsUsed], pvData, uNumBytesOfData );
    m_uWordsUsed = uReqdCapacity;

    return true;
}
// End function Tools_PS2_DMAChainWriter::AddData().

//////////////////////////////////////////////////////////////////////////////
//
// FUNCTION     Tools_PS2_DMAChainWriter::AddVifCode().
// PARAMETERS   uVifCode - 
// RETURNS      True iff successful.
// DESCRIPTION  
// AUTHOR       MP - 08.01.2003.
//
//////////////////////////////////////////////////////////////////////////////
bool Tools_PS2_DMAChainWriter::AddVifCode( const u_int uVifCode )
{
    // Verify that this isn't a DIRECT or DIRECTHL VIF code. We don't support
    // them because they have specific alignment requirements. DIRECT's CMD
    // field is ?1010000 in binary and DIRECTHL is ?1010001; that's 0x50 and
    // 0x51 in hex respectively.
    Asura_Assert( (uVifCode & 0x7f000000) != 0x50000000, "Can't use DIRECT." );
    Asura_Assert( (uVifCode & 0x7f000000) != 0x51000000, "Can't use DIRECTHL." );

    // Might as well just pretend it's data internally.
    return AddDataWord( uVifCode );
}
// End function Tools_PS2_DMAChainWriter::AddVifCode().

//////////////////////////////////////////////////////////////////////////////
//
// FUNCTION     Tools_PS2_DMAChainWriter::BeginChain().
// PARAMETERS   None.
// RETURNS      True iff successful.
// DESCRIPTION  Starts a new DMA chain. Assumes you have already called
//              EndChain() if there has been a previous chain. 
// AUTHOR       MP - 07.01.2003.
//
//////////////////////////////////////////////////////////////////////////////
bool Tools_PS2_DMAChainWriter::BeginChain( void )
{
    Asura_Assert( m_puInternalBfr, "Either you forgot to call Init() or it failed." );
    Asura_Assert( !m_uWordsUsed, "Did you forget to call EndChain()?" );

    m_uTotalBytesWrittenForChain = 0;
    m_uPointForChainSplit        = 0xFFFFFFFFu;

    return true;
}
// End function Tools_PS2_DMAChainWriter::BeginChain().

//////////////////////////////////////////////////////////////////////////////
//
// FUNCTION     Tools_PS2_DMAChainWriter::EndChain().
// PARAMETERS   None.
// RETURNS      True iff successful.
// DESCRIPTION  Ends the current DMA chain and outputs it.
// AUTHOR       MP - 18.01.2003.
//
//////////////////////////////////////////////////////////////////////////////
bool Tools_PS2_DMAChainWriter::EndChain( void )
{
    Asura_Assert( m_uWordsUsed, "Attempting to end empty chain." );
    Asura_Assert( !m_uTotalBytesWrittenForChain, "Shouldn't have written anything for this chain yet." );

    const bool bSplit = ( 0xFFFFFFFFu != m_uPointForChainSplit );

    // Calculate total words used, including extra tag if splitting and
    // consequent padding.
    u_int uWordsUsedIncPadding;
    u_int uIdxOfPostSplitTagInFirstPiece;
    if( bSplit )
    {
        u_int uWordsBeforeSplit = m_uPointForChainSplit;

        // Will need to align to QW before split.
        u_int uAlignedWordsBeforeSplit = 2 + (((Asura_Maths::Max(2u, uWordsBeforeSplit) - 2) + 3) & ~3);
        Asura_Assert( !((2 + uAlignedWordsBeforeSplit) & 3), "" );
        uIdxOfPostSplitTagInFirstPiece = (2 + uAlignedWordsBeforeSplit) >> 2;

        // For simplicity, assume split occurs before the end of the first
        // piece and that there's space for more data after it. This all gets
        // rather more complicated otherwise and at the moment this
        // assumption is valid.
        Asura_Assert( uIdxOfPostSplitTagInFirstPiece < m_uMaxQWsPerPiece, "" );

        u_int uWordsAfterSplit = m_uWordsUsed - m_uPointForChainSplit;
        // We'll need a new tag at the beginning of the remainder of the
        // chain, though of course we can again put data in the spare words
        // of that tag.
        if( uWordsAfterSplit ) { uWordsAfterSplit += 2; }

        uWordsUsedIncPadding = uAlignedWordsBeforeSplit + uWordsAfterSplit;
    }
    else
    {
        uIdxOfPostSplitTagInFirstPiece = 0xFFFFFFFFu;
        uWordsUsedIncPadding = m_uWordsUsed;
    }

    m_uNumPiecesUsed = (uWordsUsedIncPadding + (m_uMaxDataWordsPerPiece - 1)) / m_uMaxDataWordsPerPiece;

    u_int uNumQWsInFirstPiece = ( m_uNumPiecesUsed > 1 ? m_uMaxQWsPerPiece : 1 + (((uWordsUsedIncPadding - 2) + 3) >> 2) );

    u_int uNumQWsInLastPiece;
    if( m_uNumPiecesUsed > 1 )
    {
        u_int uRemainder = uWordsUsedIncPadding % m_uMaxDataWordsPerPiece;
        if( uRemainder )
        {
            uNumQWsInLastPiece = 1 + (uRemainder > 2 ? (((Asura_Maths::Max(2u, uRemainder) - 2) + 3) >> 2) : 0);
        }
        else
        {
            // Last piece will be full then.
            uNumQWsInLastPiece = m_uMaxQWsPerPiece;
        }
    }
    else
    {
        uNumQWsInLastPiece = uNumQWsInFirstPiece;
    }

    u_int uBytesFromCallback = 0;
    if( m_pfnEndChainCallback )
    {
        EndChainInfo xEndChainInfo =
        {
            m_uNumPiecesUsed,               // uNumPiecesUsed
            uNumQWsInFirstPiece,            // uQWsInFirstPiece
            uNumQWsInLastPiece,             // uQWsInLastPiece
            uIdxOfPostSplitTagInFirstPiece, // uIdxOfPostSplitTagInFirstPiece
        };

        void* pvData;
        if( !m_pfnEndChainCallback(xEndChainInfo, &pvData, &uBytesFromCallback) )
        {
            return false;
        }

        if( pvData )
        {
            Asura_Assert( uBytesFromCallback > 0 && !(uBytesFromCallback & 15), "" );
            if( !WriteData(pvData, uBytesFromCallback) ) { return false; }
        }
    }

    const u_int auNOPs[3] = { PS2STRIP_VIF1_SET_NOP(false), PS2STRIP_VIF1_SET_NOP(false), PS2STRIP_VIF1_SET_NOP(false) };

    u_int auTag[2];
    u_int uQWC;
    u_int uNumPiecesOutput  = 0;
    const u_int* puNextWord = m_puInternalBfr;

    for( u_int uWordsLeft = m_uWordsUsed; uWordsLeft; )
    {
        Asura_Assert( uNumPiecesOutput < m_uNumPiecesUsed, "" );

        bool bFirstPiece     = !uNumPiecesOutput;
        bool bLastPiece      = ( uNumPiecesOutput + 1 == m_uNumPiecesUsed );
        bool bSplitThisPiece = ( bSplit && bFirstPiece );

        // Calculate num words to write (before split, if there's one in this
        // piece.)
        u_int uNumWordsToWrite = ( bSplitThisPiece ? m_uPointForChainSplit : Asura_Maths::Min(uWordsLeft, m_uMaxDataWordsPerPiece) );

        // Calculate QWC for tag.
        {
            u_int uWordsAfterTag = Asura_Maths::Max(2u, uNumWordsToWrite) - 2;
            uQWC = (uWordsAfterTag + 3) >> 2;
        }

        // Write DMA tag.
        {
            const u_int uTagType = ( bSplitThisPiece || bLastPiece ? PS2STRIP_DMA_TAG_TYPE_RET : m_uTagTypeExceptFinal );

            auTag[0] = (uTagType << PS2STRIP_DMA_TAG_ID_SHIFT) | uQWC;
            auTag[1] = 0; // If this is a NEXT, the address will be filled in at runtime. Otherwise this is ignored anyway.

            if( !WriteData(auTag, 8) ) { return false; }
        }

        // Write data (before split, if there's one in this piece.)
        if( uNumWordsToWrite )
        {
            if( !WriteData(puNextWord, (uNumWordsToWrite << 2)) ) { return false; }

            puNextWord += uNumWordsToWrite;
            uWordsLeft -= uNumWordsToWrite;
        }

        // Pad to QW alignment if necesary.
        {
            const u_int uWordsWrittenIncTag = 2 + uNumWordsToWrite;
            if( uWordsWrittenIncTag & 3 )
            {
                // Padding should only be required if we're splitting or if
                // we've finished now.
                Asura_Assert( bSplitThisPiece || (bLastPiece && !uWordsLeft), "" );

                const u_int uPad = 4 - (uWordsWrittenIncTag & 3);
                if( !WriteData(auNOPs, uPad << 2) ) { return false; }
            }
        }
        
        u_int uQWsWrittenForThisPiece = 1 + uQWC;
        
        // Deal with split, if one is required in this piece.
        if( bSplitThisPiece )
        {
            u_int uWordsUnusedInThisPiece = (uNumQWsInFirstPiece - uQWsWrittenForThisPiece) << 2;
            Asura_Assert( uWordsUnusedInThisPiece >= 4, "" );

            u_int uNumDataWordsToWrite = Asura_Maths::Min( uWordsUnusedInThisPiece - 2, uWordsLeft );

            // Write new DMA tag.
            {
                u_int uWordsAfterSecondTag = Asura_Maths::Max(2u, uNumDataWordsToWrite) - 2;
                uQWC = (uWordsAfterSecondTag + 3) >> 2;

                auTag[0] = ((bLastPiece ? PS2STRIP_DMA_TAG_TYPE_RET : m_uTagTypeExceptFinal) << PS2STRIP_DMA_TAG_ID_SHIFT) | uQWC;
                auTag[1] = 0; // If this is a NEXT, the address will be filled in at runtime. Otherwise this is ignored anyway.
                
                if( !WriteData(auTag, 8) ) { return false; }
            }
            
            // Rest of this piece's data.
            if( uNumDataWordsToWrite )
            {
                if( !WriteData(puNextWord, (uNumDataWordsToWrite << 2)) ) { return false; }

                puNextWord += uNumDataWordsToWrite;
                uWordsLeft -= uNumDataWordsToWrite;
            }

            // Pad to QW alignment if necesary.
            const u_int uWordsWrittenIncTag = 2 + uNumDataWordsToWrite;
            if( uWordsWrittenIncTag & 3 )
            {
                // Should only be padding required if we've finished now.
                Asura_Assert( bLastPiece && !uWordsLeft, "" );

                const u_int uPad = 4 - (uWordsWrittenIncTag & 3);
                if( !WriteData(auNOPs, (uPad << 2)) ) { return false; }
            }

            uQWsWrittenForThisPiece += 1 + uQWC;
        }

        Asura_Assert( uQWsWrittenForThisPiece <= m_uMaxQWsPerPiece, "" );
        if( bFirstPiece ) { Asura_Assert( uQWsWrittenForThisPiece == uNumQWsInFirstPiece, "" ); }
        if( bLastPiece  ) { Asura_Assert( uQWsWrittenForThisPiece == uNumQWsInLastPiece,  "" ); }

        ++uNumPiecesOutput;
    }

    Asura_Assert( uNumPiecesOutput == m_uNumPiecesUsed, "" );

    // If necessary, add padding to account for last piece being smaller than
    // maximum.
    if( m_bPadLastPiece && uNumQWsInLastPiece < m_uMaxQWsPerPiece )
    {
        if( !SkipBytes((m_uMaxQWsPerPiece - uNumQWsInLastPiece) << 4) ) { return false; }
    }

    // Reset word count now we're done with this chain.
    m_uWordsUsed = 0;

    return true;
}
// End function Tools_PS2_DMAChainWriter::EndChain().

//////////////////////////////////////////////////////////////////////////////
//
// FUNCTION     Tools_PS2_DMAChainWriter::GrowBuffer().
// PARAMETERS   uMinAdditionalWordsReqd - See DESCRIPTION.
// RETURNS      True iff successful.
// DESCRIPTION  Expands the internal data buffer by at least as many words
//              as specified.
// AUTHOR       MP - 18.01.2003.
//
//////////////////////////////////////////////////////////////////////////////
bool Tools_PS2_DMAChainWriter::GrowBuffer( u_int uMinAdditionalWordsReqd )
{
    u_int uNewCapacity = m_uCapacity + Asura_Maths::Max(uMinAdditionalWordsReqd, MIN_WORDS_TO_GROW_INTERNAL_BFR_BY);

    u_int* puNewBfr = new u_int[uNewCapacity];
    if( !puNewBfr )
    {
        PS2Strip::LogError( "ERROR: Out of memory!" WINDOWS_NEWLINE );
        return false;
    }

    Asura_Assert( !m_puInternalBfr == !m_uWordsUsed, "" );
    if( m_puInternalBfr )
    {
        memcpy( puNewBfr, m_puInternalBfr, (m_uWordsUsed << 2) );
    }

    delete[] m_puInternalBfr;

    m_puInternalBfr = puNewBfr;
    m_uCapacity = uNewCapacity;
    return true;
}
// End function Tools_PS2_DMAChainWriter::GrowBuffer().

//////////////////////////////////////////////////////////////////////////////
//
// FUNCTION     Tools_PS2_DMAChainWriter::Init().
// PARAMETERS   hFile - Output file.
// RETURNS      True iff successful.
// DESCRIPTION  Initialises the class.
// AUTHOR       MP - 07.01.2003.
//
//////////////////////////////////////////////////////////////////////////////
bool Tools_PS2_DMAChainWriter::Init( HANDLE hFile )
{
    Asura_Assert( INVALID_HANDLE_VALUE != hFile, "Caller should handle problems with opening files, etc." );

    // Allocate buffer.
    {
        Asura_Assert( !m_puInternalBfr, "Already Init()'d?" );

        m_uCapacity  = 0;
        m_uWordsUsed = 0;

        if( !GrowBuffer(INITIAL_NUM_WORDS_FOR_INTERNAL_BFR) )
        {
            return false;
        }
    }

    // Setup remaining state.
    m_hFile                      = hFile;
    m_uWordsUsed                 = 0;
    m_uTotalBytesWrittenForChain = 0;
    m_pfnEndChainCallback        = NULL;
    m_bPadLastPiece              = false;

    SetMaxQWCPerTag( DEFAULT_MAX_QWC_PER_TAG );
    SetTagTypeForNonFinalPieces( PS2STRIP_DMA_TAG_TYPE_CNT );

    return true;
}
// End function Tools_PS2_DMAChainWriter::Init().

//////////////////////////////////////////////////////////////////////////////
//
// FUNCTION     Tools_PS2_DMAChainWriter::SetMaxQWCPerTag().
// PARAMETERS   uMaxQWC - Maximum value for QWC field of each piece's tag.
// RETURNS      Nothing.
// DESCRIPTION  Sets the maximum QWC for each tag. The size of each piece
//              will then be limited to (1 + max QWC) QWs.
// AUTHOR       MP - 10.05.2004.
//
//////////////////////////////////////////////////////////////////////////////
void Tools_PS2_DMAChainWriter::SetMaxQWCPerTag( u_int uMaxQWC )
{
    Asura_Assert( uMaxQWC < (1 << 16), "" );
    m_uMaxQWsPerPiece = 1 + uMaxQWC;
    m_uMaxDataWordsPerPiece = 2 + (uMaxQWC << 2);
}
// End function Tools_PS2_DMAChainWriter::SetMaxQWCPerTag().

//////////////////////////////////////////////////////////////////////////////
//
// FUNCTION     Tools_PS2_DMAChainWriter::SetTagTypeForNonFinalPieces().
// PARAMETERS   uTagType - Tag type.
// RETURNS      Nothing.
// DESCRIPTION  Specifies the DMA tag type that should be used for all pieces
//              except the last (which will be a RET.) Currently only CNT and
//              NEXT can be specified.
// AUTHOR       MP - 10.05.2004.
//
//////////////////////////////////////////////////////////////////////////////
void Tools_PS2_DMAChainWriter::SetTagTypeForNonFinalPieces( u_int uTagType )
{
    Asura_Assert( PS2STRIP_DMA_TAG_TYPE_CNT == uTagType || PS2STRIP_DMA_TAG_TYPE_NEXT == uTagType, "" );
    m_uTagTypeExceptFinal = uTagType;
}
// End function Tools_PS2_DMAChainWriter::SetTagTypeForNonFinalPieces().

//////////////////////////////////////////////////////////////////////////////
//
// FUNCTION     Tools_PS2_DMAChainWriter::SetEndChainCallback().
// PARAMETERS   pfnCallback - Ptr to callback function.
// RETURNS      Nothing.
// DESCRIPTION  Sets callback function to be called before the chain is
//              written in EndChain(). This can be used for writing header
//              information, etc. The amount of data to be written must be a
//              multiple of 16 bytes.
// AUTHOR       MP - 10.05.2004.
//
//////////////////////////////////////////////////////////////////////////////
void Tools_PS2_DMAChainWriter::SetEndChainCallback( EndChainCallback pfnCallback )
{
    m_pfnEndChainCallback = pfnCallback;
}
// End function Tools_PS2_DMAChainWriter::SetEndChainCallback().

//////////////////////////////////////////////////////////////////////////////
//
// FUNCTION     Tools_PS2_DMAChainWriter::EnablePaddingOfLastPiece().
// PARAMETERS   bPad - Whether or not padding should be enabled.
// RETURNS      Nothing.
// DESCRIPTION  Sets whether or not the last piece of the chain should be
//              padded to the specified maximum length.
// AUTHOR       MP - 10.05.2004.
//
//////////////////////////////////////////////////////////////////////////////
void Tools_PS2_DMAChainWriter::EnablePaddingOfLastPiece( bool bPad )
{
    m_bPadLastPiece = bPad;
}
// End function Tools_PS2_DMAChainWriter::EnablePaddingOfLastPiece().

//////////////////////////////////////////////////////////////////////////////
//
// FUNCTION     Tools_PS2_DMAChainWriter::SkipBytes().
// PARAMETERS   uNumBytes - Number of bytes to skip.
// RETURNS      True iff successful.
// DESCRIPTION  Skips specified number of bytes in the file specified at
//              initialisation time.
// AUTHOR       MP - 12.11.2002.
//
//////////////////////////////////////////////////////////////////////////////
bool Tools_PS2_DMAChainWriter::SkipBytes( u_int uNumBytes )
{
    Asura_Assert( uNumBytes, "Pointless call." );

    if( INVALID_SET_FILE_POINTER == SetFilePointer(m_hFile, uNumBytes, NULL, FILE_CURRENT) )
    {
        PS2Strip::LogError( "ERROR: Couldn't skip bytes in file. Reason: %s" WINDOWS_NEWLINE, PS2Strip::GetLastSystemError() );
        return false;
    }

    if( !SetEndOfFile(m_hFile) )
    {
        PS2Strip::LogError( "ERROR: Failed to move EOF. Reason: %s" WINDOWS_NEWLINE, PS2Strip::GetLastSystemError() );
        return false;
    }

    m_uTotalBytesWrittenForChain += uNumBytes;
    return true;
}
// End function Tools_PS2_DMAChainWriter::SkipBytes().

//////////////////////////////////////////////////////////////////////////////
//
// FUNCTION     Tools_PS2_DMAChainWriter::WriteData().
// PARAMETERS   pvData                   - Ptr to the buffer containing the
//                                         data to be output.
//              uNumBytes                - Num bytes to be written out.
//              bUpdateTotalBytesWritten - Whether or not to update the total
//                                         number of bytes written out for
//                                         this DMA chain.
// RETURNS      True iff successful.
// DESCRIPTION  Writes specified number of bytes from specified buffer to the
//              file specified at initialisation time.
// AUTHOR       MP - 12.11.2002.
//
//////////////////////////////////////////////////////////////////////////////
bool Tools_PS2_DMAChainWriter::WriteData( const void* pvData, u_int uNumBytes, bool bUpdateTotalBytesWritten /*= true*/ )
{
    Asura_Assert( uNumBytes, "Pointless call." );
    Asura_Assert( pvData, "Must specify buffer." );

    // Output.
    {
        DWORD uNumBytesWritten;  
        BOOL bOK = WriteFile( m_hFile, pvData, uNumBytes, &uNumBytesWritten, NULL );
        if( !bOK || uNumBytesWritten != uNumBytes )
        {
            PS2Strip::LogError( "ERROR: Failed to write all bytes to file. Reason: %s" WINDOWS_NEWLINE, PS2Strip::GetLastSystemError() );
            return false;
        }
    }

    if( bUpdateTotalBytesWritten )
    {
        m_uTotalBytesWrittenForChain += uNumBytes;
    }

    // Done.
    return true;
}
// End function Tools_PS2_DMAChainWriter::WriteData().

//////////////////////////////////////////////////////////////////////////////
//
// FUNCTION     Tools_PS2_DMAChainWriter::MarkPointForChainSplit().
// PARAMETERS   None.
// RETURNS      Nothing.
// DESCRIPTION  Forces an additional split in the chain at the current point.
//              This will result in two subchains being output that need to
//              be CALLed in sequence.
// AUTHOR       MP - 16.12.2003.
//
//////////////////////////////////////////////////////////////////////////////
void Tools_PS2_DMAChainWriter::MarkPointForChainSplit( void )
{
    Asura_Assert( 0xFFFFFFFFu == m_uPointForChainSplit, "Only a single split is supported at the moment." );
    m_uPointForChainSplit = m_uWordsUsed;
}
// End function Tools_PS2_DMAChainWriter::MarkPointForChainSplit().


bool Tools_PS2_DMAChainWriter::EnsureMinimumCapacity( u_int uMinBytes )
{
    u_int uMinWords = (uMinBytes + 3) >> 2;
    if( uMinWords > m_uCapacity )
    {
        return GrowBuffer( uMinWords - m_uCapacity );
    }
    else
    {
        return true;
    }
}
