#include "D3DDevice.h"

D3DDevice::D3DPerfMonitor::D3DPerfMonitor(ID3D10Device1* device)
: _device(device), _query(NULL), _valid(false), _frequency(0)
{
    D3D10_QUERY_DESC queryDesc;
    queryDesc.Query = D3D10_QUERY_TIMESTAMP_DISJOINT;
    queryDesc.MiscFlags = 0;

    _device->CreateQuery(&queryDesc, &_query);
}

D3DDevice::D3DPerfMonitor::~D3DPerfMonitor()
{
    _query->Release();
}

void D3DDevice::D3DPerfMonitor::begin()
{
    _query->Begin();
}

void D3DDevice::D3DPerfMonitor::end()
{
    _query->End();

    D3D10_QUERY_DATA_TIMESTAMP_DISJOINT data;
    while (_query->GetData(&data, _query->GetDataSize(), 0) != S_OK);

    _valid = (data.Disjoint == FALSE);
    _frequency = data.Frequency;
}

bool D3DDevice::D3DPerfMonitor::isValid()
{
    return _valid;
}

uint64 D3DDevice::D3DPerfMonitor::getFrequency()
{
    return _frequency;
}


D3DDevice::D3DPerfCounter::D3DPerfCounter(ID3D10Device1* device)
: _device(device), _query(NULL), _tickCount(0)
{
    D3D10_QUERY_DESC queryDesc;
    queryDesc.Query = D3D10_QUERY_TIMESTAMP;
    queryDesc.MiscFlags = 0;

    _device->CreateQuery(&queryDesc, &_query);
}

D3DDevice::D3DPerfCounter::~D3DPerfCounter()
{
    _query->Release();
}

void D3DDevice::D3DPerfCounter::mark()
{
    _query->End();
}

uint64 D3DDevice::D3DPerfCounter::recall()
{
    _tickCount = 0;
    while (_query->GetData(&_tickCount, _query->GetDataSize(), 0) != S_OK);
    return _tickCount;
}


void convertRgbHexToRgbFloat(uint32 rgb, float& r, float& g, float& b)
{
    uint32 rMask = 0x00ff0000;
    uint32 gMask = 0x0000ff00;
    uint32 bMask = 0x000000ff;

    r = ((rgb & rMask) >> 16) / 255.0f;
    g = ((rgb & gMask) >>  8) / 255.0f;
    b =  (rgb & bMask)        / 255.0f;
}


/*static*/ const Vertex D3DDevice::_spriteVertexArray[] =
{
    { D3DXVECTOR3( 0.0f, 0.0f, 0.0f ), D3DXVECTOR3( 0.0f, 0.0f, 0.0f ), D3DXVECTOR3( 0.0f, 0.0f, 0.0f ), D3DXVECTOR2( 0.0f, 0.0f ) },
    { D3DXVECTOR3( 1.0f, 0.0f, 0.0f ), D3DXVECTOR3( 0.0f, 0.0f, 0.0f ), D3DXVECTOR3( 0.0f, 0.0f, 0.0f ), D3DXVECTOR2( 1.0f, 0.0f ) },
    { D3DXVECTOR3( 1.0f, 1.0f, 0.0f ), D3DXVECTOR3( 0.0f, 0.0f, 0.0f ), D3DXVECTOR3( 0.0f, 0.0f, 0.0f ), D3DXVECTOR2( 1.0f, 1.0f ) },
    { D3DXVECTOR3( 0.0f, 1.0f, 0.0f ), D3DXVECTOR3( 0.0f, 0.0f, 0.0f ), D3DXVECTOR3( 0.0f, 0.0f, 0.0f ), D3DXVECTOR2( 0.0f, 1.0f ) }
};

/*static*/ const uint32 D3DDevice::_spriteIndexArray[] =
{
    //3, 1, 0,
    //2, 1, 3
    0, 1, 2,
    2, 3, 0
};


D3DDevice::D3DDevice() :
    RenderDevice(),
    _dxgiFactory(NULL), _dxgiDevice(NULL), _adapter(NULL),
    _swapChain(NULL), _device(NULL),
    _renderTarget(NULL), _renderTargetView(NULL),
    _depthStencil(NULL), _depthStencilView(NULL),
    _spriteVertexBuffer(NULL), _spriteIndexBuffer(NULL),
    _renderTargetCount(0), _depthBufferView(0)
{
    ZeroMemory(&_adapterDesc, sizeof(_adapterDesc));
    ZeroMemory(&_deviceInfo, 1024 * sizeof(wchar_t));
    ZeroMemory(_viewList, 16 * sizeof(ID3D10RenderTargetView*));

    wcscpy_s(_deviceInfo, 1024, L"Direct3D Device (unknown feature set)\n(unknown hardware device)\nDedicated video memory: (unknown)\nShared system memory: (unknown)\n");
}

D3DDevice::~D3DDevice()
{
}

const wchar_t* D3DDevice::getDeviceName()
{
    return ::getDeviceName();
}

const wchar_t* D3DDevice::getDeviceInfo()
{
    return _deviceInfo;
}

RenderDevice::PerfMonitor* D3DDevice::createPerfMonitor()
{
    return new D3DPerfMonitor(_device);
}

RenderDevice::PerfCounter* D3DDevice::createPerfCounter()
{
    return new D3DPerfCounter(_device);
}

