Page 1 of 1

How does KQ8 determine whether Direct3D is supported?

Posted: Mon Jul 27, 2015 11:27 am
by Expack3
I'm currently testing the latest version of dgVoodoo2, an upgraded version of dgVoodoo which now features robust DirectX1-7 and DDraw emulation, with King's Quest 8 (aka King's Quest: Mask of Eternity), and while DirectDraw mode works fine, and has worked fine with prior WIP versions and the latest stable release, Direct3D support has never been properly detected. As such, before I file a bug report with the developer, I was wondering how the game detects Direct3D support. If someone could please get back to me regarding this information, I'd greatly appreciate it!

Re: How does KQ8 determine whether Direct3D is supported?

Posted: Mon Jul 27, 2015 12:08 pm
by Collector
Aroenai or NicoDE probably could better answer that than I could, but they have not been very active here lately.

Re: How does KQ8 determine whether Direct3D is supported?

Posted: Mon Jul 27, 2015 4:48 pm
by Expack3
Collector wrote:Aroenai or NicoDE probably could better answer that than I could, but they have not been very active here lately.
Would you know where they tend to be active these days?

Re: How does KQ8 determine whether Direct3D is supported?

Posted: Mon Jul 27, 2015 7:27 pm
by Collector
Not off hand, but they might see this thread within a couple of weeks or so.

Re: How does KQ8 determine whether Direct3D is supported?

Posted: Wed Sep 16, 2015 1:57 am
by Aroenai
Not a fault of dgVoodoo, the game disables the Direct3D option on real 3DFX hardware too. You can still force Direct3D using the assignGModeName line in Options.cs... I don't recall what you have to call it though, d3d-something?

Interesting to note, there is a dynamic lighting system for the Direct3D renderer but it's slow as molasses in normal Direct3D (hence why it's not accessible). If you use the dgVoodoo Direct3D renderer (no glide2x.dll) it will be full speed. Other than that, the only differences between the two are the lack of 800x600 and transparent shadows. I'd recommend playing at 640x480 with forced 1280x960 resolution if you can though, much better result than 800x600 since the interface art doesn't get warped (use an even multiple of 640x480). I actually have it set to 1920x1440 since my display is 3200x1800 and uses Nvidia Optimus + Intel HD 4600 so it doesn't have proper scaling :-p

Re: How does KQ8 determine whether Direct3D is supported?

Posted: Wed Nov 04, 2015 4:25 pm
by NicoDE
Expack3 wrote:Would you know where they tend to be active these days?
Sorry for the late reply. With my upgrade to Debian "Stretch" some time ago, VMware Player stopped working and all Windows related projects are on hold.
However, IDA is running fine with Wine... and here is the analysis as pseudo code: cpp code
// MASK.EXE 1.0.0.3 DE/ES/FR/IT has been built with DXSDK6
#define DIRECTDRAW_VERSION 0x0600 /* DDCAPS, D3DDEVICEDESC */

//	template<typename T, short Grow = 5>
//	struct KQArray {
//		int   count;
//		int   capacity;
//		short grow;
//		short flags;
//		T    *array;
//	};
//	template<typename T, short Grow = 5>
//	struct KQPtrArray : public KQArray<T*, Grow> {
//	};

////////////////////////////////////////////////////////////////////////////////
// Direct Draw
////////////////////////////////////////////////////////////////////////////////

HMODULE      dd_module     = NULL;
HWND         dd_window     = NULL;
LPDIRECTDRAW dd            = NULL;
DDCAPS       dd_hal_caps;
DDCAPS       dd_hel_caps;
BOOL         dd_write_only = FALSE;

