3D C/C++ tutorials - Software rendering - Simple software renderer
3D C/C++ tutorials -> Software rendering -> Simple software renderer
Use for personal or educational purposes only. Commercial and other profit uses strictly prohibited. Exploitation of content on a website or in a publication prohibited.
To compile and run these tutorials some or all of these libraries are required: FreeImage 3.16.0, GLEW 1.11.0, GLUT 3.7.6 / GLUT for Dev-C++, GLM 0.9.5.4
simple_software_renderer_multithreading_v1.h
// ----------------------------------------------------------------------------------------------------------------------------

#include <windows.h>

#ifndef WM_MOUSWHEEL
    #define WM_MOUSWHEEL 0x020A
#endif

#include "string.h"
#include "glmath.h"

#include <FreeImage.h> // http://freeimage.sourceforge.net/

#pragma comment(lib, "FreeImage.lib")
#pragma comment(lib, "winmm.lib")

#pragma warning(disable : 4996)

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

class CTexture
{
private:
    FIBITMAP *DIB;
    BYTE *Bits;

private:
    int Width, Height, Pitch, BPP;

public:
    CTexture();
    ~CTexture();

private:
    void SetDefaults();

public:
    bool LoadTexture(char *TextureFileName);
    void GetColorNearest(float s, float t, float *r, float *g, float *b);
    void GetColorBilinear(float s, float t, float *r, float *g, float *b);
    void Destroy();
};

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

class CCamera
{
public:
    vec3 X, Y, Z, Position, Reference;

public:
    mat4x4 ViewMatrix, ProjectionMatrix, ViewProjectionMatrix;

public:
    CCamera();
    ~CCamera();

public:
    void Look(const vec3 &Position, const vec3 &Reference, bool RotateAroundReference = false);
    void Move(const vec3 &Movement);
    vec3 OnKeys(BYTE Keys, float FrameTime);
    void OnMouseMove(int dx, int dy);
    void OnMouseWheel(float zDelta);
    void SetPerspective(float fovy, float aspect, float n, float f);

private:
    void CalculateViewMatrix();
};

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

class CVertex
{
public:
    vec3 Position;
    vec3 Color;
    vec2 TexCoord;
    vec3 Normal;
};

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

class CObject
{
public:
    CTexture Texture;

public:
    CVertex *Vertices;
    int VerticesCount;

public:
    vec3 Min, Max;

public:
    CObject();
    ~CObject();

private:
    void SetDefaults();

public:
    bool Load(char *Directory, char *ObjFileName);
    void Translate(const vec3 &Translation);
    void Scale(float ScaleFactor);
    void Rotate(float Angle, const vec3 &Axis);
    void Destroy();

private:
    bool ReadSource(char *Directory, char *FileName, char **Source, long &Length);
    bool ParseMtl(char *Directory, char *MtlFileName);
    void GetMinMax();
};

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

class CLight
{
public:
    vec3 Position;
    vec3 Ambient;
    vec3 Diffuse;
    float ConstantAttenuation;
    float LinearAttenuation;
    float QuadraticAttenuation;
};

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

class CFragment
{
public:
    float x, y, z, w, r, g, b, s, t;
};

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

class CEdge
{
public:
    CFragment *Fragment1, *Fragment2;

public:
    float dx, dy, dz, dw, dr, dg, db, ds, dt;

public:
    void Set(CFragment *Fragment1, CFragment *Fragment2);

private:
    void CalculateDiffs();
};

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

class CSpan
{
public:
    CFragment *Fragment1, *Fragment2;

public:
    float dx, dy, dz, dw, dr, dg, db, ds, dt;

public:
    void Set(CFragment *Fragment1, CFragment *Fragment2);

private:
    void CalculateDiffs();
};

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

#define NONE 0x00

#define CULL_FACE_FRONT 0x01
#define CULL_FACE_BACK 0x02

#define ANTI_ALIASING_2X2 0x03
#define ANTI_ALIASING_3X3 0x04
#define ANTI_ALIASING_4X4 0x05

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

#define THREAD_SLEEP_TIME 0

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

class CThreadData
{
public:
    HANDLE Thread;
    DWORD Id;
    void *SoftwareGL;
    int ThreadId, Function;
    bool ThreadIsRunning;
};

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

class CSoftwareGL
{
private:
    int Width, Height;

private:
    BYTE *AntiAliasingColorBuffer;
    int AntiAliasingColorBufferWidth, AntiAliasingColorBufferHeight;

private:
    BYTE *StandardColorBuffer;
    int StandardColorBufferWidth, StandardColorBufferHeight;
    BITMAPINFO StandardColorBufferInfo;

private:
    BYTE *ColorBuffer;
    int ColorBufferWidth, ColorBufferHeight;

private:
    USHORT *DepthBuffer;
    int DepthBufferWidth, DepthBufferHeight;

private:
    int AntiAliasing;
    mat4x4 ModelViewProjectionMatrix;
    CLight *Light;
    int CullFace;
    bool BilinearTextureFiltering;
    CTexture *Texture;

private:
    CVertex *Vertices;
    int FirstIndex, LastIndex;

private:
    CThreadData *ThreadsData;
    int ThreadsCount;

public:
    CSoftwareGL();
    ~CSoftwareGL();

public:
    int GetAntiAliasing();
    void SetAntiAliasing(int AntiAliasing);
    int GetCullFace();
    void SetCullFace(int CullFace);
    bool GetBilinearTextureFiltering();
    void SetBilinearTextureFiltering(bool BilinearTextureFiltering);

public:
    int GetThreadsCount();

public:
    void Clear();
    void LoadModelViewProjectionMatrix(const mat4x4 &ModelViewProjectionMatrix);
    void BindLight(CLight *Light);
    void BindTexture(CTexture *Texture);
    void DrawTriangles(CVertex *Vertices, int FirstIndex, int Count);

private:
    void RunFunctionMultiThreadedAndWaitForCompletion(int Function);
    bool ThreadsAreRunning();
    static DWORD WINAPI ThreadProc(LPVOID lpParam);
    void DrawTriangles(int ThreadId);
    void ClipTriangle(CFragment *Fragments, int ThreadId, int ClipPlane = 1);
    void RasterizeTriangle(CFragment *Fragments, int ThreadId);
    void DrawSpansBetweenEdges(CEdge *Edge1, CEdge *Edge2, int ThreadId);
    void DrawSpan(CSpan *Span, int y);
    void BlitAntiAliasingColorBuffer2x2(int Line);
    void BlitAntiAliasingColorBuffer3x3(int Line);
    void BlitAntiAliasingColorBuffer4x4(int Line);

public:
    void Viewport(int Width, int Height);
    void SwapBuffers(HDC hDC);
};

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

class CSoftwareGLRenderer : public CSoftwareGL
{
private:
    int LastX, LastY, LastClickedX, LastClickedY;

private:
    CCamera Camera;

private:
    CTexture Texture;

private:
    CVertex *Vertices;

private:
    CObject Object[3];

private:
    int RenderObject;

private:
    bool Lighting, Texturing;

private:
    CLight Light;

public:
    CSoftwareGLRenderer();
    ~CSoftwareGLRenderer();

public:
    bool Init();
    void Render(float FrameTime);
    void Resize(int Width, int Height);
    void Destroy();

public:
    void CheckCameraKeys(float FrameTime);

public:
    void OnKeyDown(UINT Key);
    void OnLButtonDown(int X, int Y);
    void OnLButtonUp(int X, int Y);
    void OnMouseMove(int X, int Y);
    void OnMouseWheel(short zDelta);
    void OnRButtonDown(int X, int Y);
    void OnRButtonUp(int X, int Y);
};

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

class CSoftwareGLView
{
private:
    char *Title;
    int Width, Height;
    HWND hWnd;
    HDC hDC;

private:
    CSoftwareGLRenderer SoftwareGLRenderer;

public:
    CSoftwareGLView();
    ~CSoftwareGLView();

public:
    bool Create(HINSTANCE hInstance, char *Title, int Width, int Height);
    void Show(bool Maximized = false);
    void MsgLoop();
    void Destroy();

public:
    void OnKeyDown(UINT Key);
    void OnLButtonDown(int X, int Y);
    void OnLButtonUp(int X, int Y);
    void OnMouseMove(int X, int Y);
    void OnMouseWheel(short zDelta);
    void OnPaint();
    void OnRButtonDown(int X, int Y);
    void OnRButtonUp(int X, int Y);
    void OnSize(int Width, int Height);
};

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

LRESULT CALLBACK WndProc(HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR sCmdLine, int iShow);

// ----------------------------------------------------------------------------------------------------------------------------
simple_software_renderer_multithreading_v1.cpp
// ----------------------------------------------------------------------------------------------------------------------------

#include "simple_software_renderer_multithreading_v1.h"

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

CString ErrorLog, ModuleDirectory;

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

CTexture::CTexture()
{
    SetDefaults();
}

CTexture::~CTexture()
{
}

void CTexture::SetDefaults()
{
    DIB = NULL;
    Bits = NULL;

    Width = 0;
    Height = 0;
    Pitch = 0;
    BPP = 0;
}

bool CTexture::LoadTexture(char *TextureFileName)
{
    CString FileName = ModuleDirectory + TextureFileName;
    CString ErrorText = "Error loading file " + FileName + " -> ";

    Destroy();

    FREE_IMAGE_FORMAT FIF = FreeImage_GetFileType(FileName);

    if(FIF == FIF_UNKNOWN)
    {
        FIF = FreeImage_GetFIFFromFilename(FileName);
    }

    if(FIF == FIF_UNKNOWN)
    {
        ErrorLog.Append(ErrorText + "FIF is FIF_UNKNOWN!" + "\r\n");
        return false;
    }

    if(FreeImage_FIFSupportsReading(FIF))
    {
        DIB = FreeImage_Load(FIF, FileName);
    }

    if(DIB == NULL)
    {
        ErrorLog.Append(ErrorText + "DIB is NULL!" + "\r\n");
        return false;
    }

    Width = FreeImage_GetWidth(DIB);
    Height = FreeImage_GetHeight(DIB);
    Pitch = FreeImage_GetPitch(DIB);
    BPP = FreeImage_GetBPP(DIB);

    if(Width == 0 || Height == 0)
    {
        ErrorLog.Append(ErrorText + "Width or Height is 0!" + "\r\n");
        Destroy();
        return false;
    }

    if(BPP != 24 && BPP != 32)
    {
        ErrorLog.Append(ErrorText + "BPP is not 24 nor 32!" + "\r\n");
        Destroy();
        return false;
    }

    BPP /= 8;

    Bits = FreeImage_GetBits(DIB);

    if(Bits == NULL)
    {
        ErrorLog.Append(ErrorText + "Bits is NULL!" + "\r\n");
        Destroy();
        return false;
    }

    return true;
}

float OD255 = 1.0f / 255.0f;

void CTexture::GetColorNearest(float s, float t, float *r, float *g, float *b)
{
    if(Bits != NULL)
    {
        s -= (int)s;
        t -= (int)t;

        if(s < 0.0f) s += 1.0f;
        if(t < 0.0f) t += 1.0f;

        int x = (int)(s * Width), y = (int)(t * Height);

        BYTE *A = Bits + Pitch * y + BPP * x;

        *b = *A * OD255;
        A++;
        *g = *A * OD255;
        A++;
        *r = *A * OD255;
    }
    else
    {
        *r = *g = *b = 1.0f;
    }
}

void CTexture::GetColorBilinear(float s, float t, float *r, float *g, float *b)
{
    if(Bits != NULL)
    {
        s -= (int)s;
        t -= (int)t;

        if(s < 0.0f) s += 1.0f;
        if(t < 0.0f) t += 1.0f;

        float fx = s * Width - 0.5f, fy = t * Height - 0.5f;

        if(fx < 0.0f) fx += Width;
        if(fy < 0.0f) fy += Height;

        int x0 = (int)fx, y0 = (int)fy, x1 = (x0 + 1) % Width, y1 = (y0 + 1) % Height;

        BYTE *LineAB = Bits + Pitch * y0;
        BYTE *LineCD = Bits + Pitch * y1;

        int BPPMx0 = BPP * x0, BPPMx1 = BPP * x1;

        BYTE *A = LineAB + BPPMx0;
        BYTE *B = LineAB + BPPMx1;
        BYTE *C = LineCD + BPPMx1;
        BYTE *D = LineCD + BPPMx0;

        float u1 = fx - x0, v1 = fy - y0, u0 = 1.0f - u1, v0 = 1.0f - v1;

        u0 *= OD255;
        u1 *= OD255;

        float u0v0 = u0 * v0, u1v0 = u1 * v0, u1v1 = u1 * v1, u0v1 = u0 * v1;

        *b = *A * u0v0 + *B * u1v0 + *C * u1v1 + *D * u0v1;
        A++; B++; C++; D++;
        *g = *A * u0v0 + *B * u1v0 + *C * u1v1 + *D * u0v1;
        A++; B++; C++; D++;
        *r = *A * u0v0 + *B * u1v0 + *C * u1v1 + *D * u0v1;
    }
    else
    {
        *r = *g = *b = 1.0f;
    }
}

void CTexture::Destroy()
{
    if(DIB != NULL)
    {
        FreeImage_Unload(DIB);
    }

    SetDefaults();
}

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

CCamera::CCamera()
{
    X = vec3(1.0f, 0.0f, 0.0f);
    Y = vec3(0.0f, 1.0f, 0.0f);
    Z = vec3(0.0f, 0.0f, 1.0f);

    Position = vec3(0.0f, 0.0f, 5.0f);
    Reference = vec3(0.0f, 0.0f, 0.0f);

    CalculateViewMatrix();
}

CCamera::~CCamera()
{
}