Shader* D3DDevice::createShader(const wchar_t* fileName)
{
    stdext::hash_map<std::wstring, HlslShader*>::iterator iShader = _shaders.find(fileName);

    if (iShader == _shaders.end()) {
        ID3D10Effect*           effect = NULL;
        ID3D10EffectTechnique*  technique = NULL;
        ID3D10InputLayout*      layout = NULL;

        HRESULT hr = E_FAIL;

        hr = D3DX10CreateEffectFromFile(fileName, NULL, NULL, "fx_4_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, _device, NULL, NULL, &effect, NULL, NULL);
        if (FAILED(hr)) {
            MessageBox(NULL, (std::wstring(L"Unable to load '") + std::wstring(fileName) + std::wstring(L"' effect file.")).c_str(), L"Error", MB_OK | MB_ICONERROR);
            return NULL;
        }

        technique = effect->GetTechniqueByName("Render");

        if ((technique == NULL) || (!technique->IsValid())) {
            MessageBox(NULL, (std::wstring(L"Unable to find technique 'Render' in '") + std::wstring(fileName) + std::wstring(L"' effect file.")).c_str(), L"Error", MB_OK | MB_ICONERROR);
            return NULL;
        }

        D3D10_INPUT_ELEMENT_DESC inputLayout[] = {
            { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D10_APPEND_ALIGNED_ELEMENT, D3D10_INPUT_PER_VERTEX_DATA, 0 },
            { "NORMAL",   0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D10_APPEND_ALIGNED_ELEMENT, D3D10_INPUT_PER_VERTEX_DATA, 0 },
            { "TANGENT",  0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D10_APPEND_ALIGNED_ELEMENT, D3D10_INPUT_PER_VERTEX_DATA, 0 },
            { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT,    0, D3D10_APPEND_ALIGNED_ELEMENT, D3D10_INPUT_PER_VERTEX_DATA, 0 }
        };
       
        D3D10_PASS_DESC passDesc;
        technique->GetPassByIndex(0)->GetDesc(&passDesc);

        hr = _device->CreateInputLayout(
            inputLayout,
            ARRAYSIZE(inputLayout),
            passDesc.pIAInputSignature,
            passDesc.IAInputSignatureSize,
            &layout
            );

        if (FAILED(hr)) {
            MessageBox(NULL, (std::wstring(L"Unable to create input layout for technique 'Render' in '") + std::wstring(fileName) + std::wstring(L"' effect file.")).c_str(), L"Error", MB_OK | MB_ICONERROR);
            return NULL;
        }

        HlslShader* shader = new HlslShader(effect, technique, layout);

        shader->g_WorldMatrix = shader->createMatrixVariable("g_WorldMatrix");
        shader->g_ViewMatrix = shader->createMatrixVariable("g_ViewMatrix");
        shader->g_ProjectionMatrix = shader->createMatrixVariable("g_ProjectionMatrix");
        shader->g_NormalMatrix = shader->createMatrixVariable("g_NormalMatrix");

        shader->g_WorldMatrixInverse = shader->createMatrixVariable("g_WorldMatrixInverse");
        shader->g_ViewMatrixInverse = shader->createMatrixVariable("g_ViewMatrixInverse");
        shader->g_ProjectionMatrixInverse = shader->createMatrixVariable("g_ProjectionMatrixInverse");

        shader->g_Eye = shader->createVectorVariable("g_Eye");
        shader->g_Center = shader->createVectorVariable("g_Center");
        shader->g_Up = shader->createVectorVariable("g_Up");

        shader->g_Viewport = shader->createVectorVariable("g_Viewport");

        shader->g_Texture = shader->createShaderResourceVariable("g_Texture");
        shader->g_NormalMap = shader->createShaderResourceVariable("g_NormalMap");

        shader->g_MRT[ 0] = shader->createShaderResourceVariable("g_MRT0");
        shader->g_MRT[ 1] = shader->createShaderResourceVariable("g_MRT1");
        shader->g_MRT[ 2] = shader->createShaderResourceVariable("g_MRT2");
        shader->g_MRT[ 3] = shader->createShaderResourceVariable("g_MRT3");
        shader->g_MRT[ 4] = shader->createShaderResourceVariable("g_MRT4");
        shader->g_MRT[ 5] = shader->createShaderResourceVariable("g_MRT5");
        shader->g_MRT[ 6] = shader->createShaderResourceVariable("g_MRT6");
        shader->g_MRT[ 7] = shader->createShaderResourceVariable("g_MRT7");
        shader->g_MRT[ 8] = shader->createShaderResourceVariable("g_MRT8");
        shader->g_MRT[ 9] = shader->createShaderResourceVariable("g_MRT9");
        shader->g_MRT[10] = shader->createShaderResourceVariable("g_MRT10");
        shader->g_MRT[11] = shader->createShaderResourceVariable("g_MRT11");
        shader->g_MRT[12] = shader->createShaderResourceVariable("g_MRT12");
        shader->g_MRT[13] = shader->createShaderResourceVariable("g_MRT13");
        shader->g_MRT[14] = shader->createShaderResourceVariable("g_MRT14");
        shader->g_MRT[15] = shader->createShaderResourceVariable("g_MRT15");

        shader->g_DiffuseMaterial = shader->createVectorVariable("g_DiffuseMaterial");
        shader->g_SpecularMaterial = shader->createVectorVariable("g_SpecularMaterial");

        _shaders.insert(std::pair<std::wstring, HlslShader*>(fileName, shader));

        return shader;
    }

    else {
        return iShader->second;
    }
}


RenderTarget* D3DDevice::createRenderTarget(uint32 width, uint32 height, uint32 sampleCount)
{
    D3DRenderTarget* renderTarget = new D3DRenderTarget(width, height, sampleCount, _device);
    return renderTarget;
}

DepthStencilBuffer* D3DDevice::createDepthStencilBuffer(uint32 width, uint32 height, uint32 sampleCount)
{
    D3DDepthStencilBuffer* depthStencilBuffer = new D3DDepthStencilBuffer(width, height, sampleCount, _device);
    return depthStencilBuffer;
}



HRESULT D3DDevice::create()
{
    HRESULT hr = S_OK;

    hr = CoInitialize(NULL);
    if (FAILED(hr))
        return hr;

    hr = createDeviceIndependentResources();
    if (FAILED(hr))
        return hr;

    hr = createDeviceResources();

    return hr;
}


/******************************************************************
*                                                                 *
*  D3DDevice::createDeviceIndependentResources                    *
*                                                                 *
*  This method is used to create resources which are not bound    *
*  to any device. Their lifetime effectively extends for the      *
*  duration of the app. These resources include the D2D,          *
*  DWrite, and WIC factories.                                     *
*                                                                 *
******************************************************************/

HRESULT D3DDevice::createDeviceIndependentResources()
{
    HRESULT hr = S_OK;
    return hr;
}

/*******************************************************************
*                                                                  *
*  D3DDevice::createDeviceResources                                *
*                                                                  *
*  This method is responsible for creating the D3D device and      *
*  all corresponding device-bound resources that are required to   *
*  render.                                                         *
*                                                                  *
*  Of the objects created in this method, 2 are interesting:       *
*                                                                  *
*     1. D3D Device (_device)                                      *
*                                                                  *
*     2. DXGI Swap Chain (_swapChain) -- Mapped to current         *
*           window (_hWnd)                                         *
*                                                                  *
*******************************************************************/
HRESULT D3DDevice::createDeviceResources()
{
    HRESULT hr = S_OK;

    UINT nWidth =  _dmScreenSettings.dmPelsWidth;
    UINT nHeight = _dmScreenSettings.dmPelsHeight;

    // If we don't have a device, need to create one now and all
    // accompanying D3D resources.
    if (!_device) {
        UINT nDeviceFlags = D3D10_CREATE_DEVICE_BGRA_SUPPORT;
        #ifdef _DEBUG
            nDeviceFlags |= D3D10_CREATE_DEVICE_DEBUG;
        #endif

        D3D10_FEATURE_LEVEL1 levelAttempts[] = {
            D3D10_FEATURE_LEVEL_10_1,
            D3D10_FEATURE_LEVEL_10_0
        };

        for (UINT level = 0; level < ARRAYSIZE(levelAttempts); ++level) {
            _device = NULL;
            hr = D3D10CreateDevice1(
                NULL,
                D3D10_DRIVER_TYPE_HARDWARE,
                NULL,
                nDeviceFlags,
                levelAttempts[level],
                D3D10_1_SDK_VERSION,
                &_device
                );

            if (SUCCEEDED(hr))
                break;
        }

        if (SUCCEEDED(hr)) {
            hr = _device->QueryInterface(&_dxgiDevice);
        }
        if (SUCCEEDED(hr)) {
            hr = _dxgiDevice->GetAdapter(&_adapter);
        }
        if (SUCCEEDED(hr)) {
            hr = _adapter->GetParent(IID_PPV_ARGS(&_dxgiFactory));
        }
        if (SUCCEEDED(hr)) {
            DXGI_SWAP_CHAIN_DESC swapDesc;
            ::ZeroMemory(&swapDesc, sizeof(swapDesc));

            swapDesc.BufferDesc.Width = nWidth;
            swapDesc.BufferDesc.Height = nHeight;
            swapDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
            swapDesc.BufferDesc.RefreshRate.Numerator = _dmScreenSettings.dmDisplayFrequency;
            swapDesc.BufferDesc.RefreshRate.Denominator = 1;
            swapDesc.SampleDesc.Count = 1;
            swapDesc.SampleDesc.Quality = 0;
            swapDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
            swapDesc.BufferCount = 1;
            swapDesc.OutputWindow = _hWnd;
            swapDesc.Windowed = TRUE;

            hr = _dxgiFactory->CreateSwapChain(_device, &swapDesc, &_swapChain);
        }
        if (SUCCEEDED(hr)) {
            hr = _dxgiFactory->MakeWindowAssociation(_hWnd, DXGI_MWA_NO_WINDOW_CHANGES | DXGI_MWA_NO_ALT_ENTER | DXGI_MWA_NO_PRINT_SCREEN);
        }
        if (SUCCEEDED(hr)) {
            _adapter->GetDesc(&_adapterDesc);

            std::wstringstream stream;

            if (_device->GetFeatureLevel() == D3D10_FEATURE_LEVEL_10_1)
                stream << L"Direct3D 10.1 Device\n";
            else if (_device->GetFeatureLevel() == D3D10_FEATURE_LEVEL_10_0)
                stream << L"Direct3D 10.0 Device\n";
            else
                stream << L"Direct3D Device (unknown feature set)\n";

            stream << _adapterDesc.Description << L"\n";
            stream << L"Dedicated video memory: " << _adapterDesc.DedicatedVideoMemory / 1048576 << L" MB\n";
            stream << L"Shared system memory: " << _adapterDesc.SharedSystemMemory / 1048576 << L" MB\n";

            wcscpy_s(_deviceInfo, 1024, stream.str().c_str());
        }
        if (SUCCEEDED(hr)) {
            hr = createD3DDeviceResources();
        }
        if (SUCCEEDED(hr)) {
            hr = recreateSizedResources();
        }
    }

    return hr;
}

/******************************************************************
*                                                                 *
*  D3DDevice::recreateSizedResources                          *
*                                                                 *
*  This method is responsible for (re)creating all device         *
*  resources that depend on the window size.                      *
*                                                                 *
******************************************************************/
HRESULT D3DDevice::recreateSizedResources()
{
    UINT nWidth =  _dmScreenSettings.dmPelsWidth;
    UINT nHeight = _dmScreenSettings.dmPelsHeight;

    HRESULT hr = S_OK;
    ID3D10RenderTargetView *viewList[1] = {NULL};

    // Ensure that nobody is holding onto one of the old resources
  
    _device->OMSetRenderTargets(1, viewList, NULL);
    safeRelease(&_renderTargetView);
    safeRelease(&_renderTarget);
    safeRelease(&_depthStencilView);
    safeRelease(&_depthStencil);
    
    // Resize render target buffers
    hr = _swapChain->ResizeBuffers(1, nWidth, nHeight, DXGI_FORMAT_R8G8B8A8_UNORM, 0);

    if (SUCCEEDED(hr)) {
        D3D10_TEXTURE2D_DESC texDesc;
        texDesc.ArraySize = 1;
        texDesc.BindFlags = D3D10_BIND_DEPTH_STENCIL;
        texDesc.CPUAccessFlags = 0;
        texDesc.Format = DXGI_FORMAT_D32_FLOAT;
        texDesc.Height = nHeight;
        texDesc.Width = nWidth;
        texDesc.MipLevels = 1;
        texDesc.MiscFlags = 0;
        texDesc.SampleDesc.Count = 1;
        texDesc.SampleDesc.Quality = 0;
        texDesc.Usage = D3D10_USAGE_DEFAULT;

        safeRelease(&_depthStencil);
        hr = _device->CreateTexture2D(&texDesc, NULL, &_depthStencil);
    }

    if (SUCCEEDED(hr)) {
        hr = _swapChain->GetBuffer(0, IID_PPV_ARGS(&_renderTarget));
    }
    if (SUCCEEDED(hr)) {
        D3D10_RENDER_TARGET_VIEW_DESC renderDesc;
        renderDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
        renderDesc.ViewDimension = D3D10_RTV_DIMENSION_TEXTURE2D;
        renderDesc.Texture2D.MipSlice = 0;

        hr = _device->CreateRenderTargetView(_renderTarget, &renderDesc, &_renderTargetView);
    }
    if (SUCCEEDED(hr)) {
        D3D10_DEPTH_STENCIL_VIEW_DESC depthViewDesc;
        depthViewDesc.Format = DXGI_FORMAT_D32_FLOAT;
        depthViewDesc.ViewDimension = D3D10_DSV_DIMENSION_TEXTURE2D;
        depthViewDesc.Texture2D.MipSlice = 0;

        hr = _device->CreateDepthStencilView(_depthStencil, &depthViewDesc, &_depthStencilView);
    }
    if (SUCCEEDED(hr)) {
        viewList[0] = _renderTargetView;
        _device->OMSetRenderTargets(1, viewList, _depthStencilView);

        // Set a new viewport based on the new dimensions
        D3D10_VIEWPORT viewport;
        viewport.Width = nWidth;
        viewport.Height = nHeight;
        viewport.TopLeftX = 0;
        viewport.TopLeftY = 0;
        viewport.MinDepth = 0;
        viewport.MaxDepth = 1;
        _device->RSSetViewports(1, &viewport);
    }

    return hr;
}

/******************************************************************
*                                                                 *
*  D3DDevice::createD3DDeviceResources                        *
*                                                                 *
*  This method creates all the remaining D3D device resources     *
*  required to render the scene. This method is called from       *
*  CreateDeviceResources, and as such, it relies on the device    *
*  and swap chain aleady being created.                           *
*                                                                 *
******************************************************************/
HRESULT D3DDevice::createD3DDeviceResources()
{
    HRESULT hr = S_OK;

    // Create rasterizer state object
    D3D10_RASTERIZER_DESC rsDesc;
    rsDesc.AntialiasedLineEnable = FALSE;
    rsDesc.CullMode = D3D10_CULL_BACK; //D3D10_CULL_NONE;
    rsDesc.DepthBias = 0;
    rsDesc.DepthBiasClamp = 0;
    rsDesc.DepthClipEnable = TRUE;
    rsDesc.FillMode = D3D10_FILL_SOLID;
    rsDesc.FrontCounterClockwise = FALSE; // Must be FALSE for 10on9
    rsDesc.MultisampleEnable = FALSE;
    rsDesc.ScissorEnable = FALSE;
    rsDesc.SlopeScaledDepthBias = 0;

    hr = _device->CreateRasterizerState(&rsDesc, &_state);
    if (FAILED(hr))
        return hr;

    _device->RSSetState(_state);
    _device->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);


    D3D10_BUFFER_DESC bdVB;
    bdVB.Usage = D3D10_USAGE_DEFAULT;
    bdVB.ByteWidth = sizeof(_spriteVertexArray);
    bdVB.BindFlags = D3D10_BIND_VERTEX_BUFFER;
    bdVB.CPUAccessFlags = 0;
    bdVB.MiscFlags = 0;
    D3D10_SUBRESOURCE_DATA initDataVB;
    initDataVB.pSysMem = _spriteVertexArray;

    hr = _device->CreateBuffer(&bdVB, &initDataVB, &_spriteVertexBuffer);
    if (FAILED(hr))
        return hr;

   
    D3D10_BUFFER_DESC bdIB;
    bdIB.Usage = D3D10_USAGE_DEFAULT;
    bdIB.ByteWidth = sizeof(_spriteIndexArray);
    bdIB.BindFlags = D3D10_BIND_INDEX_BUFFER;
    bdIB.CPUAccessFlags = 0;
    bdIB.MiscFlags = 0;
    D3D10_SUBRESOURCE_DATA initDataIB;
    initDataIB.pSysMem = _spriteIndexArray;

    hr = _device->CreateBuffer(&bdIB, &initDataIB, &_spriteIndexBuffer);

    return hr;
}


