Sign in to follow this  
Programmer16

[C++/DX9] Weird texture editing & render issue

Recommended Posts

Programmer16    2321
I coded up some drawing routines for my Texture class, including a Layer class (it all wraps up creating an instance of IDirect3DSurface9, locking, setting/getting pixel data, and unlocking.) It all works fine...except if I try to use it during runtime. The weird part? It does work...kind of. If I save the texture to a file, the file looks perfect, however it doesn't render correctly. I've double checked all of my code and the texture is being rendered every frame. Here's the code for my wrappers
class Texture
{
public:
	static TextureManager TexturePool;
	typedef boost::shared_ptr<Texture> Pointer;

	class Layer
	{
		IDirect3DSurface9 *ComPtr;
		D3DLOCKED_RECT LockedRect;
		dft::Math::Rectangle LockedSection;
		DWORD *Bits;
		Texture *Owner;
		friend Texture;
	public:
		typedef boost::shared_ptr<Layer> Pointer;

		Layer();
		~Layer();
		bool Create(dft::Graphics::Texture *SourceTexture, int Layer = 0);

		bool Lock(dft::Math::Rectangle *LockSection, int Flags = D3DLOCK_READONLY);
		dft::Graphics::Color GetPixel(int X, int Y);
		dft::Graphics::Color GetPixel(const dft::Math::Point Location);
		dft::Graphics::Color *GetPixelChunk(dft::Math::Rectangle *SourceRectangle);
		void SetPixel(int X, int Y, dft::Graphics::Color PixelColor);
		void SetPixel(const dft::Math::Point Location, dft::Graphics::Color PixelColor);
		void DrawLine(int StartX, int StartY, int EndX, int EndY, dft::Graphics::Color PixelColor);
		void DrawLine(const dft::Math::Point &StartPoint, const dft::Math::Point &EndPoint, dft::Graphics::Color PixelColor);
		void DrawRectangle(int X, int Y, int Width, int Height, dft::Graphics::Color PixelColor);
		void DrawRectangle(const dft::Math::Rectangle &DestinationRectangle, dft::Graphics::Color PixelColor);
		void DrawRectangle(const dft::Math::Point &Location, const dft::Math::Dimensions &Size, dft::Graphics::Color PixelColor);
		void DrawChunk(const dft::Math::Point &Location, dft::Graphics::Color *Chunk, const dft::Math::Dimensions &ChunkSize);
		void CopyFromLayer(Layer::Pointer Source, const dft::Math::Rectangle &SourceRectangle, const dft::Math::Rectangle &Destination);
		void Unlock();
	};

	class Information
	{
	public:
		int Levels;
		unsigned long Usage;
		D3DFORMAT Format;
		D3DPOOL Pool;
		unsigned long Filter;
		unsigned long MipFilter;
	};

	IDirect3DTexture9 *ComPtr;
	std::string FilePath;
	Math::Dimensions Size;
	Math::Dimensions ImageSize;
	Device *GfxDevice;
	Information Info;

	Texture();
	~Texture();

	ReturnValueEx<HRESULT> LoadFromFile(Device &GfxDevice, const std::string &FilePath);
	ReturnValueEx<HRESULT> LoadFromFileEx(Device &GfxDevice, const std::string &FilePath, int Width, int Height, int Levels, unsigned long Usage, D3DFORMAT Format, D3DPOOL Pool, unsigned long Filter, unsigned long MipFilter);
	ReturnValueEx<HRESULT> CreateBlank(Device &GfxDevice, int Width, int Height, int Levels, unsigned long Usage, D3DFORMAT Format, D3DPOOL Pool, const std::string &Name);
	ReturnValueEx<HRESULT> Save(const std::string &OutputFilePath, D3DXIMAGE_FILEFORMAT ImageFileFormat);
	Layer::Pointer GetLayer(int Layer = 0);
	bool Reload();
	void Release();