void CCamera::Look(const vec3 &Position, const vec3 &Reference, bool RotateAroundReference)
{
    this->Position = Position;
    this->Reference = Reference;

    Z = normalize(Position - Reference);

    GetXY(Z, X, Y);

    if(!RotateAroundReference)
    {
        this->Reference = this->Position - Z * 0.05f;
    }

    CalculateViewMatrix();
}

void CCamera::Move(const vec3 &Movement)
{
    Position += Movement;
    Reference += Movement;

    CalculateViewMatrix();
}

vec3 CCamera::OnKeys(BYTE Keys, float FrameTime)
{
    float Speed = 5.0f;

    if(Keys & 0x40) Speed *= 2.0f;
    if(Keys & 0x80) Speed *= 0.5f;

    float Distance = Speed * FrameTime;

    vec3 Up(0.0f, 1.0f, 0.0f);
    vec3 Right = X;
    vec3 Forward = cross(Up, Right);

    Up *= Distance;
    Right *= Distance;
    Forward *= Distance;

    vec3 Movement = vec3(0.0f);

    if(Keys & 0x01) Movement += Forward;
    if(Keys & 0x02) Movement -= Forward;
    if(Keys & 0x04) Movement -= Right;
    if(Keys & 0x08) Movement += Right;
    if(Keys & 0x10) Movement += Up;
    if(Keys & 0x20) Movement -= Up;

    return Movement;
}

void CCamera::OnMouseMove(int dx, int dy)
{
    float Sensitivity = 0.25f;

    Position -= Reference;

    if(dx != 0)
    {
        float DeltaX = (float)dx * Sensitivity;

        X = rotate(X, DeltaX, vec3(0.0f, 1.0f, 0.0f));
        Y = rotate(Y, DeltaX, vec3(0.0f, 1.0f, 0.0f));
        Z = rotate(Z, DeltaX, vec3(0.0f, 1.0f, 0.0f));
    }

    if(dy != 0)
    {
        float DeltaY = (float)dy * Sensitivity;

        Y = rotate(Y, DeltaY, X);
        Z = rotate(Z, DeltaY, X);

        if(Y.y < 0.0f)
        {
            Z = vec3(0.0f, Z.y > 0.0f ? 1.0f : -1.0f, 0.0f);
            Y = cross(Z, X);
        }
    }

    Position = Reference + Z * length(Position);

    CalculateViewMatrix();
}

void CCamera::OnMouseWheel(float zDelta)
{
    Position -= Reference;

    if(zDelta < 0 && length(Position) < 500.0f)
    {
        Position += Position * 0.1f;
    }

    if(zDelta > 0 && length(Position) > 0.05f)
    {
        Position -= Position * 0.1f;
    }

    Position += Reference;

    CalculateViewMatrix();
}

void CCamera::SetPerspective(float fovy, float aspect, float n, float f)
{
    ProjectionMatrix = perspective(fovy, aspect, n, f);
    ViewProjectionMatrix = ProjectionMatrix * ViewMatrix;
}

void CCamera::CalculateViewMatrix()
{
    ViewMatrix = mat4x4(X.x, Y.x, Z.x, 0.0f, X.y, Y.y, Z.y, 0.0f, X.z, Y.z, Z.z, 0.0f, -dot(X, Position), -dot(Y, Position), -dot(Z, Position), 1.0f);
    ViewProjectionMatrix = ProjectionMatrix * ViewMatrix;
}

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

CObject::CObject()
{
    SetDefaults();
}

CObject::~CObject()
{
}

void CObject::SetDefaults()
{
    Vertices = NULL;
    VerticesCount = 0;
}

bool CObject::Load(char *Directory, char *ObjFileName)
{
    char *ObjSource;
    long ObjLength;

    Destroy();

    if(!ReadSource(Directory, ObjFileName, &ObjSource, ObjLength)) return false;

    char *Line, *End = ObjSource + ObjLength;
    float x, y, z;
    int PositionsCount = 0, TexCoordsCount = 0, NormalsCount = 0, TrianglesCount = 0;
    int i1, i2, i3, i4, i5, i6, i7, i8, i9;
    int p = 0, tc = 0, n = 0, v = 0;

    Line = ObjSource;

    while(Line < End)
    {
        while(Line < End && (*Line == ' ' || *Line == '\t')) Line++;

        if(Line[0] == 'm' && Line[1] == 't' && Line[2] == 'l' && Line[3] == 'l' && Line[4] == 'i' && Line[5] == 'b' && (Line[6] == ' ' || Line[6] == '\t'))
        {
            char *MtlFileName = Line + 6;

            while(MtlFileName < End && (*MtlFileName == ' ' || *MtlFileName == '\t')) MtlFileName++;

            if(!ParseMtl(Directory, MtlFileName))
            {
                delete [] ObjSource;
                return false;
            }
        }
        else if(sscanf(Line, "v %f %f %f", &x, &y, &z) == 3)
        {
            PositionsCount++;
        }
        else if(sscanf(Line, "vt %f %f", &x, &y) == 2)
        {
            TexCoordsCount++;
        }
        else if(sscanf(Line, "vn %f %f %f", &x, &y, &z) == 3)
        {
            NormalsCount++;
        }
        else if(sscanf(Line, "f %d/%d/%d %d/%d/%d %d/%d/%d", &i1, &i2, &i3, &i4, &i5, &i6, &i7, &i8, &i9) == 9)
        {
            TrianglesCount++;
        }
        else if(sscanf(Line, "f %d//%d %d//%d %d//%d", &i1, &i2, &i3, &i4, &i5, &i6) == 6)
        {
            TrianglesCount++;
        }
        else if(sscanf(Line, "f %d/%d %d/%d %d/%d", &i1, &i2, &i3, &i4, &i5, &i6) == 6)
        {
            TrianglesCount++;
        }
        else if(sscanf(Line, "f %d %d %d", &i1, &i2, &i3) == 3)
        {
            TrianglesCount++;
        }

        while(Line < End && *Line != 0) Line++;
        while(Line < End && *Line == 0) Line++;
    }

    if(TrianglesCount == 0)
    {
        ErrorLog.Append("Error loading file %s!\r\n", ObjFileName);
        delete [] ObjSource;
        Destroy();
        return false;
    }

    vec3 *Positions = NULL;
    vec2 *TexCoords = NULL;
    vec3 *Normals = NULL;

    if(PositionsCount > 0) Positions = new vec3[PositionsCount];
    if(TexCoordsCount > 0) TexCoords = new vec2[TexCoordsCount];
    if(NormalsCount > 0) Normals = new vec3[NormalsCount];

    VerticesCount = TrianglesCount * 3;

    Vertices = new CVertex[VerticesCount];

    Line = ObjSource;

    while(Line < End)
    {
        while(Line < End && (*Line == ' ' || *Line == '\t')) Line++;

        if(sscanf(Line, "v %f %f %f", &x, &y, &z) == 3)
        {
            Positions[p++] = vec3(x, y, z);
        }
        else if(sscanf(Line, "vt %f %f", &x, &y) == 2)
        {
            TexCoords[tc++] = vec2(x, y);
        }
        else if(sscanf(Line, "vn %f %f %f", &x, &y, &z) == 3)
        {
            Normals[n++] = normalize(vec3(x, y, z));
        }
        else if(sscanf(Line, "f %d/%d/%d %d/%d/%d %d/%d/%d", &i1, &i2, &i3, &i4, &i5, &i6, &i7, &i8, &i9) == 9)
        {
            Vertices[v].Position = Positions[i1 - 1];
            Vertices[v].TexCoord = TexCoords[i2 - 1];
            Vertices[v].Normal = Normals[i3 - 1];
            v++;
            Vertices[v].Position = Positions[i4 - 1];
            Vertices[v].TexCoord = TexCoords[i5 - 1];
            Vertices[v].Normal = Normals[i6 - 1];
            v++;
            Vertices[v].Position = Positions[i7 - 1];
            Vertices[v].TexCoord = TexCoords[i8 - 1];
            Vertices[v].Normal = Normals[i9 - 1];
            v++;
        }
        else if(sscanf(Line, "f %d//%d %d//%d %d//%d", &i1, &i2, &i3, &i4, &i5, &i6) == 6)
        {
            Vertices[v].Position = Positions[i1 - 1];
            Vertices[v].Normal = Normals[i2 - 1];
            v++;
            Vertices[v].Position = Positions[i3 - 1];
            Vertices[v].Normal = Normals[i4 - 1];
            v++;
            Vertices[v].Position = Positions[i5 - 1];
            Vertices[v].Normal = Normals[i6 - 1];
            v++;
        }
        else if(sscanf(Line, "f %d/%d %d/%d %d/%d", &i1, &i2, &i3, &i4, &i5, &i6) == 6)
        {
            Vertices[v].Position = Positions[i1 - 1];
            if(TexCoords != NULL && i1 - 1 < TexCoordsCount) Vertices[v].TexCoord = TexCoords[i1 - 1];
            if(TexCoords != NULL && i2 - 1 < TexCoordsCount) Vertices[v].TexCoord = TexCoords[i2 - 1];
            if(Normals != NULL && i1 - 1 < NormalsCount) Vertices[v].Normal = Normals[i1 - 1];
            if(Normals != NULL && i2 - 1 < NormalsCount) Vertices[v].Normal = Normals[i2 - 1];
            v++;
            Vertices[v].Position = Positions[i3 - 1];
            if(TexCoords != NULL && i3 - 1 < TexCoordsCount) Vertices[v].TexCoord = TexCoords[i3 - 1];
            if(TexCoords != NULL && i4 - 1 < TexCoordsCount) Vertices[v].TexCoord = TexCoords[i4 - 1];
            if(Normals != NULL && i3 - 1 < NormalsCount) Vertices[v].Normal = Normals[i3 - 1];
            if(Normals != NULL && i4 - 1 < NormalsCount) Vertices[v].Normal = Normals[i4 - 1];
            v++;
            Vertices[v].Position = Positions[i5 - 1];
            if(TexCoords != NULL && i5 - 1 < TexCoordsCount) Vertices[v].TexCoord = TexCoords[i5 - 1];
            if(TexCoords != NULL && i6 - 1 < TexCoordsCount) Vertices[v].TexCoord = TexCoords[i6 - 1];
            if(Normals != NULL && i5 - 1 < NormalsCount) Vertices[v].Normal = Normals[i5 - 1];
            if(Normals != NULL && i6 - 1 < NormalsCount) Vertices[v].Normal = Normals[i6 - 1];
            v++;
        }
        else if(sscanf(Line, "f %d %d %d", &i1, &i2, &i3) == 3)
        {
            Vertices[v].Position = Positions[i1 - 1];
            if(TexCoords != NULL && i1 - 1 < TexCoordsCount) Vertices[v].TexCoord = TexCoords[i1 - 1];
            if(Normals != NULL && i1 - 1 < NormalsCount) Vertices[v].Normal = Normals[i1 - 1];
            v++;
            Vertices[v].Position = Positions[i2 - 1];
            if(TexCoords != NULL && i2 - 1 < TexCoordsCount) Vertices[v].TexCoord = TexCoords[i2 - 1];
            if(Normals != NULL && i2 - 1 < NormalsCount) Vertices[v].Normal = Normals[i2 - 1];
            v++;
            Vertices[v].Position = Positions[i3 - 1];
            if(TexCoords != NULL && i3 - 1 < TexCoordsCount) Vertices[v].TexCoord = TexCoords[i3 - 1];
            if(Normals != NULL && i3 - 1 < NormalsCount) Vertices[v].Normal = Normals[i3 - 1];
            v++;
        }

        while(Line < End && *Line != 0) Line++;
        while(Line < End && *Line == 0) Line++;
    }

    delete [] Positions;
    delete [] TexCoords;
    delete [] Normals;

    delete [] ObjSource;

    for(int i = 0; i < VerticesCount; i++)
    {
        Vertices[i].Color = vec3(1.0f);
    }

    if(NormalsCount == 0)
    {
        vec3 A, B, Normal;

        for(int i = 0; i < VerticesCount; i += 3)
        {
            int i0 = i, i1 = i + 1, i2 = i + 2;

            A = Vertices[i1].Position - Vertices[i0].Position;
            B = Vertices[i2].Position - Vertices[i0].Position;

            Normal = normalize(cross(A, B));

            Vertices[i0].Normal = Normal;
            Vertices[i1].Normal = Normal;
            Vertices[i2].Normal = Normal;
        }
    }

    GetMinMax();

    return true;
}

void CObject::Translate(const vec3 &Translation)
{
    for(int i = 0; i < VerticesCount; i++)
    {
        Vertices[i].Position += Translation;
    }

    Min += Translation;
    Max += Translation;
}

void CObject::Scale(float ScaleFactor)
{
    for(int i = 0; i < VerticesCount; i++)
    {
        Vertices[i].Position *= ScaleFactor;
    }

    Min *= ScaleFactor;
    Max *= ScaleFactor;
}

void CObject::Rotate(float Angle, const vec3 &Axis)
{
    mat3x3 RotationMatrix = mat3x3(rotate(Angle, Axis));

    for(int i = 0; i < VerticesCount; i++)
    {
        Vertices[i].Position = RotationMatrix * Vertices[i].Position;
        Vertices[i].Normal = RotationMatrix * Vertices[i].Normal;
    }

    GetMinMax();
}

void CObject::Destroy()
{
    Texture.Destroy();

    if(Vertices != NULL)
    {
        delete [] Vertices;
    }

    SetDefaults();
}

