//=================================================================
//
// Copyright (c) Microsoft Corporation 1999 - All Rights Reserved.
//
//=================================================================

#include <windows.h>
#include <ddraw.h>
#include <d3d.h>
#include <string.h>
#include <assert.h>

#include "softm.h"
#include "globals.h"
#include "smddraw.h"
#include "smd3d.h"
#include "grftypes.h"
#include "color.h"
#include "raster.h"
#include "imath.h"      // for length() function
#include "grf_timers.h"

DBG_INIT();

#define ASSERT assert

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
unsigned int log2(unsigned int x)
{
    unsigned int    n;

    ASSERT(x > 0);

    for (n=0; x > 1; n++, x >>= 1);

    ASSERT(x == 1);

    return n;
}

//--------------------------------------------------------------------------------
//--------------------------------------------------------------------------------
RenderDevice::RenderDevice()
{

    m_dwStatus = 0;
    // force evaluation of render state first time this is drawn to
    m_pfnDrawTri = &RenderDevice::ReevaluateState;
    // force updating of cached global state, in case a render device is
    // destroyed and a new one immediately created at the same address; this
    // way we can still tell it's new
    g_grdRenderData.pRenderDevice = NULL;
}

RenderDevice::~RenderDevice()
{
}

//--------------------------------------------------------------------------------
//--------------------------------------------------------------------------------

// Function that needs to be called to initialized the render device's target surface (screen).

void RenderDevice::InitScreen(DWORD dwFormat, BYTE *pbScreen, DWORD cbPitch, DWORD cxWidth, DWORD cyHeight)
{

    m_dwScreenFormat = dwFormat;
    m_pbScreen       = pbScreen;
    m_cbScreenPitch  = cbPitch;
    m_cxScreenWidth  = cxWidth;
    m_cyScreenHeight = cyHeight;

    assert((dwFormat == 555) || (dwFormat == 565) || (dwFormat == 1555));

    switch (dwFormat)
    {
        case 555:
            m_cbPixel = 2;
            break;

        case 1555:
        case 565:
            m_cbPixel = 2;
            break;

        default:
            m_cbPixel = 0;
            break;
    }

    m_dwStatus |= GRF_SCREEN_INITIALIZED;
}

//--------------------------------------------------------------------------------
//--------------------------------------------------------------------------------

// Function that needs to be called to initialized the render device's z-buffer surface.
//   Note that the z-buffer surface must match the dimensions of the render surface
//   (or be at least as big).

void RenderDevice::InitZBuffer(DWORD dwFormat, BYTE *pbZBuffer, DWORD cbPitch)
{
    m_dwZBufferFormat = dwFormat;
    m_pbZBuffer       = pbZBuffer;
    m_cbZBufferPitch  = cbPitch;

    m_dwStatus |= GRF_ZBUFFER_INITIALIZED;
}

//--------------------------------------------------------------------------------
//--------------------------------------------------------------------------------

// Function that needs to be called to initialized the render device's active texture surface.