BOOL dd_create(HWND a_window)
{
	dd_module = LoadLibraryA("DDRAW.DLL");
	LPDIRECTDRAWCREATE DirectDrawCreate = GetProcAddress(dd_module, "DirectDrawCreate");
	if (NULL == DirectDrawCreate) {
		if (dd_module != NULL) {
			FreeLibrary(dd_module);
			// yes, dd_module isn't reset
		}
		return (FALSE);
	}
	dd_window = a_window;
	if (DirectDrawCreate(NULL, &dd, NULL) != DD_OK) {
		return (FALSE);
	}
	dd_hal_caps.dwSize = sizeof(dd_hal_caps);
	dd_hel_caps.dwSize = sizeof(dd_hel_caps);
	if (IDirectDraw_GetCaps(dd, &dd_hal_caps, &dd_hel_caps) != DD_OK) {
		return (FALSE);
	}
	if (DDSCAPS_WRITEONLY & dd_hal_caps.ddsCaps.dwCaps) {
		dd_write_only = TRUE;
	}
	return (TRUE);
}

////////////////////////////////////////////////////////////////////////////////
// Direct 3D
////////////////////////////////////////////////////////////////////////////////

struct KQD3DDeviceInfo {
	BOOL          valid;
	GUID          guid;
	D3DDEVICEDESC desc;
	char         *driver_name;
	BOOL          filter_mip;
	BOOL          filter_mode;
	BOOL          blend_color;
	BOOL          blend_inv;
	BOOL          blend_mode;
	BOOL          unknown;
	BOOL          filter_linear;
	BOOL          texture_transparency;
	BOOL          alpha_src;
	BOOL          alpha_cmp;
};

struct KQD3DDisplayMode {
	int           width;
	int           height;
	int           bpp;
	DDSURFACEDESC desc;
};

BOOL                                  d3d_created              = FALSE;
HWND                                  d3d_window               = NULL;
LPDIRECTDRAWCREATE                    d3d_DirectDrawCreate     = NULL;
LPDIRECTDRAWENUMERATEA                d3d_DirectDrawEnumerateA = NULL;
KQArray<LPDIRECTDRAW2>                d3d_driver_dd;
KQPtrArray<char>                      d3d_driver_name;
KQArray<LPDIRECT3D2>                  d3d_driver_d3d;
KQArray<KQD3DDeviceInfo>              d3d_device_info;
KQArray<LPDIRECT3DDEVICE2>            d3d_device_intf;
KQPtrArray<KQArray<KQD3DDisplayMode>> d3d_display_modes;

BOOL d3d_driver_add(GUID *a_guid, LPSTR a_desc, LPSTR a_name)
{
	LPDIRECTDRAW dd = NULL;
	LPDIRECTDRAW2 dd2 = NULL;
	if (DD_OK == d3d_DirectDrawCreate(a_guid, &dd, NULL)) {
		if (S_OK == IDirectDraw_QueryInterface(dd, &IID_IDirectDraw2, &dd2)) {
			DDCAPS hal_caps;
			DDCAPS hel_caps;
			hal_caps.dwSize = sizeof(hal_caps);
			hel_caps.dwSize = sizeof(hel_caps);
			IDirectDraw2_GetCaps(dd2, &hal_caps, &hel_caps);
			if ((DDCAPS_3D & hal_caps.dwCaps) != 0) {
				char *name = new char[strlen("D3D-") + strlen(a_name) + 2];
				strcpy(name, "D3D-");
				strcat(name, a_name);
				d3d_driver_dd.push_back(dd2);
				d3d_driver_name.push_back(name);
			} else {
				IDirectDraw2_Release(dd2);
			}
		}
		IDirectDraw_Release(dd);
	}
	return (TRUE);
}

BOOL PASCAL d3d_driver_enum(GUID *a_guid, LPSTR a_desc, LPSTR a_name, LPVOID a_info)
{
	return (d3d_driver_add(a_guid, a_desc, a_name));
}

