Sign in to follow this  
.3lite

Direct3D matrix scalling problem

Recommended Posts

Hey guys,

I'm having some kind of issue with scaling my sprites, if they are scaled by for example value of 1.5f there are some spaces between them in rendered game image, first of all let me show you my drawing engine functions:


[code]
void D3D9_engine::BeginScene(){
g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 1.0f, 0);
g_pd3dDevice->BeginScene();
gSprite->Begin(D3DXSPRITE_ALPHABLEND);
}

void D3D9_engine::SetScaleLevel(float scale){
scaleLevel = scale;

D3DXMatrixScaling(&matrix, scale, scale, 0.0f);
gSprite->SetTransform(&matrix);
}

float D3D9_engine::GetScaleLevel(){
return scaleLevel;
}

void D3D9_engine::PresentScene(){
gSprite->End();
g_pd3dDevice->EndScene();
g_pd3dDevice->Present(NULL, NULL, NULL, NULL);
}

void D3D9_engine::DrawSprite(uint16_t sprite, uint16_t x, uint16_t y){
uint16_t vectPos = sprite / spr_limits; // TODO for bitshift operator.
uint16_t spritePos = sprite & (spr_limits - 1);

RECT srcRect;
srcRect.top = spritePos << 5;
srcRect.left = 0;
srcRect.bottom = srcRect.top + 32;
srcRect.right = 32;

static D3DXVECTOR3 vCenter(0.0f, 0.0f, 0.0f);
D3DXVECTOR3 vPosition(x, y, 0.0f);


gSprite->Draw(spritesVector[vectPos],
&srcRect,
&vCenter,
&vPosition,
D3DCOLOR_COLORVALUE(1.0f,1.0f,1.0f,1.0f));
}[/code]




Sprites are basically images of size 32x32 loaded into several textures, it works like charm and the memory usage as well as rendering speed is well above expected value.


I'm creating a Tibia game ( [url="http://tibia.com"]http://tibia.com[/url] ) client (old one, over 7 years old) just out of fun and the problem appears when I want to scale sprites using that SetScaleLevel function, sprites are not draw exactly as expected and there are some blank pixel lines between them:

[img]http://img687.imageshack.us/img687/9114/clipboard01lip.jpg[/img]




Back to the blank pixel lines problem, CLAMP won't help at this point:

g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);

g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);




I'm stuck right now with this scaling problem (and I really need it but I think it might be problem with single precision numbers - hope not), hopefully you can help me.

Share this post


Link to post
Share on other sites
Since we aren’t talking about hardware, I’d say your framerate is actually a bit on the low side; my hardware gets me around 5,000 FPS on a full 3D scene (no physics, just sorting and rendering), and for what I see here I would expect nothing below 10,000 FPS. You would benefit from some batching. With your hardware, I estimate that you are fairly optimized if you can reach 6,000 FPS. If you use both GPU’s. Take it as meaning you aren’t done optimizing just because your hardware draws you into a false sense of security. I believe batching 3D triangles will be faster than a bunch of sprite renders.

You may also benefit from some D3DSAMP_MAGFILTER and D3DSAMP_MINFILTER settings of D3DTEXF_POINT.
This will get you the vintage blocky look of nostalgic times that we all enjoy.

If you aren’t after that type of look, then you need to be aware of the center point for pixels in Direct3D 9, which is the center of the pixel, not the upper-left corner.
You can either adjust your orthographic projection to account for this with a small half-pixel shift, or add a small half-pixel shift to all of your sprites.


L. Spiro

Share this post


Link to post
Share on other sites
To be honest I find it kind of enough, it's not like I need 10k fps when even on my low-end laptop I can reach over 100 fps in places with a lot of objects (laptop with core i3 330m and integrated graphics).

However indeed I would be glad to learn how to render using multiple GPU's, haven't found any tutorial yet.