bool CObject::ReadSource(char *Directory, char *FileName, char **Source, long &Length)
{
    CString PathFileName = ModuleDirectory + Directory + FileName;

    FILE *File;

    if((File = fopen(PathFileName, "rb")) == NULL)
    {
        ErrorLog.Append("Error opening file " + PathFileName + "!\r\n");
        return false;
    }

    fseek(File, 0, SEEK_END);
    Length = ftell(File);
    fseek(File, 0, SEEK_SET);
    *Source = new char[Length + 1];
    fread(*Source, 1, Length, File);
    (*Source)[Length] = 0;

    fclose(File);

    for(long i = 0; i < Length; i++)
    {
        if((*Source)[i] == '\r' || (*Source)[i] == '\n') (*Source)[i] = 0;
    }

    return true;
}

bool CObject::ParseMtl(char *Directory, char *MtlFileName)
{
    char *MtlSource;
    long MtlLength;

    if(!ReadSource(Directory, MtlFileName, &MtlSource, MtlLength)) return false;

    char *Line = MtlSource, *End = MtlSource + MtlLength;

    bool Error = false;

    while(Line < End)
    {
        while(Line < End && (*Line == ' ' || *Line == '\t')) Line++;

        if(Line[0] == 'm' && Line[1] == 'a' && Line[2] == 'p' && Line[3] == '_' && Line[4] == 'K' && Line[5] == 'a' && (Line[6] == ' ' || Line[6] == '\t'))
        {
            char *TextureFileName = Line + 6;

            while(TextureFileName < End && (*TextureFileName == ' ' || *TextureFileName == '\t')) TextureFileName++;

            Error |= !Texture.LoadTexture(CString(Directory) + TextureFileName);
        }

        while(Line < End && *Line != 0) Line++;
        while(Line < End && *Line == 0) Line++;
    }

    delete [] MtlSource;

    return !Error;
}

void CObject::GetMinMax()
{
    Min = Max = Vertices[0].Position;

    for(int i = 1; i < VerticesCount; i++)
    {
        if(Min.x > Vertices[i].Position.x) Min.x = Vertices[i].Position.x;
        if(Min.y > Vertices[i].Position.y) Min.y = Vertices[i].Position.y;
        if(Min.z > Vertices[i].Position.z) Min.z = Vertices[i].Position.z;
        if(Max.x < Vertices[i].Position.x) Max.x = Vertices[i].Position.x;
        if(Max.y < Vertices[i].Position.y) Max.y = Vertices[i].Position.y;
        if(Max.z < Vertices[i].Position.z) Max.z = Vertices[i].Position.z;
    }
}

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

void CEdge::Set(CFragment *Fragment1, CFragment *Fragment2)
{
    if(Fragment1->y <= Fragment2->y)
    {
        this->Fragment1 = Fragment1;
        this->Fragment2 = Fragment2;
    }
    else
    {
        this->Fragment1 = Fragment2;
        this->Fragment2 = Fragment1;
    }

    CalculateDiffs();
}

void CEdge::CalculateDiffs()
{
    dx = Fragment2->x - Fragment1->x;
    dy = Fragment2->y - Fragment1->y;
    dz = Fragment2->z - Fragment1->z;
    dw = Fragment2->w - Fragment1->w;

    dr = Fragment2->r - Fragment1->r;
    dg = Fragment2->g - Fragment1->g;
    db = Fragment2->b - Fragment1->b;

    ds = Fragment2->s - Fragment1->s;
    dt = Fragment2->t - Fragment1->t;
}

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

void CSpan::Set(CFragment *Fragment1, CFragment *Fragment2)
{
    if(Fragment1->x <= Fragment2->x)
    {
        this->Fragment1 = Fragment1;
        this->Fragment2 = Fragment2;
    }
    else
    {
        this->Fragment1 = Fragment2;
        this->Fragment2 = Fragment1;
    }

    CalculateDiffs();
}

void CSpan::CalculateDiffs()
{
    dx = Fragment2->x - Fragment1->x;
    dy = Fragment2->y - Fragment1->y;
    dz = Fragment2->z - Fragment1->z;
    dw = Fragment2->w - Fragment1->w;

    dr = Fragment2->r - Fragment1->r;
    dg = Fragment2->g - Fragment1->g;
    db = Fragment2->b - Fragment1->b;

    ds = Fragment2->s - Fragment1->s;
    dt = Fragment2->t - Fragment1->t;
}

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

CSoftwareGL::CSoftwareGL()
{
    Width = 0;
    Height = 0;

    AntiAliasingColorBuffer = NULL;
    AntiAliasingColorBufferWidth = 0;
    AntiAliasingColorBufferHeight = 0;

    StandardColorBuffer = NULL;
    StandardColorBufferWidth = 0;
    StandardColorBufferHeight = 0;

    ColorBuffer = NULL;
    ColorBufferWidth = 0;
    ColorBufferHeight = 0;

    DepthBuffer = NULL;
    DepthBufferWidth = 0;
    DepthBufferHeight = 0;

    AntiAliasing = ANTI_ALIASING_2X2;
    ModelViewProjectionMatrix = mat4x4(1.0f);
    CullFace = CULL_FACE_BACK;
    BilinearTextureFiltering = true;
    Light = NULL;
    Texture = NULL;

    SYSTEM_INFO SystemInfo;

    GetSystemInfo(&SystemInfo);

    ThreadsCount = SystemInfo.dwNumberOfProcessors;

    ThreadsData = new CThreadData[ThreadsCount];

    for(int i = 0; i < ThreadsCount; i++)
    {
        ThreadsData[i].SoftwareGL = this;
        ThreadsData[i].ThreadId = i;
        ThreadsData[i].ThreadIsRunning = false;
        ThreadsData[i].Thread = CreateThread(NULL, 0, ThreadProc, &ThreadsData[i], 0, &ThreadsData[i].Id);
        SetThreadAffinityMask(ThreadsData[i].Thread, 1 << i);
    }

    if(THREAD_SLEEP_TIME > 0)
    {
        timeBeginPeriod(THREAD_SLEEP_TIME);
    }
}

CSoftwareGL::~CSoftwareGL()
{
    if(AntiAliasingColorBuffer != NULL)
    {
        delete [] AntiAliasingColorBuffer;
    }

    if(StandardColorBuffer != NULL)
    {
        delete [] StandardColorBuffer;
    }

    if(DepthBuffer != NULL)
    {
        delete [] DepthBuffer;
    }

    for(int i = 0; i < ThreadsCount; i++)
    {
        TerminateThread(ThreadsData[i].Thread, 0);
        CloseHandle(ThreadsData[i].Thread);
    }

    delete [] ThreadsData;

    if(THREAD_SLEEP_TIME > 0)
    {
        timeEndPeriod(THREAD_SLEEP_TIME);
    }
}

int CSoftwareGL::GetAntiAliasing()
{
    return AntiAliasing;
}

void CSoftwareGL::SetAntiAliasing(int AntiAliasing)
{
    if(AntiAliasing == NONE || AntiAliasing == ANTI_ALIASING_2X2 || AntiAliasing == ANTI_ALIASING_3X3 || AntiAliasing == ANTI_ALIASING_4X4)
    {
        if(this->AntiAliasing != AntiAliasing)
        {
            this->AntiAliasing = AntiAliasing;

            Viewport(Width, Height);
        }
    }
}

int CSoftwareGL::GetCullFace()
{
    return CullFace;
}

void CSoftwareGL::SetCullFace(int CullFace)
{
    if(CullFace == NONE || CullFace == CULL_FACE_FRONT || CullFace == CULL_FACE_BACK)
    {
        this->CullFace = CullFace;
    }
}

bool CSoftwareGL::GetBilinearTextureFiltering()
{
    return BilinearTextureFiltering;
}

void CSoftwareGL::SetBilinearTextureFiltering(bool BilinearTextureFiltering)
{
    this->BilinearTextureFiltering = BilinearTextureFiltering;
}

int CSoftwareGL::GetThreadsCount()
{
    return ThreadsCount;
}

void CSoftwareGL::Clear()
{
    if(ColorBuffer != NULL)
    {
        memset(ColorBuffer, 0, ColorBufferWidth * ColorBufferHeight * 3);
    }

    if(DepthBuffer != NULL)
    {
        memset(DepthBuffer, 255, DepthBufferWidth * DepthBufferHeight * 2);
    }
}

void CSoftwareGL::LoadModelViewProjectionMatrix(const mat4x4 &ModelViewProjectionMatrix)
{
    this->ModelViewProjectionMatrix = ModelViewProjectionMatrix;
}

void CSoftwareGL::BindLight(CLight *Light)
{
    this->Light = Light;
}

void CSoftwareGL::BindTexture(CTexture *Texture)
{
    this->Texture = Texture;
}

void CSoftwareGL::DrawTriangles(CVertex *Vertices, int FirstIndex, int Count)
{
    if(ColorBuffer == NULL || DepthBuffer == NULL || Vertices == NULL || FirstIndex < 0 || Count < 3) return;

    this->Vertices = Vertices;
    this->FirstIndex = FirstIndex;
    this->LastIndex = FirstIndex + Count / 3 * 3;

    RunFunctionMultiThreadedAndWaitForCompletion(1);
}

void CSoftwareGL::RunFunctionMultiThreadedAndWaitForCompletion(int Function)
{
    for(int i = 0; i < ThreadsCount; i++)
    {
        ThreadsData[i].Function = Function;
        ThreadsData[i].ThreadIsRunning = true;
    }

    while(ThreadsAreRunning())
    {
        Sleep(THREAD_SLEEP_TIME);
    }
}

bool CSoftwareGL::ThreadsAreRunning()
{
    bool ThreadsAreRunning = false;

    for(int i = 0; i < ThreadsCount; i++)
    {
        ThreadsAreRunning |= ThreadsData[i].ThreadIsRunning;
    }

    return ThreadsAreRunning;
}

DWORD WINAPI CSoftwareGL::ThreadProc(LPVOID lpParam)
{
    CThreadData *ThreadData = (CThreadData*)lpParam;
    CSoftwareGL *SoftwareGL = (CSoftwareGL*)ThreadData->SoftwareGL;

    while(1)
    {
        while(!ThreadData->ThreadIsRunning)
        {
            Sleep(THREAD_SLEEP_TIME);
        }

        switch(ThreadData->Function)
        {
            case 1: SoftwareGL->DrawTriangles(ThreadData->ThreadId); break;
            case 2: SoftwareGL->BlitAntiAliasingColorBuffer2x2(ThreadData->ThreadId); break;
            case 3: SoftwareGL->BlitAntiAliasingColorBuffer3x3(ThreadData->ThreadId); break;
            case 4: SoftwareGL->BlitAntiAliasingColorBuffer4x4(ThreadData->ThreadId); break;
        }

        ThreadData->ThreadIsRunning = false;
    }

    return 0;
}

void CSoftwareGL::DrawTriangles(int ThreadId)
{
    float *ModelViewProjectionMatrix = &this->ModelViewProjectionMatrix;

    // primitive assembly

    CFragment Fragments[3];

    for(int i = FirstIndex; i < LastIndex; i += 3)
    {
        CVertex *Vertex = Vertices + i;
        CFragment *Fragment = Fragments;

        for(int j = 0; j < 3; j++)
        {
            // vertex shader

            Fragment->x = ModelViewProjectionMatrix[0] * Vertex->Position.x + ModelViewProjectionMatrix[4] * Vertex->Position.y + ModelViewProjectionMatrix[8] * Vertex->Position.z + ModelViewProjectionMatrix[12];
            Fragment->y = ModelViewProjectionMatrix[1] * Vertex->Position.x + ModelViewProjectionMatrix[5] * Vertex->Position.y + ModelViewProjectionMatrix[9] * Vertex->Position.z + ModelViewProjectionMatrix[13];
            Fragment->z = ModelViewProjectionMatrix[2] * Vertex->Position.x + ModelViewProjectionMatrix[6] * Vertex->Position.y + ModelViewProjectionMatrix[10] * Vertex->Position.z + ModelViewProjectionMatrix[14];
            Fragment->w = ModelViewProjectionMatrix[3] * Vertex->Position.x + ModelViewProjectionMatrix[7] * Vertex->Position.y + ModelViewProjectionMatrix[11] * Vertex->Position.z + ModelViewProjectionMatrix[15];

            Fragment->r = Vertex->Color.r;
            Fragment->g = Vertex->Color.g;
            Fragment->b = Vertex->Color.b;

            Fragment->s = Vertex->TexCoord.s;
            Fragment->t = Vertex->TexCoord.t;

            if(Light != NULL)
            {
                vec3 LightDirection;

                LightDirection.x = Light->Position.x - Vertex->Position.x;
                LightDirection.y = Light->Position.y - Vertex->Position.y;
                LightDirection.z = Light->Position.z - Vertex->Position.z;

                float LightDistance2 = LightDirection.x * LightDirection.x + LightDirection.y * LightDirection.y + LightDirection.z * LightDirection.z;

                float LightDistance = sqrt(LightDistance2);

                float NdotLD = (LightDirection.x * Vertex->Normal.x + LightDirection.y * Vertex->Normal.y + LightDirection.z * Vertex->Normal.z) / LightDistance;

                if(NdotLD < 0.0f) NdotLD = 0.0f;

                float LightAttenuation = 1.0f / (Light->ConstantAttenuation + Light->LinearAttenuation * LightDistance + Light->QuadraticAttenuation * LightDistance2);

                Fragment->r *= (Light->Ambient.r + Light->Diffuse.r * NdotLD) * LightAttenuation;
                Fragment->g *= (Light->Ambient.g + Light->Diffuse.g * NdotLD) * LightAttenuation;
                Fragment->b *= (Light->Ambient.b + Light->Diffuse.b * NdotLD) * LightAttenuation;
            }

            Vertex++;
            Fragment++;
        }

        // clipping

        ClipTriangle(Fragments, ThreadId);
    }
}

