From 3fb5f3351732a78dbd1ca28439653ac65e886445 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Thu, 31 Jan 2013 22:43:15 -0500 Subject: [PATCH 1/3] Add plQuality entry for Shader Model 3 --- Sources/Plasma/CoreLib/plQuality.h | 7 ++--- .../PubUtilLib/plPipeline/plDXPipeline.cpp | 26 ++++++++++++++----- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/Sources/Plasma/CoreLib/plQuality.h b/Sources/Plasma/CoreLib/plQuality.h index fde95339..7d2f3b5b 100644 --- a/Sources/Plasma/CoreLib/plQuality.h +++ b/Sources/Plasma/CoreLib/plQuality.h @@ -50,9 +50,10 @@ public: { kMinimum = 0, - kPS_1_1 = 2, - kPS_1_4 = 3, - kPS_2_Plus = 4 + kPS_1_1 = 2, // DirectX 8.0 + kPS_1_4 = 3, // DirectX 8.1 + kPS_2 = 4, // DirectX 9 + kPS_3 = 5, // DirectX 9.0c }; protected: // These two are instanciated in plLoadMask.cpp, as well as diff --git a/Sources/Plasma/PubUtilLib/plPipeline/plDXPipeline.cpp b/Sources/Plasma/PubUtilLib/plPipeline/plDXPipeline.cpp index 79850342..bdafdf60 100644 --- a/Sources/Plasma/PubUtilLib/plPipeline/plDXPipeline.cpp +++ b/Sources/Plasma/PubUtilLib/plPipeline/plDXPipeline.cpp @@ -1100,16 +1100,28 @@ void plDXPipeline::ISetGraphicsCapability(uint32_t v) { int pixelMajor = D3DSHADER_VERSION_MAJOR(v); int pixelMinor = D3DSHADER_VERSION_MINOR(v); - if( pixelMajor > 1 ) - { - plQuality::SetCapability(plQuality::kPS_2_Plus); - } - else if( pixelMajor > 0 ) + + switch (pixelMajor) { - if( pixelMinor >= 4 ) + case 1: + if (pixelMinor >= 4) plQuality::SetCapability(plQuality::kPS_1_4); - else if( pixelMinor > 0 ) + else if (pixelMinor > 0) plQuality::SetCapability(plQuality::kPS_1_1); + break; + case 2: + plQuality::SetCapability(plQuality::kPS_2); + break; + case 3: + plQuality::SetCapability(plQuality::kPS_3); + break; + default: + // Hopefully this is always FALSE. If not, may gawd have mercy upon your soul. + if (pixelMajor == 0) + plQuality::SetCapability(plQuality::kMinimum); + else + plQuality::SetCapability(plQuality::kPS_3); + break; } } From f2f1bea3451017248232e534c25a1960da70d04d Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Fri, 1 Feb 2013 01:41:15 -0500 Subject: [PATCH 2/3] Non Power-of-Two DynamicCapMaps Epic K'veer floor is now even more epic. --- .../PubUtilLib/plPipeline/plDXPipeline.cpp | 96 ++++++++++--------- .../PubUtilLib/plPipeline/plDXPipeline.h | 49 +++++----- .../PubUtilLib/plPipeline/plDynamicEnvMap.cpp | 16 ++++ .../PubUtilLib/plPipeline/plDynamicEnvMap.h | 1 + .../PubUtilLib/plPipeline/plRenderTarget.h | 22 ++--- 5 files changed, 103 insertions(+), 81 deletions(-) diff --git a/Sources/Plasma/PubUtilLib/plPipeline/plDXPipeline.cpp b/Sources/Plasma/PubUtilLib/plPipeline/plDXPipeline.cpp index bdafdf60..d8614cbb 100644 --- a/Sources/Plasma/PubUtilLib/plPipeline/plDXPipeline.cpp +++ b/Sources/Plasma/PubUtilLib/plPipeline/plDXPipeline.cpp @@ -1008,33 +1008,38 @@ void plDXPipeline::ISetCaps() fSettings.fD3DCaps = kCapsNone; // Set relevant caps (ones we can do something about). - if( fCurrentDevice->fDDCaps.RasterCaps & D3DPRASTERCAPS_DEPTHBIAS ) + if (fCurrentDevice->fDDCaps.RasterCaps & D3DPRASTERCAPS_DEPTHBIAS) fSettings.fD3DCaps |= kCapsZBias; - if( fCurrentDevice->fDDCaps.RasterCaps & D3DPRASTERCAPS_FOGRANGE ) + if (fCurrentDevice->fDDCaps.RasterCaps & D3DPRASTERCAPS_FOGRANGE) fSettings.fD3DCaps |= kCapsRangeFog; - if( fCurrentDevice->fDDCaps.RasterCaps & D3DPRASTERCAPS_FOGTABLE ) + if (fCurrentDevice->fDDCaps.RasterCaps & D3DPRASTERCAPS_FOGTABLE) fSettings.fD3DCaps |= kCapsLinearFog | kCapsExpFog | kCapsExp2Fog | kCapsPixelFog; else fSettings.fD3DCaps |= kCapsLinearFog; - if( fCurrentDevice->fDDCaps.TextureFilterCaps & D3DPTFILTERCAPS_MIPFLINEAR ) + if (fCurrentDevice->fDDCaps.TextureFilterCaps & D3DPTFILTERCAPS_MIPFLINEAR) fSettings.fD3DCaps |= kCapsMipmap; - if( fCurrentDevice->fDDCaps.TextureCaps & D3DPTEXTURECAPS_MIPCUBEMAP ) + if (fCurrentDevice->fDDCaps.TextureCaps & D3DPTEXTURECAPS_MIPCUBEMAP) fSettings.fD3DCaps |= kCapsCubicMipmap; - if( fCurrentDevice->fDDCaps.RasterCaps & D3DPRASTERCAPS_WBUFFER ) + if (fCurrentDevice->fDDCaps.RasterCaps & D3DPRASTERCAPS_WBUFFER) fSettings.fD3DCaps |= kCapsWBuffer; - if( fCurrentDevice->fDDCaps.RasterCaps & D3DPRASTERCAPS_DITHER ) + if (fCurrentDevice->fDDCaps.RasterCaps & D3DPRASTERCAPS_DITHER) fSettings.fD3DCaps |= kCapsDither; - if( fSettings.fNumAASamples > 0 ) + if (fSettings.fNumAASamples > 0) fSettings.fD3DCaps |= kCapsFSAntiAlias; - if( fCurrentDevice->fDDCaps.RasterCaps & D3DPRASTERCAPS_WFOG ) + if (fCurrentDevice->fDDCaps.RasterCaps & D3DPRASTERCAPS_WFOG) fSettings.fD3DCaps |= kCapsDoesWFog; - if( fCurrentDevice->fDDCaps.TextureCaps & D3DPTEXTURECAPS_CUBEMAP ) + if (fCurrentDevice->fDDCaps.TextureCaps & D3DPTEXTURECAPS_CUBEMAP) fSettings.fD3DCaps |= kCapsCubicTextures; + // Unconditional Non-Power of Two Textures + // To make life easy for us, we can have non POT textures or we can't + if (!(fCurrentDevice->fDDCaps.TextureCaps & D3DPTEXTURECAPS_POW2 && + fCurrentDevice->fDDCaps.TextureCaps & D3DPTEXTURECAPS_NONPOW2CONDITIONAL)) + fSettings.fD3DCaps |= kCapsNpotTextures; + /// New 1.5.2000 - cull out mixed vertex processing - if( fCurrentDevice->fDDCaps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT - && fCurrentMode->fDDBehavior == D3DCREATE_HARDWARE_VERTEXPROCESSING - ) + if (fCurrentDevice->fDDCaps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT && + fCurrentMode->fDDBehavior == D3DCREATE_HARDWARE_VERTEXPROCESSING) fSettings.fD3DCaps |= kCapsHWTransform; @@ -1046,14 +1051,14 @@ void plDXPipeline::ISetCaps() fSettings.fD3DCaps |= kCapsDoesSmallTextures; /// Look for supported texture formats - if( IFindCompressedFormats() ) + if ( IFindCompressedFormats() ) fSettings.fD3DCaps |= kCapsCompressTextures; - if( IFindLuminanceFormats() ) + if ( IFindLuminanceFormats() ) fSettings.fD3DCaps |= kCapsLuminanceTextures; /// Max # of hardware lights fSettings.fMaxNumLights = fCurrentDevice->fDDCaps.MaxActiveLights; - if( fSettings.fMaxNumLights > kD3DMaxTotalLights ) + if ( fSettings.fMaxNumLights > kD3DMaxTotalLights ) fSettings.fMaxNumLights = kD3DMaxTotalLights; // Intel Extreme chips report 0 lights, meaning T&L is done @@ -1062,7 +1067,7 @@ void plDXPipeline::ISetCaps() // since the extreme can't really afford them, and record // the fact this is the extreme for other driver problem // workarounds. - if( !fSettings.fMaxNumLights ) + if ( !fSettings.fMaxNumLights ) { fSettings.fMaxNumLights = kD3DMaxTotalLights; fSettings.fIsIntel = true; @@ -1071,7 +1076,7 @@ void plDXPipeline::ISetCaps() /// Max # of textures at once fSettings.fMaxLayersAtOnce = fCurrentDevice->fDDCaps.MaxSimultaneousTextures; - if( fCurrentDevice->fDDCaps.DevCaps & D3DDEVCAPS_SEPARATETEXTUREMEMORIES ) + if ( fCurrentDevice->fDDCaps.DevCaps & D3DDEVCAPS_SEPARATETEXTUREMEMORIES ) fSettings.fMaxLayersAtOnce = 1; // Alloc half our simultaneous textures to piggybacks. // Won't hurt us unless we try to many things at once. @@ -4390,14 +4395,20 @@ hsGDeviceRef *plDXPipeline::MakeRenderTargetRef( plRenderTarget *owner ) IDirect3DCubeTexture9 *cTexture = nil; D3DFORMAT surfFormat = D3DFMT_UNKNOWN, depthFormat = D3DFMT_UNKNOWN; D3DRESOURCETYPE resType; - int i; plCubicRenderTarget *cubicRT; - uint16_t width, height; hsAssert(!fManagedAlloced, "Allocating non-managed resource with managed resources alloc'd"); - + + // If we have Shader Model 3 and support non-POT textures, let's make reflections the pipe size + plDynamicCamMap* camMap = plDynamicCamMap::ConvertNoRef(owner); + if (camMap) + { + if ((plQuality::GetCapability() > plQuality::kPS_2) && fSettings.fD3DCaps & kCapsNpotTextures) + camMap->ResizeViewport(IGetViewTransform()); + } + /// Check--is this renderTarget really a child of a cubicRenderTarget? - if( owner->GetParent() != nil ) + if( owner->GetParent() ) { /// This'll create the deviceRefs for all of its children as well MakeRenderTargetRef( owner->GetParent() ); @@ -4405,7 +4416,7 @@ hsGDeviceRef *plDXPipeline::MakeRenderTargetRef( plRenderTarget *owner ) } // If we already have a rendertargetref, we just need it filled out with D3D resources. - if( owner->GetDeviceRef() != nil ) + if( owner->GetDeviceRef() ) ref = (plDXRenderTargetRef *)owner->GetDeviceRef(); // Look for supported format. Note that the surfFormat and depthFormat are @@ -4417,7 +4428,6 @@ hsGDeviceRef *plDXPipeline::MakeRenderTargetRef( plRenderTarget *owner ) return nil; } - /// Create the render target now // Start with the depth surface. // Note that we only ever give a cubic rendertarget a single shared depth buffer, @@ -4467,19 +4477,19 @@ hsGDeviceRef *plDXPipeline::MakeRenderTargetRef( plRenderTarget *owner ) // See if it's a cubic render target. // Primary consumer here is the vertex/pixel shader water. cubicRT = plCubicRenderTarget::ConvertNoRef( owner ); - if( cubicRT != nil ) + if( cubicRT ) { /// And create the ref (it'll know how to set all the flags) - if( ref != nil ) + if( ref ) ref->Set( surfFormat, 0, owner ); else ref = new plDXRenderTargetRef( surfFormat, 0, owner ); - if( !FAILED( fD3DDevice->CreateCubeTexture( owner->GetWidth(), 1, D3DUSAGE_RENDERTARGET, surfFormat, + if( !FAILED( fD3DDevice->CreateCubeTexture( owner->GetWidth(), 1, D3DUSAGE_RENDERTARGET, surfFormat, D3DPOOL_DEFAULT, (IDirect3DCubeTexture9 **)&cTexture, NULL ) ) ) { /// Create a CUBIC texture - for( i = 0; i < 6; i++ ) + for( int i = 0; i < 6; i++ ) { plRenderTarget *face = cubicRT->GetFace( i ); plDXRenderTargetRef *fRef; @@ -4499,7 +4509,7 @@ hsGDeviceRef *plDXPipeline::MakeRenderTargetRef( plRenderTarget *owner ) hsRefCnt_SafeUnRef( face->GetDeviceRef() ); } } - + D3DSURF_MEMNEW(cTexture); ref->SetTexture( cTexture, depthSurface ); @@ -4516,12 +4526,12 @@ hsGDeviceRef *plDXPipeline::MakeRenderTargetRef( plRenderTarget *owner ) else if( owner->GetFlags() & plRenderTarget::kIsTexture ) { /// Create a normal texture - if( ref != nil ) + if( ref ) ref->Set( surfFormat, 0, owner ); else ref = new plDXRenderTargetRef( surfFormat, 0, owner ); - if( !FAILED( fD3DDevice->CreateTexture( owner->GetWidth(), owner->GetHeight(), 1, D3DUSAGE_RENDERTARGET, surfFormat, + if( !FAILED( fD3DDevice->CreateTexture( owner->GetWidth(), owner->GetHeight(), 1, D3DUSAGE_RENDERTARGET, surfFormat, D3DPOOL_DEFAULT, (IDirect3DTexture9 **)&texture, NULL ) ) ) { D3DSURF_MEMNEW(texture); @@ -4546,19 +4556,16 @@ hsGDeviceRef *plDXPipeline::MakeRenderTargetRef( plRenderTarget *owner ) else if( owner->GetFlags() & plRenderTarget::kIsOffscreen ) { /// Create a blank surface - if( ref != nil ) + if( ref ) ref->Set( surfFormat, 0, owner ); else ref = new plDXRenderTargetRef( surfFormat, 0, owner ); - width = owner->GetWidth(); - height = owner->GetHeight(); - // Specify true for lockable, otherwise I'm not sure what we'd do with it. I guess we // could copyrect to another surface, presumably a texture. But right now the only // thing we use this for is to render a snapshot and copy it to sysmem, which implies // lockable. - if( !FAILED( fD3DDevice->CreateRenderTarget( width, height, surfFormat, + if( !FAILED( fD3DDevice->CreateRenderTarget( owner->GetWidth(), owner->GetHeight(), surfFormat, D3DMULTISAMPLE_NONE, 0, TRUE, &surface, NULL ) ) ) { @@ -4674,7 +4681,7 @@ hsGDeviceRef* plDXPipeline::SharedRenderTargetRef(plRenderTarget* share, plRende ref = new plDXRenderTargetRef( surfFormat, 0, owner ); hsAssert(!fManagedAlloced, "Alloc default with managed alloc'd"); - if( !FAILED( fD3DDevice->CreateCubeTexture( owner->GetWidth(), 1, D3DUSAGE_RENDERTARGET, surfFormat, + if( !FAILED( fD3DDevice->CreateCubeTexture( owner->GetWidth(), 1, D3DUSAGE_RENDERTARGET, surfFormat, D3DPOOL_DEFAULT, (IDirect3DCubeTexture9 **)&cTexture, NULL ) ) ) { @@ -4810,13 +4817,16 @@ bool plDXPipeline::IPrepRenderTargetInfo( plRenderTarget *owner, D3DFORMAT &sur if( flags & plRenderTarget::kIsTexture ) { /// Do an extra check for width and height here - for( i = width >> 1, j = 0; i != 0; i >>= 1, j++ ); - if( width != ( 1 << j ) ) - return false; + if (!(fSettings.fD3DCaps & kCapsNpotTextures)) + { + for( i = width >> 1, j = 0; i != 0; i >>= 1, j++ ); + if( width != ( 1 << j ) ) + return false; - for( i = height >> 1, j = 0; i != 0; i >>= 1, j++ ); - if( height!= ( 1 << j ) ) - return false; + for( i = height >> 1, j = 0; i != 0; i >>= 1, j++ ); + if( height!= ( 1 << j ) ) + return false; + } resType = D3DRTYPE_TEXTURE; } diff --git a/Sources/Plasma/PubUtilLib/plPipeline/plDXPipeline.h b/Sources/Plasma/PubUtilLib/plPipeline/plDXPipeline.h index 99713f8f..b14cc03c 100644 --- a/Sources/Plasma/PubUtilLib/plPipeline/plDXPipeline.h +++ b/Sources/Plasma/PubUtilLib/plPipeline/plDXPipeline.h @@ -177,30 +177,31 @@ class plDXPipeline : public plPipeline { protected: enum { - kCapsNone = 0x0, - kCapsCompressTextures = 0x1, - kCapsMipmap = 0x2, - kCapsHWTransform = 0x4, - kCapsHWLighting = 0x8, - kCapsZBias = 0x10, - kCapsLinearFog = 0x20, - kCapsExpFog = 0x40, - kCapsExp2Fog = 0x80, - kCapsRangeFog = 0x100, - kCapsWBuffer = 0x200, - kCapsTexBoundToStage = 0x400, - kCapsDither = 0x800, - kCapsLODWatch = 0x1000, - kCapsFSAntiAlias = 0x2000, - kCapsLuminanceTextures = 0x4000, - kCapsDoesSmallTextures = 0x8000, - kCapsDoesWFog = 0x10000, - kCapsPixelFog = 0x20000, - kCapsHasBadYonStuff = 0x40000, - kCapsNoKindaSmallTexs = 0x80000, - - kCapsCubicTextures = 0x200000, - kCapsCubicMipmap = 0x400000 + kCapsNone = 0x00000000, + kCapsCompressTextures = 0x00000001, + kCapsMipmap = 0x00000002, + kCapsHWTransform = 0x00000004, + kCapsHWLighting = 0x00000008, + kCapsZBias = 0x00000010, + kCapsLinearFog = 0x00000020, + kCapsExpFog = 0x00000040, + kCapsExp2Fog = 0x00000080, + kCapsRangeFog = 0x00000100, + kCapsWBuffer = 0x00000200, + kCapsTexBoundToStage = 0x00000400, + kCapsDither = 0x00000800, + kCapsLODWatch = 0x00001000, + kCapsFSAntiAlias = 0x00002000, + kCapsLuminanceTextures = 0x00004000, + kCapsDoesSmallTextures = 0x00008000, + kCapsDoesWFog = 0x00010000, + kCapsPixelFog = 0x00020000, + kCapsHasBadYonStuff = 0x00040000, + kCapsNoKindaSmallTexs = 0x00080000, + kCapsNpotTextures = 0x00100000, + + kCapsCubicTextures = 0x00200000, + kCapsCubicMipmap = 0x00400000 }; enum { kKNone = 0x0, diff --git a/Sources/Plasma/PubUtilLib/plPipeline/plDynamicEnvMap.cpp b/Sources/Plasma/PubUtilLib/plPipeline/plDynamicEnvMap.cpp index 80153e34..2bdb150a 100644 --- a/Sources/Plasma/PubUtilLib/plPipeline/plDynamicEnvMap.cpp +++ b/Sources/Plasma/PubUtilLib/plPipeline/plDynamicEnvMap.cpp @@ -537,6 +537,22 @@ void plDynamicCamMap::Init() plgDispatch::Dispatch()->RegisterForExactType(plRenderMsg::Index(), GetKey()); } +void plDynamicCamMap::ResizeViewport(const plViewTransform& vt) +{ + if (!fProportionalViewport) + { + fWidth = vt.GetViewPortWidth(); + fHeight = vt.GetViewPortHeight(); + + fViewport.fAbsolute.fBottom = vt.GetViewPortBottom(); + fViewport.fAbsolute.fLeft = vt.GetViewPortLeft(); + fViewport.fAbsolute.fRight = vt.GetViewPortRight(); + fViewport.fAbsolute.fTop = vt.GetViewPortTop(); + + fReq.SetViewTransform(vt); + } +} + void plDynamicCamMap::SetRefreshRate(float secs) { fRefreshRate = secs; diff --git a/Sources/Plasma/PubUtilLib/plPipeline/plDynamicEnvMap.h b/Sources/Plasma/PubUtilLib/plPipeline/plDynamicEnvMap.h index 93c3abe2..c9f6b603 100644 --- a/Sources/Plasma/PubUtilLib/plPipeline/plDynamicEnvMap.h +++ b/Sources/Plasma/PubUtilLib/plPipeline/plDynamicEnvMap.h @@ -210,6 +210,7 @@ public: void ReRender(); void Init(); + void ResizeViewport(const plViewTransform& vt); void SetIncludeCharacters(bool b); void SetRefreshRate(float secs); diff --git a/Sources/Plasma/PubUtilLib/plPipeline/plRenderTarget.h b/Sources/Plasma/PubUtilLib/plPipeline/plRenderTarget.h index 56464855..5ed7b958 100644 --- a/Sources/Plasma/PubUtilLib/plPipeline/plRenderTarget.h +++ b/Sources/Plasma/PubUtilLib/plPipeline/plRenderTarget.h @@ -87,9 +87,9 @@ class plRenderTarget : public plBitmap bool fApplyTexQuality; bool fProportionalViewport; - uint8_t fZDepth, fStencilDepth; - - plCubicRenderTarget *fParent; + uint8_t fZDepth, fStencilDepth; + + plCubicRenderTarget* fParent; virtual void SetKey(plKey k); @@ -101,18 +101,12 @@ class plRenderTarget : public plBitmap GETINTERFACE_ANY( plRenderTarget, plBitmap ); plRenderTarget() + : fWidth(0), fHeight(0), fZDepth(0), fStencilDepth(0), fApplyTexQuality(false), + fProportionalViewport(true), fParent(nullptr) { - fWidth = 0; - fHeight = 0; + fFlags = 0; fPixelSize = 0; - fZDepth = 0; - fStencilDepth = 0; - fApplyTexQuality = false; - fProportionalViewport = true; SetViewport( 0, 0, 1.f, 1.f ); - fFlags = 0; - fParent = nil; - plPipeResReq::Request(); } @@ -175,8 +169,8 @@ class plRenderTarget : public plBitmap fViewport.fProportional.fBottom = bottom; } - uint16_t GetWidth( void ) { return fWidth; } - uint16_t GetHeight( void ) { return fHeight; } + uint16_t GetWidth( void ) const { return fWidth; } + uint16_t GetHeight( void ) const { return fHeight; } uint8_t GetZDepth( void ) { return fZDepth; } uint8_t GetStencilDepth( void ) { return fStencilDepth; } From e4e718e243b721cde94ea39335574360a3a4805e Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Fri, 1 Feb 2013 17:19:50 -0500 Subject: [PATCH 3/3] Bypass ATI Generic fudging with Radeon HD cards --- .../Plasma/PubUtilLib/plPipeline/hsG3DDeviceSelector.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Sources/Plasma/PubUtilLib/plPipeline/hsG3DDeviceSelector.cpp b/Sources/Plasma/PubUtilLib/plPipeline/hsG3DDeviceSelector.cpp index b6bf7127..1b025765 100644 --- a/Sources/Plasma/PubUtilLib/plPipeline/hsG3DDeviceSelector.cpp +++ b/Sources/Plasma/PubUtilLib/plPipeline/hsG3DDeviceSelector.cpp @@ -1568,6 +1568,14 @@ void hsG3DDeviceSelector::IFudgeDirectXDevice( hsG3DDeviceRecord &record, } //// ATI-based Cards ////////////////////////////////////////////////////// + /// Detect ATI Radeon HD + else if (strstr( desc, "radeon hd" ) != nullptr) + { + hsStatusMessage( "== Using fudge factors for an ATI Radeon HD chipset ==\n" ); + plDemoDebugFile::Write( " Using fudge factors for an ATI Radeon HD chipset" ); + ISetFudgeFactors( kDefaultChipset, record ); + } + /// Detect ATI Rage 128 Pro chipset else if( ( deviceID == 0x00005046 && // Normal ATI Rage 128 Pro detection ( stricmp( szDriver, "ati2dvaa.dll" ) == 0