Sign in to follow this  

Strange behavior with LockRect and scaling a sprite.

This topic is 2756 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

If I scale a sprite and use the texture I edited the pixels of with LockRect then my changes don't seem to show up. In fact depending on how much I scale the sprite the worse it gets. So at a scale of 1 (100%) then it works fine anything less not so much.
void XenStats::RenderArc(float angle, bool top, bool left, byte r, byte g, byte b, D3DXVECTOR2 arcPos, float scale)
{
	if(!top)
		angle = 90 - angle;

	float angleRatio = tan(angle * PI / 180);

	D3DSURFACE_DESC arcDesc;
	arcTexture->GetLevelDesc(0, &arcDesc);

	D3DXVECTOR2 arcScale;
	arcScale.x =  0.5; //This doesn't seem to work but 1.0 does.
	arcScale.y = 0.5;

	
	D3DLOCKED_RECT lockedRect;
	arcTexture->LockRect(0, &lockedRect, NULL, 0);
	byte *pixelBuffer = (byte*)(lockedRect.pBits);

	//Utils::Log(Utils::ToString<int>(lockedRect.Pitch).c_str());

	for (int x = 0; x < arcDesc.Width; ++x) {
		for (int y = 0; y < arcDesc.Height; ++y) {
			//int currentPixel = (arcDesc.Width * y + x) * 4;
			DWORD currentPixel=(x*4+(y*(lockedRect.Pitch)));

//Change all pixels to be blue. These become less and less blue the more I scale the sprite.
				pixelBuffer[currentPixel] = 255;
				pixelBuffer[currentPixel+1] = 0;
				pixelBuffer[currentPixel+2] = 0;
			
		}
	}

	arcTexture->UnlockRect(0);

	D3DXMATRIX arcMatrix;
	D3DXMatrixTransformation2D(&arcMatrix, NULL, 0.0, &arcScale, NULL, 0, &arcPos);


	statsBgSprite->Begin(D3DXSPRITE_ALPHABLEND);
	statsBgSprite->SetTransform(&arcMatrix);
	statsBgSprite->Draw(arcTexture, NULL, NULL, NULL, D3DCOLOR_RGBA(255, 255, 255, 255));
	statsBgSprite->End();
	
}

Share this post


Link to post
Share on other sites
1. How do you scale the sprite?
2. How do you draw the sprite?
3. Is this not just because of texture filtering? What if you set the texture filtering to point with:

pDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_POINT);
pDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_POINT);