// ----------------------------------------------------------------------------------------------------------------------------
//
// clipping using homogeneous coordinates
//
// ----------------------------------------------------------------------------------------------------------------------------
//
// c equals the x or y or z coordinate of a fragment
//
// ----------------------------------------------------------------------------------------------------------------------------
//
// a fragment of an edge is in front of or lies on a negative clip plane: - w <= c
//
// a fragment of an edge is behind a negative clip plane: - w > c
//
// to find the intersection of an edge with a negative clip plane we need to find t for which: - w = c
//
// ----------------------------------------------------------------------------------------------------------------------------
//
// w = w1 + (w2 - w1) * t
// c = c1 + (c2 - c1) * t
//
// - w = c
//
// - (w1 + (w2 - w1) * t) = c1 + (c2 - c1) * t
// - w1 - (w2 - w1) * t = c1 + (c2 - c1) * t
// - (w2 - w1) * t = c1 + w1 + (c2 - c1) * t
// - (c2 - c1) * t - (w2 - w1) * t = c1 + w1
// (c2 - c1) * t + (w2 - w1) * t = - c1 - w1
// (c2 - c1 + w2 - w1) * t = - c1 - w1
// t = (- c1 - w1) / (c2 - c1 + w2 - w1)
// t = (c1 + w1) / (- c2 + c1 - w2 + w1)
// t = (c1 + w1) / (c1 - c2 + w1 - w2)
//
// ----------------------------------------------------------------------------------------------------------------------------
//
// a fragment of an edge is in front of or lies on a positive clip plane: c <= w
//
// a fragment of an edge is behind a positive clip plane: c > w
//
// to find the intersection of an edge with a positive clip plane we need to find t for which: c = w
//
// ----------------------------------------------------------------------------------------------------------------------------
//
// c = c1 + (c2 - c1) * t
// w = w1 + (w2 - w1) * t
//
// c = w
//
// c1 + (c2 - c1) * t = w1 + (w2 - w1) * t
// (c2 - c1) * t = w1 - c1 + (w2 - w1) * t
// (c2 - c1) * t - (w2 - w1) * t = w1 - c1
// ((c2 - c1) - (w2 - w1)) * t = w1 - c1
// (c2 - c1 - w2 + w1) * t = w1 - c1
// t = (w1 - c1) / (c2 - c1 - w2 + w1)
// t = (- w1 + c1) / (- c2 + c1 + w2 - w1)
// t = (c1 - w1) / (c1 - c2 - w1 + w2)
//
// ----------------------------------------------------------------------------------------------------------------------------

void CSoftwareGL::ClipTriangle(CFragment *Fragments, int ThreadId, int ClipPlane)
{
    if(ClipPlane >= 1 && ClipPlane <= 6)
    {
        int fifocp, fbcp, fbcpc = 0; // fragment in front of clip plane, fragment behind clip plane, fragments behind clip plane count

        // visibility testing

        CFragment *Fragment = Fragments;

        for(int i = 0; i < 3; i++)
        {
            bool Visible;

            switch(ClipPlane)
            {
                case 1: Visible = -Fragment->w <= Fragment->x; break;
                case 2: Visible =  Fragment->x <= Fragment->w; break;
                case 3: Visible = -Fragment->w <= Fragment->y; break;
                case 4: Visible =  Fragment->y <= Fragment->w; break;
                case 5: Visible = -Fragment->w <= Fragment->z; break;
                case 6: Visible =  Fragment->z <= Fragment->w; break;
            }

            if(Visible)
            {
                fifocp = i;
            }
            else
            {
                fbcp = i;
                fbcpc++;
            }

            Fragment++;
        }

        // clipping

        if(fbcpc == 3)
        {
            return;
        }
        else if(fbcpc == 2)
        {
            CFragment *Fragment1 = Fragments + (fifocp + 1) % 3;
            CFragment *Fragment2 = Fragments + (fifocp + 2) % 3;
            CFragment *Fragment3 = Fragments + fifocp;

            float t1, t2;

            switch(ClipPlane)
            {
                case 1:
                    t1 = (Fragment1->x + Fragment1->w) / (Fragment1->x - Fragment3->x + Fragment1->w - Fragment3->w);
                    t2 = (Fragment2->x + Fragment2->w) / (Fragment2->x - Fragment3->x + Fragment2->w - Fragment3->w);
                    break;

                case 2:
                    t1 = (Fragment1->x - Fragment1->w) / (Fragment1->x - Fragment3->x - Fragment1->w + Fragment3->w);
                    t2 = (Fragment2->x - Fragment2->w) / (Fragment2->x - Fragment3->x - Fragment2->w + Fragment3->w);
                    break;

                case 3:
                    t1 = (Fragment1->y + Fragment1->w) / (Fragment1->y - Fragment3->y + Fragment1->w - Fragment3->w);
                    t2 = (Fragment2->y + Fragment2->w) / (Fragment2->y - Fragment3->y + Fragment2->w - Fragment3->w);
                    break;

                case 4:
                    t1 = (Fragment1->y - Fragment1->w) / (Fragment1->y - Fragment3->y - Fragment1->w + Fragment3->w);
                    t2 = (Fragment2->y - Fragment2->w) / (Fragment2->y - Fragment3->y - Fragment2->w + Fragment3->w);
                    break;

                case 5:
                    t1 = (Fragment1->z + Fragment1->w) / (Fragment1->z - Fragment3->z + Fragment1->w - Fragment3->w);
                    t2 = (Fragment2->z + Fragment2->w) / (Fragment2->z - Fragment3->z + Fragment2->w - Fragment3->w);
                    break;

                case 6:
                    t1 = (Fragment1->z - Fragment1->w) / (Fragment1->z - Fragment3->z - Fragment1->w + Fragment3->w);
                    t2 = (Fragment2->z - Fragment2->w) / (Fragment2->z - Fragment3->z - Fragment2->w + Fragment3->w);
                    break;
            }

            Fragment1->x += (Fragment3->x - Fragment1->x) * t1;
            Fragment1->y += (Fragment3->y - Fragment1->y) * t1;
            Fragment1->z += (Fragment3->z - Fragment1->z) * t1;
            Fragment1->w += (Fragment3->w - Fragment1->w) * t1;

            Fragment1->r += (Fragment3->r - Fragment1->r) * t1;
            Fragment1->g += (Fragment3->g - Fragment1->g) * t1;
            Fragment1->b += (Fragment3->b - Fragment1->b) * t1;

            Fragment1->s += (Fragment3->s - Fragment1->s) * t1;
            Fragment1->t += (Fragment3->t - Fragment1->t) * t1;

            Fragment2->x += (Fragment3->x - Fragment2->x) * t2;
            Fragment2->y += (Fragment3->y - Fragment2->y) * t2;
            Fragment2->z += (Fragment3->z - Fragment2->z) * t2;
            Fragment2->w += (Fragment3->w - Fragment2->w) * t2;

            Fragment2->r += (Fragment3->r - Fragment2->r) * t2;
            Fragment2->g += (Fragment3->g - Fragment2->g) * t2;
            Fragment2->b += (Fragment3->b - Fragment2->b) * t2;

            Fragment2->s += (Fragment3->s - Fragment2->s) * t2;
            Fragment2->t += (Fragment3->t - Fragment2->t) * t2;

            ClipTriangle(Fragments, ThreadId, ClipPlane + 1);
        }
        else if(fbcpc == 1)
        {
            int if1 = (fbcp + 1) % 3, if2 = (fbcp + 2) % 3;

            CFragment *Fragment1 = Fragments + if1;
            CFragment *Fragment2 = Fragments + if2;
            CFragment *Fragment3 = Fragments + fbcp;

            CFragment NewFragments[3];

            CFragment *NewFragment1 = NewFragments + if1;
            CFragment *NewFragment2 = NewFragments + if2;
            CFragment *NewFragment3 = NewFragments + fbcp;

            float t1, t2;

            switch(ClipPlane)
            {
                case 1:
                    t1 = (Fragment3->x + Fragment3->w) / (Fragment3->x - Fragment1->x + Fragment3->w - Fragment1->w);
                    t2 = (Fragment3->x + Fragment3->w) / (Fragment3->x - Fragment2->x + Fragment3->w - Fragment2->w);
                    break;

                case 2:
                    t1 = (Fragment3->x - Fragment3->w) / (Fragment3->x - Fragment1->x - Fragment3->w + Fragment1->w);
                    t2 = (Fragment3->x - Fragment3->w) / (Fragment3->x - Fragment2->x - Fragment3->w + Fragment2->w);
                    break;

                case 3:
                    t1 = (Fragment3->y + Fragment3->w) / (Fragment3->y - Fragment1->y + Fragment3->w - Fragment1->w);
                    t2 = (Fragment3->y + Fragment3->w) / (Fragment3->y - Fragment2->y + Fragment3->w - Fragment2->w);
                    break;

                case 4:
                    t1 = (Fragment3->y - Fragment3->w) / (Fragment3->y - Fragment1->y - Fragment3->w + Fragment1->w);
                    t2 = (Fragment3->y - Fragment3->w) / (Fragment3->y - Fragment2->y - Fragment3->w + Fragment2->w);
                    break;

                case 5:
                    t1 = (Fragment3->z + Fragment3->w) / (Fragment3->z - Fragment1->z + Fragment3->w - Fragment1->w);
                    t2 = (Fragment3->z + Fragment3->w) / (Fragment3->z - Fragment2->z + Fragment3->w - Fragment2->w);
                    break;

                case 6:
                    t1 = (Fragment3->z - Fragment3->w) / (Fragment3->z - Fragment1->z - Fragment3->w + Fragment1->w);
                    t2 = (Fragment3->z - Fragment3->w) / (Fragment3->z - Fragment2->z - Fragment3->w + Fragment2->w);
                    break;
            }

            NewFragment3->x = Fragment3->x;
            NewFragment3->y = Fragment3->y;
            NewFragment3->z = Fragment3->z;
            NewFragment3->w = Fragment3->w;

            NewFragment3->r = Fragment3->r;
            NewFragment3->g = Fragment3->g;
            NewFragment3->b = Fragment3->b;

            NewFragment3->s = Fragment3->s;
            NewFragment3->t = Fragment3->t;

            NewFragment2->x = Fragment2->x;
            NewFragment2->y = Fragment2->y;
            NewFragment2->z = Fragment2->z;
            NewFragment2->w = Fragment2->w;

            NewFragment2->r = Fragment2->r;
            NewFragment2->g = Fragment2->g;
            NewFragment2->b = Fragment2->b;

            NewFragment2->s = Fragment2->s;
            NewFragment2->t = Fragment2->t;

            Fragment3->x += (Fragment1->x - Fragment3->x) * t1;
            Fragment3->y += (Fragment1->y - Fragment3->y) * t1;
            Fragment3->z += (Fragment1->z - Fragment3->z) * t1;
            Fragment3->w += (Fragment1->w - Fragment3->w) * t1;

            Fragment3->r += (Fragment1->r - Fragment3->r) * t1;
            Fragment3->g += (Fragment1->g - Fragment3->g) * t1;
            Fragment3->b += (Fragment1->b - Fragment3->b) * t1;

            Fragment3->s += (Fragment1->s - Fragment3->s) * t1;
            Fragment3->t += (Fragment1->t - Fragment3->t) * t1;

            NewFragment1->x = Fragment3->x;
            NewFragment1->y = Fragment3->y;
            NewFragment1->z = Fragment3->z;
            NewFragment1->w = Fragment3->w;

            NewFragment1->r = Fragment3->r;
            NewFragment1->g = Fragment3->g;
            NewFragment1->b = Fragment3->b;

            NewFragment1->s = Fragment3->s;
            NewFragment1->t = Fragment3->t;

            NewFragment3->x += (NewFragment2->x - NewFragment3->x) * t2;
            NewFragment3->y += (NewFragment2->y - NewFragment3->y) * t2;
            NewFragment3->z += (NewFragment2->z - NewFragment3->z) * t2;
            NewFragment3->w += (NewFragment2->w - NewFragment3->w) * t2;

            NewFragment3->r += (NewFragment2->r - NewFragment3->r) * t2;
            NewFragment3->g += (NewFragment2->g - NewFragment3->g) * t2;
            NewFragment3->b += (NewFragment2->b - NewFragment3->b) * t2;

            NewFragment3->s += (NewFragment2->s - NewFragment3->s) * t2;
            NewFragment3->t += (NewFragment2->t - NewFragment3->t) * t2;

            ClipTriangle(Fragments, ThreadId, ClipPlane + 1);
            ClipTriangle(NewFragments, ThreadId, ClipPlane + 1);
        }
        else if(fbcpc == 0)
        {
            ClipTriangle(Fragments, ThreadId, ClipPlane + 1);
        }
    }
    else if(ClipPlane == 7)
    {
        RasterizeTriangle(Fragments, ThreadId);
    }
}