/******************************************************************
*                                                                 *
*  D3DDevice::discardDeviceResources                          *
*                                                                 *
*  Certain resources (eg. device, swap chain, RT) are bound to a  *
*  particular D3D device. Under certain conditions (eg. change    *
*  display mode, remoting, removing a video adapter), it is       *
*  necessary to discard device-specific resources. This method    *
*  just releases all of the device-bound resources that we're     *
*  holding onto.                                                  *
*                                                                 *
******************************************************************/

void D3DDevice::discardDeviceResources()
{
    safeRelease(&_device);
    safeRelease(&_swapChain);
    safeRelease(&_state);

    safeRelease(&_renderTarget);
    safeRelease(&_renderTargetView);

    safeRelease(&_depthStencil);
    safeRelease(&_depthStencilView);
}

void D3DDevice::destroy()
{
    discardDeviceResources();
    CoUninitialize();
}

void D3DDevice::drawText(Text* text)
{
    if (_shader.empty()) return;
    if (_shader.front() == 0) return;
    if (text == 0) return;
    if (text->getText() == 0) {
        text->setDirty(false);
        return;
    }

    ID3D10Buffer* pVertexBuffer = _spriteVertexBuffer;
    UINT stride = sizeof(Vertex); 
    UINT offset = 0;

    _device->IASetVertexBuffers(0, 1, &pVertexBuffer, &stride, &offset);
    _device->IASetIndexBuffer(_spriteIndexBuffer, DXGI_FORMAT_R32_UINT, 0);
    _device->IASetInputLayout(((HlslShader*)_shader.front())->getLayout());

    if (_shader.front()->g_WorldMatrix) _shader.front()->g_WorldMatrix->setValue(_worldMatrix.front()[0]);
    if (_shader.front()->g_NormalMatrix) _shader.front()->g_NormalMatrix->setValue(_normalMatrix.front()[0]);

    if (_shader.front()->g_Viewport) {
        float viewport[4] = { (float)_dmScreenSettings.dmPelsWidth, (float)_dmScreenSettings.dmPelsHeight, 0.0f, 0.0f };
        if (!_activeCamera.empty()) {
            if ((_activeCamera.front()->getRenderTargetImplementation() == Camera::TEXTURE) && (_activeCamera.front()->getOutputBuffers()[0] != 0)) {
                viewport[0] = (float)_activeCamera.front()->getOutputBuffers()[0]->getWidth();
                viewport[1] = (float)_activeCamera.front()->getOutputBuffers()[0]->getHeight();
            }
        }
        _shader.front()->g_Viewport->setValue(viewport);
    }

    TextResource* textResource = (TextResource*)text->getResourceHandle();

    if ((textResource == NULL) || text->isDirty()) {
        if (textResource != 0) {
            safeRelease(&(textResource->brush));
            safeRelease(&(textResource->renderTarget));
            safeRelease(&(textResource->textureView));
            safeRelease(&(textResource->texture));
        }
        else {
            textResource = new TextResource();
            ZeroMemory(textResource, sizeof(TextResource));
        }

        D3D10_TEXTURE2D_DESC texDesc;
        texDesc.ArraySize = 1;
        texDesc.BindFlags = D3D10_BIND_RENDER_TARGET | D3D10_BIND_SHADER_RESOURCE;
        texDesc.CPUAccessFlags = 0;
        texDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
        texDesc.Height = (UINT)text->getMaxHeight();
        texDesc.Width = (UINT)text->getMaxWidth();
        texDesc.MipLevels = 1;
        texDesc.MiscFlags = 0;
        texDesc.SampleDesc.Count = 1;
        texDesc.SampleDesc.Quality = 0;
        texDesc.Usage = D3D10_USAGE_DEFAULT;

        _device->CreateTexture2D(&texDesc, NULL, &(textResource->texture));
        _device->CreateShaderResourceView(textResource->texture, NULL, &(textResource->textureView));

        IDXGISurface* dxgiSurface = NULL;
        textResource->texture->QueryInterface(&dxgiSurface);

        D2D1_RENDER_TARGET_PROPERTIES props =
            D2D1::RenderTargetProperties (
                D2D1_RENDER_TARGET_TYPE_DEFAULT,
                D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED),
                96,
                96
            );

        text->getD2DFactory()->CreateDxgiSurfaceRenderTarget(
            dxgiSurface,
            &props,
            &(textResource->renderTarget)
            );

        uint32 rgb = 0;
        float r = 0.0f, g = 0.0, b = 0.0f;
        std::wstringstream stream;
        stream << text->getFontColor();
        stream >> std::hex >> rgb;
        convertRgbHexToRgbFloat(rgb, r, g, b);

        textResource->renderTarget->CreateSolidColorBrush(
            D2D1::ColorF(r, g, b, 1.0f),
            &(textResource->brush)
            );

        text->setResourceHandle(textResource);


        textResource->renderTarget->BeginDraw();
        textResource->renderTarget->Clear(D2D1::ColorF(0.0f, 0.0f, 0.0f, 0.0f));

        textResource->renderTarget->DrawTextLayout(
            D2D1::Point2F(0.0f, 0.0f),
            text->getTextLayout(),
            textResource->brush
            );
        
        textResource->renderTarget->EndDraw();

        text->setDirty(false);
    }

    if (_shader.front()->g_Texture) _shader.front()->g_Texture->setValue(textResource->textureView);

    ((HlslShader*)_shader.front())->getTechnique()->GetPassByIndex(0)->Apply(0);

    _device->DrawIndexed(ARRAYSIZE(_spriteIndexArray), 0, 0);
}

