Skip to main content
GameDev.net gamedev.net
🔒 Locked

2D using Direct3D, some problems drawing sprites

Started by Magos Aug 23, 2003 at 12:31 PM 28 replies 2.2k views
Original Post
Magos
Magos
Hi! I'm making a graphics engine for 2D graphics using direct 3D. Below is my DrawSprite function. As you can see you can specify a source rectangle to tell which part of a bitmap (texture) you want to render (or use NULL to use the whole bitmap). You can also specify a destination rectangle to tell where on the screen it should be rendered (or use NULL and it will be calculated automatically). The function works fine if both fields are set to NULL. The problem I have is when the source rectangle is set to something. The result will be some weird "cracks" in the resulting image (hard to explain, but you can see a cross-like crack if the image is moving). What causes this? Also, if the source rectangle is set to NULL and the destination rectangle is something, the resulting picture is weirdly scaled (mainly smaller than what it should be). I've checked though the code over and over unable to find the cause. Can some of you find any errors? Thanks in advance!
BOOL GRAPHICS::DrawSprite(INT X, INT Y, INT SpriteNr, RECT* SrcRect, RECT* DestRect, DWORD VertexColor)
{
	//Data

	INT TextureWidth;
	INT TextureHeight;
	RECT TempRect1;
	RECT TempRect2;
	VERTEX* Vertices;
	D3DSURFACE_DESC SurfaceDesc;

	//Aborts if the sprite nr is out of bounds

	if((SpriteNr < 0) || (SpriteNr >= NrOfTextures))
	{
		return FALSE;
	}

	//Get the texture width and height

	TextureBuffer[SpriteNr]->GetLevelDesc(0, &SurfaceDesc);
	TextureWidth = SurfaceDesc.Width;
	TextureHeight = SurfaceDesc.Height;

	//Prevent division by 0

	if((TextureWidth == 0) || (TextureHeight == 0))
	{
		return FALSE;
	}

	//Locks the vertex buffer

	VertexBuffer->Lock(0, 0, (void**)&Vertices, NULL);

	//Upper left vertex

	Vertices[0].Color = VertexColor;
	Vertices[0].Z = 0.0f;
	Vertices[0].RHW = 1.0f;

	//Upper right vertex

	Vertices[1].Color = VertexColor;
	Vertices[1].Z = 0.0f;
	Vertices[1].RHW = 1.0f;

	//Lower right vertex

	Vertices[2].Color = VertexColor;
	Vertices[2].Z = 0.0f;
	Vertices[2].RHW = 1.0f;

	//Lower left vertex

	Vertices[3].Color = VertexColor;
	Vertices[3].Z = 0.0f;
	Vertices[3].RHW = 1.0f;

	//If no source rectangle is specified, assume the whole texture

	if(SrcRect == NULL)
	{
		TempRect1.left = 0;
		TempRect1.right = TextureWidth;
		TempRect1.top = 0;
		TempRect1.bottom = TextureHeight;

		SrcRect = &TempRect1

		//Upper left vertex

		Vertices[0].TexX = 0.0f;
		Vertices[0].TexY = 0.0f;

		//Upper right vertex

		Vertices[1].TexX = 1.0f;
		Vertices[1].TexY = 0.0f;

		//Lower right vertex

		Vertices[2].TexX = 1.0f;
		Vertices[2].TexY = 1.0f;

		//Lower left vertex

		Vertices[3].TexX = 0.0f;
		Vertices[3].TexY = 1.0f;
	}
	else
	{
		//Upper left vertex

		Vertices[0].TexX = (FLOAT)SrcRect->left / (FLOAT)TextureWidth;
		Vertices[0].TexY = (FLOAT)SrcRect->top / (FLOAT)TextureHeight;

		//Upper right vertex

		Vertices[1].TexX = (FLOAT)SrcRect->right / (FLOAT)TextureWidth;
		Vertices[1].TexY = (FLOAT)SrcRect->top / (FLOAT)TextureHeight;

		//Lower right vertex

		Vertices[2].TexX = (FLOAT)SrcRect->right / (FLOAT)TextureWidth;
		Vertices[2].TexY = (FLOAT)SrcRect->bottom / (FLOAT)TextureHeight;

		//Lower left vertex

		Vertices[3].TexX = (FLOAT)SrcRect->left / (FLOAT)TextureWidth;
		Vertices[3].TexY = (FLOAT)SrcRect->bottom / (FLOAT)TextureHeight;
	}

	//If no destination rectangle is specified, use the X & Y position to create one

	if(DestRect == NULL)
	{
		TempRect2.left = X;
		TempRect2.right = X + (SrcRect->right - SrcRect->left);
		TempRect2.top = Y;
		TempRect2.bottom = Y + (SrcRect->bottom - SrcRect->top);

		DestRect = &TempRect2
	}

	//Upper left vertex

	Vertices[0].X = (FLOAT)DestRect->left;
	Vertices[0].Y = (FLOAT)DestRect->top;

	//Upper right vertex

	Vertices[1].X = (FLOAT)DestRect->right;
	Vertices[1].Y = (FLOAT)DestRect->top;

	//Lower right vertex

	Vertices[2].X = (FLOAT)DestRect->right;
	Vertices[2].Y = (FLOAT)DestRect->bottom;

	//Lower left vertex

	Vertices[3].X = (FLOAT)DestRect->left;
	Vertices[3].Y = (FLOAT)DestRect->bottom;

	//Unlocks the vertex buffer

	VertexBuffer->Unlock();

	//Sets the texture

	if(FAILED(Direct3DDevice->SetTexture(0, TextureBuffer[SpriteNr])))
	{
		return FALSE;
	}

	//Draws the sprite

	if(FAILED(Direct3DDevice->DrawPrimitive(D3DPT_TRIANGLEFAN, 0, 2)))
	{
		return FALSE;
	}

	//Return success

	return TRUE;
}
EDIT: It's SOURCE tags, not CODE ^^ Games and Programming http://www20.brinkster.com/magos818 [edited by - Magos on August 23, 2003 1:33:00 PM] [edited by - Magos on August 23, 2003 1:33:38 PM] [edited by - Magos on August 23, 2003 1:34:42 PM]
----------------------------------------MagosX.com
CrazyEddie
CrazyEddie
Have you read "Directly Mapping Texels to Pixels" under texturing in the SDK docs? I had some problems with 2D in D3D until I realised you have to properly allow for the way the texture is mapped to screen pixels - sometimes things end up where you don''t want them!