void CSoftwareGL::RasterizeTriangle(CFragment *Fragments, int ThreadId)
{
    CFragment *Fragment1 = Fragments, *Fragment2 = Fragment1 + 1, *Fragment3 = Fragment2 + 1;

    // projection

    Fragment1->w = 1.0f / Fragment1->w;

    Fragment1->x *= Fragment1->w;
    Fragment1->y *= Fragment1->w;

    Fragment2->w = 1.0f / Fragment2->w;

    Fragment2->x *= Fragment2->w;
    Fragment2->y *= Fragment2->w;

    Fragment3->w = 1.0f / Fragment3->w;

    Fragment3->x *= Fragment3->w;
    Fragment3->y *= Fragment3->w;

    // culling

    if(CullFace)
    {
        float a = 0.0f;

        a += Fragment1->x * Fragment2->y - Fragment2->x * Fragment1->y;
        a += Fragment2->x * Fragment3->y - Fragment3->x * Fragment2->y;
        a += Fragment3->x * Fragment1->y - Fragment1->x * Fragment3->y;

        switch(CullFace)
        {
            case CULL_FACE_FRONT:
                if(a >= 0.0f) return;
                break;

            case CULL_FACE_BACK:
                if(a <= 0.0f) return;
                break;
        }
    }

    // perspective correct z and attribute values interpolation

    // http://www.comp.nus.edu.sg/~lowkl/publications/lowk_persp_interp_techrep.pdf

    Fragment1->x = (Fragment1->x * 0.5f + 0.5f) * DepthBufferWidth;
    Fragment1->y = (Fragment1->y * 0.5f + 0.5f) * DepthBufferHeight;
    Fragment1->z = Fragment1->z * Fragment1->w * 0.5f + 0.5f;

    Fragment1->r *= Fragment1->w;
    Fragment1->g *= Fragment1->w;
    Fragment1->b *= Fragment1->w;

    Fragment1->s *= Fragment1->w;
    Fragment1->t *= Fragment1->w;

    Fragment2->x = (Fragment2->x * 0.5f + 0.5f) * DepthBufferWidth;
    Fragment2->y = (Fragment2->y * 0.5f + 0.5f) * DepthBufferHeight;
    Fragment2->z = Fragment2->z * Fragment2->w * 0.5f + 0.5f;

    Fragment2->r *= Fragment2->w;
    Fragment2->g *= Fragment2->w;
    Fragment2->b *= Fragment2->w;

    Fragment2->s *= Fragment2->w;
    Fragment2->t *= Fragment2->w;

    Fragment3->x = (Fragment3->x * 0.5f + 0.5f) * DepthBufferWidth;
    Fragment3->y = (Fragment3->y * 0.5f + 0.5f) * DepthBufferHeight;
    Fragment3->z = Fragment3->z * Fragment3->w * 0.5f + 0.5f;

    Fragment3->r *= Fragment3->w;
    Fragment3->g *= Fragment3->w;
    Fragment3->b *= Fragment3->w;

    Fragment3->s *= Fragment3->w;
    Fragment3->t *= Fragment3->w;

    // triangle rasterization

    // http://joshbeam.com/articles/triangle_rasterization/

    CEdge Edges[3], *Edge1 = Edges, *Edge2 = Edge1 + 1, *Edge3 = Edge2 + 1;

    Edge1->Set(Fragment1, Fragment2);
    Edge2->Set(Fragment2, Fragment3);
    Edge3->Set(Fragment3, Fragment1);

    CEdge *LongEdge = Edge1, *ShortEdge1 = Edge2, *ShortEdge2 = Edge3;

    if(Edge2->dy > LongEdge->dy)
    {
        LongEdge = Edge2;
        ShortEdge1 = Edge3;
        ShortEdge2 = Edge1;
    }

    if(Edge3->dy > LongEdge->dy)
    {
        LongEdge = Edge3;
        ShortEdge1 = Edge1;
        ShortEdge2 = Edge2;
    }

    DrawSpansBetweenEdges(LongEdge, ShortEdge1, ThreadId);
    DrawSpansBetweenEdges(LongEdge, ShortEdge2, ThreadId);
}

void CSoftwareGL::DrawSpansBetweenEdges(CEdge *Edge1, CEdge *Edge2, int ThreadId)
{
    if(Edge1->dy == 0.0f || Edge2->dy == 0.0f) return;

    int iy1 = (int)Edge2->Fragment1->y, iy2 = (int)Edge2->Fragment2->y;

    while(iy1 % ThreadsCount != ThreadId) iy1++;

    if(iy2 == DepthBufferHeight) iy2--;

    float fy, factor1, factor2, ode1dy = 1.0f / Edge1->dy, ode2dy = 1.0f / Edge2->dy;

    CFragment Fragment1, Fragment2;

    CSpan Span;

    for(int y = iy1; y <= iy2; y += ThreadsCount)
    {
        fy = y + 0.5f;

        factor1 = (fy - Edge1->Fragment1->y) * ode1dy;

        if(factor1 < 0.0f || factor1 > 1.0f) continue;

        factor2 = (fy - Edge2->Fragment1->y) * ode2dy;

        if(factor2 < 0.0f || factor2 > 1.0f) continue;

        Fragment1.x = Edge1->Fragment1->x + Edge1->dx * factor1;
        Fragment1.y = Edge1->Fragment1->y + Edge1->dy * factor1;
        Fragment1.z = Edge1->Fragment1->z + Edge1->dz * factor1;
        Fragment1.w = Edge1->Fragment1->w + Edge1->dw * factor1;

        Fragment1.r = Edge1->Fragment1->r + Edge1->dr * factor1;
        Fragment1.g = Edge1->Fragment1->g + Edge1->dg * factor1;
        Fragment1.b = Edge1->Fragment1->b + Edge1->db * factor1;

        Fragment1.s = Edge1->Fragment1->s + Edge1->ds * factor1;
        Fragment1.t = Edge1->Fragment1->t + Edge1->dt * factor1;

        Fragment2.x = Edge2->Fragment1->x + Edge2->dx * factor2;
        Fragment2.y = Edge2->Fragment1->y + Edge2->dy * factor2;
        Fragment2.z = Edge2->Fragment1->z + Edge2->dz * factor2;
        Fragment2.w = Edge2->Fragment1->w + Edge2->dw * factor2;

        Fragment2.r = Edge2->Fragment1->r + Edge2->dr * factor2;
        Fragment2.g = Edge2->Fragment1->g + Edge2->dg * factor2;
        Fragment2.b = Edge2->Fragment1->b + Edge2->db * factor2;

        Fragment2.s = Edge2->Fragment1->s + Edge2->ds * factor2;
        Fragment2.t = Edge2->Fragment1->t + Edge2->dt * factor2;

        Span.Set(&Fragment1, &Fragment2);

        DrawSpan(&Span, y);
    }
}

void CSoftwareGL::DrawSpan(CSpan *Span, int y)
{
    if(Span->dx == 0.0f) return;

    int ix1 = (int)Span->Fragment1->x, ix2 = (int)Span->Fragment2->x;

    if(ix2 == DepthBufferWidth) ix2--;

    float factor, odsdx = 1.0f / Span->dx, z, w, r, g, b, s, t, tr, tg, tb;
    USHORT Depth;

    int ColorBufferIndex = (ColorBufferWidth * y + ix1) * 3;
    int DepthBufferIndex = DepthBufferWidth * y + ix1;

    for(int x = ix1; x <= ix2; x++)
    {
        factor = (x + 0.5f - Span->Fragment1->x) * odsdx;

        if(factor >= 0.0f && factor <= 1.0f)
        {
            z = Span->Fragment1->z + Span->dz * factor;

            Depth = (USHORT)(z * 65535.0f);

            // depth test

            if(Depth <= DepthBuffer[DepthBufferIndex])
            {
                w = 1.0f / (Span->Fragment1->w + Span->dw * factor);

                r = (Span->Fragment1->r + Span->dr * factor) * w;
                g = (Span->Fragment1->g + Span->dg * factor) * w;
                b = (Span->Fragment1->b + Span->db * factor) * w;

                s = (Span->Fragment1->s + Span->ds * factor) * w;
                t = (Span->Fragment1->t + Span->dt * factor) * w;

                // fragment shader

                if(Texture != NULL)
                {
                    if(BilinearTextureFiltering)
                    {
                        Texture->GetColorBilinear(s, t, &tr, &tg, &tb);
                    }
                    else
                    {
                        Texture->GetColorNearest(s, t, &tr, &tg, &tb);
                    }

                    r *= tr;
                    g *= tg;
                    b *= tb;
                }

                // writing to buffers

                ColorBuffer[ColorBufferIndex++] = /*b <= 0.0f ? 0 : b >= 1.0f ? 255 :*/ (BYTE)(b * 255.0f);
                ColorBuffer[ColorBufferIndex++] = /*g <= 0.0f ? 0 : g >= 1.0f ? 255 :*/ (BYTE)(g * 255.0f);
                ColorBuffer[ColorBufferIndex++] = /*r <= 0.0f ? 0 : r >= 1.0f ? 255 :*/ (BYTE)(r * 255.0f);

                DepthBuffer[DepthBufferIndex++] = Depth;
            }
            else
            {
                ColorBufferIndex += 3;
                DepthBufferIndex++;
            }
        }
        else
        {
            ColorBufferIndex += 3;
            DepthBufferIndex++;
        }
    }
}

void CSoftwareGL::BlitAntiAliasingColorBuffer2x2(int ThreadId)
{
    int TCM2 = ThreadsCount * 2, AACBWM3 = AntiAliasingColorBufferWidth * 3;
    int LineAACB0, LineAACB1, LineSCB;
    int xm3;
    int LineAACB0Px0, LineAACB0Px1;
    int LineAACB1Px0, LineAACB1Px1;
    int LineSCBPxD2M3;
    float r, g, b;

    for(int y = ThreadId * 2; y < AntiAliasingColorBufferHeight; y += TCM2)
    {
        LineAACB0 = AACBWM3 * y;
        LineAACB1 = LineAACB0 + AACBWM3;
        LineSCB = StandardColorBufferWidth * y / 2 * 3;

        for(int x = 0; x < AntiAliasingColorBufferWidth; x += 2)
        {
            xm3 = x * 3;

            LineAACB0Px0 = LineAACB0 + xm3;
            LineAACB0Px1 = LineAACB0Px0 + 3;
            LineAACB1Px0 = LineAACB1 + xm3;
            LineAACB1Px1 = LineAACB1Px0 + 3;

            LineSCBPxD2M3 = LineSCB + x / 2 * 3;

            b = AntiAliasingColorBuffer[LineAACB0Px0++];
            g = AntiAliasingColorBuffer[LineAACB0Px0++];
            r = AntiAliasingColorBuffer[LineAACB0Px0];

            b += AntiAliasingColorBuffer[LineAACB0Px1++];
            g += AntiAliasingColorBuffer[LineAACB0Px1++];
            r += AntiAliasingColorBuffer[LineAACB0Px1];

            b += AntiAliasingColorBuffer[LineAACB1Px0++];
            g += AntiAliasingColorBuffer[LineAACB1Px0++];
            r += AntiAliasingColorBuffer[LineAACB1Px0];

            b += AntiAliasingColorBuffer[LineAACB1Px1++];
            g += AntiAliasingColorBuffer[LineAACB1Px1++];
            r += AntiAliasingColorBuffer[LineAACB1Px1];

            StandardColorBuffer[LineSCBPxD2M3++] = (BYTE)(b / 4.0f);
            StandardColorBuffer[LineSCBPxD2M3++] = (BYTE)(g / 4.0f);
            StandardColorBuffer[LineSCBPxD2M3] = (BYTE)(r / 4.0f);
        }
    }
}

void CSoftwareGL::BlitAntiAliasingColorBuffer3x3(int ThreadId)
{
    int TCM3 = ThreadsCount * 3, AACBWM3 = AntiAliasingColorBufferWidth * 3;
    int LineAACB0, LineAACB1, LineAACB2, LineSCB;
    int xm3;
    int LineAACB0Px0, LineAACB0Px1, LineAACB0Px2;
    int LineAACB1Px0, LineAACB1Px1, LineAACB1Px2;
    int LineAACB2Px0, LineAACB2Px1, LineAACB2Px2;
    int LineSCBPxD3M3;
    float r, g, b;

    for(int y = ThreadId * 3; y < AntiAliasingColorBufferHeight; y += TCM3)
    {
        LineAACB0 = AACBWM3 * y;
        LineAACB1 = LineAACB0 + AACBWM3;
        LineAACB2 = LineAACB1 + AACBWM3;
        LineSCB = StandardColorBufferWidth * y;

        for(int x = 0; x < AntiAliasingColorBufferWidth; x += 3)
        {
            xm3 = x * 3;

            LineAACB0Px0 = LineAACB0 + xm3;
            LineAACB0Px1 = LineAACB0Px0 + 3;
            LineAACB0Px2 = LineAACB0Px1 + 3;
            LineAACB1Px0 = LineAACB1 + xm3;
            LineAACB1Px1 = LineAACB1Px0 + 3;
            LineAACB1Px2 = LineAACB1Px1 + 3;
            LineAACB2Px0 = LineAACB2 + xm3;
            LineAACB2Px1 = LineAACB2Px0 + 3;
            LineAACB2Px2 = LineAACB2Px1 + 3;

            LineSCBPxD3M3 = LineSCB + x;

            b = AntiAliasingColorBuffer[LineAACB0Px0++];
            g = AntiAliasingColorBuffer[LineAACB0Px0++];
            r = AntiAliasingColorBuffer[LineAACB0Px0];

            b += AntiAliasingColorBuffer[LineAACB0Px1++];
            g += AntiAliasingColorBuffer[LineAACB0Px1++];
            r += AntiAliasingColorBuffer[LineAACB0Px1];

            b += AntiAliasingColorBuffer[LineAACB0Px2++];
            g += AntiAliasingColorBuffer[LineAACB0Px2++];
            r += AntiAliasingColorBuffer[LineAACB0Px2];

            b += AntiAliasingColorBuffer[LineAACB1Px0++];
            g += AntiAliasingColorBuffer[LineAACB1Px0++];
            r += AntiAliasingColorBuffer[LineAACB1Px0];

            b += AntiAliasingColorBuffer[LineAACB1Px1++];
            g += AntiAliasingColorBuffer[LineAACB1Px1++];
            r += AntiAliasingColorBuffer[LineAACB1Px1];

            b += AntiAliasingColorBuffer[LineAACB1Px2++];
            g += AntiAliasingColorBuffer[LineAACB1Px2++];
            r += AntiAliasingColorBuffer[LineAACB1Px2];

            b += AntiAliasingColorBuffer[LineAACB2Px0++];
            g += AntiAliasingColorBuffer[LineAACB2Px0++];
            r += AntiAliasingColorBuffer[LineAACB2Px0];

            b += AntiAliasingColorBuffer[LineAACB2Px1++];
            g += AntiAliasingColorBuffer[LineAACB2Px1++];
            r += AntiAliasingColorBuffer[LineAACB2Px1];

            b += AntiAliasingColorBuffer[LineAACB2Px2++];
            g += AntiAliasingColorBuffer[LineAACB2Px2++];
            r += AntiAliasingColorBuffer[LineAACB2Px2];

            StandardColorBuffer[LineSCBPxD3M3++] = (BYTE)(b / 9.0f);
            StandardColorBuffer[LineSCBPxD3M3++] = (BYTE)(g / 9.0f);
            StandardColorBuffer[LineSCBPxD3M3] = (BYTE)(r / 9.0f);
        }
    }
}