	static Pointer GetTexture(Device &GfxDevice, const std::string &Path);
};

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Texture::Layer::Layer()
{
	Bits = 0;
}

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Texture::Layer::~Layer()
{
	if(ComPtr)
	{
		ComPtr->Release();
		ComPtr = 0;
	}
}

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
bool Texture::Layer::Create(dft::Graphics::Texture *SourceTexture, int Layer)
{
	if(!SourceTexture)
		return false;
	if(FAILED(SourceTexture->ComPtr->GetSurfaceLevel(Layer, &ComPtr)))
		return false;
	Owner = SourceTexture;
	return true;
}

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
bool Texture::Layer::Lock(dft::Math::Rectangle *LockSection, int Flags)
{
	if(FAILED(ComPtr->LockRect(&LockedRect, LockSection != 0 ? &LockSection->ToRECT() : 0, Flags)))
		return false;
	Bits =(DWORD*)(LockedRect.pBits);
	if(LockSection)
		LockedSection = *LockSection;
	else if(Owner)
		LockedSection = dft::Math::Rectangle(0, 0, Owner->Size);
	return true;
}

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
dft::Graphics::Color Texture::Layer::GetPixel(int X, int Y)
{
	if(X > (LockedSection.Width() - LockedSection.Left) || Y > (LockedSection.Height() - LockedSection.Top) || X < 0 || Y < 0 || !Bits)
		return dft::Graphics::Color::FromArgb(0, 0, 0, 0);
	return Bits[((LockedRect.Pitch / sizeof(DWORD)) * Y) + X];
}

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
dft::Graphics::Color Texture::Layer::GetPixel(const dft::Math::Point Location)
{
	return GetPixel(Location.X, Location.Y);
}

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
dft::Graphics::Color *Texture::Layer::GetPixelChunk(dft::Math::Rectangle *SourceRectangle)
{
	if(!Bits)
		return 0;

	if(SourceRectangle)
	{
		if(SourceRectangle->Width() == 0 || SourceRectangle->Height() == 0)
			return 0;
	}
	else
		SourceRectangle = &LockedSection;

	dft::Graphics::Color *Output = new dft::Graphics::Color[SourceRectangle->Area()];
	for(int Y = SourceRectangle->Top; Y < SourceRectangle->Bottom; ++Y)
	{
		for(int X = SourceRectangle->Left; X < SourceRectangle->Right; ++X)
		{
			int Index = ((Y - SourceRectangle->Top) * SourceRectangle->Width()) + (X - SourceRectangle->Left);
			Output[((Y - SourceRectangle->Top) * SourceRectangle->Width()) + (X - SourceRectangle->Left)] = GetPixel(X, Y);
		}
	}
	return Output;
}

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void Texture::Layer::SetPixel(int X, int Y, dft::Graphics::Color PixelColor)
{
	if(X > (LockedSection.Width() - LockedSection.Left) || Y > (LockedSection.Height() - LockedSection.Top) || X < 0 || Y < 0 || !Bits)
		return;
	Bits[((LockedRect.Pitch / sizeof(DWORD)) * Y) + X] = PixelColor;
}

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void Texture::Layer::SetPixel(const dft::Math::Point Location, dft::Graphics::Color PixelColor)
{
	SetPixel(Location.X, Location.Y, PixelColor);
}

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void Texture::Layer::DrawLine(int StartX, int StartY, int EndX, int EndY, dft::Graphics::Color PixelColor)
{
	bool Steep = abs(EndY - StartY) > abs(EndX - StartX);
	if(Steep)
	{
		StartX ^= StartY;
		StartY ^= StartX;
		StartX ^= StartY;

		EndX ^= EndY;
		EndY ^= EndX;
		EndX ^= EndY;
	}

	if(StartX > EndX)
	{
		StartX ^= EndX;
		EndX ^= StartX;
		StartX ^= EndX;

		StartY ^= EndY;
		EndY ^= StartY;
		StartY ^= EndY;
	}

	int DeltaX = EndX - StartX;
	int DeltaY = abs(EndY - StartY);
	int Error = -(DeltaX + 1) / 2;
	int YStep = 0;
	int Y = StartY;
	if(StartY < EndY)
		YStep = 1;
	else
		YStep = -1;

	for(int X = StartX; X < EndX; ++X)
	{
		if(Steep)
			SetPixel(Y, X, PixelColor);
		else
			SetPixel(X, Y, PixelColor);
		Error = Error + DeltaY;
		if(Error >= 0)
		{
			Y = Y + YStep;
			Error = Error - DeltaX;
		}
	}
}

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void Texture::Layer::DrawLine(const dft::Math::Point &StartPoint, const dft::Math::Point &EndPoint, dft::Graphics::Color PixelColor)
{
	DrawLine(StartPoint.X, StartPoint.Y, EndPoint.X, EndPoint.Y, PixelColor);
}

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void Texture::Layer::DrawRectangle(int X, int Y, int Width, int Height, dft::Graphics::Color PixelColor)
{
	for(int CurrentY = Y; CurrentY < (Y + Height); ++CurrentY)
	{
		DrawLine(X, CurrentY, X + Width, CurrentY, PixelColor);
	}
}

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void Texture::Layer::DrawRectangle(const dft::Math::Rectangle &DestinationRectangle, dft::Graphics::Color PixelColor)
{
	DrawRectangle(DestinationRectangle.Left, DestinationRectangle.Top, DestinationRectangle.Width(), DestinationRectangle.Height(), PixelColor);
}

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void Texture::Layer::DrawRectangle(const dft::Math::Point &Location, const dft::Math::Dimensions &Size, dft::Graphics::Color PixelColor)
{
	DrawRectangle(Location.X, Location.Y, Size.Width, Size.Height, PixelColor);
}

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void Texture::Layer::DrawChunk(const dft::Math::Point &Location, dft::Graphics::Color *Chunk, const dft::Math::Dimensions &ChunkSize)
{
	if(!Chunk)
		return;

	for(int Y = 0; Y < ChunkSize.Height; ++Y)
	{
		for(int X = 0; X < ChunkSize.Width; ++X)
		{
			SetPixel(Location.X + X, Location.Y + Y, Chunk[(Y * ChunkSize.Width) + X]);
		}
	}
}

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void Texture::Layer::CopyFromLayer(Layer::Pointer Source, const dft::Math::Rectangle &SourceRectangle, const dft::Math::Rectangle &Destination)
{
	dft::Graphics::Color *Chunk = Source->GetPixelChunk(&dft::Math::Rectangle(SourceRectangle));
	if(!Chunk)
		return;

	dft::Math::Dimensions ChunkSize = SourceRectangle.Size();
	int ColumnCount = (int)(ceil(((float)Destination.Width() / (float)ChunkSize.Width)));
	int RowCount = (int)(ceil(((float)Destination.Height() / (float)ChunkSize.Height)));
	for(int Row = 0; Row < RowCount; ++Row)
	{
		for(int Column = 0; Column < ColumnCount; ++Column)
		{
			DrawChunk(dft::Math::Point((ChunkSize.Width * Column) + Destination.Left, (ChunkSize.Height * Row) + Destination.Top), Chunk, ChunkSize);
		}
	}
}

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void Texture::Layer::Unlock()
{
	ComPtr->UnlockRect();
	Bits = 0;
	ZeroMemory(&LockedRect, sizeof(D3DLOCKED_RECT));
	LockedSection = LockedSection.Zero;
}

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Texture::Texture()
{
	ComPtr = 0;
}

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Texture::~Texture()
{
	Release();
}

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
ReturnValueEx<HRESULT> Texture::LoadFromFile(Device &GfxDevice, const std::string &FilePath)
{
	return LoadFromFileEx(GfxDevice, FilePath, D3DX_DEFAULT, D3DX_DEFAULT, D3DX_DEFAULT, 0, D3DFMT_UNKNOWN, D3DPOOL_MANAGED, D3DX_FILTER_NONE, D3DX_FILTER_NONE);
}

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
ReturnValueEx<HRESULT> Texture::LoadFromFileEx(Device &GfxDevice, const std::string &FilePath, int Width, int Height, int Levels, unsigned long Usage, D3DFORMAT Format, D3DPOOL Pool, unsigned long Filter, unsigned long MipFilter)
{
	this->GfxDevice = &GfxDevice;
	std::string AbsoluteFilePath = FileSystem::GetAbsolutePath(FilePath);
	HRESULT Result = S_OK;
	D3DXIMAGE_INFO ImageInfo;
	if(FAILED(Result = D3DXCreateTextureFromFileEx(GfxDevice.ComPtr, AbsoluteFilePath.c_str(), Width, Height, Levels, Usage, Format, Pool, Filter, MipFilter, 0, &ImageInfo, 0, &ComPtr)))
		return ReturnValueEx<HRESULT>(false, Result, String::Format("Failed to load texture \"%s\". - %s", AbsoluteFilePath.c_str(), DXGetErrorDescription9(Result)));

	D3DSURFACE_DESC Desc;
	ComPtr->GetLevelDesc(0, &Desc);
	Size = Math::Dimensions(Desc.Width, Desc.Height);
	this->ImageSize = dft::Math::Dimensions(ImageInfo.Width, ImageInfo.Height);
	this->FilePath = AbsoluteFilePath;
	Info.Levels = Levels;
	Info.Usage = Usage;
	Info.Format = Format;
	Info.Pool = Pool;
	Info.Filter = Filter;
	Info.MipFilter = MipFilter;
	return ReturnValueEx<HRESULT>(true, Result, String::Format("Loaded the texture \"%s\".", FilePath.c_str()));
}

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
ReturnValueEx<HRESULT> Texture::CreateBlank(Device &GfxDevice, int Width, int Height, int Levels, unsigned long Usage, D3DFORMAT Format, D3DPOOL Pool, const std::string &Name)
{
	HRESULT Result = S_OK;
	if(FAILED((Result = GfxDevice.ComPtr->CreateTexture(Width, Height, Levels, Usage, Format, Pool, &ComPtr, 0))))
		return ReturnValueEx<HRESULT>(false, Result, String::Format("Failed to create a blank texture with the name \"%s\". - %s", Name.c_str(), DXGetErrorDescription9(Result)));

	D3DSURFACE_DESC Desc;
	ComPtr->GetLevelDesc(0, &Desc);
	Size = Math::Dimensions(Desc.Width, Desc.Height);
	this->FilePath = Name;
	return ReturnValueEx<HRESULT>(true, Result, String::Format("Created the blank texture \"%s\".", FilePath.c_str()));
}

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
ReturnValueEx<HRESULT> Texture::Save(const std::string &OutputFilePath, D3DXIMAGE_FILEFORMAT ImageFileFormat)
{
	HRESULT Result = S_OK;
	if(FAILED(Result = D3DXSaveTextureToFile(dft::FileSystem::GetAbsolutePath(OutputFilePath).c_str(), ImageFileFormat, ComPtr, 0)))
		return ReturnValueEx<HRESULT>(false, Result, String::Format("Failed to save the texture \"%s\" to the file \"%s\". - %s", FilePath.c_str(), OutputFilePath.c_str(), DXGetErrorDescription9(Result)));
	return ReturnValueEx<HRESULT>(true, Result, String::Format("Successfully saved the texture \"%s\" to the file \"%s.\"", FilePath.c_str(), OutputFilePath.c_str()));
}

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Texture::Layer::Pointer Texture::GetLayer(int LayerIndex)
{
	Layer::Pointer NewLayer(new Layer);
	if(NewLayer->Create(this, LayerIndex))
		return NewLayer;
	return Layer::Pointer();
}

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
bool Texture::Reload()
{
	if(!GfxDevice)
		return false;
	Information Info = this->Info;
	std::string FilePath = this->FilePath;
	Release();
	return LoadFromFileEx(*GfxDevice, FilePath, Size.Width, Size.Height, Info.Levels, Info.Usage, Info.Format, Info.Pool, Info.Filter, Info.MipFilter).Succeeded;
	return true;
}

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void Texture::Release()
{
	if(ComPtr)
	{
		ComPtr->Release();
		ComPtr = 0;
		FilePath = "";
		Size = Math::Dimensions(0, 0);
		ZeroMemory(&Info, sizeof(Information));
	}
}

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Texture::Pointer Texture::GetTexture(Device &GfxDevice, const std::string &Path)
{
	return TexturePool.GetTexture(GfxDevice, Path);
}



