I have one game, that needs DX6, but lowest clone version of DX is 7. The game don't run, because in <source>dlls/ddraw/surface.c:IDirectDrawSurface7Impl_AddAttachedSurface is check the surface have z-buffer. There's a notice, that the check been added, because of information in msdn about this function. This information suggested, that DX returning error if surface don't have z-buffer. If I remove this block and " Caps.dwCaps |= DDSCAPS_BACKBUFFER;" line in IDirectDrawSurfaceImpl_Flip, the game will run and there's no graphics issue.
I think, that game written for DX7 won't give surface without z-buffer as parameter to this functions, but code, that i removed is bad for dx less 7 games. I read in winehq.org, that some games(like capitalism) have the same issue(displaying the same error message), so my hack will repair it.
Sorry for my english, I still learning.
Am Freitag, den 27.06.2008, 11:26 +0200 schrieb slawek:
I have one game, that needs DX6, but lowest clone version of DX is 7.
That statement is wrong. dlls/ddraw implements DirectDraw 1 to 7. Core functionality for surfaces and DX7 special functions are in surface.c, whereas DX1-5 surfaces are implemented in surface_thunks.c.
The problem you identified is nevertheless real. DirectDraw6 comes with IDirectDrawSurface4, which is currently (as the vtable layout is compatible) implemented via IDirectDrawSurface7 (there is no IDirectDrawSurface{5,6}), so the DX6 version of surface directly uses the DX7 code, and does not use the relaxed thunk from IDirectDrawSurface3 (which is also used for IDirectDrawSurface and IDirectDrawSurface2).
If I get it right, the correct fix is to add a thunk vtable for IDirectDraw4 that uses relaxed parameter checks on AddAttachedSurface.
Regards, Michael Karcher
Michael Karcher wrote:
Am Freitag, den 27.06.2008, 11:26 +0200 schrieb slawek:
I have one game, that needs DX6, but lowest clone version of DX is 7.
That statement is wrong. dlls/ddraw implements DirectDraw 1 to 7. Core functionality for surfaces and DX7 special functions are in surface.c, whereas DX1-5 surfaces are implemented in surface_thunks.c.
The problem you identified is nevertheless real. DirectDraw6 comes with IDirectDrawSurface4, which is currently (as the vtable layout is compatible) implemented via IDirectDrawSurface7 (there is no IDirectDrawSurface{5,6}), so the DX6 version of surface directly uses the DX7 code, and does not use the relaxed thunk from IDirectDrawSurface3 (which is also used for IDirectDrawSurface and IDirectDrawSurface2).
If I get it right, the correct fix is to add a thunk vtable for IDirectDraw4 that uses relaxed parameter checks on AddAttachedSurface.
Regards, Michael Karcher
Are we sure its 6.0 and not 6.1? from what I remember and can find 6.0 was mainly for the dreamcast and possibly CE. I don't have my manual handy (stuck in a bloody airport all night due to weather) but I think it should work with a 6.1 implementation which wouldn't need to be thunk'ed just the relaxed parm check.
Am Freitag, den 27.06.2008, 07:46 -0400 schrieb Christopher J. Ahrendt:
If I get it right, the correct fix is to add a thunk vtable for IDirectDraw4 that uses relaxed parameter checks on AddAttachedSurface.
Regards, Michael Karcher
Are we sure its 6.0 and not 6.1? from what I remember and can find 6.0 was mainly for the dreamcast and possibly CE.
I don't know. Probably it is 6.1. The Interface is called IDirectDraw6. It does not matter in this case.
I don't have my manual handy (stuck in a bloody airport all night due to weather) but I think it should work with a 6.1 implementation which wouldn't need to be thunk'ed just the relaxed parm check.
The problem is that currently, if the application requests a IDirectDrawSurface4 what it gets is a IDirectDrawSurface7. Which is (on the first look) not a issue, as the vtables are compatible (same functions with same signature at same offsets, except for IDirectDrawSurface7 having added some functions at the end), but on the second look, the IDirectDrawSurface7 vtable contains the pointer to the strict AddAttachedSurface, whereas the vtable for IDirectDraw4 must contain a pointer to a relaxed AddAttachedSurface. This we need two different vtables (You can query an IDirectDraw7 and an IDirectDraw4 surface on the same object, so a flag in the object does not do, probably[1]) to get the different behaviours. Two different vtables for the same object inherently is thunking.
Regards, Michael Karcher
[1] In fact, an API test is required. Strict or relaxed semantics might also depend on how the surface was created (from IDirectDraw4 or IDirectDraw7) instead of the interfaced used to add an attached surface. This would be strange IMO, as the idea about COM is that the interface describes semantics that work regardless how the object was created, but let's test that, nevertheless.
The problem is that currently, if the application requests a IDirectDrawSurface4 what it gets is a IDirectDrawSurface7. Which is (on the first look) not a issue, as the vtables are compatible (same functions with same signature at same offsets, except for IDirectDrawSurface7 having added some functions at the end), but on the second look, the IDirectDrawSurface7 vtable contains the pointer to the strict AddAttachedSurface, whereas the vtable for IDirectDraw4 must contain a pointer to a relaxed AddAttachedSurface. This we need two different vtables (You can query an IDirectDraw7 and an IDirectDraw4 surface on the same object, so a flag in the object does not do, probably[1]) to get the different behaviours. Two different vtables for the same object inherently is thunking.
Regards, Michael Karcher
[1] In fact, an API test is required. Strict or relaxed semantics might also depend on how the surface was created (from IDirectDraw4 or IDirectDraw7) instead of the interfaced used to add an attached surface. This would be strange IMO, as the idea about COM is that the interface describes semantics that work regardless how the object was created, but let's test that, nevertheless.
I wonder if there is an easy way to do an API abstraction of some sort... not with a flag perse but like you can in C++ with the different instanciations depending upon the parms passed. Then instead of thunking , which is inherently buggy and tends to be slow, you use the v7 structure and return it without the v7 specific items at the end. That way you store one table instead of 2. (again sorry if this is fuzzy I am working on no sleep here).
Am Freitag, den 27.06.2008, 09:13 -0400 schrieb Chris Ahrendt:
The problem is that currently, if the application requests a IDirectDrawSurface4 what it gets is a IDirectDrawSurface7. Which is (on the first look) not a issue, as the vtables are compatible (same functions with same signature at same offsets, except for IDirectDrawSurface7 having added some functions at the end), but on the second look, the IDirectDrawSurface7 vtable contains the pointer to the strict AddAttachedSurface, whereas the vtable for IDirectDraw4 must contain a pointer to a relaxed AddAttachedSurface. This we need two different vtables (You can query an IDirectDraw7 and an IDirectDraw4 surface on the same object, so a flag in the object does not do, probably[1]) to get the different behaviours. Two different vtables for the same object inherently is thunking.
I wonder if there is an easy way to do an API abstraction of some sort... not with a flag perse but like you can in C++ with the different instanciations depending upon the parms passed.
If I understand you correctly, you are thinking of C++ templates or overloaded functions here. They are not comparable, as both are compile-time polymorphism, while here run-time polymorphism is needed. If I write "cout << 1", the compiler uses std::ostream& operator <<(std::ostream&, int) and if I write "cout << 1.2", the compiler uses std::ostream& operator <<(std::ostream&, double) These are two different functions, that just happen to have the same name in the source code. In object code, the mangled names are used, which are different.
Templates also go along this line: The compiler instantiates the template with concrete types (so we have std::vector<int> and std::vector<double>), that just both happen to be instantiated from the same template, but have nothing in common from the viewpoint of the linker or runtime-environment.
Run-time polymorphism in C++ (and in COM, DirectDraw is a COM interface) is implemented via vtables. If you want IDirectDrawSurface7::AddAttachedSurface to call a different function than IDirectDirectDrawSurface4::AddAttachedSurface, then the function pointer at address 0x0c in the vtable for IDirectDrawSurface4 must be different from the function pointer at address 0x0c in the vtable for IDirectDrawSurface7. So it is obvious, that two different vtables are needed.
A COM interface pointer points directly to the vtable pointer of the interface you requested. So for the even same surface, the IDirectDraw4 interface pointer must be different from the IDirectDraw7 interface pointer, as the address stored at the destination of the interface pointer is either the IDirectDraw4 or the IDirectDraw7 vtable address. So we need two different interface pointers into the same object. As the core functions need a pointer that points to one defined location in the object, the methods called via the second vtable have to adjust the interface pointer they received into the original interface pointer, and pass it on to the core implementation. And this is where I discovered what a thunk it: Exactly a function that adjusts the this pointer before calling the main implementation; these thunks are unavoidable if functions from one vtable are forwarded to functions belonging to another vtable implemented by the same object.
Then instead of thunking, which is inherently buggy
I don't agree with this statement.
and tends to be slow, you use the v7 structure and return it without the v7 specific items at the end.
Tables get not returned. Table pointers are included in the COM objects and pointed to by interface pointers. Using the v7 table for v4 is what wine currently does, and causes the problem discussed in this thread.
That way you store one table instead of 2. (again sorry if this is fuzzy I am working on no sleep here).
Doesn't work. The fourth entry in that table *either* points to the strict *or* to the relaxed version of AddAttachedSurface. I might get around to send a patch on sunday.
Regards, Michael Karcher
Good summary about C++ templates and Co skipped
And this is where I discovered what a thunk it: Exactly a function that adjusts the this pointer before calling the main implementation; these thunks are unavoidable if functions from one vtable are forwarded to functions belonging to another vtable implemented by the same object.
That's not necessarily the case, I think it is not legal to compare the vtable pointers, so they should not matter. If a vtable inherits from the other, using one vtable is ok. Here we thought IDDS7 inherits from IDSS4. While it looks like that in the interface, the behavior is different, so the inheritance relation isn't given. (Just to make it clear)
If I get it right, the correct fix is to add a thunk vtable for IDirectDraw4 that uses relaxed parameter checks on AddAttachedSurface.
Most likely yes
[1] In fact, an API test is required. Strict or relaxed semantics might
also > depend on how the surface was created (from IDirectDraw4 or
IDirectDraw7) instead of the interfaced used to add an attached surface. This would be strange IMO, as the idea about COM is that the interface describes semantics that work regardless how the object was created, but let's test that, nevertheless.
Yes, a test is needed, no way around that. Other IDirectDrawSurface methods depend on how the object was created indeed, like GetDDInterface.
On a sidenode, I think it is impossible to QueryInterface IDirectDrawSurface7 from IDirectDrawSurface < 7, or vice versa, but I am not sure. I think there are tests for IDirectDrawX, more tests would never hurt.
I wonder if there is an easy way to do an API abstraction of some sort... not with a flag perse but like you can in C++ with the different instanciations depending upon the parms passed. Then instead of thunking , which is inherently buggy and tends to be slow, you use the v7 structure and return it without the v7 specific items at the end. That way you store one table instead of 2
I recommend to read how object orientation and inheritance are implemented on assembler level by the C++ compiler. It's multiple vtables and thunking in many places.
The C++ equivalent here would be to keep 2 vtables, which differ only in the implementation of the AddAttachedSurface and Flip functions, and otherwise use the same function pointers without thunks. C++ uses thunks e.g. in the case of multiple inheritance. A vtable construct like IDirectDrawSurface3, IDirectDrawSurface4, IDirectDrawSurface7 and IDirectDrawGammaControl on the same object in the same time is not really expressable as far as I can see.
Thunks are not a pressing performance issue in general, but I want to avoid them if its possible in a clean way, since the 0.5-1.0% performance gains are where you gain your framerate differences that matter to the user.
Am Freitag, den 27.06.2008, 18:24 +0200 schrieb Stefan Dösinger:
On a sidenode, I think it is impossible to QueryInterface IDirectDrawSurface7 from IDirectDrawSurface < 7, or vice versa, but I am not sure.
Nope. You can sucessfully query IDirectDrawSurface{,2,3,4,7} from IDirectDraw{,2,3,4,7} without problems, at least on DirectX 9. Wine matches Windows behaviour here.
Windows (DirectX 9) has seperate refcounts for seperate IDirectDrawSurfaceX, as it also has for IDirectDrawX. Wine does not have it. This means that we need different vtables for all IDirectDrawSurface versions, as DDraw does, too.
Regards, Michael Karcher