void D3DDevice::drawSprite(Sprite* sprite)
{
    if (_shader.empty()) return;
    if (_shader.front() == 0) return;
    if (sprite == 0) return;

    bool needsToBeDrawn = false;

    if (!_activeCamera.empty()) {
        RenderTarget** inputBuffers = _activeCamera.front()->getInputBuffers();

        for (int i = 0; i < 16; ++i) {
            if ((inputBuffers[i] != 0) && (_shader.front()->g_MRT[i] != 0)) {
                needsToBeDrawn = true;
                _shader.front()->g_MRT[i]->setValue(inputBuffers[i]);
            }
        }
    }

    
    if ((_shader.front()->g_Texture != 0) && (sprite->getTexture() != 0)) {
        ID3D10ShaderResourceView* srView = (ID3D10ShaderResourceView*)sprite->getTexture()->getResourceHandle();
        if (srView != 0) { 
            needsToBeDrawn = true;
            _shader.front()->g_Texture->setValue(sprite->getTexture());
        }
    }

    if (needsToBeDrawn) {
        ID3D10Buffer* pVertexBuffer = _spriteVertexBuffer;
        UINT stride = sizeof(Vertex); 
        UINT offset = 0;

        _device->IASetVertexBuffers(0, 1, &pVertexBuffer, &stride, &offset);
        _device->IASetIndexBuffer(_spriteIndexBuffer, DXGI_FORMAT_R32_UINT, 0);
        _device->IASetInputLayout(((HlslShader*)_shader.front())->getLayout());

        if ((!_worldMatrix.empty()) && (_shader.front()->g_WorldMatrix)) _shader.front()->g_WorldMatrix->setValue(_worldMatrix.front()[0]);
        if ((!_normalMatrix.empty()) && (_shader.front()->g_NormalMatrix)) _shader.front()->g_NormalMatrix->setValue(_normalMatrix.front()[0]);

        if (_shader.front()->g_Viewport) {
            float viewport[4] = { (float)_dmScreenSettings.dmPelsWidth, (float)_dmScreenSettings.dmPelsHeight, 0.0f, 0.0f };
            if (!_activeCamera.empty()) {
                if ((_activeCamera.front()->getRenderTargetImplementation() == Camera::TEXTURE) && (_activeCamera.front()->getOutputBuffers()[0] != 0)) {
                    viewport[0] = (float)_activeCamera.front()->getOutputBuffers()[0]->getWidth();
                    viewport[1] = (float)_activeCamera.front()->getOutputBuffers()[0]->getHeight();
                }
            }
            _shader.front()->g_Viewport->setValue(viewport);
        }

        ((HlslShader*)_shader.front())->getTechnique()->GetPassByIndex(0)->Apply(0);

        _device->DrawIndexed(ARRAYSIZE(_spriteIndexArray), 0, 0); 
    }
}