And here's my project's code that uses it:
// Creating the texture -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// this works fine
ScratchTexture->CreateBlank(GfxDevice, Background->Size.Width, Background->Size.Height, 1, 0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, Name + "Scratch");
dft::Graphics::Texture::Layer::Pointer Layer = ScratchTexture->GetLayer(0);
Layer->Lock(0);
for(std::vector<ScratchArea>::iterator Itor = ScratchAreas.begin(); Itor != ScratchAreas.end(); ++Itor)
Layer->DrawRectangle(Itor->Location, Itor->Size, dft::Graphics::Color(255, 128, 128, 128));
Layer->Unlock();

// Modifying Function -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
dft::Math::Point Temp = Event.Position;
dft::Graphics::Texture::Layer::Pointer Layer = ScratchTexture->GetLayer(0);
Layer->Lock(0);
Layer->DrawLine(Temp.X - 4, Temp.Y + 4, Temp.X + 4, Temp.Y - 4, dft::Graphics::Color(0, 0, 0, 0));
Layer->DrawLine(Temp.X - 4, Temp.Y + 3, Temp.X + 3, Temp.Y - 4, dft::Graphics::Color(0, 0, 0, 0));
Layer->DrawLine(Temp.X - 3, Temp.Y + 4, Temp.X + 4, Temp.Y - 3, dft::Graphics::Color(0, 0, 0, 0));
Layer->Unlock();
// temp line ##TEMP##
ScratchTexture->Save("Output.png", D3DXIFF_PNG);



If you need any other information, just say so and I'll post it.

Share this post


Link to post
Share on other sites
Programmer16    2321
Ok, thanks to EDI we've got the problem fixed. The problem was that I was locking the texture using D3DLOCK_READONLY (forgot that I had the Lock method default to read only.) The weird part being that it worked at all and not giving me some form of access violation or error.

Share this post


Link to post
Share on other sites

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