void RenderDevice::InitTexture(DWORD dwFormat, BYTE *pbTexture, DWORD cbPitch, DWORD cxWidth, DWORD cyHeight,
                               WORD * pwColorTable)
{

    assert(cbPitch <= 512);
    assert(cxWidth <= 256);
    assert(cyHeight <= 256);

    m_dwTextureFormat  = dwFormat;
    m_pwTexture        = (WORD *)pbTexture;
    m_cxTextureWidth   = cxWidth;
    m_TextureWidth     = (float)cxWidth;
    m_cyTextureHeight  = cyHeight;
    m_TextureHeight    = (float)cyHeight;

    // TODO: need to make the y shift and mask truly the y shift and mask and create a
    //       a different shift and mask value for the multiply by x shift and mask.

    m_cxTextureMask    = m_cxTextureWidth - 1;

    switch (cbPitch)
    {
    case 512:
        m_cyTextureShift = 8;
        break;
    case 256:
        m_cyTextureShift = 9;
        break;
    case 128:
        m_cyTextureShift = 10;
        break;
    case 64:
        m_cyTextureShift = 11;
        break;
    case 32:
        m_cyTextureShift = 12;
        break;
    case 16:
        m_cyTextureShift = 13;
        break;
    case 8:
        m_cyTextureShift = 14;
        break;
    case 4:
        m_cyTextureShift = 15;
        break;
    case 2:
        m_cyTextureShift = 16;
        break;
    default:
        // invalid texture pitch
        assert(0);
        m_cyTextureShift = 16 - log2((cbPitch >> 1));
        break;
    }

    assert((int)m_cxTextureWidth <= (0x10000 >> m_cyTextureShift));

    m_cyTextureMask = (m_cyTextureHeight - 1) << (16 - m_cyTextureShift);

    // If this device is the currently active device we need to update the
    //   global render data that is used by the scan line functions.

    if (g_grdRenderData.pRenderDevice == this)
    {
        g_grdChunkData.pwTexture        = m_pwTexture;
        g_grdChunkData.cxTextureMask    = m_cxTextureMask; 
        g_grdChunkData.cyTextureMask    = m_cyTextureMask; 
        g_grdChunkData.cyTextureShift   = m_cyTextureShift;
    }

    m_dwStatus |= GRF_TEXTURE_INITIALIZED;
}


//--------------------------------------------------------------------------------
//--------------------------------------------------------------------------------
void RenderDevice::SetRenderState(DWORD dwRenderState)
{

    START_TIME(grf_setrenderstate);

    // If the new render state is the same as the current one nothing needs to be done.
    if (dwRenderState == m_dwRenderState)
    {
        STOP_TIME(grf_setrenderstate);
        return;
    }

    // Save the render state that is being used now.
    m_dwRenderState = dwRenderState;

    STOP_TIME(grf_setrenderstate);
}


//------------------------------------------------------------------------------
//------------------------------------------------------------------------------

// Translate from an RGB color into the screen format.

DWORD RenderDevice::Color2Pixel(COLOR col)
{
    ASSERT(m_dwStatus & GRF_SCREEN_INITIALIZED);

    switch(m_dwScreenFormat)
    {
    case 555:
        return RGB555(R_888(col), G_888(col), B_888(col));

    case 565:
        return RGB565(R_888(col), G_888(col), B_888(col));

    default:
        return 0;
    }
}


//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
void RenderDevice::Line(unsigned int color, PIXEL x1, PIXEL y1, PIXEL x2, PIXEL y2)
{
    assert((x1 >= 0) && (x1 < (int)m_cxScreenWidth));
    assert((x2 >= 0) && (x2 < (int)m_cxScreenWidth));
    assert((y1 >= 0) && (y1 < (int)m_cyScreenHeight));
    assert((y2 >= 0) && (y2 < (int)m_cyScreenHeight));

    UpdateRenderData();

    if (m_pcd3dd->PixDouble())
    {
        g_grdChunkData.cPixDouble = 1;
        g_grdChunkData.cbScanDoubleOffset = m_cbScreenPitch;
        g_grdChunkData.cbScreenPitch = m_cbScreenPitch << 1;
    }
    else
    {
        g_grdChunkData.cPixDouble = 0;
        g_grdChunkData.cbScreenPitch = m_cbScreenPitch;
    }

    GRF_LineNoClip (Color2Pixel(color), x1, y1, x2, y2);
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
void RenderDevice::Point(unsigned int color, PIXEL x1, PIXEL y1)
{
    assert((x1 >= 0) && (x1 < (int)m_cxScreenWidth));
    assert((y1 >= 0) && (y1 < (int)m_cyScreenHeight));

    UpdateRenderData();

    if (m_pcd3dd->PixDouble())
    {
        g_grdChunkData.cPixDouble = 1;
        g_grdChunkData.cbScanDoubleOffset = m_cbScreenPitch;
        g_grdChunkData.cbScreenPitch = m_cbScreenPitch << 1;
    }
    else
    {
        g_grdChunkData.cPixDouble = 0;
        g_grdChunkData.cbScreenPitch = m_cbScreenPitch;
    }

    GRF_PointNoClip (Color2Pixel(color), x1, y1);
}