(Assuming you're not using pixel shaders)

Share this post


Link to post
Share on other sites
I did try what you suggested. That did not seem to make a difference. Im not exactly sure how to answer the other 2 questions. The drawing and the scaling are done in the code above.

Share this post


Link to post
Share on other sites
Unless you understand how the drawing and scaling is done, then you won't have an idea as to how it should work. Without knowing how the code works at least at an higher level then its pointless to try and trouble shoot. Did you write the code/ or did you cut and paste it from somewhere. Scaling an sprite(image) is just a resampling problem. By that I mean for a scale factor of 1( 1 to 1) mapping, each pixel in the target it mapped to the same pixel in the destination. If you scale up by a factor on n then for each pixel in the the input, you have 3n pixels written to the destination. If you scale down by n then for for every n pixels in the input you have 1/n pixels in the destination. This is just a simplification and does not factor in filtering, but you can see that just by understanding how scaling works, you should be able to double check you code and find the error in your logic (if there is one) and fix it.

Share this post


Link to post
Share on other sites
I wrote the code. It uses a matrix and scales it using the line

D3DXMatrixTransformation2D(&arcMatrix, NULL, 0.0, &arcScale, NULL, 0, &arcPos);

as seen above. The reason I don't know how to answer your question is because all the code used for drawing and scaling is shown in my snippet. I am using built in D3DXSprites and thats it.

The lockrect stuff is because I change the color of each pixel in the texture based on a percentage to create a rounded health bar. That works fine but when I scale this it does exactly as you mentioned (re-sampling) and messes it up. Now I would guess the best way to do this would be to modify the texture or sprite after it has been scaled except that I'm not sure how this would be done exactly. I don't think textures get scaled and instead may get mapped to the sprite. I have no clue how that actually works to be honest. So If I could modify the texture after its been applied to the sprite this would probably work fine.

Share this post


Link to post
Share on other sites
From what I can tell your application of D3DXMatrixTransformation2D is fine, i.e. scaling and translating the sprite. I'm not quite sure if you ought to SetTransform before Sprite->Begin() though, but probably not.

But, and please apologize, I still don't understand what you want to achieve:

Quote:
The lockrect stuff is because I change the color of each pixel in the texture based on a percentage to create a rounded health bar


As I read your double loop, you set all pixels to blue (leaving alpha as it is), independant of method arguments or globals . Apart from being rather useless and time-consuming - such a texture can be setup/loaded once - I doubt that's what you want.

I suggest you make a screenshot and a drawing/explanation what you really want.

Share this post


Link to post
Share on other sites
Lucky for me I have a screenshot already for what this accomplishes. It works great but I haven't figured out a more efficient way to do this or support scaling.

http://xenergy.murderdev.com/images/itemcompass.png

The hp/mp/xp bars in the center of the screen is this code in action and working. I have a black and white png image that contains half of one bar. I then duplicate that texture to make the other half. To get the color I use the method above to change the pixels blue element to 255.

Share this post


Link to post
Share on other sites
(Off topic: Nice work! The screenshot looks promising)

Ok, still not sure whats going wrong. Can you send the health bar texture and a screenshot with wrong/undesired output (i.e. the scaling), too, please. And the code actually calling XenStats::RenderArc might be enlightening, too.

But here some hints:
Quote:
...to change the pixels blue element to 255


You can achieve this far cheaper with a tint. Currently, you are using a tint of opaque white, which will leave the texture unchanged. Try using a blue tint:

statsBgSprite->Draw(arcTexture, NULL, NULL, NULL, D3DCOLOR_RGBA(0, 0, 255, 255));


Maybe you need to set the texture stage states accordingly but AFAIR the Sprite class will do that for you in this case. This way you can also fade in/out sprites if you use a alpha other than 255.

You probably will want to change the method signature to reflect that, i.e. provide a additional parameter for color.

Quote:
I then duplicate that texture to make the other half.


How do you do that ? Drawing two or more sprites and put them together like tiles and flip some ? And if so: How do you flip ?

If not using sprites, i.e. working with vertex buffers, you normally achieve this by properly setting the texture wrapping modes and the tex-coords. With sprites this is probably not possible that easy, but I suggest having a look at the parameters of sprite->Draw you are currently ignoring, especially pSrcRect which allows you to only take a part of the texture. Flipping can then be done again with an appropriate transformation.

Hope that helps

Share this post


Link to post
Share on other sites
I am not currently home at the moment but I will take some screenshots later showing this in more detail as well as the issue with scaling.

That said wouldn't

statsBgSprite->Draw(arcTexture, NULL, NULL, NULL, D3DCOLOR_RGBA(0, 0, 255, 255));

color the entire sprite blue? That is not exactly what I want to happen. If you notice in this screenshot (below) the mp is less than full. This also shows what I am accomplishing. This is done using some math and drawing a line from one corner of the texture (which is half of one of those bars) at an angle across the texture. Everything below that line is colored.

http://xenergy.murderdev.com/Images/XenergyNewBars.png

void XenStats::RenderArc(float angle, bool top, bool left, byte r, byte g, byte b, D3DXVECTOR2 arcPos, float scale)

As far as this goes angle is the angle of the line (which is calculated from a health/mana percentage elsewhere). Top indicates whether this half of the arc is top or bottom because since they are rotated differently they need to be aware of what is the bottom for coloring purposes. Left is the same idea as the top. r g and b is the color of the bar. arcPos and scale is the position and scale of the bar respectively.

To answer your question about how I duplicate the textures, I used to just use one texture and modify it every frame and apply it to the sprite. That worked but was inefficient since the HP/MP bars do not always change. I then created 4 textures and modified them separately and only when they changed. I don't remember exactly off hand why my design required me to create a texture for each sprite. Something to do with remembering the previous state or color? Not sure. I may have overlooked that and should go back to it but its not a big deal really.

Share this post


Link to post
Share on other sites
Quote:
That said wouldn't ... color the entire sprite blue?

Yes it would. But actually, isn't that what you actually do in the double loop ? And you do have a comment saying so (//Change all pixels to be blue..).

Finally I somehow understand the approach, though, i.e. masking pixels with that line defined by an arbitrary angle. But either I'm totally blind or the code you provided does not do that.

Yes, XenStats::RenderArc got arguments for the angle and the quadrant (bool top, bool left), but in that code snipped they are ignored. Well you do a little calculation, but neither of them has an impact on the texture update or the sprite drawing (Transformation). You sure this is (all) the code responsible for those health bars ?

Share this post


Link to post
Share on other sites
Your right... I apparently cleaned it up to show the issue but here is the full working code for the method as well as the method that calls it. Some math to do some drawing is really all i removed.


void XenStats::RenderBar(float percent, bool left, byte r, byte g, byte b, D3DXVECTOR2 arcPos, LPDIRECT3DTEXTURE9 topArcTexture, LPDIRECT3DTEXTURE9 bottomArcTexture, bool redraw, float scale, float rotationAngle)
{
float topAngle = percent > 0.5 ? (percent - 0.5) * 2.0 * 90.0 : 0.0;
float bottomAngle = percent >= 0.5 ? 90.0 : percent * 2.0 * 90.0;

//Render top half of bar
RenderArc(topAngle, true, left, r, g, b, arcPos, topArcTexture, redraw, scale, rotationAngle);
//Render bottom half of bar
RenderArc(bottomAngle, false, left, r, g, b, arcPos, bottomArcTexture, redraw, scale, rotationAngle);

}

void XenStats::RenderArc(float angle, bool top, bool left, byte r, byte g, byte b, D3DXVECTOR2 arcPos, LPDIRECT3DTEXTURE9 arcTexture, bool redraw, float scale, float rotationAngle)
{
if(!top)
angle = 90 - angle;

float angleRatio = tan(angle * PI / 180);

D3DSURFACE_DESC arcDesc;
arcTexture->GetLevelDesc(0, &arcDesc);

D3DXVECTOR2 arcScale;
//arcScale.x = left ? -1 * scaleX * scale: scaleX * scale;
//arcScale.y = top ? -1 * scaleY * scale : scaleY * scale;

arcScale.x = left ? -1 : 1.0;
arcScale.y = top ? -1 : 1.0;
//TODO: For some reason scaling does not work.
//arcScale.x *= 0.5;
//arcScale.y *= 0.5;

if(redraw) {
D3DLOCKED_RECT lockedRect;
arcTexture->LockRect(0, &lockedRect, NULL, 0);
byte *pixelBuffer = (byte*)(lockedRect.pBits);

//Utils::Log(Utils::ToString<int>(lockedRect.Pitch).c_str());

for (int x = 0; x < arcDesc.Width; ++x) {
for (int y = 0; y < arcDesc.Height; ++y) {
//int currentPixel = (arcDesc.Width * y + x) * 4;
DWORD currentPixel=(x*4+(y*(lockedRect.Pitch)));
float maskHeight = angleRatio * (float)x;

if((!top && maskHeight < y && angle < 90.0 || (!top && angle == 0)) ||
(top && maskHeight > y || (top && angle == 90.0)) ) {
pixelBuffer[currentPixel] = b;
pixelBuffer[currentPixel+1] = g;
pixelBuffer[currentPixel+2] = r;
//pixelBuffer[currentPixel+3] = 200;
} else {
pixelBuffer[currentPixel] = 50;
pixelBuffer[currentPixel+1] = 50;
pixelBuffer[currentPixel+2] = 50;
}
}
}

arcTexture->UnlockRect(0);
}

D3DXMATRIX arcMatrix;
D3DXMatrixTransformation2D(&arcMatrix, NULL, 0.0, &arcScale, NULL, rotationAngle * PI / 180, &arcPos);

arcSprite->Begin(D3DXSPRITE_ALPHABLEND);
//pIDirect3DDevice9->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
//pIDirect3DDevice9->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE);
//pIDirect3DDevice9->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ZERO);
arcSprite->SetTransform(&arcMatrix);
arcSprite->Draw(arcTexture, NULL, NULL, NULL, D3DCOLOR_RGBA(255, 255, 255, ConfigFile::StatsOpacity));
arcSprite->End();
}

Share this post


Link to post
Share on other sites
Now we are talking. And sorry to say: I'm a little bit frustrated since you stated twice that your original snippet was all drawing code.

Since I'm still waiting for a screenshot with a different scaling my guess is something going wrong with your tiling, especially since you rotate them.

As for the "percentaging-logic": I could think of some multitexturing where one texture operates as a mask for the actual texture, but I doubt ID3DXSprite can be abused for that (Edit: Since you need to set different texture coordinates for the two textures).

Share this post


Link to post
Share on other sites
When asked for drawing I was thinking you wanted the actual draw (sprite->draw) code which is in my original snippet. That said I apologize for the misunderstanding. I will get that screenshot here soon. I am currently not home and had to remote in to get the previous snippet. Unfortunately DirectX wont work well in a remote desktop. :)