void CSoftwareGL::BlitAntiAliasingColorBuffer4x4(int ThreadId)
{
    int TCM4 = ThreadsCount * 4, AACBWM3 = AntiAliasingColorBufferWidth * 3;
    int LineAACB0, LineAACB1, LineAACB2, LineAACB3, LineSCB;
    int xm3;
    int LineAACB0Px0, LineAACB0Px1, LineAACB0Px2, LineAACB0Px3;
    int LineAACB1Px0, LineAACB1Px1, LineAACB1Px2, LineAACB1Px3;
    int LineAACB2Px0, LineAACB2Px1, LineAACB2Px2, LineAACB2Px3;
    int LineAACB3Px0, LineAACB3Px1, LineAACB3Px2, LineAACB3Px3;
    int LineSCBPxD4M3;
    float r, g, b;

    for(int y = ThreadId * 4; y < AntiAliasingColorBufferHeight; y += TCM4)
    {
        LineAACB0 = AACBWM3 * y;
        LineAACB1 = LineAACB0 + AACBWM3;
        LineAACB2 = LineAACB1 + AACBWM3;
        LineAACB3 = LineAACB2 + AACBWM3;
        LineSCB = StandardColorBufferWidth * y / 4 * 3;

        for(int x = 0; x < AntiAliasingColorBufferWidth; x += 4)
        {
            xm3 = x * 3;

            LineAACB0Px0 = LineAACB0 + xm3;
            LineAACB0Px1 = LineAACB0Px0 + 3;
            LineAACB0Px2 = LineAACB0Px1 + 3;
            LineAACB0Px3 = LineAACB0Px2 + 3;
            LineAACB1Px0 = LineAACB1 + xm3;
            LineAACB1Px1 = LineAACB1Px0 + 3;
            LineAACB1Px2 = LineAACB1Px1 + 3;
            LineAACB1Px3 = LineAACB1Px2 + 3;
            LineAACB2Px0 = LineAACB2 + xm3;
            LineAACB2Px1 = LineAACB2Px0 + 3;
            LineAACB2Px2 = LineAACB2Px1 + 3;
            LineAACB2Px3 = LineAACB2Px2 + 3;
            LineAACB3Px0 = LineAACB3 + xm3;
            LineAACB3Px1 = LineAACB3Px0 + 3;
            LineAACB3Px2 = LineAACB3Px1 + 3;
            LineAACB3Px3 = LineAACB3Px2 + 3;

            LineSCBPxD4M3 = LineSCB + x / 4 * 3;

            b = AntiAliasingColorBuffer[LineAACB0Px0++];
            g = AntiAliasingColorBuffer[LineAACB0Px0++];
            r = AntiAliasingColorBuffer[LineAACB0Px0];

            b += AntiAliasingColorBuffer[LineAACB0Px1++];
            g += AntiAliasingColorBuffer[LineAACB0Px1++];
            r += AntiAliasingColorBuffer[LineAACB0Px1];

            b += AntiAliasingColorBuffer[LineAACB0Px2++];
            g += AntiAliasingColorBuffer[LineAACB0Px2++];
            r += AntiAliasingColorBuffer[LineAACB0Px2];

            b += AntiAliasingColorBuffer[LineAACB0Px3++];
            g += AntiAliasingColorBuffer[LineAACB0Px3++];
            r += AntiAliasingColorBuffer[LineAACB0Px3];

            b += AntiAliasingColorBuffer[LineAACB1Px0++];
            g += AntiAliasingColorBuffer[LineAACB1Px0++];
            r += AntiAliasingColorBuffer[LineAACB1Px0];

            b += AntiAliasingColorBuffer[LineAACB1Px1++];
            g += AntiAliasingColorBuffer[LineAACB1Px1++];
            r += AntiAliasingColorBuffer[LineAACB1Px1];

            b += AntiAliasingColorBuffer[LineAACB1Px2++];
            g += AntiAliasingColorBuffer[LineAACB1Px2++];
            r += AntiAliasingColorBuffer[LineAACB1Px2];

            b += AntiAliasingColorBuffer[LineAACB1Px3++];
            g += AntiAliasingColorBuffer[LineAACB1Px3++];
            r += AntiAliasingColorBuffer[LineAACB1Px3];

            b += AntiAliasingColorBuffer[LineAACB2Px0++];
            g += AntiAliasingColorBuffer[LineAACB2Px0++];
            r += AntiAliasingColorBuffer[LineAACB2Px0];

            b += AntiAliasingColorBuffer[LineAACB2Px1++];
            g += AntiAliasingColorBuffer[LineAACB2Px1++];
            r += AntiAliasingColorBuffer[LineAACB2Px1];

            b += AntiAliasingColorBuffer[LineAACB2Px2++];
            g += AntiAliasingColorBuffer[LineAACB2Px2++];
            r += AntiAliasingColorBuffer[LineAACB2Px2];

            b += AntiAliasingColorBuffer[LineAACB2Px3++];
            g += AntiAliasingColorBuffer[LineAACB2Px3++];
            r += AntiAliasingColorBuffer[LineAACB2Px3];

            b += AntiAliasingColorBuffer[LineAACB3Px0++];
            g += AntiAliasingColorBuffer[LineAACB3Px0++];
            r += AntiAliasingColorBuffer[LineAACB3Px0];

            b += AntiAliasingColorBuffer[LineAACB3Px1++];
            g += AntiAliasingColorBuffer[LineAACB3Px1++];
            r += AntiAliasingColorBuffer[LineAACB3Px1];

            b += AntiAliasingColorBuffer[LineAACB3Px2++];
            g += AntiAliasingColorBuffer[LineAACB3Px2++];
            r += AntiAliasingColorBuffer[LineAACB3Px2];

            b += AntiAliasingColorBuffer[LineAACB3Px3++];
            g += AntiAliasingColorBuffer[LineAACB3Px3++];
            r += AntiAliasingColorBuffer[LineAACB3Px3];

            StandardColorBuffer[LineSCBPxD4M3++] = (BYTE)(b / 16.0f);
            StandardColorBuffer[LineSCBPxD4M3++] = (BYTE)(g / 16.0f);
            StandardColorBuffer[LineSCBPxD4M3] = (BYTE)(r / 16.0f);
        }
    }
}

void CSoftwareGL::Viewport(int Width, int Height)
{
    this->Width = Width;
    this->Height = Height;

    if(AntiAliasingColorBuffer != NULL)
    {
        delete [] AntiAliasingColorBuffer;
        AntiAliasingColorBuffer = NULL;
    }

    if(StandardColorBuffer != NULL)
    {
        delete [] StandardColorBuffer;
        StandardColorBuffer = NULL;
    }

    if(DepthBuffer != NULL)
    {
        delete [] DepthBuffer;
        DepthBuffer = NULL;
    }

    if(Width > 0 && Height > 0)
    {
        if(AntiAliasing)
        {
            switch(AntiAliasing)
            {
                case ANTI_ALIASING_2X2:
                    AntiAliasingColorBufferWidth = Width * 2;
                    AntiAliasingColorBufferHeight = Height * 2;
                    break;

                case ANTI_ALIASING_3X3:
                    AntiAliasingColorBufferWidth = Width * 3;
                    AntiAliasingColorBufferHeight = Height * 3;
                    break;

                case ANTI_ALIASING_4X4:
                    AntiAliasingColorBufferWidth = Width * 4;
                    AntiAliasingColorBufferHeight = Height * 4;
                    break;
            }

            AntiAliasingColorBuffer = new BYTE[AntiAliasingColorBufferWidth * AntiAliasingColorBufferHeight * 3];
        }

        StandardColorBufferWidth = Width;
        StandardColorBufferHeight = Height;

        int WidthMod4 = Width % 4;

        if(WidthMod4 > 0)
        {
            StandardColorBufferWidth += 4 - WidthMod4;
        }

        StandardColorBuffer = new BYTE[StandardColorBufferWidth * StandardColorBufferHeight * 3];

        memset(&StandardColorBufferInfo, 0, sizeof(BITMAPINFO));
        StandardColorBufferInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
        StandardColorBufferInfo.bmiHeader.biPlanes = 1;
        StandardColorBufferInfo.bmiHeader.biBitCount = 24;
        StandardColorBufferInfo.bmiHeader.biCompression = BI_RGB;
        StandardColorBufferInfo.bmiHeader.biWidth = StandardColorBufferWidth;
        StandardColorBufferInfo.bmiHeader.biHeight = StandardColorBufferHeight;

        switch(AntiAliasing)
        {
            case NONE:
                DepthBufferWidth = Width;
                DepthBufferHeight = Height;
                break;

            case ANTI_ALIASING_2X2:
                DepthBufferWidth = Width * 2;
                DepthBufferHeight = Height * 2;
                break;

            case ANTI_ALIASING_3X3:
                DepthBufferWidth = Width * 3;
                DepthBufferHeight = Height * 3;
                break;

            case ANTI_ALIASING_4X4:
                DepthBufferWidth = Width * 4;
                DepthBufferHeight = Height * 4;
                break;
        }

        DepthBuffer = new USHORT[DepthBufferWidth * DepthBufferHeight];
    }

    if(AntiAliasing)
    {
        ColorBuffer = AntiAliasingColorBuffer;
        ColorBufferWidth = AntiAliasingColorBufferWidth;
        ColorBufferHeight = AntiAliasingColorBufferHeight;
    }
    else
    {
        ColorBuffer = StandardColorBuffer;
        ColorBufferWidth = StandardColorBufferWidth;
        ColorBufferHeight = StandardColorBufferHeight;
    }
}

void CSoftwareGL::SwapBuffers(HDC hDC)
{
    if(StandardColorBuffer != NULL)
    {
        if(AntiAliasing)
        {
            if(AntiAliasingColorBuffer != NULL)
            {
                switch(AntiAliasing)
                {
                    case ANTI_ALIASING_2X2: RunFunctionMultiThreadedAndWaitForCompletion(2); break;
                    case ANTI_ALIASING_3X3: RunFunctionMultiThreadedAndWaitForCompletion(3); break;
                    case ANTI_ALIASING_4X4: RunFunctionMultiThreadedAndWaitForCompletion(4); break;
                }
            }
        }

        StretchDIBits(hDC, 0, 0, Width, Height, 0, 0, Width, Height, StandardColorBuffer, &StandardColorBufferInfo, DIB_RGB_COLORS, SRCCOPY);
    }
}

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

CSoftwareGLRenderer::CSoftwareGLRenderer()
{
    RenderObject = 3;
    Lighting = true;
    Texturing = true;
}

CSoftwareGLRenderer::~CSoftwareGLRenderer()
{
}

