crosire / d3d8to9

A D3D8 pseudo-driver which converts API calls and bytecode shaders to equivalent D3D9 ones.
BSD 2-Clause "Simplified" License
892 stars 80 forks source link

Refcounting in Direct3DDevice8::Release (again) #15

Closed CookiePLMonster closed 7 years ago

CookiePLMonster commented 7 years ago

First of all, do note I don't have this issue backed by anything but theory.

Something that has been bugging me out for a while - feels like if (myRef <= 3) might not be safe in all cases and sometimes it may leave device in semi-corrupted case. To visualise this, let's consider three cases:

  1. Both _current_rendertarget and _current_depthstencil are not null: Device wrapper has 3 references left when there is one reference left in user code (because the other two come from rendertarget and depthstencil) - they are released once the last pointer from user code is released. Everything works as expected.
  2. Both _current_rendertarget and _current_depthstencil are null: Device wrapper has 3 references left when there are three references left in user code. Affected code path is thus entered too early, but since both pointers are null there is nothing to release. Everything works as expected.
  3. Only one of _current_rendertarget and _current_depthstencil is not null: Device wrapper has 3 references left when there are two references left in user code. Affected code path is thus entered too early - non-null surface is released when releasing second to last pointer in user code. The last user pointer still holds a valid device handle, but its surfaces are already released! Device is left in semi-corrupted state!

Fixing this is trivial enough, albeit introduces even more thread unsafety to already thread unsafe device code (this should be considered a separate issue however). What do you think?

crosire commented 7 years ago
auto &rt = _current_rendertarget;
auto &ds = _current_depthstencil;
if (rt != nullptr && ds != nullptr && myRef <= 3)
{
    rt->Release(); rt = nullptr;
    ds->Release(); ds = nullptr;
}
else if (rt != nullptr && myRef <= 2)
{
    rt->Release(); rt = nullptr;
}
else if (ds != nullptr && myRef <= 2)
{
    ds->Release(); ds = nullptr;
}
elishacloud commented 7 years ago

I don't think those changes are safe. I tried that with one of my games (Rayman 3: Hoodlum Havoc) and it crashed on exit, whereas it never crashed before. Event Viewer showed it as crashing in d3d8.dll (this wrapper).

crosire commented 7 years ago

Mmh. Well, then it's probably better to follow the "Never change a running system" matra.

elishacloud commented 7 years ago

Ok, if I change the declaration then it works without crashing:

auto* rt = _current_rendertarget;
auto* ds = _current_depthstencil;
if (rt != nullptr && ds != nullptr && myRef <= 3)
{
    rt->Release(); rt = nullptr;
    ds->Release(); ds = nullptr;
}
else if (rt != nullptr && myRef <= 2)
{
    rt->Release(); rt = nullptr;
}
else if (ds != nullptr && myRef <= 2)
{
    ds->Release(); ds = nullptr;
}
crosire commented 7 years ago

That doesn't reset the fields to nullptr though, which is bad too.

CookiePLMonster commented 7 years ago

Indeed, auto& shouldn't be used in here. But other than this, it looks good and it does reset the fields to nullptr (missed the assignments after Release(), crosire?).

The only thing I'd change is <= to == - defensive approach is really not needed in here, I guess.

elishacloud commented 7 years ago

Yes, that code seems to work for me. No crashes or other issues.

CookiePLMonster commented 7 years ago

The one I posted in here was wrong again though (too sleepy, it seems). I'll fix and issue a PR.

crosire commented 7 years ago

No, it doesn't reset the fields to nullptr. It only sets the local auto variables to nullptr. But I see you fixed that in the PR.

crosire commented 7 years ago

I don't have any way to test #18 currently, so would greatly appreciate if @elishacloud could verify things still work with it.

elishacloud commented 7 years ago

Yes, I just verified #18 and it seemed to work fine. I did not find any issues with it.