My problem was fixed by applying a simple -0.5f bias to all destination co-ords, which then places the ''lines'' that form the quad into the centre of the pixels rather than at the edge of the pixels (which can cause some problems).
Magos
Magos
Ah, I found the problem. It seems like whena texture is read it converts it to the closest "power of 2" square dimension abov its current. If I had a 256 * 200 texture, it would be converted to a 256*256 meaning the bottom 56 pixels are gone when rendering. I believe the solution to this is to store the dimensions somewhere when loading the texture. Unless someone has a better solution.

Thanks for your help. I followed the tutorial on this site with quads, and they never mentioned an offset by 0.5. Hm...
----------------------------------------MagosX.com
CrazyEddie
CrazyEddie
What you suggest is about right. I have a class that represents each texture (which contains a set of sprites) - I use D3DXCreateTextureFromFileEx to load the texture and get the dimensions from the D3DXIMAGE_INFO that it fills in, these are stored in data fields in the class and used later by the sprite drawing routines.

Probably your only other option would be to limit your images to powers of 2 in the first place.
glassJAw
glassJAw
quote:
Original post by Magos
Ah, I found the problem. It seems like whena texture is read it converts it to the closest "power of 2" square dimension abov its current. If I had a 256 * 200 texture, it would be converted to a 256*256 meaning the bottom 56 pixels are gone when rendering. I believe the solution to this is to store the dimensions somewhere when loading the texture. Unless someone has a better solution.

Thanks for your help. I followed the tutorial on this site with quads, and they never mentioned an offset by 0.5. Hm...


If you''re talking about my article, read the appendix: you do NOT use an 0.5 bias for destination coordinates. Direct3D uses inclusive-exclusive coordinates.

