/**************************************************************************
 *
 * Copyright (c) 2002 - 2011 by Computer Architecture Department,
 * Universitat Politecnica de Catalunya.
 * All rights reserved.
 *
 * The contents of this file may not be disclosed to third parties,
 * copied or duplicated in any form, in whole or in part, without the
 * prior permission of the authors, Computer Architecture Department
 * and Universitat Politecnica de Catalunya.
 *
 */



#include "AGPTraceDriver.h"
#include <cstdio>
#include <iostream>
#include <cstring>

using namespace gpu3d;
using namespace std;

AGPTraceDriver::AGPTraceDriver(gzifstream *traceFile, u32bit startFrame_, u32bit traceFirstFrame_) :
    startFrame(startFrame_), currentFrame(0), traceFirstFrame(traceFirstFrame_),
    startTransaction(0),
    fragmentProgramPC(0), fragmentProgramAddress(0), fragmentProgramSize(0),
    vertexProgramPC(0), vertexProgramAddress(0), vertexProgramSize(0),
    lastProgramUpload(NULL), agpTransCount(0), shaderProgramLoadPhase(0)
{
    agpTraceFile = traceFile;

    //  Clear the shader program data caches
    memset(fragProgramCache, 0, sizeof(fragProgramCache));
    memset(vertProgramCache, 0, sizeof(vertProgramCache));    

    //  Check if the driver must skip frames.
    if (startFrame != currentFrame)
        currentPhase = TP_PREINIT;
    else
        currentPhase = TP_SIMULATION;
        
}

int AGPTraceDriver::startTrace()
{
    // do not do anything :-)
    return 0; // return state of TraceDriver object ( 0 means ready )
}