Share this post


Link to post
Share on other sites
Thanks. Yes, the scaling fades your bars to gray somehow. Though the effect does not totally disappear, i.e. the bars are still blueish, reddish, yellowish.

Hmmmm, I guess after your texture update only the first surface is changed, not the deeper mipmap-levels. So two "different" textures will be interpolated, one with color, one without. What happens if you use some scaling between 0.5 and 1 ? Will the colors reappear ? Or even better: Use PIX to actually see the mipmap chain. How do you create your texture ? Will automatic mipmapping be done after texture update ?

Share this post


Link to post
Share on other sites
As far as mipmapping goes I just load a png with the CreateTextureFromFile command. I don't do any mipmapping myself so I assume there is no mipmapping. When I scaled between .5-1 it does the same thing just not as bad.

Share this post


Link to post
Share on other sites
Sounds like a missing mipmapping. No need to do it yourself:

- Either load the texture with D3DXCreateTextureFromFileEx and include D3DUSAGE_AUTOGENMIPMAP for the usage flag

or

- call texture->GenerateMipSubLevels() after the lock

Don't know if it's that simple in your case. Just realized that a glance to PIX is not always conclusive. Obviously sometimes the mipmapping is "hidden" by the driver/DirectX:

Quote:
To generate a mipmap automatically, set a new usage D3DUSAGE_AUTOGENMIPMAP before calling CreateTexture. Sublevel generation from this point on is completely transparent to the application


More here : Automatic Generation of Mipmaps (Direct3D 9)

Share this post


Link to post
Share on other sites

This topic is 2756 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