And I believe I did mention that Direct3D would create a nearest power-of-2 size texture for irregular sized textures on almost all video cards.
glassJAw
glassJAw
quote:
Original post by CrazyEddie
What you suggest is about right. I have a class that represents each texture (which contains a set of sprites) - I use D3DXCreateTextureFromFileEx to load the texture and get the dimensions from the D3DXIMAGE_INFO that it fills in, these are stored in data fields in the class and used later by the sprite drawing routines.

If you do not make your images powers of 2 in the first place, this is the best way of handling them.

To get the D3DXIMAGE_INFO structure about a file, you call D3DXGetImageInfoFromFile().

Then your texture coordinates should range from 0.0f to imageWidth/textureWidth (for the u coordinate) where imageWidth is the value you get in the D3DXIMAGE_INFO struct, and textureWidth is the width of the actual texture. For the v coordinate, the range is 0.0f to imageHeight/textureHeight.
CrazyEddie
CrazyEddie
quote:
Original post by glassJAw
you do NOT use an 0.5 bias for destination coordinates.


Ah, so the people who wrote the DirectX SDK documentation should have asked you about this then, instead of writing what they did about texel to pixel mapping and the need for a bias to ensure things always appear correct?

quote:
From the SDK docs
...
Given an understanding of this mapping, you can apply a simple bias to your screen-space geometry coordinates to force the system to map each texel to a corresponding pixel. For example, to draw a four-sided polygon that maps each texel from the preceding texture to one, and only one, pixel on the screen, you must force geometry coordinates to overlap the pixels, effectively placing the center of each texel at the center of each pixel. The result is the 1-to-1 mapping often sought-after by applications.
...



What are they talking about here then?

Also, in my own recent experience, I had a problem which was not even visible in all systems, but after re-reading the docs properly and understanding them, the solution became obvious. Applying the bias made the problem go away, along with the need for a messy kludge which I had to overcome another (obviously related) issue.


[edited by - CrazyEddie on August 24, 2003 2:12:08 AM]
Magos
Magos
Another question. When I''m in the program, then Alt-Tabs out I cannot enter again. What is causing this?
----------------------------------------MagosX.com
CrazyEddie
CrazyEddie
quote:
Original post by Magos
Another question. When I''m in the program, then Alt-Tabs out I cannot enter again. What is causing this?


You mean you can''t Alt-Tab back in?

This is probably due to the device being in a ''lost'' state. When you call the Present() function on the device it will return D3DERR_DEVICELOST. You can read about lost devices in the SDK docs, and ways to determine how to handle the situation. This may mean releasing all your textures, VBs, etc, calling Reset() and re-allocating textures, etc, and re-loading any images.
Magos
Magos
So you have to manually reload all textures again? In DirectDraw, all you had to to was call a method IsLost() to check if a surface was lost then call a method Restore() and everything was fine again. Manually reloading the files will be a little problematic to me since the graphics class I''m making doesn''t know of the files. They are opened through external method calls.

I''ll check the docs.

----------------------------------------
Games and Programming
http://www20.brinkster.com/magos818
----------------------------------------MagosX.com
CrazyEddie
CrazyEddie
quote:
Original post by Magos
So you have to manually reload all textures again?


If your resources are in the D3DPOOL_DEAFULT pool (i.e. not D3DPOOL_MANAGED or D3DPOOL_SYSTEMMEM pools), they have to be totally destroyed and remade, and then the images reloaded.

If you make sure your textures are in the managed pool, you do not have to reload the imagery.

The only problem you may have is if you are using dynamic vertex buffers or index buffers, as these can't be placed in the managed pool (but static ones are ok).

Also, depending on what you're doing, managed buffers can take a performance hit if the data in them is constantly changing - because each time you change the data it has to be transfered to the video device memory from the managed (system memory?) buffer.

So, each possibility has some good points and some potentially bad points.

[edited by - CrazyEddie on August 24, 2003 10:19:19 AM]
Magos
Magos
It works better now. I forgot to fill in the parameters when calling the Reset() method :ashamed:
Anyway, when returning into the program everything is pitch black. Seems like the textures are unloaded, but I''ve been using the D3DPOOL_MANAGED when loading the textures.

