diff --git a/src/core/hle/D3D8/Direct3D9/CxbxPixelShaderTemplate.hlsl b/src/core/hle/D3D8/Direct3D9/CxbxPixelShaderTemplate.hlsl index 40981a6f9..b77643ea5 100644 --- a/src/core/hle/D3D8/Direct3D9/CxbxPixelShaderTemplate.hlsl +++ b/src/core/hle/D3D8/Direct3D9/CxbxPixelShaderTemplate.hlsl @@ -253,14 +253,23 @@ static bool alphakill[4] = ALPHAKILL; static const float4 WarningColor = float4(0, 1, 1, 1); // Returned when unhandled scenario is encountered +#define unsigned_to_signed(x) (((x) * 2) - 1) // Shifts range from [0..1] to [-1..1] (just like s_bx2) +#define signed_to_unsigned(x) (((x) + 1) / 2) // Shifts range from [-1..1] to [0..1] + float4 PerformColorSign(const float4 ColorSign, float4 t) { - // Per color channel, optionally convert the value range into two's complement signed values (from (0, +1) to (-1, +1), using s_bx2): - // This is often used for bumpmaps - if (ColorSign.r > 0) t.r = s_bx2(t.r); - if (ColorSign.g > 0) t.g = s_bx2(t.g); - if (ColorSign.b > 0) t.b = s_bx2(t.b); - if (ColorSign.a > 0) t.a = s_bx2(t.a); + // Per color channel, based on the ColorSign setting : + // either keep the value range as-is (when ColorSign is zero) + // or convert from [0..1] to [-1..+1] (when ColorSign is more than zero, often used for bumpmaps), + // or convert from [-1..1] to [0..1] (when ColorSign is less than zero): + if (ColorSign.r > 0) t.r = unsigned_to_signed(t.r); + if (ColorSign.g > 0) t.g = unsigned_to_signed(t.g); + if (ColorSign.b > 0) t.b = unsigned_to_signed(t.b); + if (ColorSign.a > 0) t.a = unsigned_to_signed(t.a); + if (ColorSign.r < 0) t.r = signed_to_unsigned(t.r); + if (ColorSign.g < 0) t.g = signed_to_unsigned(t.g); + if (ColorSign.b < 0) t.b = signed_to_unsigned(t.b); + if (ColorSign.a < 0) t.a = signed_to_unsigned(t.a); // TODO : Instead of the above, create a mirror texture with a host format that has identical component layout, but with all components signed. // Then, in here, when any component has to be read as signed, sample the signed texture (ouch : with what dimension and coordinate?!) // and replace the components that we read from the unsigned texture, but which have to be signed, with the signed components read from the signed mirror texture. diff --git a/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp b/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp index 30759e035..9422175ca 100644 --- a/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp +++ b/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp @@ -184,6 +184,7 @@ static xbox::PVOID g_pXbox_Palette_Data[xbox::X_D3DTS_STAGECOU static unsigned g_Xbox_Palette_Size[xbox::X_D3DTS_STAGECOUNT] = { 0 }; // cached palette size + D3DFORMAT g_HostTextureFormats[xbox::X_D3DTS_STAGECOUNT]; // Updated by CxbxUpdateHostTextures(), read by CxbxCalcColorSign xbox::X_D3DBaseTexture *g_pXbox_SetTexture[xbox::X_D3DTS_STAGECOUNT] = {0,0,0,0}; // Set by our D3DDevice_SetTexture and D3DDevice_SwitchTexture patches static xbox::X_D3DBaseTexture CxbxActiveTextureCopies[xbox::X_D3DTS_STAGECOUNT] = {}; // Set by D3DDevice_SwitchTexture. Cached active texture @@ -905,8 +906,13 @@ void *GetDataFromXboxResource(xbox::X_D3DResource *pXboxResource) return (uint8_t*)pData; } +DWORD D3DUSAGE_INVALID = -1; + typedef struct _resource_info_t { + ComPtr pHostResource; + D3DFORMAT HostFormat = D3DFMT_UNKNOWN; + DWORD HostUsage = D3DUSAGE_INVALID; DWORD dwXboxResourceType = 0; void* pXboxData = xbox::zeroptr; size_t szXboxDataSize = 0; @@ -1149,6 +1155,42 @@ void SetHostResource(xbox::X_D3DResource* pXboxResource, IDirect3DResource* pHos resourceInfo.lastUpdate = std::chrono::steady_clock::now(); resourceInfo.nextHashTime = resourceInfo.lastUpdate + resourceInfo.hashLifeTime; resourceInfo.forceRehash = false; + + HRESULT hRet = STATUS_INVALID_PARAMETER; // Default to an error condition, so we can use D3D_OK to check for success + D3DSURFACE_DESC surfaceDesc; + D3DVOLUME_DESC volumeDesc; + UINT Level = 0; // TODO : When should Level every be something other than zero, and if so : what other value? + switch (resourceInfo.dwXboxResourceType) {// TODO : Better check pHostResource class type + case xbox::X_D3DRTYPE_SURFACE: + hRet = ((IDirect3DSurface*)pHostResource)->GetDesc(&surfaceDesc); + break; + case xbox::X_D3DRTYPE_TEXTURE: + hRet = ((IDirect3DTexture*)pHostResource)->GetLevelDesc(Level, &surfaceDesc); + break; + case xbox::X_D3DRTYPE_VOLUMETEXTURE: { + hRet = ((IDirect3DVolumeTexture*)pHostResource)->GetLevelDesc(Level, &volumeDesc); + break; } + case xbox::X_D3DRTYPE_CUBETEXTURE: + hRet = ((IDirect3DCubeTexture*)pHostResource)->GetLevelDesc(Level, &surfaceDesc); + break; + } + + D3DFORMAT hostFormat = D3DFMT_UNKNOWN; + DWORD hostUsage = D3DUSAGE_INVALID; + + if (SUCCEEDED(hRet)) { + if (resourceInfo.dwXboxResourceType == xbox::X_D3DRTYPE_VOLUMETEXTURE) { + hostFormat = volumeDesc.Format; + hostUsage = volumeDesc.Usage; + } + else { + hostFormat = surfaceDesc.Format; + hostUsage = surfaceDesc.Usage; + } + } + + resourceInfo.HostFormat = hostFormat; + resourceInfo.HostUsage = hostUsage; } IDirect3DSurface *GetHostSurface(xbox::X_D3DResource *pXboxResource, DWORD D3DUsage = 0) @@ -2280,30 +2322,20 @@ static void EmuVerifyResourceIsRegistered(xbox::X_D3DResource *pResource, DWORD auto& ResourceCache = GetResourceCache(key); auto it = ResourceCache.find(key); if (it != ResourceCache.end()) { + // TODO : Should this check be (D3DUsage & D3DUSAGE_RENDERTARGET) instead? if (D3DUsage == D3DUSAGE_RENDERTARGET && IsResourceAPixelContainer(pResource) && EmuXBFormatIsRenderTarget(GetXboxPixelContainerFormat((xbox::X_D3DPixelContainer*)pResource))) { // Render targets have special behavior: We can't trash them on guest modification // this fixes an issue where CubeMaps were broken because the surface Set in GetCubeMapSurface // would be overwritten by the surface created in SetRenderTarget // However, if a non-rendertarget surface is used here, we'll need to recreate it as a render target! - auto hostResource = it->second.pHostResource.Get(); - auto xboxSurface = (xbox::X_D3DSurface*)pResource; - auto xboxTexture = (xbox::X_D3DTexture*)pResource; - auto xboxResourceType = GetXboxD3DResourceType(pResource); - - // Determine if the associated host resource is a render target already, if so, do nothing - HRESULT hRet = STATUS_INVALID_PARAMETER; // Default to an error condition, so we can use D3D_OK to check for success - D3DSURFACE_DESC surfaceDesc; - if (xboxResourceType == xbox::X_D3DRTYPE_SURFACE) { - hRet = ((IDirect3DSurface*)hostResource)->GetDesc(&surfaceDesc); - } else if (xboxResourceType == xbox::X_D3DRTYPE_TEXTURE) { - hRet = ((IDirect3DTexture*)hostResource)->GetLevelDesc(0, &surfaceDesc); - } + auto hostResource = it->second.pHostResource; + assert(xboxResourceType == GetXboxD3DResourceType(pResource)); // Only continue checking if we were able to get the surface desc, if it failed, we fall-through // to previous resource management behavior - if (SUCCEEDED(hRet)) { + if (it->second.HostUsage != D3DUSAGE_INVALID) { // If this resource is already created as a render target on the host, simply return - if (surfaceDesc.Usage & D3DUSAGE_RENDERTARGET) { + if (it->second.HostUsage & D3DUSAGE_RENDERTARGET) { return; } @@ -2311,6 +2343,8 @@ static void EmuVerifyResourceIsRegistered(xbox::X_D3DResource *pResource, DWORD // We need to re-create it as a render target switch (xboxResourceType) { case xbox::X_D3DRTYPE_SURFACE: { + auto xboxSurface = (xbox::X_D3DSurface*)pResource; + // Free the host surface FreeHostResource(key); @@ -7301,7 +7335,6 @@ void CxbxUpdateHostTextures() auto pXboxBaseTexture = g_pXbox_SetTexture[stage]; IDirect3DBaseTexture* pHostBaseTexture = nullptr; bool bNeedRelease = false; - if (pXboxBaseTexture != xbox::zeroptr) { DWORD XboxResourceType = GetXboxCommonResourceType(pXboxBaseTexture); switch (XboxResourceType) { @@ -7320,6 +7353,15 @@ void CxbxUpdateHostTextures() LOG_TEST_CASE("ActiveTexture set to an unhandled resource type!"); break; } + + // Read HostFormat from GetResourceCache : + // TODO : Optimize this, as we're doing the lookup twice (once in GetHostBaseTexture, once here) + auto key = GetHostResourceKey(pXboxBaseTexture, stage); + auto& ResourceCache = GetResourceCache(key); + auto it = ResourceCache.find(key); + if (it != ResourceCache.end()) { + g_HostTextureFormats[stage] = it->second.HostFormat; + } } HRESULT hRet = g_pD3DDevice->SetTexture(stage, pHostBaseTexture); diff --git a/src/core/hle/D3D8/Direct3D9/Direct3D9.h b/src/core/hle/D3D8/Direct3D9/Direct3D9.h index e508ff39a..bf220715d 100644 --- a/src/core/hle/D3D8/Direct3D9/Direct3D9.h +++ b/src/core/hle/D3D8/Direct3D9/Direct3D9.h @@ -59,6 +59,8 @@ extern xbox::dword_xt g_Xbox_VertexShader_Handle; extern xbox::X_PixelShader *g_pXbox_PixelShader; +extern D3DFORMAT g_HostTextureFormats[xbox::X_D3DTS_STAGECOUNT]; + extern xbox::X_D3DBaseTexture *g_pXbox_SetTexture[xbox::X_D3DTS_STAGECOUNT]; namespace xbox { diff --git a/src/core/hle/D3D8/Direct3D9/FixedFunctionPixelShader.hlsl b/src/core/hle/D3D8/Direct3D9/FixedFunctionPixelShader.hlsl index 9df049a1e..cf8a26604 100644 --- a/src/core/hle/D3D8/Direct3D9/FixedFunctionPixelShader.hlsl +++ b/src/core/hle/D3D8/Direct3D9/FixedFunctionPixelShader.hlsl @@ -4,17 +4,23 @@ static const float4 WarningColor = float4(0, 1, 1, 1); // Returned when unhandled scenario is encountered -#define s_bx2(x) (( 2 * max(0, x)) - 1) // PS_INPUTMAPPING_EXPAND_NORMAL= 0x40L, // invalid for final combiner // Shifts range from [0..1] to [-1..1] -#define s_bias(x) (max(0, x) - 0.5) // PS_INPUTMAPPING_HALFBIAS_NORMAL= 0x80L, // invalid for final combiner // Clamps negative x to 0 and then subtracts 0.5 +#define unsigned_to_signed(x) (((x) * 2) - 1) // Shifts range from [0..1] to [-1..1] (just like s_bx2) +#define signed_to_unsigned(x) (((x) + 1) / 2) // Shifts range from [-1..1] to [0..1] float4 PerformColorSign(const float4 ColorSign, float4 t) { - // Per color channel, optionally convert the value range into two's complement signed values (from (0, +1) to (-1, +1), using s_bx2): - // This is often used for bumpmaps - if (ColorSign.r > 0) t.r = s_bx2(t.r); - if (ColorSign.g > 0) t.g = s_bx2(t.g); - if (ColorSign.b > 0) t.b = s_bx2(t.b); - if (ColorSign.a > 0) t.a = s_bx2(t.a); + // Per color channel, based on the ColorSign setting : + // either keep the value range as-is (when ColorSign is zero) + // or convert from [0..1] to [-1..+1] (when ColorSign is more than zero, often used for bumpmaps), + // or convert from [-1..1] to [0..1] (when ColorSign is less than zero): + if (ColorSign.r > 0) t.r = unsigned_to_signed(t.r); + if (ColorSign.g > 0) t.g = unsigned_to_signed(t.g); + if (ColorSign.b > 0) t.b = unsigned_to_signed(t.b); + if (ColorSign.a > 0) t.a = unsigned_to_signed(t.a); + if (ColorSign.r < 0) t.r = signed_to_unsigned(t.r); + if (ColorSign.g < 0) t.g = signed_to_unsigned(t.g); + if (ColorSign.b < 0) t.b = signed_to_unsigned(t.b); + if (ColorSign.a < 0) t.a = signed_to_unsigned(t.a); // TODO : Instead of the above, create a mirror texture with a host format that has identical component layout, but with all components signed. // Then, in here, when any component has to be read as signed, sample the signed texture (ouch : with what dimension and coordinate?!) // and replace the components that we read from the unsigned texture, but which have to be signed, with the signed components read from the signed mirror texture. diff --git a/src/core/hle/D3D8/XbPixelShader.cpp b/src/core/hle/D3D8/XbPixelShader.cpp index 27a882212..a376657d6 100644 --- a/src/core/hle/D3D8/XbPixelShader.cpp +++ b/src/core/hle/D3D8/XbPixelShader.cpp @@ -968,28 +968,54 @@ float AsFloat(uint32_t value) return *(float*)&v; } -// This mimicks behaviour of XDK LazySetShaderStageProgram, which we bypass due to our drawing patches without trampolines. -DWORD CxbxGetColorSign(int stage_nr) +// Determines the Cxbx ColorSign requirement, as handled in the HLSL shaders by PerformColorSign() +float CxbxComponentColorSignFromXboxAndHost(bool XboxMarksComponentSigned, bool HostComponentIsSigned) { - // When bump environment mapping is enabled - if (XboxTextureStates.Get(stage_nr, xbox::X_D3DTSS_COLOROP) >= xbox::X_D3DTOP_BUMPENVMAP) - // Always mark the blue (alias for U) and green (alias for V) color channels as signed : - return xbox::X_D3DTSIGN_GSIGNED | xbox::X_D3DTSIGN_BSIGNED; + // Equal "signedness" between Xbox and host implies we must not convert the component scale : + if (XboxMarksComponentSigned == HostComponentIsSigned) + return 0.0f; + + // Xbox wants the components to be signed (even though host has them unsigned) + if (XboxMarksComponentSigned) + return 1.0f; // Mark the component for scaling from unsigned_to_signed + + // Xbox doesn't want signed values, but host has them signed : + return -1.0f; // Mark the component for scaling from signed_to_unsigned +} + +D3DXCOLOR CxbxCalcColorSign(int stage_nr) +{ + // Without overrides, just use what the running executable put in COLORSIGN : + DWORD XboxColorSign = XboxTextureStates.Get(stage_nr, xbox::X_D3DTSS_COLORSIGN); + + { // This mimicks behaviour of XDK LazySetShaderStageProgram, which we bypass due to our drawing patches without trampolines. + // When bump environment mapping is enabled + if (XboxTextureStates.Get(stage_nr, xbox::X_D3DTSS_COLOROP) >= xbox::X_D3DTOP_BUMPENVMAP) + // Always mark the blue (alias for U) and green (alias for V) color channels as signed : + XboxColorSign |= xbox::X_D3DTSIGN_GSIGNED | xbox::X_D3DTSIGN_BSIGNED; + } #if 0 // When this block is enabled, XDK samples BumpEarth and BumpLens turn red-ish, so keep this off for now... - // TODO : Perhaps turn COLORSIGN off when the active host texture is already signed (like D3DFMT_L6V5U5 if supported) - // and enable COLORSIGN when the active host texture was converted from signed to unsigned? - // Check if the pixel shader specifies bump mapping for this stage (TODO : How to handle this with the fixed function shader?) DWORD PSTextureModes = XboxRenderStates.GetXboxRenderState(xbox::X_D3DRS_PSTEXTUREMODES); PS_TEXTUREMODES StageTextureMode = (PS_TEXTUREMODES)((PSTextureModes >> (stage_nr * 5)) & PS_TEXTUREMODES_MASK); if (StageTextureMode == PS_TEXTUREMODES_BUMPENVMAP || StageTextureMode == PS_TEXTUREMODES_BUMPENVMAP_LUM) - return xbox::X_D3DTSIGN_GSIGNED | xbox::X_D3DTSIGN_BSIGNED; + XboxColorSign |= xbox::X_D3DTSIGN_GSIGNED | xbox::X_D3DTSIGN_BSIGNED; #endif + // Host D3DFMT's with one or more signed components : D3DFMT_V8U8, D3DFMT_Q8W8V8U8, D3DFMT_V16U16, D3DFMT_Q16W16V16U16, D3DFMT_CxV8U8 + D3DFORMAT HostTextureFormat = g_HostTextureFormats[stage_nr]; + bool HostTextureFormatIsSignedForA = (HostTextureFormat == D3DFMT_Q8W8V8U8); // No need to check for unused formats : D3DFMT_Q16W16V16U16, D3DFMT_CxV8U8, D3DFMT_A2W10V10U10 + bool HostTextureFormatIsSignedForR = (HostTextureFormat == D3DFMT_V8U8) || (HostTextureFormat == D3DFMT_V16U16) || (HostTextureFormat == D3DFMT_L6V5U5) || (HostTextureFormat == D3DFMT_X8L8V8U8) || HostTextureFormatIsSignedForA; + bool HostTextureFormatIsSignedForG = HostTextureFormatIsSignedForR; + bool HostTextureFormatIsSignedForB = HostTextureFormatIsSignedForA; - // Without overrides, just return what the running executable put in COLORSIGN : - return XboxTextureStates.Get(stage_nr, xbox::X_D3DTSS_COLORSIGN); + D3DXCOLOR CxbxColorSign; + CxbxColorSign.r = CxbxComponentColorSignFromXboxAndHost(XboxColorSign & xbox::X_D3DTSIGN_RSIGNED, HostTextureFormatIsSignedForR); // Maps to COLORSIGN.r + CxbxColorSign.g = CxbxComponentColorSignFromXboxAndHost(XboxColorSign & xbox::X_D3DTSIGN_GSIGNED, HostTextureFormatIsSignedForG); // Maps to COLORSIGN.g + CxbxColorSign.b = CxbxComponentColorSignFromXboxAndHost(XboxColorSign & xbox::X_D3DTSIGN_BSIGNED, HostTextureFormatIsSignedForB); // Maps to COLORSIGN.b + CxbxColorSign.a = CxbxComponentColorSignFromXboxAndHost(XboxColorSign & xbox::X_D3DTSIGN_ASIGNED, HostTextureFormatIsSignedForA); // Maps to COLORSIGN.a + return CxbxColorSign; } // Set constant state for the fixed function pixel shader @@ -1010,11 +1036,11 @@ void UpdateFixedFunctionPixelShaderState() for (int i = 0; i < xbox::X_D3DTS_STAGECOUNT; i++) { auto stage = &ffPsState.stages[i]; stage->COLORKEYOP = (float)XboxTextureStates.Get(i, xbox::X_D3DTSS_COLORKEYOP); - DWORD colorsign = CxbxGetColorSign(i); - stage->COLORSIGN.x = (colorsign & xbox::X_D3DTSIGN_RSIGNED) ? 1.0f : 0.0f; // Maps to COLORSIGN.r - stage->COLORSIGN.y = (colorsign & xbox::X_D3DTSIGN_GSIGNED) ? 1.0f : 0.0f; // Maps to COLORSIGN.g - stage->COLORSIGN.z = (colorsign & xbox::X_D3DTSIGN_BSIGNED) ? 1.0f : 0.0f; // Maps to COLORSIGN.b - stage->COLORSIGN.w = (colorsign & xbox::X_D3DTSIGN_ASIGNED) ? 1.0f : 0.0f; // Maps to COLORSIGN.a + auto CxbxColorSign = CxbxCalcColorSign(i); + stage->COLORSIGN.x = CxbxColorSign.r; + stage->COLORSIGN.y = CxbxColorSign.g; + stage->COLORSIGN.z = CxbxColorSign.b; + stage->COLORSIGN.w = CxbxColorSign.a; stage->ALPHAKILL = (float)XboxTextureStates.Get(i, xbox::X_D3DTSS_ALPHAKILL); stage->BUMPENVMAT00 = AsFloat(XboxTextureStates.Get(i, xbox::X_D3DTSS_BUMPENVMAT00)); stage->BUMPENVMAT01 = AsFloat(XboxTextureStates.Get(i, xbox::X_D3DTSS_BUMPENVMAT01)); @@ -1136,11 +1162,7 @@ void DxbxUpdateActivePixelShader() // NOPATCH // Texture color sign for (int stage_nr = 0; stage_nr < xbox::X_D3DTS_STAGECOUNT; stage_nr++) { - auto colorsign = CxbxGetColorSign(stage_nr); - fColor[PSH_XBOX_CONSTANT_COLORSIGN + stage_nr].r = (colorsign & xbox::X_D3DTSIGN_RSIGNED) ? 1.0f : 0.0f; // Maps to COLORSIGN[stage].r (treated as bool, true when > 0.0) - fColor[PSH_XBOX_CONSTANT_COLORSIGN + stage_nr].g = (colorsign & xbox::X_D3DTSIGN_GSIGNED) ? 1.0f : 0.0f; // Maps to COLORSIGN[stage].g - fColor[PSH_XBOX_CONSTANT_COLORSIGN + stage_nr].b = (colorsign & xbox::X_D3DTSIGN_BSIGNED) ? 1.0f : 0.0f; // Maps to COLORSIGN[stage].b - fColor[PSH_XBOX_CONSTANT_COLORSIGN + stage_nr].a = (colorsign & xbox::X_D3DTSIGN_ASIGNED) ? 1.0f : 0.0f; // Maps to COLORSIGN[stage].a + fColor[PSH_XBOX_CONSTANT_COLORSIGN + stage_nr] = CxbxCalcColorSign(stage_nr); } #if 0 // New, doesn't work yet