Help with Creating 2D Texture Renderer

Started by
8 comments, last by KoldGames 10 years, 1 month ago

Hello everyone! I'm trying to create a 2D texture renderer for my engine and cannot get anything working or to show up. Here's how it's currently setup. I have a class called NXSpriteBatch. When NXSpriteBatch::Initialize() is called, it sets up the vertex buffer. After the vertex buffer is setup, I render textures using the method below:


bool NXSpriteBatch::Render(ID3D11ShaderResourceView* view, XMFLOAT4 color, XMFLOAT2 position, XMFLOAT2 scale)

In this bool, I start by updating the vertices based on the texture size:


bool NXSpriteBatch::UpdateVertices(XMFLOAT2 size)
		{
			float sizeX = (float) size.x / 2;
			float sizeY = (float) size.y / 2;

			Vertices[0].Position = XMFLOAT3(sizeX, sizeY, 1.0f);
			Vertices[1].Position = XMFLOAT3(sizeX, -sizeY, 1.0f);
			Vertices[2].Position = XMFLOAT3(-sizeX, -sizeY, 1.0f);
			Vertices[3].Position = XMFLOAT3(-sizeX, -sizeY, 1.0f);
			Vertices[4].Position = XMFLOAT3(-sizeX, sizeY, 1.0f);
			Vertices[5].Position = XMFLOAT3(sizeX, sizeY, 1.0f);

			Vertices[0].TextureCoordinate = XMFLOAT2(1.0f, 0.0f);
			Vertices[1].TextureCoordinate = XMFLOAT2(1.0f, 1.0f);
			Vertices[2].TextureCoordinate = XMFLOAT2(0.0f, 1.0f);

			Vertices[3].TextureCoordinate = XMFLOAT2(0.0f, 1.0f);
			Vertices[4].TextureCoordinate = XMFLOAT2(0.0f, 0.0f);
			Vertices[5].TextureCoordinate = XMFLOAT2(1.0f, 0.0f);

			return true;
		}

Next, I update the vertex buffer:


bool NXSpriteBatch::UpdateVertexBuffer()
		{
			if (OriginalVertices[0] == Vertices[0] || OriginalVertices[1] == Vertices[1] || OriginalVertices[2] == Vertices[2]
				|| OriginalVertices[3] == Vertices[3] || OriginalVertices[4] == Vertices[4] || OriginalVertices[5] == Vertices[5])
				return true;

			VertexBuffer.Update(&Vertices);

			OriginalVertices = Vertices;

			return true;
		}

You can check out my NXVertexBuffer code using the links below. After updating my vertex buffer (hopefully I did it right),I finish off the method by rendering. Here's the full Render method:


bool NXSpriteBatch::Render(ID3D11ShaderResourceView* view, XMFLOAT4 color, XMFLOAT2 position, XMFLOAT2 scale)
		{
			//Update Vertices
			if (!UpdateVertices(position, NXGetSRVSize(view)))
				return false;

			//Update Buffer
			if (!UpdateVertexBuffer())
				return false;

			XMMATRIX viewMatrix = XMMatrixIdentity();
			XMMATRIX projMatrix; NXGPU::Context->GetOrthoMatrix(projMatrix);
			XMMATRIX vp = XMMatrixMultiply(viewMatrix, projMatrix);
			XMMATRIX mvp = XMMatrixMultiply(CalculateWorldMatrix(position, scale, 0), vp);
			mvp = XMMatrixTranspose(mvp);

			Shaders::NXTextureShader* shader = GetTextureShader();

			shader->SetVSConstants(mvp);
			shader->SetPSConstants(XMLoadFloat4(&color), view);
			shader->Apply();

			NXGPU::DisableZBuffer();

			NXGPU::Draw(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST, 6, 0);

			return true;
		}

I have my ortho matrix setup like this:


XMStoreFloat4x4(&m_orthoMatrix, XMMatrixOrthographicOffCenterLH(0.0f, (float) width, 0.0f, (float) height, snear, sdepth));

Here's all of the code:

NXSpriteBatch(.h | .cpp)

NXVertexBuffer(.h | .cpp)

I know the texture is being loaded correctly because I used it for 3D objects and I also know the texture shader works correctly as well. I find it funny that I could get deferred renderer working great, but can't get a simple texture to render. LOL. Anyways, any help? Thanks! smile.png

Advertisement

*bump*

This:


 bool NXVertexBuffer::Update(vector<NXVertex>* vertices, vector<unsigned long>* indices) 
{ 
	if (vertices) 
	{ 
		if (FAILED(Core::NXGPU::Map(VertexBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &vMS))) 
		{ 
			return false; 
		} 

		Vertices = (vector<NXVertex>*)vMS.pData; 
		Vertices = vertices; 

		Core::NXGPU::Unmap(VertexBuffer, 0); 
	} 

	return true; 
}
doesn't do what you hope for.
The D3D11_MAPPED_SUBRESOURCE.pData pointer you're given points to where you should writo your data to. Currently you're just copying some pointers around. Use memcpy instead.
Since you're using std::vector instead of arrays - and my C++ is rather rusty wink.png - here's a SO entry how to get to the raw data of the elements: How to get std::vector pointer to the raw data?.