void D3DDevice::drawGeode(Geode* geode)
{
    if (_shader.empty()) return;
    if (_shader.front() == 0) return;
    if (geode == 0) return;
    if (geode->getNumDrawables() == 0) return;
    
    Geode::Drawable** drawables = geode->getDrawables();

    for (size_t drawableID = 0; drawableID < geode->getNumDrawables(); ++drawableID) {
        GeodeResource* geodeResource = (GeodeResource*)drawables[drawableID]->getResourceHandle();

        ID3D10Buffer* pVertexBuffer = geodeResource->vertexBuffer;
        UINT stride = sizeof(Vertex); 
        UINT offset = 0;

        _device->IASetVertexBuffers(0, 1, &pVertexBuffer, &stride, &offset);
        _device->IASetIndexBuffer(geodeResource->indexBuffer, DXGI_FORMAT_R32_UINT, 0);
        _device->IASetInputLayout(((HlslShader*)_shader.front())->getLayout());

        if (_shader.front()->g_WorldMatrix) _shader.front()->g_WorldMatrix->setValue(_worldMatrix.front()[0]);
        if (_shader.front()->g_NormalMatrix) _shader.front()->g_NormalMatrix->setValue(_normalMatrix.front()[0]);

        if (_shader.front()->g_Viewport) {
            float viewport[4] = { (float)_dmScreenSettings.dmPelsWidth, (float)_dmScreenSettings.dmPelsHeight, 0.0f, 0.0f };
            if (!_activeCamera.empty()) {
                if ((_activeCamera.front()->getRenderTargetImplementation() == Camera::TEXTURE) && (_activeCamera.front()->getOutputBuffers()[0] != 0)) {
                    viewport[0] = (float)_activeCamera.front()->getOutputBuffers()[0]->getWidth();
                    viewport[1] = (float)_activeCamera.front()->getOutputBuffers()[0]->getHeight();
                }
            }
            _shader.front()->g_Viewport->setValue(viewport);
        }

        Material* material = drawables[drawableID]->getMaterial();

        if (_shader.front()->g_DiffuseMaterial) _shader.front()->g_DiffuseMaterial->setValue(material->getDiffuse());
        if (_shader.front()->g_SpecularMaterial) _shader.front()->g_SpecularMaterial->setValue(material->getSpecular());

        if (_shader.front()->g_Texture) _shader.front()->g_Texture->setValue(material->getDiffuseTexture());
        if (_shader.front()->g_NormalMap) _shader.front()->g_NormalMap->setValue(material->getNormalMap());

        ((HlslShader*)_shader.front())->getTechnique()->GetPassByIndex(0)->Apply(0);

        _device->DrawIndexed(geodeResource->triangleCount, 0, 0);
    }
}