I noticed the vertices I create are also placed in the managed pool (D3DPOOL_MANAGED), but you say they cannot? What do you mean by dynamic? I modify it in every call to DrawSprite.
----------------------------------------MagosX.com
CrazyEddie
CrazyEddie
quote:
Original post by Magos
It works better now. I forgot to fill in the parameters when calling the Reset() method :ashamed:
Anyway, when returning into the program everything is pitch black. Seems like the textures are unloaded, but I''ve been using the D3DPOOL_MANAGED when loading the textures.

I noticed the vertices I create are also placed in the managed pool (D3DPOOL_MANAGED), but you say they cannot? What do you mean by dynamic? I modify it in every call to DrawSprite.


According to the docs, textures in the managed pool are preserved through changes between the lost and operational states on the device - I''ve never tested that assertion though

Buffers can by static or dynamic - this has nothing to do with the data you are putting in but is more to do with the way the memory is handled internally. Dynamic buffers can''t be created in the managed pool. If you have not specified that your buffer is dynamic, then it is static and you have nothing to worry about.

After the Reset() you have to restore all the render state settings - this may be why it''s all black?
Magos
Magos
I tried resetting the render states after the Reset() call, but it''s still pitch black. At the beginning of each render cycle I clear the screen. I tried changing that color to red, and it works. It can clear the screen in one color. It''s just the sprite rendering that doesn''t work...

I''ll attach some code if it will help you:

Loading a texture:

//Attempt to create a texture from a file

if(FAILED(D3DXCreateTextureFromFileEx(Direct3DDevice, FileName, D3DX_DEFAULT, D3DX_DEFAULT, 1, 0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, D3DX_FILTER_NONE, D3DX_DEFAULT, TransparentColor, &ImageInfo, NULL, &TextureBuffer[Index])))
{
return FALSE;
}

The method called before very render cycle:

BOOL GRAPHICS::BeginRender()
{
//Data

HRESULT Result;

//Checks if the program lost focus

Result = Direct3DDevice->TestCooperativeLevel();
if(Result != D3D_OK)
{
//If you cannot simply reset, wait until you can

if(Result == D3DERR_DEVICELOST)
{
return FALSE;
}

//Resets the device

Direct3DDevice->Reset(&PresentParameters);

//Re-sets the render states

SetupRenderStates();
}

//Clears the back buffer

if(FAILED(Direct3DDevice->Clear(0, NULL, D3DCLEAR_TARGET, 0x00000000, 0, 0)))
{
return FALSE;
}

//Begins rendering

if(FAILED(Direct3DDevice->BeginScene()))
{
return FALSE;
}

//Return success

return TRUE;
}

Thanks a lot for the help so far!
----------------------------------------MagosX.com
CrazyEddie
CrazyEddie
When you have your red background, all your sprites are rendered as black squares?

I ran a couple of tests using my library here, and switching to the managed pool, the textures definately survive the alt-tab process.

The only way I could get black squares was to disable part of the render-state setup (in my case alpha testing). But as you seem to have all that done in your call to SetupRenderStates() this shouldn't be it.

It really doesn't make any sense At the moment I am at a loss as to what to suggest - hopefully someone else might come up with the answer, but in the mean time I'll keep my thinking cap on.

Do you get any debugger output at all (from Direct3D)?

[edited by - CrazyEddie on August 24, 2003 2:18:28 PM]
Magos
Magos
No, not black squares. They don''t show up at all.
And the only reason why my DrawSprite methods wouldn''t be called is if BeginRender (see above) returns FALSE. Which I highly doubt.

Thanks for your time!

----------------------------------------
Games and Programming
http://www20.brinkster.com/magos818
----------------------------------------MagosX.com
CrazyEddie
CrazyEddie
quote:
Original post by Magos
No, not black squares. They don''t show up at all.
And the only reason why my DrawSprite methods wouldn''t be called is if BeginRender (see above) returns FALSE. Which I highly doubt.



Are you able to test that? (output a debug string or something) Your code looks good, but something fishy is going on!!
Magos
Magos
Yes. I tested it and the DrawSprite methods are running. Not working though...