Also: Be careful with those pointers. I fear there are dangling pointers and/or memory leaks waiting to happen.
Hey! Using memcopy fixed it but now I am having another problem. Positioning of the textute doesn't seem to work right. Placing it at zero, zero doesn't put it at the top left corner of the screen. It just doesn't show. Using [1, 1] shows it at the top left corner of the screen but increasing either number changes it's position drastically. I'm not able to position it based on pixels. Using [2, 1] sends it over a lot. I am using XMMatrixTransformation2D now. I'll update dropbox when I get home with the new files. Any help? Thanks!
Ok, missed that earlier.


XMStoreFloat4x4(&m_orthoMatrix, XMMatrixOrthographicOffCenterLH(0.0f, (float) width, 0.0f, (float) height, snear, sdepth));

This is actually good if you want to use pixel coordinates. But if you want your top to be at 0 and your bottom at height you need to swap those parameters. Look up the docs, ViewBottom comes before ViewTop.

Not sure if something else is still amiss. Sometimes one forgets to transpose the final matrix before sending to the shader (also depends on your shader and how you compiled it: Check column vs row-major layout of your matrices). Looks like you're doing it right though, but neither do we know what's happening in shader->SetVSConstants(mvp) nor how your shader looks.

By the way: If you're doing this for learning, keep going smile.png
Otherwise, use, or at least have a look at DirectX Tool Kit Library which has a sprite renderer.

This is actually good if you want to use pixel coordinates. But if you want your top to be at 0 and your bottom at height you need to swap those parameters. Look up the docs, ViewBottom comes before ViewTop.

Hey! Haha, I didn't notice that. Although, when I switch them around, my texture is inverted. I also saw on the docs that ViewTop is the maximum y-value of the view volume. Is swapping still correct and my vertices just need to be flipped? Also I updated the files in Dropbox. I also checked out my rendering code and I am transposing the matrix. Here is how NXTextureShader is currently setup:

.H


class NXTextureShader : public Graphics::NXShader
		{
			public:
				const string Name()
				{
					return "Texture";
				}

				NXTextureShader();
				bool SetVSConstants(XMMATRIX& worldMatrix, XMMATRIX& viewMatrix, XMMATRIX& projectionMatrix);
				bool SetVSConstants(XMMATRIX& wvp);
				bool SetPSConstants(XMVECTOR color, Graphics::NXTexture2D* texture);
				bool SetPSConstants(XMVECTOR color, ID3D11ShaderResourceView* texture);

			private:
				D3D11_MAPPED_SUBRESOURCE mappedResource;
				ID3D11Buffer* colorBuffer = nullptr;
				bool CreateBuffers();
				void DisposeShader();
		};

.CPP