void D3DDevice::clear(float r, float g, float b, float a)
{
    float clearColor[4] = { r, g, b, a };
    _device->ClearRenderTargetView(_renderTargetView, clearColor);
    _device->ClearDepthStencilView(_depthStencilView, D3D10_CLEAR_DEPTH, 1, 0);
}

void D3DDevice::flush()
{
    _swapChain->Present(0, 0);
}


void D3DDevice::setRenderTargets(RenderTarget** renderTargets, DepthStencilBuffer* depthStencilBuffer)
{
    D3D10_VIEWPORT viewport;
    viewport.Width = 0;
    viewport.Height = 0;
    viewport.TopLeftX = 0;
    viewport.TopLeftY = 0;
    viewport.MinDepth = 0;
    viewport.MaxDepth = 1;

    _renderTargetCount = 0;
    ZeroMemory(_viewList, 16 * sizeof(ID3D10RenderTargetView*));

    if (renderTargets == 0) {
        _viewList[0] = _renderTargetView;
        _device->OMSetRenderTargets(1, _viewList, _depthStencilView);

        viewport.Width = _dmScreenSettings.dmPelsWidth;
        viewport.Height = _dmScreenSettings.dmPelsHeight;
        _device->RSSetViewports(1, &viewport);
    }
    else {
        for (int i = 0; i < 16; ++i) {
            if (renderTargets[i] != 0) {
                _viewList[_renderTargetCount] = ((D3DRenderTarget*)renderTargets[i])->getRenderTargetView();
                _renderTargetCount++;
            }
        }

        if (depthStencilBuffer != 0) {
            _depthBufferView = ((D3DDepthStencilBuffer*)depthStencilBuffer)->getDepthStencilView();
        }
        else {
            _depthBufferView = 0;
        }

        _device->OMSetRenderTargets(_renderTargetCount, _viewList, _depthBufferView);

        if (renderTargets[0] != 0) {
            viewport.Width = renderTargets[0]->getWidth();
            viewport.Height = renderTargets[0]->getHeight();
            _device->RSSetViewports(1, &viewport);
        }
    }
}