HRESULT d3d_device_find(GUID *a_guid, LPSTR a_desc, LPSTR a_name, D3DDEVICEDESC *a_hal, D3DDEVICEDESC *a_hel, KQD3DDeviceInfo *a_info)
{
	if (0 == (D3DCOLOR_RGB & a_hal->dcmColorModel)) {
		return (DDENUMRET_OK);
	}
	BOOL valid = TRUE;
	if (0 == (D3DDD_DEVICEZBUFFERBITDEPTH & a_hal->dwFlags)) {
		valid = FALSE;
	}
	if (0 == ((DDBD_16 | DDBD_24 | DDBD_32) & a_hal->dwDeviceZBufferBitDepth)) {
		valid = FALSE;
	}
	if (0 == (D3DPCMPCAPS_LESSEQUAL & a_hal->dpcTriCaps.dwZCmpCaps)) {
		valid = FALSE;
	}
	if (0 == (D3DPRASTERCAPS_FOGVERTEX & a_hal->dpcTriCaps.dwRasterCaps)) {
		valid = FALSE;
	}
	if (0 == (D3DPSHADECAPS_FOGGOURAUD & a_hal->dpcTriCaps.dwShadeCaps)) {
		valid = FALSE;
	}
	if (((D3DPBLENDCAPS_SRCALPHA    & a_hal->dpcTriCaps.dwSrcBlendCaps) != 0) &&
	    ((D3DPBLENDCAPS_INVSRCALPHA & a_hal->dpcTriCaps.dwDestBlendCaps) != 0)) {
		a_info->alpha_src = FALSE;
	} else if (
	    ((D3DPBLENDCAPS_INVSRCALPHA & a_hal->dpcTriCaps.dwSrcBlendCaps) != 0) &&
	    ((D3DPBLENDCAPS_SRCALPHA    & a_hal->dpcTriCaps.dwDestBlendCaps) != 0)) {
		valid = FALSE;
	} else {
		a_info->alpha_src = TRUE;
	}
	a_info->alpha_cmp = TRUE;
	if (a_info->alpha_src != FALSE) {
		if (0 == (D3DPCMPCAPS_ALWAYS & a_hal->dpcTriCaps.dwAlphaCmpCaps)) {
			a_info->alpha_cmp = FALSE;
		}
	} else {
		if (0 == (D3DPCMPCAPS_LESSEQUAL & a_hal->dpcTriCaps.dwAlphaCmpCaps)) {
			a_info->alpha_cmp = FALSE;
		}
	}
	if (0 == (D3DPSHADECAPS_COLORGOURAUDRGB & a_hal->dpcTriCaps.dwShadeCaps)) {
		valid = FALSE;
	}
	a_info->filter_mip = (((
		D3DPTFILTERCAPS_MIPNEAREST |
		D3DPTFILTERCAPS_MIPLINEAR |
		D3DPTFILTERCAPS_LINEARMIPNEAREST |
		D3DPTFILTERCAPS_LINEARMIPLINEAR) &
		a_hal->dpcTriCaps.dwTextureFilterCaps) != 0) ? TRUE : FALSE;
	a_info->filter_mode = 2;
	if ((D3DPTEXTURECAPS_PERSPECTIVE | D3DPTEXTURECAPS_ALPHA) != (
	    (D3DPTEXTURECAPS_PERSPECTIVE | D3DPTEXTURECAPS_ALPHA) & a_hal->dpcTriCaps.dwTextureCaps)) {
		valid = FALSE;
	}
	a_info->texture_transparency = TRUE;
	if (0 == (D3DPTEXTURECAPS_TRANSPARENCY & a_hal->dpcTriCaps.dwTextureCaps)) {
		a_info->texture_transparency = FALSE;
	}
	if (0 == (D3DPTBLENDCAPS_MODULATE & a_hal->dpcTriCaps.dwTextureBlendCaps)) {
		valid = FALSE;
	}
	if ((((D3DPBLENDCAPS_SRCCOLOR | D3DPBLENDCAPS_INVSRCCOLOR) & a_hal->dpcTriCaps.dwDestBlendCaps) != 0) &&
	    ((D3DPTFILTERCAPS_LINEAR & a_hal->dpcTriCaps.dwTextureFilterCaps) != 0)) {
		a_info->filter_linear = TRUE;
		a_info->blend_color = TRUE;
		if ((D3DPBLENDCAPS_SRCCOLOR & a_hal->dpcTriCaps.dwDestBlendCaps) != 0) {
			a_info->blend_inv = FALSE;
			a_info->blend_mode = 3;
		} else {
			a_info->blend_inv = TRUE;
			a_info->blend_mode = 4;
		}
	} else if (
	    (((D3DPBLENDCAPS_SRCALPHA | D3DPBLENDCAPS_INVSRCALPHA) & a_hal->dpcTriCaps.dwDestBlendCaps) != 0) &&
	    ((D3DPTFILTERCAPS_LINEAR & a_hal->dpcTriCaps.dwTextureFilterCaps) != 0)) {
		a_info->filter_linear = TRUE;
		a_info->blend_color = FALSE;
		if ((D3DPBLENDCAPS_SRCCOLOR & a_hal->dpcTriCaps.dwDestBlendCaps) != 0) {
			a_info->blend_inv = FALSE;
			a_info->blend_mode = 5;
		} else {
			a_info->blend_inv = TRUE;
			a_info->blend_mode = 6;
		}
	} else {
		a_info->filter_linear = FALSE;
		valid = FALSE;
	}
	if (0 == (D3DPSHADECAPS_SPECULARGOURAUDRGB & a_hal->dpcTriCaps.dwShadeCaps)) {
		valid = FALSE;
	}
	if (valid != FALSE) {
		a_info->valid = TRUE;
		a_info->guid = *a_guid;
		memcpy(&a_info->desc, a_hal, sizeof(a_info->desc));
		int n = strlen(a_info->driver_name) + 1;
		for (int i = 0; i < n; ++i) {
			char *c = &a_info->driver_name;
			if (' ' == *c) {
				*c = '-';
			}
		}
		return (DDENUMRET_CANCEL);
	}
	a_info->driver_name = NULL;
	return (DDENUMRET_OK);
}