I did some more testing:

//Sets the texture

if(FAILED(Direct3DDevice->SetTexture(0, TextureBuffer[SpriteNr])))
{
return FALSE;
}

//Draws the sprite

if(FAILED(Direct3DDevice->DrawPrimitive(D3DPT_TRIANGLEFAN, 0, 2)))
{
return FALSE;
}

That is the last part of my DrawSprite function. I found out that DrawPrimitive() returns D3DERR_INVALIDCALL after I''ve Alt-Tabbed out then in again.
That''s the error when an invalid parameter is passed, right? Well, I don''t understand how any of those parameters can be invalid... (D3DPT_TRIANGLEFAN, 0 or 2).
----------------------------------------MagosX.com
glassJAw
glassJAw
quote:
Original post by CrazyEddie
quote:
Original post by glassJAw
you do NOT use an 0.5 bias for destination coordinates.


Ah, so the people who wrote the DirectX SDK documentation should have asked you about this then, instead of writing what they did about texel to pixel mapping and the need for a bias to ensure things always appear correct?

quote:
From the SDK docs
...
Given an understanding of this mapping, you can apply a simple bias to your screen-space geometry coordinates to force the system to map each texel to a corresponding pixel. For example, to draw a four-sided polygon that maps each texel from the preceding texture to one, and only one, pixel on the screen, you must force geometry coordinates to overlap the pixels, effectively placing the center of each texel at the center of each pixel. The result is the 1-to-1 mapping often sought-after by applications.
...



What are they talking about here then?

Also, in my own recent experience, I had a problem which was not even visible in all systems, but after re-reading the docs properly and understanding them, the solution became obvious. Applying the bias made the problem go away, along with the need for a messy kludge which I had to overcome another (obviously related) issue.


This issue has been addressed many times, most notably here

In my own experience, the methods presented in that article have always been correct, while an 0.5 bias caused alignment problems. While not technically incorrect, that section of the DirectX SDK documentation is, as you have demonstrated, very misleading.

quote:
Robert Dunlop (X-Zone) suggests that you expand your target rectangle "by 0.5 on all sides to allow proper mapping of texels to pixels." This will "compensate for the texel alignment rules of Direct3D" that are causing this problem [2, 3]

But it doesn't work.

quote:
Interestingly, there's a very simple solution to this problem: extend the right and bottom sides of your sprite by 1 pixel.

quote:
Maybe there's a logical explanation for it. Maybe they aren't referring to "pixel coordinates". The API documentation doesn't explicitly say this (but most people would interpret it that way).

Nevertheless, it does explain why extending the right and bottom of a sprite by 1 fixes the problem.

The DirectX 8 documentation for rectangles is wrong. You can find this page under DirectX Graphics:

* Introduction to DirectX Graphics
o Getting Started With DirectX Graphics
+ Rectangles

According to the documentation, the coordinates (right, bottom) refer to the bottom-right pixel of the rectangle. This is wrong. The coordinates (right, bottom) are actually 1-pixel outside the rectangle.

The documentation describes an inclusive-inclusive coordinate system. But DirectX uses inclusive-exclusive coordinates; the last pixel (right, bottom) is not part of the rectangle.


[edited by - glassJAw on August 24, 2003 11:14:00 PM]
glassJAw
glassJAw
quote:
Original post by Magos
Yes. I tested it and the DrawSprite methods are running. Not working though...

I did some more testing:

//Sets the texture

if(FAILED(Direct3DDevice->SetTexture(0, TextureBuffer[SpriteNr])))
{
return FALSE;
}

//Draws the sprite

if(FAILED(Direct3DDevice->DrawPrimitive(D3DPT_TRIANGLEFAN, 0, 2)))
{
return FALSE;
}

That is the last part of my DrawSprite function. I found out that DrawPrimitive() returns D3DERR_INVALIDCALL after I''ve Alt-Tabbed out then in again.
That''s the error when an invalid parameter is passed, right? Well, I don''t understand how any of those parameters can be invalid... (D3DPT_TRIANGLEFAN, 0 or 2).


The code you posted there is fine. You''ll need to post some more.

Is there any debug output?

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.