void D3DDevice::clearRenderTargets(float r, float g, float b, float a)
{
    float clearColor[4] = { r, g, b, a };
    
    for (UINT i = 0; i < _renderTargetCount; ++i) {
        _device->ClearRenderTargetView(_viewList[i], clearColor);
    }

    if (_depthBufferView != 0)  {
        _device->ClearDepthStencilView(_depthBufferView, D3D10_CLEAR_DEPTH, 1, 0);
    }
}


void* D3DDevice::loadTextureFromMemory(byte* data, uint32 size)
{
    ID3D10ShaderResourceView* srView = NULL;
    D3DX10CreateShaderResourceViewFromMemory(_device, data, size, NULL, NULL, &srView, NULL);
    return srView;
}


void** D3DDevice::loadMeshArrayFromMemory(aiMesh** rawMeshes, uint32 meshNum)
{
    GeodeResource** geodeResources = new GeodeResource*[meshNum];
    ZeroMemory(geodeResources, sizeof(GeodeResource*) * meshNum);

    for (uint32 meshID = 0; meshID < meshNum; ++meshID) {
        Vertex* vertices = new Vertex[rawMeshes[meshID]->mNumVertices];
        ZeroMemory(vertices, sizeof(Vertex) * rawMeshes[meshID]->mNumVertices);

        for (unsigned int i = 0; i < rawMeshes[meshID]->mNumVertices; ++i) {
            vertices[i].Pos[0] = rawMeshes[meshID]->mVertices[i].x;
            vertices[i].Pos[1] = rawMeshes[meshID]->mVertices[i].y;
            vertices[i].Pos[2] = rawMeshes[meshID]->mVertices[i].z;

            if (rawMeshes[meshID]->HasTextureCoords(0)) {
                vertices[i].Tex[0] = rawMeshes[meshID]->mTextureCoords[0][i].x;
                vertices[i].Tex[1] = rawMeshes[meshID]->mTextureCoords[0][i].y;
            }

            if (rawMeshes[meshID]->HasNormals()) {
                vertices[i].Norm[0] = rawMeshes[meshID]->mNormals[i].x;
                vertices[i].Norm[1] = rawMeshes[meshID]->mNormals[i].y;
                vertices[i].Norm[2] = rawMeshes[meshID]->mNormals[i].z;
            }

            if (rawMeshes[meshID]->HasTangentsAndBitangents()) {
                vertices[i].Tang[0] = rawMeshes[meshID]->mTangents[i].x;
                vertices[i].Tang[1] = rawMeshes[meshID]->mTangents[i].y;
                vertices[i].Tang[2] = rawMeshes[meshID]->mTangents[i].z;
            }
        }

        uint32* indices = new uint32[rawMeshes[meshID]->mNumFaces * 3];
        ZeroMemory(indices, sizeof(uint32) * rawMeshes[meshID]->mNumFaces * 3);
        
        uint32 k = 0;
        for(unsigned int i = 0; i < rawMeshes[meshID]->mNumFaces; ++i) {
            indices[k] = rawMeshes[meshID]->mFaces[i].mIndices[0]; k++;
            indices[k] = rawMeshes[meshID]->mFaces[i].mIndices[1]; k++;
            indices[k] = rawMeshes[meshID]->mFaces[i].mIndices[2]; k++;
        }

        geodeResources[meshID] = new GeodeResource();
        ZeroMemory(geodeResources[meshID], sizeof(GeodeResource));

        D3D10_BUFFER_DESC bd;
        bd.Usage = D3D10_USAGE_DEFAULT;
        bd.ByteWidth = sizeof(Vertex) * rawMeshes[meshID]->mNumVertices;
        bd.BindFlags = D3D10_BIND_VERTEX_BUFFER;
        bd.CPUAccessFlags = 0;
        bd.MiscFlags = 0;
        D3D10_SUBRESOURCE_DATA initData;
        initData.pSysMem = vertices;
        _device->CreateBuffer(&bd, &initData, &(geodeResources[meshID]->vertexBuffer));

        bd.Usage = D3D10_USAGE_DEFAULT;
        bd.ByteWidth = sizeof(uint32) * rawMeshes[meshID]->mNumFaces * 3;
        bd.BindFlags = D3D10_BIND_INDEX_BUFFER;
        bd.CPUAccessFlags = 0;
        bd.MiscFlags = 0;
        initData.pSysMem = indices;
        _device->CreateBuffer(&bd, &initData, &(geodeResources[meshID]->indexBuffer));

        geodeResources[meshID]->triangleCount = rawMeshes[meshID]->mNumFaces * 3;

        delete [] vertices;
        delete [] indices;
    }

    return (void**)geodeResources;
}


