Refcounting fun

H. Verbeet hverbeet at gmail.com
Sun Feb 12 08:03:00 CST 2006


I've run into a somewhat annoying problem with d3d9/wined3d recently.
It appears that on d3d9 on Windows a surface either forwards its
AddRef / Release calls to its container if it has one, or just shares
the refcount directly. Either way, it comes down to the same thing.
Calling  AddRef on the surface increases the texture's refcount and
calling AddRef on the texture increases the surface's refcount. Same
thing for Release.

As a consequence, on Windows, it's perfectly legal to do something like this:

/* Create a texture */
IDirect3DDevice9_CreateTexture()
/* Get a surface. Adds a reference to the surface/texture combination */
IDirect3DTexture9_GetSurfaceLevel()
/* Release the texture. Note that the texture isn't freed yet, because
we still hold a reference to the surface */
IDirect3DTexture9_Release()
<Do some stuff with the surface, perhaps call GetContainer, etc>
/* Release the surface. The texture and all of it's surfaces will now
be released. */
IDirect3DSurface9_Release()

In wine, the call to IDirect3DTexture9_Release() frees the texture.
(It also calls Release on it's surfaces, but since the surface had an
extra reference from the call to GetSurfaceLevel the surface is still
ok). Calling any function on the surface that needs access to the
container will now have undefines results, most likely a crash.

Naively, I tried to fix this by having the surface keep a reference to
it's container. That keeps the code above from crashing, but only
because it creates a circular reference between the surface and the
container. Oops.

Fixing the problem for wined3d is probably not even that hard. Just
separate the Release code from the Cleanup code, then have
WineD3DSurface forward its AddRef / Release calls to the container, if
it has one. The container can then call the Cleanup code for its
surfaces when it gets deleted.

However, there is another problem, related to the way d3d9 (and
ddraw/d3d7/d3d8, when their respective rewrites are finished) wraps
wined3d. After fixing the problem for wined3d as above, the relation
between IDirect3DSurface9 and IDirect3DTexture9 in wine would look
roughly like this:


    |-------------------|       |-------------------|
    | IDirect3DSurface9 |       | IDirect3DTexture9 |
    |-------------------|       |-------------------|
            ^                           ^
            |                           |
    |----------------|           |----------------|
    | WineD3DSurface |<--------->| WineD3DTexture |
    |----------------|           |----------------|

WineD3DSurface and WineD3DTexture "share" reference counts,
IDirect3DSurface9 keeps a reference to WineD3DSurface, and
IDirect3DTexture keeps a reference to WineD3DTexture. WineD3DSurface
and WineD3DTexture don't keep references to their parents (and can't,
without creating circular references).

So releasing the texture before the surface would still not work,
since the d3d9 texture still has only 1 reference, the one it got when
it was created. That means that in the call to
IDirect3DTexture9_Release the d3d9 texture gets freed, while the
wined3d texture doesn't. When we do something with the d3d9 surface
that needs the container, the wined3d surface correctly returns its
container, but when d3d9 then tries to get the parent of that
container it still dies.

One way to solve this would be to have wined3d handle the reference
counting for d3d7/8/9 as well, so that their AddRef/Release calls just
call AddRef/Release on the wined3d object they wrap. That would also
mean we need to pass a pointer to the d3d7/8/9 object's cleanup
function. (Releasing a d3d9 surface to 0 should cause a d3d9 texture
to be freed). Note that for creating a texture, currently a similair
construction is used, in that IWineD3DDeviceImpl_CreateTexture gets
passed a pointer to a d3d9 surface create function, in order to create
the texture's surfaces.

Obviously doing all that would be quite a change in the way d3d is
setup in wine, and would also have some implications for the d3d7 and
d3d8 rewrites. I'm wondering if this solution would be acceptable, and
whether perhaps there are other, better solutions. Anyone care to
comment?



More information about the wine-devel mailing list