Sign in to follow this  

Grid code is overloading and not working

This topic is 2533 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

hey guys
i am having a problem with my code. It keeps building up and taking system resources in RAM(nearly 1 GB!!!) and not executing at all(there is no output at present() and no windows created what so ever)
it is your basic win32 and DirectX 9.0c initializing code(all done through the "App" class), basic vertex and pixel shader with basic lighting equations, nothing too fancy.
The code just draws a grid on the floor(indexed buffers)
here is the code for the rendering process(the other code is just basic win32 & directx stuff and i don't think it is the problem)
App.h

#ifndef _APP_
#define _APP_

#include "ErrorsAndHeaders.h"

#define WINDOW_WIDTH 640
#define WINDOW_HEIGHT 480

class App{
public:
App();
virtual ~App();
HINSTANCE GetInstance(){return Hins;}
void SetInstance(HINSTANCE hins){Hins = hins;}
HWND GetHwnd() {return gHwnd;}
D3DFORMAT GetBBFormat();
void FullScreenCheck();
void init_window();
void init_d3d();
virtual void init_scene();
virtual void run();
virtual void update(float dt);
virtual void render();
virtual void kill_scene();

static float xTrans;
static float zTrans;
//App Specific members
void DrawGrid(float Width, float Depth, float dx, float dz, D3DXVECTOR3 Center, std::vector<D3DXVECTOR3>* Vert, std::vector<DWORD>* Indi);
protected:
DWORD Width;
DWORD Height;
HINSTANCE Hins;
HWND gHwnd;
LPDIRECT3D9 gd3d;
LPDIRECT3DDEVICE9 gd3dDev;
IDirect3DVertexBuffer9* VB;
IDirect3DIndexBuffer9* IB;
IDirect3DVertexDeclaration9* Decl;
static char* gAppName;
bool FS;
bool gAppDone;
float CameraHeight;
float CameraYRotationAngle;
float CameraRadius;
std::vector<D3DXVECTOR3> Vert;
std::vector<DWORD> Indi;
//Vertex Structure
typedef struct Vertex
{
Vertex(D3DXVECTOR3 pos):pos(pos){}
D3DXVECTOR3 pos;
D3DXVECTOR3 normal;
}Vertex;
//Matrices
D3DXMATRIX ProjMat;
D3DXMATRIX ViewMat;
D3DXMATRIX WorldMat;
D3DXMATRIX ITWMat;
};

//prototypes
INT WINAPI WinMain(HINSTANCE Hins, HINSTANCE pHins, LPSTR CmdShow, INT CmdLine);
LRESULT CALLBACK MsgProc(HWND hwnd, UINT Msg, WPARAM wparam, LPARAM lparam);


#endif



App.cpp(this is where the magic happens)

#include "App.h"

App::App()
{
gd3d = NULL;
gd3dDev = NULL;
gHwnd = NULL;
VB = NULL;
IB = NULL;
Decl = NULL;

CameraHeight = 10.0f;
CameraYRotationAngle = D3DX_PI * 1.5f;
CameraRadius = 5.0f;
}

float App::xTrans = 0.0f;
float App::zTrans = 0.0f;

App::~App()
{
if(!UnregisterClass("WC", Hins))
Error("Can't unregister class");

Releaser(gd3d);
Releaser(gd3dDev);
Releaser(Decl);
Releaser(VB);
Releaser(IB);
DestroyWindow(gHwnd);
}

char* App::gAppName = "TriGridApp";

void App::FullScreenCheck()
{
UINT i = MessageBox(NULL, "FullScreen???", gAppName, MB_YESNO|MB_ICONQUESTION);

switch(i)
{
case IDYES:
FS = true;
break;
case IDNO:
FS = false;
break;
case IDCLOSE:
exit(4);
break;
}
}

void App::init_window()
{
WNDCLASS WC;
WC.style = CS_HREDRAW | CS_VREDRAW;
WC.lpfnWndProc = MsgProc;
WC.cbClsExtra = 0;
WC.cbWndExtra = 0;
WC.hInstance = Hins;
WC.hIcon = NULL;
WC.hCursor = LoadCursor( NULL, IDC_ARROW );
WC.hbrBackground = NULL;
WC.lpszMenuName = NULL;
WC.lpszClassName = "WClass";

HR(!RegisterClass(&WC), "RegisterClass()");

DWORD Style;
Width = WINDOW_WIDTH;
Height = WINDOW_HEIGHT;

gHwnd = CreateWindow("WClass", "TriGridApp", WS_OVERLAPPED|WS_SYSMENU, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, NULL, NULL, Hins, NULL);

if(!gHwnd)
Error("Couldn't Create Class");

ShowWindow(gHwnd, NULL);
UpdateWindow(gHwnd);
SetFocus(gHwnd);

}

void App::init_d3d()
{
D3DPRESENT_PARAMETERS pp;
D3DFORMAT format;
D3DDISPLAYMODE DM;
gd3d = Direct3DCreate9(D3D_SDK_VERSION);

format = GetBBFormat();

gd3d->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &DM);

memset(&pp, 0, sizeof(pp));
pp.Windowed = true;
pp.SwapEffect = D3DSWAPEFFECT_FLIP;
pp.BackBufferFormat = DM.Format;
pp.BackBufferWidth = Width;
pp.BackBufferHeight = Height;
pp.BackBufferCount = 1;
pp.EnableAutoDepthStencil = true;
pp.AutoDepthStencilFormat = D3DFMT_D16;
pp.hDeviceWindow = gHwnd;
pp.MultiSampleType = D3DMULTISAMPLE_NONE;
pp.MultiSampleQuality = 0;

HR(gd3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, gHwnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &pp, &gd3dDev), "CreateDevice()");

}