// new version to allow hotStart with identical memory footprint
AGPTransaction* AGPTraceDriver::nextAGPTransaction()
{
    AGPTransaction* agpt;

    agpt = NULL;

    //  Check for the AGP trace file
    if (agpTraceFile != NULL)
    {
        //  Keep reading the AGP trace file until an AGP transaction can be sent to the simulator
        while (!agpTraceFile->eof() && agpt == NULL)
        {
            //  Check if the AGP transactions are being generated by the trace reader or
            //  read from the AGP transaction trace file.
            if ((currentPhase == TP_PREINIT) || (currentPhase == TP_SIMULATION))
            {
                //  Read the next AGP transaction from the input AGP trace file.
                agpt = new AGPTransaction(agpTraceFile);
                
                //  Check for end of file.
                if (agpTraceFile->eof())
                {
                    delete agpt;
                    agpt = NULL;
                    break;
                }
                    
                //agpt->dump();

                //  Update agp transaction counter.
                agpTransCount++;
            }
            
            //  Check in which processing phase is the AGP Trace Driver.
            switch(currentPhase)
            {
            
                case TP_PREINIT:
            
                    //  Check if skipping commands has to end.
                    if ((startTransaction > 0) && (agpTransCount == startTransaction))
                    {
                        //  Start loading the cached shader programs.
                        currentPhase = TP_LOAD_SHADERS;
                    }
                            
                    //  Determine what to do based on the kind of AGP transaction.
                    switch(agpt->getAGPCommand())
                    {
                        case AGP_WRITE:
                        
                            //  Convert all the upload operations into AGP_PRELOAD transactions.
                            agpt->forcePreload();
                            
                            //  Check if this upload operation may be a shader program upload.
                            if (agpt->getSize() <= (SHADERINSTRUCTIONSIZE * MAXSHADERINSTRUCTIONS))
                            {
                                lastProgramUpload = new ProgramUpload(agpt->getAddress(),
                                                                      agpt->getSize(),
                                                                      agpt->getData(),
                                                                      agpt->getMD(),
                                                                      agpTransCount);
                                

                                //  Search the address in the list of program uploads
                                ProgramUploadsIterator upIt = programUploads.find(agpt->getAddress());
                                
                                //  Check if the upload was found
                                if (upIt != programUploads.end())
                                {
                                    //  Update data.
                                    (upIt->second)->updateData(lastProgramUpload->data, lastProgramUpload->size,
                                        lastProgramUpload->agpTransID);
                                }
                                else
                                {
                                    //  Insert the new program upload.
                                    programUploads.insert(make_pair(lastProgramUpload->address, lastProgramUpload)); 
                                }
                            }
                            
                            break;
                            
                        case AGP_PRELOAD:
                        
                            //  Check if this upload operation may be a shader program upload.
                            if (agpt->getSize() <= (SHADERINSTRUCTIONSIZE * MAXSHADERINSTRUCTIONS))
                            {
                                //delete lastProgramUpload;
                                
                                lastProgramUpload = new ProgramUpload(agpt->getAddress(),
                                                                      agpt->getSize(),
                                                                      agpt->getData(),
                                                                      agpt->getMD(),
                                                                      agpTransCount);
                                                                      
                                //  Search the address in the list of program uploads
                                ProgramUploadsIterator upIt = programUploads.find(agpt->getAddress());
                                
                                //  Check if the upload was found
                                if (upIt != programUploads.end())
                                {
                                    //  Update data.
                                    (upIt->second)->updateData(lastProgramUpload->data, lastProgramUpload->size,
                                        lastProgramUpload->agpTransID);
                                }
                                else
                                {
                                    //  Insert the new program upload.
                                    programUploads.insert(make_pair(lastProgramUpload->address, lastProgramUpload)); 
                                }
                            }

                            break;
                            
                        case AGP_COMMAND:
                        
                            //  Determine how to skip the AGP transaction based on the AGP command.
                            switch(agpt->getGPUCommand())
                            {
                                case GPU_DRAW:
                                
                                    //  Ignore all the GPU_DRAW transactions.
                                    delete agpt;
                                    agpt = NULL;
                                    
                                    break;
                                    
                                case GPU_SWAPBUFFERS:
                              
                                    cout << "Frame " << (traceFirstFrame + currentFrame) << " Skipped" << endl;
                                    
                                    //  Ignore the SWAPBUFFER command from a frame being skipped.
                                    delete agpt;
                                    agpt = NULL;
                                    
                                    //  Update frame counter.  
                                    currentFrame++;
                                    
                                    //  Check if all the frames were skipped.
                                    if (currentFrame == startFrame)
                                    {
                                        cout << "TraceDriverAGP::nextAGPTransaction() -> Disabling preload..." << endl;
                                            
                                        //  Check if skipping commands has to end.
                                        if (startTransaction == 0)
                                        {
                                            //  Start loading the cached shader programs.
                                            currentPhase = TP_LOAD_SHADERS;
                                        }
                                    }
                                    
                                    
                                    break;

                                case GPU_LOAD_VERTEX_PROGRAM:
                                
                                    
                                    //  Check the address of the program load with the last upload address.
                                    if ((lastProgramUpload != NULL) && (lastProgramUpload->address == vertexProgramAddress))
                                    {
                                        //  Search the address in the list of program uploads
                                        ProgramUploadsIterator upIt = programUploads.find(vertexProgramAddress);
                                        
                                        //  Check if the upload was found
                                        if (upIt != programUploads.end())
                                        {
                                            //  Update data.
                                            //(upIt->second)->updateData(lastProgramUpload->data, lastProgramUpload->size,
                                            //    lastProgramUpload->agpTransID);
                                            
                                            //  Update the shader program cache.
                                            memcpy(&vertProgramCache[vertexProgramPC * SHADERINSTRUCTIONSIZE], lastProgramUpload->data, vertexProgramSize);
                                        }
                                        
                                        lastProgramUpload = NULL;
                                    }
                                    else
                                    {
                                        //  Search the upload address in the list of uploaded programas.
                                        
                                        //  Search the address in the list of program uploads
                                        ProgramUploadsIterator upIt = programUploads.find(vertexProgramAddress);
                                        
                                        //  Check if the upload was found
                                        if (upIt != programUploads.end())
                                        {
                                            if (upIt->second->size < vertexProgramSize)
                                            {
                                                printf("(Vertex) PC %x Address %x Size %d Upload Size %d\n", vertexProgramPC,
                                                    vertexProgramAddress, vertexProgramSize, upIt->second->size);
                                                panic("AGPTraceDriver", "nextAGPTransaction", "Upload program data smaller than required by load command.");
                                            }
                                            
                                            //  Update the shader program cache.
                                            memcpy(&vertProgramCache[vertexProgramPC * SHADERINSTRUCTIONSIZE], upIt->second->data, vertexProgramSize);
                                        }
                                        else
                                        {
                                            panic("AGPTraceDriver", "nextAGPTransaction", "Data for shader program load operations was not found.");
                                        }
                                        
                                    }
                                    
                                    //  Ignoring all commands;
                                    delete agpt;
                                    agpt = NULL;
                                    
                                    break;
                                
                                case GPU_LOAD_FRAGMENT_PROGRAM:
                                
                                    //  Check the address of the program load with the last upload address.
                                    if ((lastProgramUpload != NULL) && (lastProgramUpload->address == fragmentProgramAddress))
                                    {
                                        //  Search the address in the list of program uploads
                                        ProgramUploadsIterator upIt = programUploads.find(fragmentProgramAddress);
                                        
                                        //  Check if the upload was found
                                        if (upIt != programUploads.end())
                                        {
                                            //  Update data.
                                            //(upIt->second)->updateData(lastProgramUpload->data, lastProgramUpload->size,
                                            //    lastProgramUpload->agpTransID);
                                            
                                            //  Update the shader program cache.
                                            memcpy(&fragProgramCache[fragmentProgramPC * SHADERINSTRUCTIONSIZE], lastProgramUpload->data, fragmentProgramSize);
                                        }
                                    }
                                    else
                                    {
                                        //  Search the upload address in the list of uploaded programas.
                                        
                                        //  Search the address in the list of program uploads
                                        ProgramUploadsIterator upIt = programUploads.find(fragmentProgramAddress);
                                        
                                        //  Check if the upload was found
                                        if (upIt != programUploads.end())
                                        {
                                            if (upIt->second->size < fragmentProgramSize)
                                            {
                                                printf("(Fragment) PC %x Address %x Size %d Upload Size %d\n", fragmentProgramPC,
                                                    fragmentProgramAddress, fragmentProgramSize, upIt->second->size);
                                                panic("AGPTraceDriver", "nextAGPTransaction", "Upload program data smaller than required by load command.");
                                            }
                                            //  Update the shader program cache.
                                            memcpy(&fragProgramCache[fragmentProgramPC * SHADERINSTRUCTIONSIZE], upIt->second->data, fragmentProgramSize);
                                        }
                                        else
                                        {
                                            panic("AGPTraceDriver", "nextAGPTransaction", "Data for shader program load operations was not found.");
                                        }
                                        
                                    }
                                    
                                    //  Ignoring all commands;
                                    delete agpt;
                                    agpt = NULL;
                                    
                                    break;
                                    
                                case GPU_BLIT:
                                
                                    //  Ignore all the GPU_BLIT transactions.
                                    delete agpt;
                                    agpt = NULL;
                                    
                                    break;                                
                            }
                            
                            break;
                            
                        case AGP_REG_WRITE:
                        
                            //  Update register cache with the register write.
                            registerCache.writeRegister(agpt->getGPURegister(),
                                                        agpt->getGPUSubRegister(),
                                                        agpt->getGPURegData(),
                                                        agpt->getMD());
                            
                            //  Check for shader program related registers.
                            switch(agpt->getGPURegister())
                            {
                                case GPU_FRAGMENT_PROGRAM:
                                
                                    fragmentProgramAddress = agpt->getGPURegData().uintVal;
                                    
                                    break;
                                    
                                case GPU_FRAGMENT_PROGRAM_PC:

                                    fragmentProgramPC = agpt->getGPURegData().uintVal;
                                    
                                    break;
                                    
                                case GPU_FRAGMENT_PROGRAM_SIZE:
                                
                                    fragmentProgramSize = agpt->getGPURegData().uintVal;
                                    
                                    break;

                                case GPU_VERTEX_PROGRAM:
                                
                                    vertexProgramAddress = agpt->getGPURegData().uintVal;
                                    
                                    break;

                                case GPU_VERTEX_PROGRAM_PC:
                                
                                    vertexProgramPC = agpt->getGPURegData().uintVal;
                                    
                                    break;
                                    
                                case GPU_VERTEX_PROGRAM_SIZE:
                                    
                                    vertexProgramSize = agpt->getGPURegData().uintVal;
                                    
                                    break;

                                default:
                                    break;
                                 
                            }
                        
                            //  Ignore all the register writes for frames being skipped.
                            delete agpt;
                            agpt = NULL;
                            
                            break;
                            
                        case AGP_EVENT:
                        
                            //  Ignore events in the initialization phase.
                            delete agpt;
                            agpt = NULL;
                            
                            break;
                            
                    }            
                    
                    break;
                    
                case TP_LOAD_SHADERS:
                    {
                        GPURegData data;
                        
                        switch(shaderProgramLoadPhase)
                        {
                            case 0:
                            
                                //  Upload the fragment shader cache into the GPU memory.
                                agpt = new AGPTransaction(0, sizeof(fragProgramCache), fragProgramCache, 0);
                                
                                break;
                                
                            case 1:
                                              
                                //  Set fragment program PC register.
                                data.uintVal = 0;
                                agpt = new AGPTransaction(GPU_FRAGMENT_PROGRAM_PC, 0, data, 0);
                                
                                break;    
                                
                            case 2:
                                              
                                //  Set fragment program Address register.
                                data.uintVal = 0;
                                agpt = new AGPTransaction(GPU_FRAGMENT_PROGRAM, 0, data, 0);
                                
                                break;    

                            case 3:
                                              
                                //  Set fragment program size register.
                                data.uintVal = sizeof(fragProgramCache);
                                agpt = new AGPTransaction(GPU_FRAGMENT_PROGRAM_SIZE, 0, data, 0);
                                
                                break;    

                            case 4:
                                
                                //  Load fragment programs.
                                agpt = new AGPTransaction(GPU_LOAD_FRAGMENT_PROGRAM);
                                
                                break;
                                
                            case 5:
                            
                                //  Upload the vertex shader cache into the GPU memory.
                                agpt = new AGPTransaction(0, sizeof(vertProgramCache), vertProgramCache, 0);
                                
                                break;
                                
                            case 6:
                                              
                                //  Set vertex program PC register.
                                data.uintVal = 0;
                                agpt = new AGPTransaction(GPU_VERTEX_PROGRAM_PC, 0, data, 0);
                                
                                break;    
                                
                            case 7:
                                              
                                //  Set vertex program Address register.
                                data.uintVal = 0;
                                agpt = new AGPTransaction(GPU_VERTEX_PROGRAM, 0, data, 0);
                                
                                break;    

                            case 8:
                                              
                                //  Set vertex program size register.
                                data.uintVal = sizeof(vertProgramCache);
                                agpt = new AGPTransaction(GPU_VERTEX_PROGRAM_SIZE, 0, data, 0);
                                
                                break;    

                            case 9:
                                
                                //  Load fragment programs.
                                agpt = new AGPTransaction(GPU_LOAD_VERTEX_PROGRAM);
                                
                                break;

                            case 10:
                            
                                //  Destroy program uploads list.
                                ProgramUploadsIterator it = programUploads.begin();
                                
                                while(it != programUploads.end())
                                {
                                    delete it->second;
                                    it++;
                                }
                                
                                programUploads.clear();
                                
                                //  Start to load the cached register writes into the simulator.
                                currentPhase = TP_LOAD_REGS;
                            
                                break;
                        }
                    
                        shaderProgramLoadPhase++;
                        
                    }
                    break;
                

                case TP_LOAD_REGS:
                    {
                        //  Send all the cached register writes to the GPU.
                        GPURegister gpuReg;
                        u32bit index;
                        GPURegData data;
                        u32bit md;
                        bool found;
                        
                        //  Retrieve the next register write from the buffer.
                        found = registerCache.flushNextRegister(gpuReg, index, data, md);
                        
                        //  Check if a register write was found in the buffer.
                        if (found)
                        {
                            //  Create a new AGP transaction for writing the register into the GPU.
                            agpt = new AGPTransaction(gpuReg, index, data, md);
                        }
                        else
                        {
                            //  Start the clear z stencil buffer phase.
                            currentPhase = TP_CLEARZSTBUFFER;
                        }
                    }                    
                    break;

                case TP_CLEARZSTBUFFER:
                
                    agpt = new AGPTransaction(GPU_CLEARZSTENCILBUFFER);
                    
                    //  Start the clear color buffer phase.
                    currentPhase = TP_CLEARCOLORBUFFER;
                    
                    break;                
            
                case TP_CLEARCOLORBUFFER:

                    agpt = new AGPTransaction(GPU_CLEARCOLORBUFFER);
                    
                    //  Start the end of initialization phase.
                    currentPhase = TP_END_INIT;
                    
                    break;
                    
                case TP_END_INIT:
                
                    //  Send AGP transaction marking the end of the initialization phase
                    agpt = new AGPTransaction();
                    
                    //  Start simulation phase.
                    currentPhase = TP_SIM_START_EVENT;
                    
                    break;
                    
                case TP_SIM_START_EVENT:
                
                    //  Reset the end of frame event register.
                    agpt = new AGPTransaction(GPU_END_OF_FRAME_EVENT, "");
                    
                    //  Start simulation phase.
                    currentPhase = TP_SIMULATION;
                    
                    break;                    
                    
                case TP_SIMULATION:
                                    
                    //  Check if the current transaction is an GPU_SWAPBUFFERS command.
                    if ((agpt != NULL) && (agpt->getAGPCommand() == AGP_COMMAND) && (agpt->getGPUCommand() == GPU_SWAPBUFFERS))
                    {
                        cout << "Dumping frame " << (traceFirstFrame + currentFrame) << endl;
                        currentFrame++;
                    }
                    
                    break;
            }
        }            
    }
    else
    {
        panic("AGPTraceDriver", "nextAGPTransaction", "No AGP trace file available.");
    }
    
    return agpt;
}

//  Saves in a file the current frame position.
void AGPTraceDriver::saveTracePosition(fstream *f)
{
    f->write((char *) &currentFrame, sizeof(currentFrame));
    f->write((char *) &agpTransCount, sizeof(agpTransCount));
}

//  Loads from a file the start frame position.
void AGPTraceDriver::loadStartTracePosition(fstream *f)
{
    f->read((char *) &startFrame, sizeof(startFrame));
    f->read((char *) &startTransaction, sizeof(startTransaction));

    //  Check if the driver must skip frames.
    if ((startFrame != currentFrame) || (startTransaction > 0))
        currentPhase = TP_PREINIT;
}

//  Return the current position inside the trace file.
u32bit AGPTraceDriver::getTracePosition()
{
    return agpTransCount;
}