HRESULT CALLBACK d3d_device_enum(GUID *a_guid, LPSTR a_desc, LPSTR a_name, LPD3DDEVICEDESC a_hal, LPD3DDEVICEDESC a_hel, LPVOID a_info)
{
	return (d3d_device_find(a_guid, a_desc, a_name, a_hal, a_hel, a_info);
}

BOOL d3d_create(HWND a_window)
{
	if (TRUE == d3d_created) {
		return (TRUE);
	}
	d3d_window = window;
	d3d_module = LoadLibraryA("DDRAW.DLL");
	d3d_DirectDrawCreate = GetProcAddress(d3d_module, "DirectDrawCreate");
	d3d_DirectDrawEnumerateA = GetProcAddress(d3d_module, "DirectDrawEnumerateA");
	if ((NULL == d3d_DirectDrawCreate) ||
	    (NULL == d3d_DirectDrawEnumerateA ||
	    (d3d_DirectDrawEnumerateA(d3d_driver_enum, NULL) != DD_OK)) {
		if (d3d_DirectDrawEnumerateA != NULL) {
			for (int i = 0; i < d3d_driver_dd.count; ++i) {
				LPDIRECTDRAW2 dd = d3d_driver_dd.array;
				if (dd != NULL) {
					IDirectDraw2_Release(dd);
				}
				d3d_driver_dd.array = NULL;
			}
			d3d_driver_dd.clear();
		}
		if (d3d_module != NULL) {
			FreeLibrary(d3d_module);
		}
		d3d_window = NULL;
		return (FALSE);
	}
	d3d_driver_d3d.resize(d3d_driver_dd.count);
	d3d_device_info.resize(d3d_driver_dd.count);
	for (int i = 0; i < d3d_driver_dd.count; ++i) {
		LPDIRECT3D2 d3d = NULL;
		IDirectDraw2_QueryInterface(d3d_driver_dd.array, &IID_IDirect3D2, &d3d);
		d3d_driver_d3d.array = d3d;
		d3d_device_info.array.valid = FALSE;
		d3d_device_info.array.name = d3d_driver_name.array;
		if (d3d2 != NULL) {
			IDirect3D2_EnumDevices(d3d2, d3d_device_enum, &d3d_device_info.array);
		}
		if (TRUE == d3d_device_info.array.valid) {
			d3d_driver_name.array[i] = NULL;
		}
	}
	d3d_device_intf.resize(d3d_driver_dd.count);
	for (int i = 0; i < d3d_device_intf.count; ++i) {
		d3d_device_intf.array[i] = NULL;
	}
	d3d_display_modes.resize(d3d_driver_dd.count);
	for (int i = 0; i < d3d_display_modes.count; ++i) {
		d3d_display_modes.array[i] = new KQArray<KQD3DDisplayMode>;
	}
	d3d_created = TRUE;
	return (TRUE);
}

HRESULT PASCAL d3d_display_modes_add(LPDDSURFACEDESC a_desc, LPVOID a_info)
{
	KQArray<KQD3DDisplayMode> *modes = a_info;
	if (16 == a_desc->ddpfPixelFormat.dwRGBBitCount) {
		KQD3DDisplayMode mode;
		mode.height = a_desc->dwHeight;
		mode.width = a_desc->dwWidth;
		mode.bpp = 16;
		memcpy(&mode.desc, a_desc, sizeof(mode.desc));
		modes->push_back(mode);
	}
	return (DDENUMRET_OK);
}

HRESULT d3d_display_modes_enum(int a_driver, KQArray<KQD3DDisplayMode> *a_modes)
{
	DDSURFACEDESC ddsd;
	ddsd.dwSize = sizeof(ddsd);
	ddsd.dwFlags = DDSD_BACKBUFFERCOUNT | DDSD_CAPS;
	ddsd.dwBackBufferCount = 2;
	ddsd.ddsCaps.dwCaps = DDSCAPS_3DDEVICE | DDSCAPS_FLIP;
	a_modes->clear();
	return (IDirectDraw2_EnumDisplayModes(d3d_driver_dd.array[a_driver], 0, &ddsd, a_modes, d3d_display_modes_add);
}

This is the base DD and D3D initialization. I included the display mode enumeration to document the 16-bit filter.
All structures and member names are pure guesswork, but the "code" should be sufficient to answer the topic.

Re: How does KQ8 determine whether Direct3D is supported?

Posted: Fri Nov 06, 2015 1:36 am
by NicoDE
Got access to a Windows 7 machine: DD and D3D seem to work with dgVoodoo 2.45 with default default settings (dgVoodoo setup is detected as potential security threat and the user overrides for the virus scanner are disabled). So it seems I'm too late and the research is obsolete :-)

Re: How does KQ8 determine whether Direct3D is supported?

Posted: Fri Nov 06, 2015 7:39 am
by Expack3
NicoDE wrote:Got access to a Windows 7 machine: DD and D3D seem to work with dgVoodoo 2.45 with default default settings (dgVoodoo setup is detected as potential security threat and the user overrides for the virus scanner are disabled). So it seems I'm too late and the research is obsolete :-)
Just tested on Windows 10 Pro x64; DirectDraw works, as always, but Direct3D still doesn't work, even with default settings. Do note I'm using the GOG.com release, so if you used an original release copy and associated patches, that might have something to do with it.

Re: How does KQ8 determine whether Direct3D is supported?

Posted: Fri Nov 06, 2015 8:05 am
by Expack3
As an update, the current version of dgVoodoo2 works - just had to remove nGlide so I could see the needed option as per Aroenai's reply to this thread.

I suppose the reason for this is because the game clearly has no way to enumerate multiple graphics cards to the user; thus, if you have a 3DFX-compatible graphics card, it disables Direct3D support to prevent you from choosing a sub-standard mode for your graphics card.

Re: How does KQ8 determine whether Direct3D is supported?

Posted: Fri Nov 06, 2015 8:27 am
by Expack3
Aroenai wrote:Not a fault of dgVoodoo, the game disables the Direct3D option on real 3DFX hardware too. You can still force Direct3D using the assignGModeName line in Options.cs... I don't recall what you have to call it though, d3d-something?

Interesting to note, there is a dynamic lighting system for the Direct3D renderer but it's slow as molasses in normal Direct3D (hence why it's not accessible). If you use the dgVoodoo Direct3D renderer (no glide2x.dll) it will be full speed. Other than that, the only differences between the two are the lack of 800x600 and transparent shadows. I'd recommend playing at 640x480 with forced 1280x960 resolution if you can though, much better result than 800x600 since the interface art doesn't get warped (use an even multiple of 640x480). I actually have it set to 1920x1440 since my display is 3200x1800 and uses Nvidia Optimus + Intel HD 4600 so it doesn't have proper scaling :-p
How did I not see this until today? Thanks, Aroenai! That indeed solved the problem. Of course, now that that's sorted out, I find an actual bug with dgVoodoo2 which I have promptly reported to the developer.

Re: How does KQ8 determine whether Direct3D is supported?

Posted: Fri Nov 06, 2015 3:05 pm
by NicoDE
Expack3 wrote:Do note I'm using the GOG.com release, so if you used an original release copy and associated patches, that might have something to do with it.
I'm using the official Patch 1.3 DE/FR (also included in the official Spanish and Italian release) because it is the most recent Mask.exe (by linker timestamp) known to me. This binary has also been used for the GOG.com release (with some incomplete and missing hacks).
This binary has been compiled with the DirectX SDK 6.0 - therefore you need DX6 to run that version (I guess that the developer was not aware of the renamed "ddsCaps" member in the DDCAPS structure and that the game is now accessing a field that has been introduced with DX6). I didn't analyze the DD/D3D code of the other KQMoE releases - they might be working with earlier DX versions (IIRC the DXSDK6 has been released after the initial 1.0 release of KQMoE).

Re: How does KQ8 determine whether Direct3D is supported?

Posted: Mon Nov 09, 2015 11:05 pm
by Aroenai
Expack3 wrote: How did I not see this until today? Thanks, Aroenai! That indeed solved the problem. Of course, now that that's sorted out, I find an actual bug with dgVoodoo2 which I have promptly reported to the developer.
That issue has been around for a while, for whatever reason it's more noticeable on some video cards than others. It has something to do with the way the captions are drawn on the screen, but it's significantly better than it was originally. I think I mentioned that to Dege back when I first reached out to him about KQMOE support.

Nico, were you ever able to take a look at that mouse stuff again?

Sidenote, I've got a Mac to play around with now and I'm pretty sure we can get OS X support with Wine Bottler and Nico's runtime patch + nGlide. Haven't tried it yet though.

Re: How does KQ8 determine whether Direct3D is supported?

Posted: Tue Nov 10, 2015 2:57 am
by NicoDE
Aroenai wrote:Nico, were you ever able to take a look at that mouse stuff again?
No. In April I started a rewrite of KQMoEFix with config options, logging/tracing, important fixes, and later an installer/setup application. In the currently published version the TalkComplete bug is only fixed for one class. I already rewrote the fix to handle all affected classes (like the proposed assembly patch for the SHP installer binary). Another issue is the shim for the window creation hook. The GOG team and the current version of KQMoEFix just limit the CBT hook to the current thread. But this is not enough, it has to been done for all threads in the application to satisfy the application logic. I'm currently planning to drop all existing special mouse and window handling in the next release and retesting the game with the current versions of dgVoodoo2 and nGlide on Windows and Wine. Thereafter I have to trace down the real problems and try to fix them as clean as possible. The existing logic is too complex and seems to introduce more problems than it solves.
Aroenai wrote:Sidenote, I've got a Mac to play around with now and I'm pretty sure we can get OS X support with Wine Bottler and Nico's runtime patch + nGlide. Haven't tried it yet though.
I got some reports with issues on the Mac, but with the next release of KQMoEFix I expect it has to be retested again.