D3DFORMAT App::GetBBFormat()
{
if(SUCCEEDED(gd3d->CheckDeviceType(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, D3DFMT_R5G6B5, D3DFMT_X1R5G5B5, true)))
return D3DFMT_X1R5G5B5;
if(SUCCEEDED(gd3d->CheckDeviceType(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, D3DFMT_R5G6B5, D3DFMT_X1R5G5B5, true)))
return D3DFMT_R5G6B5;
return D3DFMT_UNKNOWN;
}


void App::init_scene()
{
D3DXMatrixPerspectiveFovLH(&ProjMat, D3DX_PI/4, Width/Height, 1.0f, 5000.0f);
HR(gd3dDev->SetTransform(D3DTS_PROJECTION, &ProjMat), "SetTransform(), Projection");
HR(gd3dDev->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME), "RenderState, Wireframe");

D3DXMatrixIdentity(&WorldMat);
D3DXMatrixInverse(&ITWMat, 0, &WorldMat);
D3DXMatrixTranspose(&ITWMat, &ITWMat);

}



void App::run()
{
MSG msg;
/*_int64 FirstStamp = 0;
_int64 CountsPerSec = 0;
QueryPerformanceCounter((LARGE_INTEGER*)FirstStamp);
QueryPerformanceFrequency((LARGE_INTEGER*)CountsPerSec);
_int64 SecsPerCount = 1 / CountsPerSec;*/

while(gAppDone)
{
if(PeekMessage(&msg, gHwnd, 0, 0, PM_REMOVE))
{
if(msg.message != WM_QUIT)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
else
{
//_int64 SecondStamp = 0;
//QueryPerformanceCounter((LARGE_INTEGER*)SecondStamp);
//float dt = float(SecsPerCount * (SecondStamp - FirstStamp));
float dt = 0;
update(dt);
render();
}
}
}

void App::update(float dt)
{
float x = cosf(CameraRadius) + xTrans;
float z = sinf(CameraRadius) + zTrans;

D3DXVECTOR3 Eye(x, CameraHeight, z);
D3DXVECTOR3 Look(0.0f, 0.0f, 0.0f);
D3DXVECTOR3 Up(0.0f, 1.0f, 0.0f);

D3DXMatrixLookAtLH(&ViewMat, &Eye, &Look, &Up);
gd3dDev->SetTransform(D3DTS_VIEW, &ViewMat);
}

void App::render()
{
float dx = 1.0f;
float dz = 1.0f;
D3DXVECTOR3 Center(0.0f, 0.0f, 0.0f);
float Width = 5.0f;
float Depth = 5.0f;
DrawGrid(Width, Depth, dx, dz, Center,&Vert,&Indi);
}

void App::DrawGrid(float Width, float Depth, float dx, float dz, D3DXVECTOR3 Center, std::vector<D3DXVECTOR3>* Vert, std::vector<DWORD>* Indi)
{

//1- generate vertices
//2- generate indicies
//3- generate buffers
//4- Render

//1- Generating vertices

float NumColVert = Width + 1;
float NumRowVert = Depth + 1;
float TotalVert = NumColVert * NumRowVert;
float TotalPoly = Width * Depth * 2;

float Xtrans = -dx * 0.5f;
float Ztrans = dz * 0.5f;
D3DXMATRIX MatTrans;
D3DXMatrixIdentity(&MatTrans);

Vert->resize(TotalVert);

int k = 0;
for(int i = 0; i < NumRowVert; i++)//Vertex generation and auto-translation to origin(0,0) then to "Center"
{
for(int j = 0; j < NumColVert; j++)
{
(*Vert)[k].x = j * dx + Xtrans;
(*Vert)[k].y = 0.0f;
(*Vert)[k].z = -i * dz + Ztrans;

if(Center != 0)
{
D3DXMatrixTranslation(&MatTrans, Center.x, Center.y, Center.z);
D3DXVec3TransformCoord(&(*Vert)[k], &(*Vert)[k], &MatTrans);
}
k++;
}
}

Indi->resize(TotalPoly * 3);
k = 0;
for(int i = 0; i < Width; i++)//Index generation per grid
{
for(int j = 0; j < Depth; j++)
{
(*Indi)[k] = i * NumColVert + j;
(*Indi)[k + 1] = (i+1) * NumColVert + j;
(*Indi)[k + 2] = (i+1) * NumColVert + j+1;
(*Indi)[k + 3] = i * NumColVert + j;
(*Indi)[k + 4] = i * NumColVert + j+1;
(*Indi)[k + 5] = (i+1) * NumColVert + j+1;

// next quad
k += 6;
}
}

//3- Generate Buffers
D3DVERTEXELEMENT9 elements[] =
{
{0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0},
{0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,0},
D3DDECL_END()
};
HR(gd3dDev->CreateVertexDeclaration(elements, &Decl), "CreateVertexDeclaration()");
Vertex* V = 0;
float TotalVertBuffer = 100*100;
float TotalIndiBuffer = 100*100;

HR((gd3dDev->CreateVertexBuffer(TotalVertBuffer * sizeof(Vertex), D3DUSAGE_WRITEONLY, NULL, D3DPOOL_MANAGED, &VB, NULL)), "CreateVertexBuffer()");

if(SUCCEEDED(VB->Lock(0, 0, (void**)&V, 0)))
{
for(int i = 0; i < TotalVert; i++)
{
V[i].pos = (*Vert)[i];
V[i].normal = D3DXVECTOR3(0.0f, 1.0f, 0.0f);
}
HR(VB->Unlock(), "Unlock() //VB");

}

DWORD* I = 0;
HR(gd3dDev->CreateIndexBuffer(TotalIndiBuffer * sizeof(DWORD), D3DUSAGE_WRITEONLY, D3DFMT_INDEX16, D3DPOOL_MANAGED, &IB, NULL), "CreateIndexBuffer()");
if(SUCCEEDED(IB->Lock(0, 0, (void**)&I, 0)))
{
for(int i = 0; i < TotalVert * 3; i++)
I[i] = (*Indi)[i];
HR(IB->Unlock(), "Unlock() //IB");
}

//The FX stuff
ID3DXEffect* fx;
D3DXCreateEffectFromFile(gd3dDev, "DefFX.fx", 0, 0, 0, 0, &fx, 0);
fx->GetTechniqueByName("DefTech");
//Handles
D3DXHANDLE HTech = fx->GetTechniqueByName("DefTech");
D3DXHANDLE HmatWVP = fx->GetParameterByName(0, "matWVP");
D3DXHANDLE HmatITW = fx->GetParameterByName(0, "matITW");
D3DXHANDLE HmatW = fx->GetParameterByName(0, "matW");
D3DXHANDLE HDiffMatr = fx->GetParameterByName(0, "DiffMatr");
D3DXHANDLE HDiffLight = fx->GetParameterByName(0, "DiffLight");
D3DXHANDLE HLightVec = fx->GetParameterByName(0, "LightVec");



HR(gd3dDev->Clear(0, 0, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(255, 255 ,255 ), 1.0f, 1.0f),"Clear()");
if(SUCCEEDED(gd3dDev->BeginScene()))
{
HR(gd3dDev->SetStreamSource(0, VB, 0, sizeof(Vertex)), "SetStreamSource()");
HR(gd3dDev->SetIndices(IB), "SetIndices()");
HR(gd3dDev->SetVertexDeclaration(Decl), "SetVertexDeclatation() in BeginScene()");
//Sets
HR(fx->SetTechnique(HTech), "SetTechnique()");
HR(fx->SetMatrix(HmatWVP, &(WorldMat*ViewMat*ProjMat)), "SetMatrix() //HmatWVP");
HR(fx->SetMatrix(HmatITW, &ITWMat), "SetMatrix() //HmatITW");
HR(fx->SetMatrix(HmatW, &WorldMat), "SetMatrix() //HmatW");
HR(fx->SetValue(HDiffMatr, &D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f), sizeof(D3DXCOLOR)), "SetMatrix() //HDiffMatr");
HR(fx->SetValue(HDiffLight, &D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f), sizeof(D3DXCOLOR)), "SetMatrix() //HDiffLight");
HR(fx->SetValue(HLightVec, &D3DXVECTOR3(-5.0f, 10.0f, 0.0f), sizeof(D3DXVECTOR3)), "SetMatrix() //HLightVec");

UINT Passes = 0;
if(SUCCEEDED(fx->Begin(&Passes, 0)))
{
for(int i = 0; i < Passes; i++)
{
if(SUCCEEDED(fx->BeginPass(i)))
{
HR(gd3dDev->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, TotalVert, 0, TotalPoly), "DrawIndexedPrimitive()");
}
HR(fx->EndPass(), "EndPass()");
}
HR(fx->End(), "End()");
}