[code]void D3D9_engine::InitD3DBasics(){
g_pd3dDevice->SetFVF(D3DFVF_XYZRHW | D3DFVF_TEX1);
g_pd3dDevice->SetRenderState (D3DRS_LIGHTING, FALSE);
g_pd3dDevice->SetRenderState (D3DRS_LASTPIXEL, FALSE );
g_pd3dDevice->SetRenderState (D3DRS_ALPHABLENDENABLE, TRUE);
g_pd3dDevice->SetRenderState (D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
g_pd3dDevice->SetRenderState (D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
g_pd3dDevice->SetRenderState (D3DRS_CULLMODE, D3DCULL_NONE);
g_pd3dDevice->SetRenderState (D3DRS_ZENABLE, false);
// g_pd3dDevice->SetRenderState(D3DRS_NORMALIZENORMALS, TRUE);
g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);

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


/* We need to get info about video card limitations and set max amount of sprites per texture */
D3DCAPS9 caps;
g_pd3dDevice->GetDeviceCaps(&caps);
uint16_t limit = caps.MaxTextureHeight >> 5;

spr_limits = limit;
D3DXCreateSprite(g_pd3dDevice, &gSprite);
}[/code]

MAG and MINFILTER doesn't change anything, it still looks the same (even using anisotropic) and it still has problems with those blank pixels.




[quote]You can either adjust your orthographic projection to account for this with a small half-pixel shift, or add a small half-pixel shift to all of your sprites.[/quote]

Shift in sprites wouldn't be such a good option, I need to be able to set the scaling (different values) all the time without any problems like that - about that adjustment, what are you suggesting?

Share this post


Link to post
Share on other sites
Multiple GPU’s are used automatically. There is no special coding to enable them. If they are there, they are used.
I assume you are using an offset orthographic projection.
So add to its X offset 1 / (screen width) * 0.5 and to its Y 1 / (screen height) * 0.5.


L. Spiro

Share this post


Link to post
Share on other sites
I do not understand how screen width/height has to do anything here, sprites are already made to imitate "3D" look from specific point and they are looking just like those items you are looking from TOP - SOUTHEAST.




This is how some sprites look like normally saved in file:


[img]http://img442.imageshack.us/img442/8461/tutspredit1.png[/img]





This is how the code looks like at least partially:



[code]
int renderTile(unsigned int pTicks, int offset, int x, int y, int z, int pz, int sx, int sy, int mainOffsetx, int mainOffsety, std::vector< std::pair< Creature*, RECT> > &namesMap){
Tile *tile = game->getMap()->getTile(Position(x + offset, y + offset, z));
TxPosition tileDrawPosition( (x - sx) * 32 + mainOffsetx, (y - sy) * 32 + mainOffsety, 0);
std::vector<Thing*> objects = tile->getThingsToDraw();
.......
//go through whole map..., bleble
engine->DrawSprite(type.sprites[spriteId], tileDrawPosition.x + tileDrawPosition.offset, tileDrawPosition.y + tileDrawPosition.offset);
.......
//end
}


engine->BeginScene();
engine->SetScaleLevel(1.5f);

for(int z = fromz; z >= toz; --z){
for(int x = sx; x < sx + 18; x++){
for(int y = sy; y < sy + 14; y++){
int offset = playerPosition.z - z;
int ret = renderTile(pTicks, offset, x, y, z, pz, sx, sy, mainOffsetx, mainOffsety, namesMap);
...
}
}
}

float scale = engine->GetScaleLevel();
engine->SetScaleLevel(1.0f);
//render creature names
engine->PresentScene();[/code]

Share this post


Link to post
Share on other sites
Can your DrawSprite() function not just subtract 0.5 from the Y that is passed to it?


If you are doing everything with integers (as it [i]appears[/i] you are, but without naming conventions I can only guess) then you can expect to have this kind of trouble.
If you are going to scale things up and down, you had better be using floating-point math.


L. Spiro

Share this post


Link to post
Share on other sites
[quote name='YogurtEmperor' timestamp='1318811468' post='4873280']
Can your DrawSprite() function not just subtract 0.5 from the Y that is passed to it?


If you are doing everything with integers (as it [i]appears[/i] you are, but without naming conventions I can only guess) then you can expect to have this kind of trouble.
If you are going to scale things up and down, you had better be using floating-point math.


L. Spiro
[/quote]

I agree but that's not really my job to scale it, every kind of position in this game is based on 32 so floating point won't help here - I do understand you, but I'm telling the MATRIX and dx to scale it for me to specified floating point value - it won't matter if parameter received is integer or float since 32 in single precision floating point = 32 in integer.

Coordinates of the sprite are based on 32 * x, where x is natural number - it doesn't change, ever.

I could understand when the problem would be about scaling by myself (like let's say texture specified size draw with triangles), but it has to do something with the Direct itself.

1.0 scale (perfect):

[img]http://img202.imageshack.us/img202/8982/clipboard02xg.jpg[/img]

and 8.5 scale:

[img]http://img685.imageshack.us/img685/6646/clipboard01xe.jpg[/img]







However I do think it has something to do with single precision floating points and calculations used in Directx, maybe double precision would help.

D3DCREATE_FPU_PRESERVE - doesn't work though.

Share this post


Link to post
Share on other sites
According to your code you are using a scale of 1.5. Not a whole number. How do whole numbers look?

What you are seeing could be caused by any number of things.
#1: You could have slightly off X/Y coordinates when you render the sprites.
-> Could be due to integer rounding errors.
#2: You could have slightly incorrect cell parameters. Be sure each cell starts at exactly a 32×32 boundary and is exactly 32×32. If off even by epsilon you will get these artifacts as you zoom in.

Are you sure point sampling did not help?
Because it is very clear that it is sampling the next texel up (or down) on the texture.

Why not give an example of where the second sprite over and down is being drawn?
The exact X and Y coordinates, the exact scale, and the exact position on the texture atlas.


L. Spiro

Share this post


Link to post
Share on other sites
[quote name='YogurtEmperor' timestamp='1318813730' post='4873286']
According to your code you are using a scale of 1.5. Not a whole number. How do whole numbers look?

What you are seeing could be caused by any number of things.
#1: You could have slightly off X/Y coordinates when you render the sprites.
-> Could be due to integer rounding errors.
#2: You could have slightly incorrect cell parameters. Be sure each cell starts at exactly a 32×32 boundary and is exactly 32×32. If off even by epsilon you will get these artifacts as you zoom in.

Are you sure point sampling did not help?
Because it is very clear that it is sampling the next texel up (or down) on the texture.

Why not give an example of where the second sprite over and down is being drawn?
The exact X and Y coordinates, the exact scale, and the exact position on the texture atlas.


L. Spiro
[/quote]




This is the scale of 2.0f :


[img]http://img838.imageshack.us/img838/3391/clipboard02nr.jpg[/img]




The problem is pretty much the same.




#1 - They are fine, at this point even a 1px offset would be noticeable, coordinates are always multiple of 32.

#2 - That's kind of interesting suggestion, I will check it for sure.




Yes - I'm pretty sure, any kind of changing MAG filter doesn't change anything in the rendered image, strange though.

Share this post


Link to post
Share on other sites
I have not used ID3DXSprite in over 15 years.
I just checked the documentation.
They batch automatically, so I was wrong to suggest doing so.

Also, sprites change many render states. You need to pass D3DXSPRITE_DONOTMODIFY_RENDERSTATE to Begin() on the sprites to avoid this.
D3DXSPRITE_DONOTSAVESTATE would probably help too to improve performance. So would D3DXSPRITE_DO_NOT_ADDREF_TEXTURE.

If you want all of the other states, except for the min/mag filter, you could alternatively set the min/mag filters [i]after[/i] calling ID3DXSprite::Begin().

[url="http://msdn.microsoft.com/en-us/library/bb174250(VS.85).aspx"]http://msdn.microsoft.com/en-us/library/bb174250(VS.85).aspx[/url]
[url="http://msdn.microsoft.com/en-us/library/bb205466(v=VS.85).aspx"]http://msdn.microsoft.com/en-us/library/bb205466(v=VS.85).aspx[/url]



L. Spiro

Share this post


Link to post
Share on other sites
Well, indeed it did fix the problem but that's not what I was looking for:

1.Anisotropic, linear looks way better in this case - as it should while resizing the sprites :)

2. It does fix the problem but animations while moving the character are still affected (tadam, - our floating point single precision problem).




I would be glad if we could find another way.




Anyway - D3DXSPRITE_DO_NOT_ADDREF_TEXTURE doesn't seem to be defined - I've used 1 << 8 instead of that but the screen is black, there are no sprites.

Share this post


Link to post
Share on other sites
Anisotropic filtering is a waste of time when drawing flat sprites that always face the camera. At most set it to linear.


Since you should have the performance to spare, one way that is guaranteed to fix the problem and work under all conditions is to use a second render target.
Render targets are simple to set up in Direct3D so this is not so hard to implement.
Render the screen at 1.0f scale to a render target, and then render a quad with that texture to the back buffer, scaling it up or down as needed during that pass.


L. Spiro

Share this post


Link to post
Share on other sites
If I understand it correctly I should do something like this:

[url]http://msdn.microsoft.com/en-us/library/windows/desktop/bb174455(v=vs.85).aspx[/url]



Let's say I got a surface, I render the whole map to that surface with normal scale of 1.0f and then tell the device to use surface and resize the whole surface using MATRIX and draw it - now here is the question, how to render surface fast? Without having to create new texture and copy the surface color data to it .

Or... create a texture, get surface data from it - set the render target to that surface, set device to use that texture, use resize MATRIX and draw it.





- it should work but it will be probably killer of performance :P

Share this post


Link to post
Share on other sites
It will hardly make a dent in your performance.
Just render to a texture using the above-mentioned link and draw at scale = 1.0f.
Then set the normal backbuffer back and render a quad over the area you want your game screen to cover. Put the previously made texture into texture slot 0 and sample from it.
It is basically like standard post-processing and should not cause any slowdown. You are not scaling on the CPU nor using any scaling matrices.


L. Spiro

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