NXTextureShader::NXTextureShader() : NXShader("../Debug/TextureVS.cso", "../Debug/TexturePS.cso") { if (!CreateBuffers()){ return; } }
			bool NXTextureShader::SetVSConstants(XMMATRIX& worldMatrix, XMMATRIX& viewMatrix, XMMATRIX& projectionMatrix)
			{
				HRESULT result;
				Graphics::MatrixConstantBuffer* dataPtr = nullptr;

				worldMatrix = XMMatrixTranspose(worldMatrix);
				viewMatrix = XMMatrixTranspose(viewMatrix);
				projectionMatrix = XMMatrixTranspose(projectionMatrix);

				result = NXGPU::GetDeviceContext()->Map(ConstantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
				if (FAILED(result))
				{
					return false;
				}

				dataPtr = (Graphics::MatrixConstantBuffer*) mappedResource.pData;

				dataPtr->WVP = projectionMatrix * viewMatrix * worldMatrix;

				NXGPU::GetDeviceContext()->Unmap(ConstantBuffer, 0);

				NXGPU::GetDeviceContext()->VSSetConstantBuffers(0, 1, &ConstantBuffer);

				return true;
			}
			bool NXTextureShader::SetVSConstants(XMMATRIX& wvp)
			{
				HRESULT result;
				Graphics::MatrixConstantBuffer* dataPtr = nullptr;

				result = NXGPU::GetDeviceContext()->Map(ConstantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
				if (FAILED(result))
				{
					return false;
				}

				dataPtr = (Graphics::MatrixConstantBuffer*) mappedResource.pData;

				dataPtr->WVP = wvp;

				NXGPU::GetDeviceContext()->Unmap(ConstantBuffer, 0);

				NXGPU::GetDeviceContext()->VSSetConstantBuffers(0, 1, &ConstantBuffer);

				return true;
			}
			bool NXTextureShader::SetPSConstants(XMVECTOR color, Graphics::NXTexture2D* texture)
			{
					Graphics::VectorConstantBuffer* dataPtr = nullptr;

				if (FAILED(NXGPU::Map(colorBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource)))
					return false;

				dataPtr = ( Graphics::VectorConstantBuffer*) mappedResource.pData;

				dataPtr->Vector = color;

				NXGPU::Unmap(colorBuffer, 0);

				NXGPU::SetPSConstantBuffers(0, 1, &colorBuffer);

				NXGPU::GetDeviceContext()->PSSetShaderResources(0, 1, &texture->Texture);

				return true;
			}

			bool NXTextureShader::SetPSConstants(XMVECTOR color, ID3D11ShaderResourceView* texture)
			{
					Graphics::VectorConstantBuffer* dataPtr = nullptr;

				if (FAILED(NXGPU::Map(colorBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource)))
					return false;

				dataPtr = ( Graphics::VectorConstantBuffer*) mappedResource.pData;

				dataPtr->Vector = color;

				NXGPU::Unmap(colorBuffer, 0);

				NXGPU::SetPSConstantBuffers(0, 1, &colorBuffer);

				NXGPU::GetDeviceContext()->PSSetShaderResources(0, 1, &texture);

				return true;
			}

			bool NXTextureShader::CreateBuffers()
			{
				D3D11_BUFFER_DESC matrixBufferDesc;
				matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
				matrixBufferDesc.ByteWidth = sizeof(Graphics::MatrixConstantBuffer);
				matrixBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
				matrixBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
				matrixBufferDesc.MiscFlags = 0;
				matrixBufferDesc.StructureByteStride = 0;

				if (FAILED(NXGPU::GetDevice()->CreateBuffer(&matrixBufferDesc, NULL, &ConstantBuffer)))
					return false;

				D3D11_BUFFER_DESC colorBufferDesc;
				colorBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
				colorBufferDesc.ByteWidth = sizeof( Graphics::VectorConstantBuffer);
				colorBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
				colorBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
				colorBufferDesc.MiscFlags = 0;
				colorBufferDesc.StructureByteStride = 0;

				if (FAILED(NXGPU::GetDevice()->CreateBuffer(&colorBufferDesc, NULL, &colorBuffer)))
					return false;

				return true;
			}
			void NXTextureShader::DisposeShader(){ if (colorBuffer) { colorBuffer->Release(); colorBuffer = nullptr; } }

Vertex Shader

Pixel Shader

It works great for rendering everything else so far. Does everything look ok? I also suspected that the problem may be here:


XMMATRIX NXSpriteBatch::CalculateWorldMatrix(XMFLOAT2 position, XMFLOAT2 size, XMFLOAT2 scale, float rotation)
		{
			//XMVECTOR origin = XMLoadFloat2(&XMFLOAT2(position.x, position.y));
			XMVECTOR pos = XMLoadFloat2(&position);
			XMVECTOR scaling = XMLoadFloat2(&scale);

			XMMATRIX finalMatrix = XMMatrixTransformation2D(pos, 0, scaling, pos, rotation, pos);
                        finalMatrix = XMMatrixTranspose(finalMatrix);

			return finalMatrix;
		}

Before I was doing something else, but today I found this function and tried it out. Anyways, I would really like to thank you for your time, I really appreciate it. smile.png


By the way: If you're doing this for learning, keep going smile.png
Otherwise, use, or at least have a look at DirectX Tool Kit Library which has a sprite renderer.

Also, thanks for the support! biggrin.png

I'm getting something totally different, and I don't even remember what I did now lol O_O. Here's what I am currently getting. When in full size (I changed the scale to 1), it is aligned with the top left corner.

6s8c.png

When I render @ [1360, 0], it is completely off the screen, but rendering @ [500, 0] puts it here:

hn7r.png

The width of my current screen is 1360. Putting it @ [1340, 0] makes it go off-screen. Here is how I am updating my vertices now.


float left, right, top, bottom;

			left = position.x;
			right = left + (float) size.x;
			top = NXGPU::GetViewportHeight() + (float) position.y;
			bottom = top - (float) size.y;

			Vertices[0].Position = XMFLOAT3(left, top, 1);
			Vertices[1].Position = XMFLOAT3(left, bottom, 1);
			Vertices[2].Position = XMFLOAT3(right, top, 1);
			Vertices[3].Position = XMFLOAT3(right, bottom, 1);

UPDATE: Pretty weird. Scaling it down half way (0.5f) and putting it @ [0, 0] gives me this as a result:

smm6.png

EDIT: I also got away from using pointers in NXVertexBuffer biggrin.png Besides NXCreateVertexBuffer (I would like to be able to use nullptr for Indices)

Slow down, you might spoil the progress. Use version control or backup. Currently you have output you can play with, be glad about that.

Not sure where to begin. I'm too confused to point at the exact problem - or what you actually want. You're combining hardcoded positioning with a world placement transformation. The documentation of the latter (XMMatrixTransformation2D) is sparse. Never used myself. If it works like D3DXMatrixTransformation2D (mind the complexity) you're parameters don't look right (three times pos instead of just for translation, if you ask me).
Also, you're doing a transpose there too/already ? Why ? (The other transpose - the real final - for mvp looks good though).

Though I could go on for ever now, here rather some general hints:
  • Get (more wink.png) familiar with transformations in general. 2D without rotation is "easy" enough to do everything by hand (try pen and paper), then understand the matrix version and the helper functions. And of course how to combine them.
  • Get (more) familiar with the terminology, the different spaces in the pipeline and the common graphics spaces (world, view, projection).
  • In the end points "land" in normalized device coordinates, left to right goes from -1 to 1, bottom to top from -1 to 1. Ever hardcoded a full screen quad ?
  • Use a PIX or the graphics debugger (if you can). You can inspect both graphically and numerically what happens with your vertices, or even debug shaders.
  • Most of all: Keep it simple first and add features/options only you can handle (both from understanding and programming).


I'm too confused to point at the exact problem - or what you actually want. You're combining hardcoded positioning with a world placement transformation. The documentation of the latter (XMMatrixTransformation2D) is sparse. Never used myself. If it works like D3DXMatrixTransformation2D (mind the complexity) you're parameters don't look right (three times pos instead of just for translation, if you ask me).

Yeah... I wasn't sure about it as I'm still learning DirectX (coming from XNA) lol. Now that I have output to play with, I would like to get everything positioned correctly even when scaled. The idea to combine the two actually came from a tutorial I found on the internet because my original code (when I tried on my own) didn't render correctly (looked ok, but had some weird issues).

Also, you're doing a transpose there too/already ? Why ? (The other transpose - the real final - for mvp looks good though).

I was just experimenting. CalculateWorldMatrix actually doesn't exist any more in the updated code as I found that multiplying the orthographic matrix by scale seems to work like I thought it would with "scale" set to one (0, 0 being the top left corner) and now it seems to just be a matter of handling the positioning when scale is increased or reduced. Any idea on how I would do this correctly?

Get more familiar with transformations in general. 2D without rotation is "easy" enough to do everything by hand (try pen and paper), then understand the matrix version and the helper functions. And of course how to combine them.
Get (more wink.png) familiar with the terminology, the different spaces in the pipeline and the common graphics spaces (world, view, projection).

I will definitely read up more about those. Thanks for the suggestion. wink.png



In the end points "land" in normalized device coordinates, left to right goes from -1 to 1, bottom to top from -1 to 1. Ever hardcoded a full screen quad ?

Yep! I currently have it working and setup. The NXSpriteBatch code I actually used before I came here was kind of working like one, then I realized that I had setup my orthographic matrix wrong which led to using XMMatrixOrthographicOffCenterLH which gave me better results.

Use a PIX or the graphics debugger (if you can). You can inspect both graphically and numerically what happens with your vertices, or even debug shaders.

Yep! I've been using Visual Studio 2013's Graphics Debugger to debug some shaders and figure out what's causing a problem. It works quite well in my opinion.

Most of all: Keep it simple first and add features/options only you can handle (both from understanding and programming).

I will remember that! smile.png Thanks! biggrin.png


I was just experimenting. CalculateWorldMatrix actually doesn't exist any more in the updated code as I found that multiplying the orthographic matrix by scale seems to work like I thought it would with "scale" set to one (0, 0 being the top left corner) and now it seems to just be a matter of handling the positioning when scale is increased or reduced. Any idea on how I would do this correctly?

I just got it working correctly. I realized that instead of multiplying scale by the orthographic matrix, I should handle scale in UpdateVertices();


float left, right, top, bottom;

left = position.x;
right = left + ((float) size.x * scale.x);
top = NXGPU::GetViewportHeight() - (float) position.y;
bottom = top - ((float) size.y * scale.y);

Vertices[0].Position = XMFLOAT3(left, top, 1);
Vertices[1].Position = XMFLOAT3(left, bottom, 1);
Vertices[2].Position = XMFLOAT3(right, top, 1);
Vertices[3].Position = XMFLOAT3(right, bottom, 1);

I feel so stupid now. Lol Anyways, it works correctly. Everything is correctly positioned. Thanks for the help Unbird, I really appreciate it. smile.png And I will definitely read up some more about transformations. smile.png I may be back later as I will probably have just a couple questions. :)

This topic is closed to new replies.

Advertisement