HR(gd3dDev->EndScene(), "EndScene()");
}
HR(gd3dDev->Present(0, 0, 0, 0), "Present()");

}

void App::kill_scene()
{
HR(gd3dDev->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW), "SetRenderState(), Kill CULLMODE");
}



the code is just not running. no output. no windows . no nothing.....
please help me!!!
Appreciated guys

Share this post


Link to post
Share on other sites
It appears you've written hundreds of lines of code without testing it until it was completely written. As you develop a project, you should write a few lines of code, compile it (to see if there are any compile errors), link it (to see if there are any link errors) and run it (to see if it does what you think it should). Compile-link-run takes maybe one minute - maybe a total of a half hour in an evening of developing. You've probably spent much more time than that at this point. Change your development style.

That being said, somewhere in your code (which you haven't posted) you create the App and initialize it. Immediately after you call App::init_window() a window should appear. Setup your program to do only that much and see if it works. Fix any problems and continue with the next function.

If something doesn't work, set some breakpoints and step through your code in debug mode, looking to see if variables get set to values you expect.

Share this post


Link to post
Share on other sites
your way is WAY simpler, buckeye. i complicated myself for nothing, i will test the functions in a simpler manner and report back.
In the mean time, Nypyern, can you elaborate on the leakage of the buffers?? do u mean that i should clear the vectors after i am done with them each iteration??? but i am overwriting them in every iteration of Render(), not appending to them.

Share this post


Link to post
Share on other sites
Quote:
the leakage of the buffers??

Every time you call DrawGrid you create several objects over and over. That allocates memory you never release.

First, start using Debug Runtime. You'll get a lot of information about what's going on "under the hood" while your program runs.

Also, I'd suggest you take a look at some of the SDK examples (that's what they're there for) to see how to do basic object creation and release.

Share this post


Link to post
Share on other sites
Quote:
Original post by Buckeye
First, start using Debug Runtime. You'll get a lot of information about what's going on "under the hood" while your program runs.

Also, I'd suggest you take a look at some of the SDK examples (that's what they're there for) to see how to do basic object creation and release.


Infact, i am using Debug Runtime, it didn't show up any errors(except for SetRenderState() being set to -137 but that is just a warning and a minor one).
I just Cleared out the vectors and arrays for the buffers after each iteration.
And, this is weird, i commented out all of my code so it will just initialize the window and show it(the InitWindow() method), and it didn't give any output whatsoever. It is just a basic window creation code, why didn't it work??
please check it out(i know i should put this in the beginner's section but i already have the thread up and running here so please just humor me...)
InitWindow() and Window Procedure(if it helps)

void App::init_window()
{
WNDCLASS WC;
WC.style = CS_HREDRAW | CS_VREDRAW;
WC.lpfnWndProc = MsgProc;
WC.cbClsExtra = 0;
WC.cbWndExtra = 0;
WC.hInstance = Hins;
WC.hIcon = NULL;
WC.hCursor = LoadCursor( NULL, IDC_ARROW );
WC.hbrBackground = NULL;
WC.lpszMenuName = NULL;
WC.lpszClassName = "WClass";

HR(RegisterClass(&WC), "RegisterClass()");

DWORD Style;
Width = WINDOW_WIDTH;
Height = WINDOW_HEIGHT;

gHwnd = CreateWindow("WClass", gAppName, WS_OVERLAPPED|WS_SYSMENU, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, NULL, NULL, Hins, NULL);

if(!gHwnd)
Error("Couldn't Create Class");

ShowWindow(gHwnd, NULL);
UpdateWindow(gHwnd);
SetFocus(gHwnd);

LRESULT CALLBACK MsgProc(HWND hwnd, UINT Msg, WPARAM wparam, LPARAM lparam)
{
//ignore the comments below
switch(Msg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
break;
/*case WM_KEYDOWN:
if(wparam == VK_LEFT)
App::xTrans = App::xTrans - 2.0f;
if(wparam == VK_RIGHT)
App::xTrans += 2.0f;
if(wparam == VK_UP)
App::zTrans += 2.0f;
if(wparam == VK_DOWN)
App::zTrans -= 2.0f;
break;*/

}
return DefWindowProc(hwnd, Msg, wparam, lparam);
}

}

Appreciated guys

[Edited by - VISQI on December 26, 2010 8:15:55 PM]

Share this post


Link to post
Share on other sites
Quote:
ShowWindow(gHwnd, NULL);

The NULL value hides the window. Try one of the SW_xxx values, such as SW_SHOW or SW_SHOWNORMAL.

Something simple you might want to do is to look at the documentation for the Windows API calls you make and see what values you should pass them.

EDIT: FYI, because you set the background brush to NULL (which is okay if you're going to fill the window with DirectX), when you see the window, the background won't be cleared.

Share this post


Link to post
Share on other sites
hey guys
I am Really sorry for delaying the thread. My net got cut off for a while
Anyway, i did comment out the Grid code and validate the Win32 and D3D initialization code, SW_SHOWNORMAL did it for me.
i tried to clear out the memory leaks in the buffers by calling Clear() method in vectors and clearing out the arrays like this:

if(SUCCEEDED(VB->Lock(0, 0, (void**)&V, 0)))
{
for(int i = 0; i < TotalVert; i++)
{
V[i].pos = (*Vert)[i];
V[i].normal = D3DXVECTOR3(0.0f, 1.0f, 0.0f);
}
HR(VB->Unlock(), "Unlock() //VB");
Vert->clear();
delete [] V;
V = 0;
}

DWORD* I = 0;
HR(gd3dDev->CreateIndexBuffer(TotalIndiBuffer * sizeof(DWORD), D3DUSAGE_WRITEONLY, D3DFMT_INDEX16, D3DPOOL_MANAGED, &IB, NULL), "CreateIndexBuffer()");
if(SUCCEEDED(IB->Lock(0, 0, (void**)&I, 0)))
{
for(int i = 0; i < TotalVert * 3; i++)
I[i] = (*Indi)[i];
HR(IB->Unlock(), "Unlock() //IB");
Indi->clear();
delete [] I;
I = 0;
}



this resulted in this error message:
"Assertion Error: _BLOCK_TYPE_IS_VALID(pHead->nBlockUse)"
i checked it out and found that clearing the arrays(V and I) are not needed because D3D already does it for you, so i only left the Clear() method for the vectors and it cleared out the error but the memory problem is still there(694 MB in the Task manager and counting!!)
One more thing, after i saw the window and what i drew, i really doubt that this is anything that resembles a grid, here is a picture
[IMG]http://img194.imageshack.us/img194/2276/trigriddemo.jpg[/IMG]

I checked my grid algorithm a million times and i really can't find the problem. I hope you guys can help me...
Appreciated

Share this post


Link to post
Share on other sites
It appears you've misunderstood the idea of releasing the vertex and index buffers.
if(SUCCEEDED(VB->Lock(0, 0, (void**)&V, 0)))
{
for(int i = 0; i < TotalVert; i++)
{
V[i].pos = (*Vert)[i];
V[i].normal = D3DXVECTOR3(0.0f, 1.0f, 0.0f);
}
HR(VB->Unlock(), "Unlock() //VB");
Vert->clear();
delete [] V;
V = 0;
}

That code is very bad, I'm afraid. You should never delete the buffer pointer returned by VB->Lock. Maintenance of that pointer is taken care of by VB->Unlock. In general, don't "delete" anything you didn't "new" yourself.

The previous posts mentioning releasing the vertex and index buffers were referring to your VB and IB variables, not the pointers to those buffers you get with Lock, or the std::vector you use to fill the vertex buffer.

In general, the idea is (in pseudo-code):

Initialize-DirectX;
CreateVertexBuffer;
CreateIndexBuffer;
FillVertexBuffer;
FillIndexBuffer;
while( not-done )
{
render-vertex-buffer; // does NOT include creation/filling of the vertex or index buffers. You've already done that
}
ReleaseVertexBuffer;
ReleaseIndexBuffer;
ExitProgram;

EDIT: It appears ~App() does the releasing you should be doing. However, good practice dictates that you delete or release objects in the reverse order in which they were created.

Share this post


Link to post
Share on other sites
the releases are taken care of by the ~App. But the point is that ~App only gets called at the termination of the whole program, not run-time. i checked "Check for memory leaks" and "Check for D3D error" in DX control panel but sadly with no luck at all.

Share this post


Link to post
Share on other sites
Quote:
But the point is that ~App only gets called at the termination of the whole program, not run-time.

Exactly. That's the whole point, though your statement is incorrect. "Run-time" is "program time" and "termination of the whole program" should include destruction of the App.

If you don't need to change the vertex buffer (assuming your grid is static), then you only need one vertex/index buffer for the entire duration of the program. You just render it multiple times. When the App destructor is called when the program terminates, the memory gets released.

If you do need to change the contents of the vertex buffer and don't need to change the size of the vertex buffer, don't create a new buffer, just lock it, fill it and unlock it.

Share this post


Link to post
Share on other sites
Quote:
any suggestion on the grid algorithm?

If you've got it working (and you now realize you only have to execute the loading code once), go with it. You may be able to improve it next project, but, for now, I'd suggest you continue on your learning curve.

Share this post


Link to post
Share on other sites

This topic is 2533 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this