bool CSoftwareGLRenderer::Init()
{
    bool Error = false;

    Error |= !Texture.LoadTexture("texture.jpg");

    Error |= !Object[0].Load("Models\\Thor\\", "thor.obj");
    Error |= !Object[1].Load("Models\\", "Bunny_Medium.obj");
    Error |= !Object[2].Load("Models\\", "Dragon_Medium.obj");

    if(Error)
    {
        return false;
    }

    Object[0].Rotate(-90.0f, vec3(0.0f, 1.0f, 0.0f));
    Object[0].Scale(1.75f / (Object[0].Max.y - Object[0].Min.y));
    Object[0].Translate(vec3(-(Object[0].Min.x + Object[0].Max.x) / 2.0f, -(Object[0].Min.y + Object[0].Max.y) / 2.0f, -(Object[0].Min.z + Object[0].Max.z) / 2.0f));

    Object[1].Rotate(90.0f, vec3(0.0f, 1.0f, 0.0f));
    Object[1].Scale(1.75f / (Object[1].Max.y - Object[1].Min.y));
    Object[1].Translate(vec3(-(Object[1].Min.x + Object[1].Max.x) / 2.0f, -(Object[1].Min.y + Object[1].Max.y) / 2.0f, -(Object[1].Min.z + Object[1].Max.z) / 2.0f));

    Object[2].Rotate(90.0f, vec3(0.0f, 1.0f, 0.0f));
    Object[2].Scale(1.75f / (Object[2].Max.y - Object[2].Min.y));
    Object[2].Translate(vec3(-(Object[2].Min.x + Object[2].Max.x) / 2.0f, -(Object[2].Min.y + Object[2].Max.y) / 2.0f, -(Object[2].Min.z + Object[2].Max.z) / 2.0f));

    Vertices = new CVertex[45];

    int v = 0;

    Vertices[v].Position = vec3(-0.5f,-0.5f, 0.0f); Vertices[v].Color = vec3(1.0f, 0.0f, 0.0f); Vertices[v].TexCoord = vec2(0.0f, 0.0f); Vertices[v].Normal = vec3( 0.0f,  0.0f,  1.0f); v++;
    Vertices[v].Position = vec3( 0.5f,-0.5f, 0.0f); Vertices[v].Color = vec3(0.0f, 1.0f, 0.0f); Vertices[v].TexCoord = vec2(1.0f, 0.0f); Vertices[v].Normal = vec3( 0.0f,  0.0f,  1.0f); v++;
    Vertices[v].Position = vec3( 0.0f, 0.5f, 0.0f); Vertices[v].Color = vec3(0.0f, 0.0f, 1.0f); Vertices[v].TexCoord = vec2(0.5f, 1.0f); Vertices[v].Normal = vec3( 0.0f,  0.0f,  1.0f); v++;

    Vertices[v].Position = vec3(-0.5f,-0.5f,-0.5f); Vertices[v].Color = vec3(1.0f, 1.0f, 1.0f); Vertices[v].TexCoord = vec2(0.0f, 0.0f); Vertices[v].Normal = vec3(-1.0f,  0.0f,  0.0f); v++;
    Vertices[v].Position = vec3(-0.5f,-0.5f, 0.5f); Vertices[v].Color = vec3(1.0f, 1.0f, 0.0f); Vertices[v].TexCoord = vec2(1.0f, 0.0f); Vertices[v].Normal = vec3(-1.0f,  0.0f,  0.0f); v++;
    Vertices[v].Position = vec3(-0.5f, 0.5f, 0.5f); Vertices[v].Color = vec3(1.0f, 0.0f, 0.0f); Vertices[v].TexCoord = vec2(1.0f, 1.0f); Vertices[v].Normal = vec3(-1.0f,  0.0f,  0.0f); v++;
    Vertices[v].Position = vec3(-0.5f, 0.5f, 0.5f); Vertices[v].Color = vec3(1.0f, 0.0f, 0.0f); Vertices[v].TexCoord = vec2(1.0f, 1.0f); Vertices[v].Normal = vec3(-1.0f,  0.0f,  0.0f); v++;
    Vertices[v].Position = vec3(-0.5f, 0.5f,-0.5f); Vertices[v].Color = vec3(1.0f, 0.0f, 1.0f); Vertices[v].TexCoord = vec2(0.0f, 1.0f); Vertices[v].Normal = vec3(-1.0f,  0.0f,  0.0f); v++;
    Vertices[v].Position = vec3(-0.5f,-0.5f,-0.5f); Vertices[v].Color = vec3(1.0f, 1.0f, 1.0f); Vertices[v].TexCoord = vec2(0.0f, 0.0f); Vertices[v].Normal = vec3(-1.0f,  0.0f,  0.0f); v++;

    Vertices[v].Position = vec3( 0.5f,-0.5f, 0.5f); Vertices[v].Color = vec3(0.0f, 1.0f, 0.0f); Vertices[v].TexCoord = vec2(0.0f, 0.0f); Vertices[v].Normal = vec3( 1.0f,  0.0f,  0.0f); v++;
    Vertices[v].Position = vec3( 0.5f,-0.5f,-0.5f); Vertices[v].Color = vec3(0.0f, 1.0f, 1.0f); Vertices[v].TexCoord = vec2(1.0f, 0.0f); Vertices[v].Normal = vec3( 1.0f,  0.0f,  0.0f); v++;
    Vertices[v].Position = vec3( 0.5f, 0.5f,-0.5f); Vertices[v].Color = vec3(0.0f, 0.0f, 1.0f); Vertices[v].TexCoord = vec2(1.0f, 1.0f); Vertices[v].Normal = vec3( 1.0f,  0.0f,  0.0f); v++;
    Vertices[v].Position = vec3( 0.5f, 0.5f,-0.5f); Vertices[v].Color = vec3(0.0f, 0.0f, 1.0f); Vertices[v].TexCoord = vec2(1.0f, 1.0f); Vertices[v].Normal = vec3( 1.0f,  0.0f,  0.0f); v++;
    Vertices[v].Position = vec3( 0.5f, 0.5f, 0.5f); Vertices[v].Color = vec3(0.0f, 0.0f, 0.0f); Vertices[v].TexCoord = vec2(0.0f, 1.0f); Vertices[v].Normal = vec3( 1.0f,  0.0f,  0.0f); v++;
    Vertices[v].Position = vec3( 0.5f,-0.5f, 0.5f); Vertices[v].Color = vec3(0.0f, 1.0f, 0.0f); Vertices[v].TexCoord = vec2(0.0f, 0.0f); Vertices[v].Normal = vec3( 1.0f,  0.0f,  0.0f); v++;

    Vertices[v].Position = vec3(-0.5f,-0.5f,-0.5f); Vertices[v].Color = vec3(1.0f, 1.0f, 1.0f); Vertices[v].TexCoord = vec2(0.0f, 0.0f); Vertices[v].Normal = vec3( 0.0f, -1.0f,  0.0f); v++;
    Vertices[v].Position = vec3( 0.5f,-0.5f,-0.5f); Vertices[v].Color = vec3(0.0f, 1.0f, 1.0f); Vertices[v].TexCoord = vec2(1.0f, 0.0f); Vertices[v].Normal = vec3( 0.0f, -1.0f,  0.0f); v++;
    Vertices[v].Position = vec3( 0.5f,-0.5f, 0.5f); Vertices[v].Color = vec3(0.0f, 1.0f, 0.0f); Vertices[v].TexCoord = vec2(1.0f, 1.0f); Vertices[v].Normal = vec3( 0.0f, -1.0f,  0.0f); v++;
    Vertices[v].Position = vec3( 0.5f,-0.5f, 0.5f); Vertices[v].Color = vec3(0.0f, 1.0f, 0.0f); Vertices[v].TexCoord = vec2(1.0f, 1.0f); Vertices[v].Normal = vec3( 0.0f, -1.0f,  0.0f); v++;
    Vertices[v].Position = vec3(-0.5f,-0.5f, 0.5f); Vertices[v].Color = vec3(1.0f, 1.0f, 0.0f); Vertices[v].TexCoord = vec2(0.0f, 1.0f); Vertices[v].Normal = vec3( 0.0f, -1.0f,  0.0f); v++;
    Vertices[v].Position = vec3(-0.5f,-0.5f,-0.5f); Vertices[v].Color = vec3(1.0f, 1.0f, 1.0f); Vertices[v].TexCoord = vec2(0.0f, 0.0f); Vertices[v].Normal = vec3( 0.0f, -1.0f,  0.0f); v++;

    Vertices[v].Position = vec3(-0.5f, 0.5f, 0.5f); Vertices[v].Color = vec3(1.0f, 0.0f, 0.0f); Vertices[v].TexCoord = vec2(0.0f, 0.0f); Vertices[v].Normal = vec3( 0.0f,  1.0f,  0.0f); v++;
    Vertices[v].Position = vec3( 0.5f, 0.5f, 0.5f); Vertices[v].Color = vec3(0.0f, 0.0f, 0.0f); Vertices[v].TexCoord = vec2(1.0f, 0.0f); Vertices[v].Normal = vec3( 0.0f,  1.0f,  0.0f); v++;
    Vertices[v].Position = vec3( 0.5f, 0.5f,-0.5f); Vertices[v].Color = vec3(0.0f, 0.0f, 1.0f); Vertices[v].TexCoord = vec2(1.0f, 1.0f); Vertices[v].Normal = vec3( 0.0f,  1.0f,  0.0f); v++;
    Vertices[v].Position = vec3( 0.5f, 0.5f,-0.5f); Vertices[v].Color = vec3(0.0f, 0.0f, 1.0f); Vertices[v].TexCoord = vec2(1.0f, 1.0f); Vertices[v].Normal = vec3( 0.0f,  1.0f,  0.0f); v++;
    Vertices[v].Position = vec3(-0.5f, 0.5f,-0.5f); Vertices[v].Color = vec3(1.0f, 0.0f, 1.0f); Vertices[v].TexCoord = vec2(0.0f, 1.0f); Vertices[v].Normal = vec3( 0.0f,  1.0f,  0.0f); v++;
    Vertices[v].Position = vec3(-0.5f, 0.5f, 0.5f); Vertices[v].Color = vec3(1.0f, 0.0f, 0.0f); Vertices[v].TexCoord = vec2(0.0f, 0.0f); Vertices[v].Normal = vec3( 0.0f,  1.0f,  0.0f); v++;

    Vertices[v].Position = vec3( 0.5f,-0.5f,-0.5f); Vertices[v].Color = vec3(0.0f, 1.0f, 1.0f); Vertices[v].TexCoord = vec2(0.0f, 0.0f); Vertices[v].Normal = vec3( 0.0f,  0.0f, -1.0f); v++;
    Vertices[v].Position = vec3(-0.5f,-0.5f,-0.5f); Vertices[v].Color = vec3(1.0f, 1.0f, 1.0f); Vertices[v].TexCoord = vec2(1.0f, 0.0f); Vertices[v].Normal = vec3( 0.0f,  0.0f, -1.0f); v++;
    Vertices[v].Position = vec3(-0.5f, 0.5f,-0.5f); Vertices[v].Color = vec3(1.0f, 0.0f, 1.0f); Vertices[v].TexCoord = vec2(1.0f, 1.0f); Vertices[v].Normal = vec3( 0.0f,  0.0f, -1.0f); v++;
    Vertices[v].Position = vec3(-0.5f, 0.5f,-0.5f); Vertices[v].Color = vec3(1.0f, 0.0f, 1.0f); Vertices[v].TexCoord = vec2(1.0f, 1.0f); Vertices[v].Normal = vec3( 0.0f,  0.0f, -1.0f); v++;
    Vertices[v].Position = vec3( 0.5f, 0.5f,-0.5f); Vertices[v].Color = vec3(0.0f, 0.0f, 1.0f); Vertices[v].TexCoord = vec2(0.0f, 1.0f); Vertices[v].Normal = vec3( 0.0f,  0.0f, -1.0f); v++;
    Vertices[v].Position = vec3( 0.5f,-0.5f,-0.5f); Vertices[v].Color = vec3(0.0f, 1.0f, 1.0f); Vertices[v].TexCoord = vec2(0.0f, 0.0f); Vertices[v].Normal = vec3( 0.0f,  0.0f, -1.0f); v++;

    Vertices[v].Position = vec3(-0.5f,-0.5f, 0.5f); Vertices[v].Color = vec3(1.0f, 1.0f, 0.0f); Vertices[v].TexCoord = vec2(0.0f, 0.0f); Vertices[v].Normal = vec3( 0.0f,  0.0f,  1.0f); v++;
    Vertices[v].Position = vec3( 0.5f,-0.5f, 0.5f); Vertices[v].Color = vec3(0.0f, 1.0f, 0.0f); Vertices[v].TexCoord = vec2(1.0f, 0.0f); Vertices[v].Normal = vec3( 0.0f,  0.0f,  1.0f); v++;
    Vertices[v].Position = vec3( 0.5f, 0.5f, 0.5f); Vertices[v].Color = vec3(0.0f, 0.0f, 0.0f); Vertices[v].TexCoord = vec2(1.0f, 1.0f); Vertices[v].Normal = vec3( 0.0f,  0.0f,  1.0f); v++;
    Vertices[v].Position = vec3( 0.5f, 0.5f, 0.5f); Vertices[v].Color = vec3(0.0f, 0.0f, 0.0f); Vertices[v].TexCoord = vec2(1.0f, 1.0f); Vertices[v].Normal = vec3( 0.0f,  0.0f,  1.0f); v++;
    Vertices[v].Position = vec3(-0.5f, 0.5f, 0.5f); Vertices[v].Color = vec3(1.0f, 0.0f, 0.0f); Vertices[v].TexCoord = vec2(0.0f, 1.0f); Vertices[v].Normal = vec3( 0.0f,  0.0f,  1.0f); v++;
    Vertices[v].Position = vec3(-0.5f,-0.5f, 0.5f); Vertices[v].Color = vec3(1.0f, 1.0f, 0.0f); Vertices[v].TexCoord = vec2(0.0f, 0.0f); Vertices[v].Normal = vec3( 0.0f,  0.0f,  1.0f); v++;

    Vertices[v].Position = vec3(-0.75f, 0.0f, 0.75f); Vertices[v].Color = vec3(0.0f, 0.0f, 1.0f); Vertices[v].TexCoord = vec2(-0.75f,-0.75f); Vertices[v].Normal = vec3( 0.0f,  1.0f,  0.0f); v++;
    Vertices[v].Position = vec3( 0.75f, 0.0f, 0.75f); Vertices[v].Color = vec3(0.0f, 1.0f, 0.0f); Vertices[v].TexCoord = vec2( 0.75f,-0.75f); Vertices[v].Normal = vec3( 0.0f,  1.0f,  0.0f); v++;
    Vertices[v].Position = vec3( 0.75f, 0.0f,-0.75f); Vertices[v].Color = vec3(0.0f, 1.0f, 1.0f); Vertices[v].TexCoord = vec2( 0.75f, 0.75f); Vertices[v].Normal = vec3( 0.0f,  1.0f,  0.0f); v++;
    Vertices[v].Position = vec3( 0.75f, 0.0f,-0.75f); Vertices[v].Color = vec3(0.0f, 1.0f, 1.0f); Vertices[v].TexCoord = vec2( 0.75f, 0.75f); Vertices[v].Normal = vec3( 0.0f,  1.0f,  0.0f); v++;
    Vertices[v].Position = vec3(-0.75f, 0.0f,-0.75f); Vertices[v].Color = vec3(1.0f, 0.0f, 0.0f); Vertices[v].TexCoord = vec2(-0.75f, 0.75f); Vertices[v].Normal = vec3( 0.0f,  1.0f,  0.0f); v++;
    Vertices[v].Position = vec3(-0.75f, 0.0f, 0.75f); Vertices[v].Color = vec3(0.0f, 0.0f, 1.0f); Vertices[v].TexCoord = vec2(-0.75f,-0.75f); Vertices[v].Normal = vec3( 0.0f,  1.0f,  0.0f); v++;

    Light.Position = Camera.Position;
    Light.Ambient = vec3(0.5f);
    Light.Diffuse = vec3(0.5f);
    Light.ConstantAttenuation = 1.0f;
    Light.LinearAttenuation = 0.0f;
    Light.QuadraticAttenuation = 0.0f;

    return true;
}