void* D3DDevice::loadTextureFromFloatArray(unsigned int width, unsigned int height, float* data)
{
    HRESULT hr;

    D3D10_TEXTURE2D_DESC desc;
    ZeroMemory(&desc, sizeof(D3D10_TEXTURE2D_DESC));
    desc.Width = width;
    desc.Height = height;
    desc.MipLevels = desc.ArraySize = 1;
    desc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
    desc.SampleDesc.Count = 1;
    desc.Usage = D3D10_USAGE_DYNAMIC;
    desc.BindFlags = D3D10_BIND_SHADER_RESOURCE;
    desc.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
    ID3D10Texture2D *pTexture = NULL;
    hr = _device->CreateTexture2D(&desc, NULL, &pTexture);
    if (FAILED(hr))
        return NULL;

    D3D10_MAPPED_TEXTURE2D mappedTex;
    ZeroMemory(&mappedTex, sizeof(D3D10_MAPPED_TEXTURE2D));
    hr = pTexture->Map(D3D10CalcSubresource(0, 0, 1), D3D10_MAP_WRITE_DISCARD, 0, &mappedTex);
    if (FAILED(hr))
        return NULL;

    float* pTexels = (float*)mappedTex.pData;
    for(UINT row = 0; row < desc.Height; ++row)
    {
        UINT rowStart = row * width * 4;
        for(UINT col = 0; col < desc.Width; ++col)
        {
            UINT colStart = col * 4;
            pTexels[rowStart + colStart + 0] = data[rowStart + colStart + 0];
            pTexels[rowStart + colStart + 1] = data[rowStart + colStart + 1];
            pTexels[rowStart + colStart + 2] = data[rowStart + colStart + 2];
            pTexels[rowStart + colStart + 3] = data[rowStart + colStart + 3];
        }
    }

    pTexture->Unmap( D3D10CalcSubresource(0, 0, 1) );

    D3D10_SHADER_RESOURCE_VIEW_DESC srvDesc;
    ZeroMemory(&srvDesc, sizeof(D3D10_SHADER_RESOURCE_VIEW_DESC));
    srvDesc.Format = desc.Format;
    srvDesc.ViewDimension = D3D10_SRV_DIMENSION_TEXTURE2D;
    srvDesc.Texture2D.MipLevels = desc.MipLevels;
    srvDesc.Texture2D.MostDetailedMip = desc.MipLevels - 1;

    ID3D10ShaderResourceView* pTextureSRV = NULL;
    hr = _device->CreateShaderResourceView(pTexture, &srvDesc, &pTextureSRV);
    if (FAILED(hr))
        return NULL;

    return pTextureSRV;
}



/**************************************
*	Device factory methods
**************************************/

const wchar_t* getDeviceName()
{
	return L"D3DDevice";
}

Device* acquireDevice()
{
	return new D3DDevice();
}

void releaseDevice(Device* device)
{
	delete device;
}