void CSoftwareGLRenderer::Render(float FrameTime)
{
    Clear();

    LoadModelViewProjectionMatrix(Camera.ViewProjectionMatrix);

    if(Lighting)
    {
        Light.Position = Camera.Position;

        BindLight(&Light);
    }

    if(Texturing)
    {
        switch(RenderObject)
        {
            case 1: BindTexture(&Texture); break;
            case 2: BindTexture(&Texture); break;
            case 3: BindTexture(&Object[0].Texture); break;
            case 4: BindTexture(&Object[1].Texture); break;
            case 5: BindTexture(&Object[2].Texture); break;
        }
    }

    switch(RenderObject)
    {
        case 1: DrawTriangles(Vertices, 0, 3); break;
        case 2: DrawTriangles(Vertices, 3, 42); break;
        case 3: DrawTriangles(Object[0].Vertices, 0, Object[0].VerticesCount); break;
        case 4: DrawTriangles(Object[1].Vertices, 0, Object[1].VerticesCount); break;
        case 5: DrawTriangles(Object[2].Vertices, 0, Object[2].VerticesCount); break;
    }

    if(Texturing)
    {
        BindTexture(NULL);
    }

    if(Lighting)
    {
        BindLight(NULL);
    }
}

void CSoftwareGLRenderer::Resize(int Width, int Height)
{
    Viewport(Width, Height);

    Camera.SetPerspective(45.0f, (float)Width / (float)Height, 0.125f, 512.0f);
}

void CSoftwareGLRenderer::Destroy()
{
    Texture.Destroy();

    for(int i = 0; i < 3; i++)
    {
        Object[i].Destroy();
    }

    delete [] Vertices;
}

void CSoftwareGLRenderer::CheckCameraKeys(float FrameTime)
{
    BYTE Keys = 0x00;

    if(GetKeyState('W') & 0x80) Keys |= 0x01;
    if(GetKeyState('S') & 0x80) Keys |= 0x02;
    if(GetKeyState('A') & 0x80) Keys |= 0x04;
    if(GetKeyState('D') & 0x80) Keys |= 0x08;
    if(GetKeyState('R') & 0x80) Keys |= 0x10;
    if(GetKeyState('F') & 0x80) Keys |= 0x20;

    if(GetKeyState(VK_SHIFT) & 0x80) Keys |= 0x40;
    if(GetKeyState(VK_CONTROL) & 0x80) Keys |= 0x80;

    if(Keys & 0x3F)
    {
        Camera.Move(Camera.OnKeys(Keys, FrameTime));
    }
}

void CSoftwareGLRenderer::OnKeyDown(UINT Key)
{
    switch(Key)
    {
        case VK_F1:
            if(GetCullFace() == CULL_FACE_FRONT) SetCullFace(CULL_FACE_BACK);
            else if(GetCullFace() == CULL_FACE_BACK) SetCullFace(CULL_FACE_FRONT);
            break;

        case VK_F2:
            SetBilinearTextureFiltering(!GetBilinearTextureFiltering());
            break;

        case VK_F5:
            RenderObject = 1;
            break;

        case VK_F6:
            RenderObject = 2;
            break;

        case VK_F7:
            RenderObject = 3;
            break;

        case VK_F8:
            RenderObject = 4;
            break;

        case VK_F9:
            RenderObject = 5;
            break;

        case '1':
            SetAntiAliasing(NONE);
            break;

        case '2':
            SetAntiAliasing(ANTI_ALIASING_2X2);
            break;

        case '3':
            SetAntiAliasing(ANTI_ALIASING_3X3);
            break;

        case '4':
            SetAntiAliasing(ANTI_ALIASING_4X4);
            break;

        case 'T':
            Texturing = !Texturing;
            break;

        case 'L':
            Lighting = !Lighting;
            break;
    }
}

void CSoftwareGLRenderer::OnLButtonDown(int X, int Y)
{
    LastClickedX = X;
    LastClickedY = Y;
}

void CSoftwareGLRenderer::OnLButtonUp(int X, int Y)
{
    if(X == LastClickedX && Y == LastClickedY)
    {
    }
}

void CSoftwareGLRenderer::OnMouseMove(int X, int Y)
{
    if(GetKeyState(VK_RBUTTON) & 0x80)
    {
        Camera.OnMouseMove(LastX - X, LastY - Y);
    }

    LastX = X;
    LastY = Y;
}

void CSoftwareGLRenderer::OnMouseWheel(short zDelta)
{
    Camera.OnMouseWheel(zDelta);
}

void CSoftwareGLRenderer::OnRButtonDown(int X, int Y)
{
    LastClickedX = X;
    LastClickedY = Y;
}

void CSoftwareGLRenderer::OnRButtonUp(int X, int Y)
{
    if(X == LastClickedX && Y == LastClickedY)
    {
    }
}

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

CSoftwareGLView::CSoftwareGLView()
{
    char *moduledirectory = new char[256];
    GetModuleFileName(GetModuleHandle(NULL), moduledirectory, 256);
    *(strrchr(moduledirectory, '\\') + 1) = 0;
    ModuleDirectory = moduledirectory;
    delete [] moduledirectory;
}

CSoftwareGLView::~CSoftwareGLView()
{
}

bool CSoftwareGLView::Create(HINSTANCE hInstance, char *Title, int Width, int Height)
{
    WNDCLASSEX WndClassEx;

    memset(&WndClassEx, 0, sizeof(WNDCLASSEX));

    WndClassEx.cbSize = sizeof(WNDCLASSEX);
    WndClassEx.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
    WndClassEx.lpfnWndProc = WndProc;
    WndClassEx.hInstance = hInstance;
    WndClassEx.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    WndClassEx.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
    WndClassEx.hCursor = LoadCursor(NULL, IDC_ARROW);
    WndClassEx.lpszClassName = "Win32OpenGLWindow";

    if(RegisterClassEx(&WndClassEx) == 0)
    {
        ErrorLog.Set("RegisterClassEx failed!");
        return false;
    }

    this->Title = Title;

    this->Width = Width;
    this->Height = Height;

    DWORD Style = WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;

    if((hWnd = CreateWindowEx(WS_EX_APPWINDOW, WndClassEx.lpszClassName, Title, Style, 0, 0, Width, Height, NULL, NULL, hInstance, NULL)) == NULL)
    {
        ErrorLog.Set("CreateWindowEx failed!");
        return false;
    }

    if((hDC = GetDC(hWnd)) == NULL)
    {
        ErrorLog.Set("GetDC failed!");
        return false;
    }

    return SoftwareGLRenderer.Init();
}

void CSoftwareGLView::Show(bool Maximized)
{
    RECT dRect, wRect, cRect;

    GetWindowRect(GetDesktopWindow(), &dRect);
    GetWindowRect(hWnd, &wRect);
    GetClientRect(hWnd, &cRect);

    wRect.right += Width - cRect.right;
    wRect.bottom += Height - cRect.bottom;

    wRect.right -= wRect.left;
    wRect.bottom -= wRect.top;

    wRect.left = dRect.right / 2 - wRect.right / 2;
    wRect.top = dRect.bottom / 2 - wRect.bottom / 2;

    MoveWindow(hWnd, wRect.left, wRect.top, wRect.right, wRect.bottom, FALSE);

    ShowWindow(hWnd, Maximized ? SW_SHOWMAXIMIZED : SW_SHOWNORMAL);
}

void CSoftwareGLView::MsgLoop()
{
    MSG Msg;

    while(GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }
}

void CSoftwareGLView::Destroy()
{
    SoftwareGLRenderer.Destroy();

    DestroyWindow(hWnd);
}

void CSoftwareGLView::OnKeyDown(UINT Key)
{
    switch(Key)
    {
        case '5':
            ShowWindow(hWnd, SW_HIDE);
            Width = 400;
            Height = 300;
            Show();
            break;

        case '6':
            ShowWindow(hWnd, SW_HIDE);
            Width = 512;
            Height = 384;
            Show();
            break;

        case '7':
            ShowWindow(hWnd, SW_HIDE);
            Width = 640;
            Height = 480;
            Show();
            break;

        case '8':
            ShowWindow(hWnd, SW_HIDE);
            Width = 800;
            Height = 600;
            Show();
            break;

        case '9':
            ShowWindow(hWnd, SW_HIDE);
            Width = 1024;
            Height = 768;
            Show();
            break;

        case '0':
            ShowWindow(hWnd, SW_HIDE);
            Width = 1280;
            Height = 800;
            Show();
            break;
    }

    SoftwareGLRenderer.OnKeyDown(Key);
}

void CSoftwareGLView::OnLButtonDown(int X, int Y)
{
    SoftwareGLRenderer.OnLButtonDown(X, Y);
}

void CSoftwareGLView::OnLButtonUp(int X, int Y)
{
    SoftwareGLRenderer.OnLButtonUp(X, Y);
}

void CSoftwareGLView::OnMouseMove(int X, int Y)
{
    SoftwareGLRenderer.OnMouseMove(X, Y);
}

void CSoftwareGLView::OnMouseWheel(short zDelta)
{
    SoftwareGLRenderer.OnMouseWheel(zDelta);
}

void CSoftwareGLView::OnPaint()
{
    PAINTSTRUCT ps;

    BeginPaint(hWnd, &ps);

    static DWORD LastFPSTime = GetTickCount(), LastFrameTime = LastFPSTime;
    static int FPS = 0;

    DWORD Time = GetTickCount();

    float FrameTime = (Time - LastFrameTime) * 0.001f;

    LastFrameTime = Time;

    if(Time - LastFPSTime > 1000)
    {
        CString Text = Title;

        Text.Append(" - %dx%d", Width, Height);
        Text.Append(", BTF: "); if(SoftwareGLRenderer.GetBilinearTextureFiltering()) Text.Append("ON"); else Text.Append("OFF");
        if(SoftwareGLRenderer.GetAntiAliasing() == NONE) Text.Append(", AA: OFF");
        else if(SoftwareGLRenderer.GetAntiAliasing() == ANTI_ALIASING_2X2) Text.Append(", AA: 4x");
        else if(SoftwareGLRenderer.GetAntiAliasing() == ANTI_ALIASING_3X3) Text.Append(", AA: 9x");
        else if(SoftwareGLRenderer.GetAntiAliasing() == ANTI_ALIASING_4X4) Text.Append(", AA: 16x");
        Text.Append(", Threads: %d", SoftwareGLRenderer.GetThreadsCount());
        Text.Append(", FPS: %d", FPS);

        SetWindowText(hWnd, Text);

        LastFPSTime = Time;
        FPS = 0;
    }
    else
    {
        FPS++;
    }

    SoftwareGLRenderer.CheckCameraKeys(FrameTime);

    SoftwareGLRenderer.Render(FrameTime);

    SoftwareGLRenderer.SwapBuffers(hDC);

    EndPaint(hWnd, &ps);

    InvalidateRect(hWnd, NULL, FALSE);
}

void CSoftwareGLView::OnRButtonDown(int X, int Y)
{
    SoftwareGLRenderer.OnRButtonDown(X, Y);
}

void CSoftwareGLView::OnRButtonUp(int X, int Y)
{
    SoftwareGLRenderer.OnRButtonUp(X, Y);
}

void CSoftwareGLView::OnSize(int Width, int Height)
{
    this->Width = Width;
    this->Height = Height;

    SoftwareGLRenderer.Resize(Width, Height);
}

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

CSoftwareGLView SoftwareGLView;

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

LRESULT CALLBACK WndProc(HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
{
    switch(uiMsg)
    {
        case WM_CLOSE:
            PostQuitMessage(0);
            break;

        case WM_KEYDOWN:
            SoftwareGLView.OnKeyDown((UINT)wParam);
            break;

        case WM_LBUTTONDOWN:
            SoftwareGLView.OnLButtonDown(LOWORD(lParam), HIWORD(lParam));
            break;

        case WM_LBUTTONUP:
            SoftwareGLView.OnLButtonUp(LOWORD(lParam), HIWORD(lParam));
            break;

        case WM_MOUSEMOVE:
            SoftwareGLView.OnMouseMove(LOWORD(lParam), HIWORD(lParam));
            break;

        case WM_MOUSWHEEL:
            SoftwareGLView.OnMouseWheel(HIWORD(wParam));
            break;

        case WM_PAINT:
            SoftwareGLView.OnPaint();
            break;

        case WM_RBUTTONDOWN:
            SoftwareGLView.OnRButtonDown(LOWORD(lParam), HIWORD(lParam));
            break;

        case WM_RBUTTONUP:
            SoftwareGLView.OnRButtonUp(LOWORD(lParam), HIWORD(lParam));
            break;

        case WM_SIZE:
            SoftwareGLView.OnSize(LOWORD(lParam), HIWORD(lParam));
            break;

        default:
            return DefWindowProc(hWnd, uiMsg, wParam, lParam);
    }

    return 0;
}

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

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR sCmdLine, int iShow)
{
    if(SoftwareGLView.Create(hInstance, "Simple software renderer - Multithreading", 800, 600))
    {
        SoftwareGLView.Show();
        SoftwareGLView.MsgLoop();
    }
    else
    {
        MessageBox(NULL, ErrorLog, "Error", MB_OK | MB_ICONERROR);
    }

    SoftwareGLView.Destroy();

    return 0;
}

// ----------------------------------------------------------------------------------------------------------------------------
Download
simple_software_renderer.zip (Visual Studio 2008 Professional)
© 2010 - 2016 Bc. Michal Belanec, michalbelanec (at) centrum (dot) sk
Last update June 25, 2016
OpenGL® is a registered trademark of Silicon Graphics Inc.