diff --git a/CMakeLists.txt b/CMakeLists.txt index 64b50be5e..8ad9094d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -117,10 +117,14 @@ file (GLOB CXBXR_HEADER_EMU "${CXBXR_ROOT_DIR}/src/common/util/gloffscreen/gloffscreen.h" "${CXBXR_ROOT_DIR}/src/common/XADPCM.h" "${CXBXR_ROOT_DIR}/src/common/xbox/Logging.hpp" + "${CXBXR_ROOT_DIR}/src/core/hle/D3D8/Direct3D9/CxbxVertexShaderTemplate.hlsl" "${CXBXR_ROOT_DIR}/src/core/hle/D3D8/Direct3D9/Direct3D9.h" + "${CXBXR_ROOT_DIR}/src/core/hle/D3D8/Direct3D9/FixedFunctionVertexShader.hlsl" + "${CXBXR_ROOT_DIR}/src/core/hle/D3D8/Direct3D9/FixedFunctionVertexShaderState.hlsli" "${CXBXR_ROOT_DIR}/src/core/hle/D3D8/Direct3D9/VertexShader.h" "${CXBXR_ROOT_DIR}/src/core/hle/D3D8/Direct3D9/VertexShaderSource.h" "${CXBXR_ROOT_DIR}/src/core/hle/D3D8/Direct3D9/WalkIndexBuffer.h" + "${CXBXR_ROOT_DIR}/src/core/hle/D3D8/FixedFunctionState.h" "${CXBXR_ROOT_DIR}/src/core/hle/D3D8/ResourceTracker.h" "${CXBXR_ROOT_DIR}/src/core/hle/D3D8/XbConvert.h" "${CXBXR_ROOT_DIR}/src/core/hle/D3D8/XbD3D8Logging.h" @@ -191,6 +195,11 @@ file (GLOB CXBXR_HEADER_EMU "${CXBXR_ROOT_DIR}/src/devices/Xbox.h" ) +# filter hlsl files into its own list +# excluding hlsli file(s) +set(CXBXR_HEADER_HLSL ${CXBXR_HEADER_EMU}) +list(FILTER CXBXR_HEADER_HLSL INCLUDE REGEX ".*\\.hlsl$") + # Common (GUI and Emulator) file (GLOB CXBXR_SOURCE_COMMON "${CXBXR_ROOT_DIR}/src/common/crypto/EmuDes.cpp" @@ -266,6 +275,7 @@ file (GLOB CXBXR_SOURCE_EMU "${CXBXR_ROOT_DIR}/src/core/hle/D3D8/Direct3D9/VertexShader.cpp" "${CXBXR_ROOT_DIR}/src/core/hle/D3D8/Direct3D9/VertexShaderSource.cpp" "${CXBXR_ROOT_DIR}/src/core/hle/D3D8/Direct3D9/WalkIndexBuffer.cpp" + "${CXBXR_ROOT_DIR}/src/core/hle/D3D8/FixedFunctionState.cpp" "${CXBXR_ROOT_DIR}/src/core/hle/D3D8/ResourceTracker.cpp" "${CXBXR_ROOT_DIR}/src/core/hle/D3D8/XbConvert.cpp" "${CXBXR_ROOT_DIR}/src/core/hle/D3D8/XbD3D8Logging.cpp" @@ -407,6 +417,12 @@ install(FILES ${cxbxr_INSTALL_files} DESTINATION bin ) +install(FILES + "${CMAKE_SOURCE_DIR}/src/core/hle/D3D8/Direct3D9/FixedFunctionVertexShaderState.hlsli" + "${CMAKE_SOURCE_DIR}/src/core/hle/D3D8/Direct3D9/FixedFunctionVertexShader.hlsl" + DESTINATION bin/hlsl +) + set(cxbxr_GLEW_DLL "${CMAKE_SOURCE_DIR}/import/glew-2.0.0/bin/Release/Win32/glew32.dll") install(PROGRAMS ${cxbxr_GLEW_DLL} diff --git a/projects/cxbx/CMakeLists.txt b/projects/cxbx/CMakeLists.txt index 49c9b8884..ad9dd6e11 100644 --- a/projects/cxbx/CMakeLists.txt +++ b/projects/cxbx/CMakeLists.txt @@ -194,3 +194,12 @@ if(${CMAKE_GENERATOR} MATCHES "Visual Studio ([^9]|[9][0-9])") endif() add_dependencies(cxbx cxbxr-ldr cxbxr-emu misc-batch) + +# Try to stop cmake from building hlsl files +# Which are all currently loaded at runtime only +set_source_files_properties( + ${CXBXR_HEADER_HLSL} + PROPERTIES + HEADER_FILE_ONLY TRUE + VS_TOOL_OVERRIDE "None" +) diff --git a/projects/cxbxr-emu/CMakeLists.txt b/projects/cxbxr-emu/CMakeLists.txt index 569b0d4b2..656554ff1 100644 --- a/projects/cxbxr-emu/CMakeLists.txt +++ b/projects/cxbxr-emu/CMakeLists.txt @@ -166,3 +166,12 @@ install(TARGETS ${PROJECT_NAME} ) add_dependencies(cxbxr-emu cxbxr-ldr misc-batch) + +# Try to stop cmake from building hlsl files +# Which are all currently loaded at runtime only +set_source_files_properties( + ${CXBXR_HEADER_HLSL} + PROPERTIES + HEADER_FILE_ONLY TRUE + VS_TOOL_OVERRIDE "None" +) diff --git a/projects/misc/batch.cmake b/projects/misc/batch.cmake index e3678c490..e29fafe0d 100644 --- a/projects/misc/batch.cmake +++ b/projects/misc/batch.cmake @@ -27,3 +27,12 @@ message("Runtime Build Directory: ${TargetRunTimeDir}") # Copy glew32.dll to build type's folder. set(CXBXR_GLEW_DLL "${CMAKE_SOURCE_DIR}/import/glew-2.0.0/bin/Release/Win32/glew32.dll") file(COPY ${CXBXR_GLEW_DLL} DESTINATION ${TargetRunTimeDir}) + +# Copy certain HLSL files to the output directory, which we will load at runtime +set(CXBXR_HLSL_FILES +"${CMAKE_SOURCE_DIR}/src/core/hle/D3D8/Direct3D9/FixedFunctionVertexShaderState.hlsli" +"${CMAKE_SOURCE_DIR}/src/core/hle/D3D8/Direct3D9/FixedFunctionVertexShader.hlsl" +) +set(HlslOutputDir ${TargetRunTimeDir}/hlsl) +file(MAKE_DIRECTORY ${HlslOutputDir}) +file(COPY ${CXBXR_HLSL_FILES} DESTINATION ${HlslOutputDir}) diff --git a/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp b/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp index 0a44a22ed..d116d9d5a 100644 --- a/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp +++ b/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp @@ -37,6 +37,7 @@ #include "core\kernel\init\CxbxKrnl.h" #include "core\kernel\support\Emu.h" #include "EmuShared.h" +#include "..\FixedFunctionState.h" #include "core\hle\D3D8\ResourceTracker.h" #include "core\hle\D3D8\Direct3D9\Direct3D9.h" // For LPDIRECTDRAWSURFACE7 #include "core\hle\D3D8\XbVertexBuffer.h" @@ -70,6 +71,10 @@ XboxRenderStateConverter XboxRenderStates; XboxTextureStateConverter XboxTextureStates; +D3D8LightState d3d8LightState = D3D8LightState(); +D3D8TransformState d3d8TransformState = D3D8TransformState(); +FixedFunctionVertexShaderState ffShaderState = {0}; // TODO find a home for this and associated code + // Allow use of time duration literals (making 16ms, etc possible) using namespace std::literals::chrono_literals; @@ -302,9 +307,9 @@ g_EmuCDPD; XB_MACRO(xbox::void_xt, WINAPI, D3DDevice_SetVertexShader_0, () ); \ XB_MACRO(xbox::void_xt, WINAPI, D3DDevice_SetVertexShaderInput, (xbox::dword_xt, xbox::uint_xt, xbox::X_STREAMINPUT*) ); \ XB_MACRO(xbox::void_xt, WINAPI, D3DDevice_SetViewport, (CONST xbox::X_D3DVIEWPORT8*) ); \ - XB_MACRO(xbox::void_xt, WINAPI, D3DDevice_SetTransform, (D3DTRANSFORMSTATETYPE, CONST D3DMATRIX*) ); \ + XB_MACRO(xbox::void_xt, WINAPI, D3DDevice_SetTransform, (xbox::X_D3DTRANSFORMSTATETYPE, CONST D3DMATRIX*) ); \ XB_MACRO(xbox::void_xt, WINAPI, D3DDevice_SetTransform_0, () ); \ - XB_MACRO(xbox::void_xt, WINAPI, D3DDevice_MultiplyTransform, (D3DTRANSFORMSTATETYPE, CONST D3DMATRIX*) ); \ + XB_MACRO(xbox::void_xt, WINAPI, D3DDevice_MultiplyTransform, (xbox::X_D3DTRANSFORMSTATETYPE, CONST D3DMATRIX*) ); \ XB_MACRO(xbox::void_xt, WINAPI, D3D_DestroyResource, (xbox::X_D3DResource*) ); \ XB_MACRO(xbox::void_xt, WINAPI, D3D_DestroyResource__LTCG, (xbox::void_xt) ); \ XB_MACRO(xbox::hresult_xt, WINAPI, Direct3D_CreateDevice, (xbox::uint_xt, D3DDEVTYPE, HWND, xbox::dword_xt, xbox::X_D3DPRESENT_PARAMETERS*, xbox::X_D3DDevice**)); \ @@ -1892,6 +1897,10 @@ static LRESULT WINAPI EmuMsgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lPar extern void DSound_PrintStats(); //TODO: move into plugin class usage. DSound_PrintStats(); } + else if (wParam == VK_F2) + { + g_UseFixedFunctionVertexShader = !g_UseFixedFunctionVertexShader; + } else if (wParam == VK_F3) { g_bClipCursor = !g_bClipCursor; @@ -2869,8 +2878,6 @@ void GetMultiSampleScaleRaw(float& xScale, float& yScale) { // Titles can render pre-transformed vertices to screen space (using XYZRHW vertex position data or otherwise) // so we need to know the space they are in to interpret it correctly void GetScreenScaleFactors(float& scaleX, float& scaleY) { - extern bool g_Xbox_VertexShader_IsPassthrough; - // Example: // NFS HP2 renders in-game at 640*480 // The title uses MSAA, which increases the rendertarget size, but leaves the screen scale unaffected @@ -2900,7 +2907,7 @@ void GetScreenScaleFactors(float& scaleX, float& scaleY) { // - Antialias sample (background gradient) // Vertex program passthrough equivalent (title does apply backbuffer scale): // - NFS:HP2 (car speed and other in-game UI elements) - if (!g_Xbox_VertexShader_IsPassthrough) { + if (g_Xbox_VertexShaderMode != VertexShaderMode::Passthrough) { scaleX *= g_Xbox_BackbufferScaleX; scaleY *= g_Xbox_BackbufferScaleY; } @@ -4153,8 +4160,6 @@ void GetXboxViewportOffsetAndScale(float (&vOffset)[4], float(&vScale)[4]) void CxbxUpdateHostViewPortOffsetAndScaleConstants() { - extern bool g_Xbox_VertexShader_IsPassthrough; - float vScaleOffset[2][4]; // 0 - scale 1 - offset GetXboxViewportOffsetAndScale(vScaleOffset[1], vScaleOffset[0]); @@ -4180,7 +4185,7 @@ void CxbxUpdateHostViewPortOffsetAndScaleConstants() // Passthrough should range 0 to 1, instead of 0 to zbuffer depth // Test case: DoA3 character select - float zOutputScale = g_Xbox_VertexShader_IsPassthrough ? 1 : g_ZScale; + float zOutputScale = g_Xbox_VertexShaderMode == VertexShaderMode::Passthrough ? 1 : g_ZScale; float screenspaceScale[4] = { xboxScreenspaceWidth / 2, -xboxScreenspaceHeight / 2, zOutputScale, 1 }; float screenspaceOffset[4] = { xboxScreenspaceWidth / 2 + aaOffsetX, xboxScreenspaceHeight / 2 + aaOffsetY, 0, 0 }; @@ -6252,6 +6257,220 @@ void CreateHostResource(xbox::X_D3DResource *pResource, DWORD D3DUsage, int iTex } // switch XboxResourceType } +D3DXVECTOR4 toVector(D3DCOLOR color) { + D3DXVECTOR4 v; + // ARGB to XYZW + v.w = (color >> 24 & 0xFF) / 255.f; + v.x = (color >> 16 & 0xFF) / 255.f; + v.y = (color >> 8 & 0xFF) / 255.f; + v.z = (color >> 0 & 0xFF) / 255.f; + return v; +} + +D3DXVECTOR4 toVector(D3DCOLORVALUE val) { + return D3DXVECTOR4(val.r, val.g, val.b, val.a); +} + +void UpdateFixedFunctionShaderLight(int d3dLightIndex, Light* pShaderLight, D3DXVECTOR4* pLightAmbient) { + if (d3dLightIndex == -1) { + pShaderLight->Type = 0; // Disable the light + return; + } + + auto d3dLight = &d3d8LightState.Lights[d3dLightIndex]; + auto viewTransform = (D3DXMATRIX)d3d8TransformState.Transforms[xbox::X_D3DTS_VIEW]; + + // TODO remove D3DX usage + // Pre-transform light position to viewspace + D3DXVECTOR4 positionV; + D3DXVec3Transform(&positionV, (D3DXVECTOR3*)&d3dLight->Position, &viewTransform); + pShaderLight->PositionV = (D3DXVECTOR3)positionV; + + // Pre-transform light direction to viewspace and normalize + D3DXVECTOR4 directionV; + D3DXMATRIX viewTransform3x3; + D3DXMatrixIdentity(&viewTransform3x3); + for (int y = 0; y < 3; y++) { + for (int x = 0; x < 3; x++) { + viewTransform3x3.m[x][y] = viewTransform.m[x][y]; + } + } + + D3DXVec3Transform(&directionV, (D3DXVECTOR3*)&d3dLight->Direction, &viewTransform3x3); + D3DXVec3Normalize((D3DXVECTOR3*)&pShaderLight->DirectionVN, (D3DXVECTOR3*)&directionV); + + bool SpecularEnable = XboxRenderStates.GetXboxRenderState(xbox::X_D3DRS_SPECULARENABLE) != FALSE; + + // Map D3D light to state struct + pShaderLight->Type = (float)((int)d3dLight->Type); + pShaderLight->Diffuse = toVector(d3dLight->Diffuse); + pShaderLight->Specular = SpecularEnable ? toVector(d3dLight->Specular) : toVector(0); + pShaderLight->Range = d3dLight->Range; + pShaderLight->Falloff = d3dLight->Falloff; + pShaderLight->Attenuation.x = d3dLight->Attenuation0; + pShaderLight->Attenuation.y = d3dLight->Attenuation1; + pShaderLight->Attenuation.z = d3dLight->Attenuation2; + + pLightAmbient->x += d3dLight->Ambient.r; + pLightAmbient->y += d3dLight->Ambient.g; + pLightAmbient->z += d3dLight->Ambient.b; + + auto cosHalfPhi = cos(d3dLight->Phi / 2); + pShaderLight->CosHalfPhi = cosHalfPhi; + pShaderLight->SpotIntensityDivisor = cos(d3dLight->Theta / 2) - cos(d3dLight->Phi / 2); +} + +float AsFloat(uint32_t value) { + auto v = value; + return *(float*)&v; +} + +void UpdateFixedFunctionVertexShaderState() +{ + using namespace xbox; + + // Vertex blending + // Prepare vertex blending mode variables used in transforms, below + auto VertexBlend = XboxRenderStates.GetXboxRenderState(X_D3DRS_VERTEXBLEND); + // Xbox and host D3DVERTEXBLENDFLAGS : + // D3DVBF_DISABLE = 0 : 1 matrix, 0 weights => final weight 1 + // D3DVBF_1WEIGHTS = 1 : 2 matrices, 1 weights => final weight calculated + // D3DVBF_2WEIGHTS = 3 : 3 matrices, 2 weights => final weight calculated + // D3DVBF_3WEIGHTS = 5 : 4 matrices, 3 weights => final weight calculated + // Xbox X_D3DVERTEXBLENDFLAGS : + // X_D3DVBF_2WEIGHTS2MATRICES = 2 : 2 matrices, 2 weights + // X_D3DVBF_3WEIGHTS3MATRICES = 4 : 3 matrices, 3 weights + // X_D3DVBF_4WEIGHTS4MATRICES = 6 : 4 matrices, 4 weights + // + if (VertexBlend > xbox::X_D3DVBF_4WEIGHTS4MATRICES) LOG_TEST_CASE("X_D3DRS_VERTEXBLEND out of range"); + // Calculate the number of matrices, by adding the LSB to turn (0,1,3,5) and (0,2,4,6) into (0,2,4,6); Then divide by 2 to get (0,1,2,3), and add 1 to get 1, 2, 3 or 4 matrices : + auto NrBlendMatrices = ((VertexBlend + (VertexBlend & 1)) / 2) + 1; + // Looking at the above values, 0 or the LSB of VertexBlend signals that the final weight needs to be calculated from all previous weigths (deducting them all from an initial 1) : + auto CalcLastBlendWeight = (VertexBlend == xbox::X_D3DVBF_DISABLE) || (VertexBlend & 1); + // Copy the resulting values over to shader state : + ffShaderState.Modes.VertexBlend_NrOfMatrices = (float)NrBlendMatrices; + ffShaderState.Modes.VertexBlend_CalcLastWeight = (float)CalcLastBlendWeight; + + // Transforms + // Transpose row major to column major for HLSL + D3DXMatrixTranspose((D3DXMATRIX*)&ffShaderState.Transforms.Projection, (D3DXMATRIX*)&d3d8TransformState.Transforms[X_D3DTS_PROJECTION]); + D3DXMatrixTranspose((D3DXMATRIX*)&ffShaderState.Transforms.View, (D3DXMATRIX*)&d3d8TransformState.Transforms[X_D3DTS_VIEW]); + + for (unsigned i = 0; i < 4; i++) { // TODO : Would it help to limit this to just the active texture channels? + D3DXMatrixTranspose((D3DXMATRIX*)&ffShaderState.Transforms.Texture[i], (D3DXMATRIX*)&d3d8TransformState.Transforms[X_D3DTS_TEXTURE0 + i]); + } + + for (unsigned i = 0; i < ffShaderState.Modes.VertexBlend_NrOfMatrices; i++) { + D3DXMatrixTranspose((D3DXMATRIX*)&ffShaderState.Transforms.WorldView[i], (D3DXMATRIX*)d3d8TransformState.GetWorldView(i)); + D3DXMatrixTranspose((D3DXMATRIX*)&ffShaderState.Transforms.WorldViewInverseTranspose[i], (D3DXMATRIX*)d3d8TransformState.GetWorldViewInverseTranspose(i)); + } + + // Lighting + ffShaderState.Modes.Lighting = (float)XboxRenderStates.GetXboxRenderState(X_D3DRS_LIGHTING); + ffShaderState.Modes.TwoSidedLighting = (float)XboxRenderStates.GetXboxRenderState(X_D3DRS_TWOSIDEDLIGHTING); + ffShaderState.Modes.LocalViewer = (float)XboxRenderStates.GetXboxRenderState(X_D3DRS_LOCALVIEWER); + + // Material sources + bool ColorVertex = XboxRenderStates.GetXboxRenderState(X_D3DRS_COLORVERTEX) != FALSE; + ffShaderState.Modes.AmbientMaterialSource = (float)(ColorVertex ? XboxRenderStates.GetXboxRenderState(X_D3DRS_AMBIENTMATERIALSOURCE) : D3DMCS_MATERIAL); + ffShaderState.Modes.DiffuseMaterialSource = (float)(ColorVertex ? XboxRenderStates.GetXboxRenderState(X_D3DRS_DIFFUSEMATERIALSOURCE) : D3DMCS_MATERIAL); + ffShaderState.Modes.SpecularMaterialSource = (float)(ColorVertex ? XboxRenderStates.GetXboxRenderState(X_D3DRS_SPECULARMATERIALSOURCE) : D3DMCS_MATERIAL); + ffShaderState.Modes.EmissiveMaterialSource = (float)(ColorVertex ? XboxRenderStates.GetXboxRenderState(X_D3DRS_EMISSIVEMATERIALSOURCE) : D3DMCS_MATERIAL); + ffShaderState.Modes.BackAmbientMaterialSource = (float)(ColorVertex ? XboxRenderStates.GetXboxRenderState(X_D3DRS_BACKAMBIENTMATERIALSOURCE) : D3DMCS_MATERIAL); + ffShaderState.Modes.BackDiffuseMaterialSource = (float)(ColorVertex ? XboxRenderStates.GetXboxRenderState(X_D3DRS_BACKDIFFUSEMATERIALSOURCE) : D3DMCS_MATERIAL); + ffShaderState.Modes.BackSpecularMaterialSource = (float)(ColorVertex ? XboxRenderStates.GetXboxRenderState(X_D3DRS_BACKSPECULARMATERIALSOURCE) : D3DMCS_MATERIAL); + ffShaderState.Modes.BackEmissiveMaterialSource = (float)(ColorVertex ? XboxRenderStates.GetXboxRenderState(X_D3DRS_BACKEMISSIVEMATERIALSOURCE) : D3DMCS_MATERIAL); + + // Point sprites + auto pointSize = XboxRenderStates.GetXboxRenderState(X_D3DRS_POINTSIZE); + auto pointSizeMin = XboxRenderStates.GetXboxRenderState(X_D3DRS_POINTSIZE_MIN); + auto pointSizeMax = XboxRenderStates.GetXboxRenderState(X_D3DRS_POINTSIZE_MAX); + ffShaderState.PointSprite.PointSize = *reinterpret_cast(&pointSize); + ffShaderState.PointSprite.PointSizeMin = *reinterpret_cast(&pointSizeMin); + ffShaderState.PointSprite.PointSizeMax = *reinterpret_cast(&pointSizeMax); + + bool PointScaleEnable = XboxRenderStates.GetXboxRenderState(X_D3DRS_POINTSCALEENABLE); + auto scaleA = XboxRenderStates.GetXboxRenderState(X_D3DRS_POINTSCALE_A); + auto scaleB = XboxRenderStates.GetXboxRenderState(X_D3DRS_POINTSCALE_B); + auto scaleC = XboxRenderStates.GetXboxRenderState(X_D3DRS_POINTSCALE_C); + ffShaderState.PointSprite.ScaleABC.x = PointScaleEnable ? *reinterpret_cast(&scaleA) : 1.0f; + ffShaderState.PointSprite.ScaleABC.y = PointScaleEnable ? *reinterpret_cast(&scaleB) : 0.0f; + ffShaderState.PointSprite.ScaleABC.z = PointScaleEnable ? *reinterpret_cast(&scaleC) : 0.0f; + ffShaderState.PointSprite.XboxRenderTargetHeight = PointScaleEnable ? (float)GetPixelContainerHeight(g_pXbox_RenderTarget) : 1.0f; + ffShaderState.PointSprite.RenderUpscaleFactor = g_RenderUpscaleFactor; + + // Fog + // Determine how fog depth is calculated + if (XboxRenderStates.GetXboxRenderState(X_D3DRS_FOGENABLE) && + XboxRenderStates.GetXboxRenderState(X_D3DRS_FOGTABLEMODE) != D3DFOG_NONE) { + auto proj = &ffShaderState.Transforms.Projection; + + if (XboxRenderStates.GetXboxRenderState(X_D3DRS_RANGEFOGENABLE)) { + LOG_TEST_CASE("Using RANGE fog"); + ffShaderState.Fog.DepthMode = FixedFunctionVertexShader::FOG_DEPTH_RANGE; + } + else if (proj->_14 == 0 && + proj->_24 == 0 && + proj->_34 == 0 && + proj->_44 == 1) { + LOG_TEST_CASE("Using Z fog"); + ffShaderState.Fog.DepthMode = FixedFunctionVertexShader::FOG_DEPTH_Z; + } + else { + // Test case: + // Fog sample + // JSRF (non-compliant projection matrix) + ffShaderState.Fog.DepthMode = FixedFunctionVertexShader::FOG_DEPTH_W; + } + } + else { + ffShaderState.Fog.DepthMode = FixedFunctionVertexShader::FOG_DEPTH_NONE; + } + + // Texture state + for (int i = 0; i < 4; i++) { + auto transformFlags = XboxTextureStates.Get(i, X_D3DTSS_TEXTURETRANSFORMFLAGS); + ffShaderState.TextureStates[i].TextureTransformFlagsCount = (float)(transformFlags & ~D3DTTFF_PROJECTED); + ffShaderState.TextureStates[i].TextureTransformFlagsProjected = (float)(transformFlags & D3DTTFF_PROJECTED); + + auto texCoordIndex = XboxTextureStates.Get(i, X_D3DTSS_TEXCOORDINDEX); + ffShaderState.TextureStates[i].TexCoordIndex = (float)(texCoordIndex & 0x7); // 8 coords + ffShaderState.TextureStates[i].TexCoordIndexGen = (float)(texCoordIndex >> 16); // D3DTSS_TCI flags + } + + // TexCoord component counts + extern xbox::X_VERTEXATTRIBUTEFORMAT* GetXboxVertexAttributeFormat(); // TMP glue + xbox::X_VERTEXATTRIBUTEFORMAT* pXboxVertexAttributeFormat = GetXboxVertexAttributeFormat(); + for (int i = 0; i < xbox::X_D3DTS_STAGECOUNT; i++) { + auto vertexDataFormat = pXboxVertexAttributeFormat->Slots[xbox::X_D3DVSDE_TEXCOORD0 + i].Format; + ffShaderState.TexCoordComponentCount[i] = (float)GetXboxVertexDataComponentCount(vertexDataFormat); + } + + // Update lights + auto LightAmbient = D3DXVECTOR4(0.f, 0.f, 0.f, 0.f); + for (size_t i = 0; i < ffShaderState.Lights.size(); i++) { + UpdateFixedFunctionShaderLight(d3d8LightState.EnabledLights[i], &ffShaderState.Lights[i], &LightAmbient); + } + + D3DXVECTOR4 Ambient = toVector(XboxRenderStates.GetXboxRenderState(X_D3DRS_AMBIENT)); + D3DXVECTOR4 BackAmbient = toVector(XboxRenderStates.GetXboxRenderState(X_D3DRS_BACKAMBIENT)); + + ffShaderState.TotalLightsAmbient.Front = (D3DXVECTOR3)(LightAmbient + Ambient); + ffShaderState.TotalLightsAmbient.Back = (D3DXVECTOR3)(LightAmbient + BackAmbient); + + // Misc flags + ffShaderState.Modes.NormalizeNormals = (float)XboxRenderStates.GetXboxRenderState(X_D3DRS_NORMALIZENORMALS); + + // Write fixed function state to shader constants + const int slotSize = 16; + const int fixedFunctionStateSize = (sizeof(FixedFunctionVertexShaderState) + slotSize - 1) / slotSize; + auto hRet = g_pD3DDevice->SetVertexShaderConstantF(0, (float*)&ffShaderState, fixedFunctionStateSize); + + if (FAILED(hRet)) { + CxbxKrnlCleanup("Failed to write fixed-function HLSL state"); + } +} + // ****************************************************************** // * patch: D3DDevice_EnableOverlay // ****************************************************************** @@ -6388,19 +6607,21 @@ xbox::void_xt __fastcall xbox::EMUPATCH(D3DDevice_SetRenderState_Simple) void CxbxImpl_SetTransform ( - D3DTRANSFORMSTATETYPE State, + xbox::X_D3DTRANSFORMSTATETYPE State, CONST D3DMATRIX *pMatrix ) { LOG_INIT - State = EmuXB2PC_D3DTS(State); + d3d8TransformState.SetTransform(State, pMatrix); - HRESULT hRet = g_pD3DDevice->SetTransform(State, pMatrix); + auto d3d9State = EmuXB2PC_D3DTS(State); + + HRESULT hRet = g_pD3DDevice->SetTransform(d3d9State, pMatrix); DEBUG_D3DRESULT(hRet, "g_pD3DDevice->SetTransform"); } -// MultiplyTransform can call SetTransform, nested call detection is required +// MultiplyTransform should call SetTransform, we'd like to know if it didn't // Test case: 25 to Life static thread_local uint32_t setTransformCount = 0; @@ -6411,7 +6632,7 @@ static thread_local uint32_t setTransformCount = 0; // so we cheat a bit by stashing the function body in a separate function static void D3DDevice_SetTransform_0 ( - D3DTRANSFORMSTATETYPE State, + xbox::X_D3DTRANSFORMSTATETYPE State, CONST D3DMATRIX *pMatrix ) { @@ -6420,7 +6641,7 @@ static void D3DDevice_SetTransform_0 LOG_FUNC_ARG(pMatrix) LOG_FUNC_END; - NestedPatchCounter call(setTransformCount); + setTransformCount++; __asm { // Trampoline to guest code to remove the need for a GetTransform patch @@ -6429,17 +6650,14 @@ static void D3DDevice_SetTransform_0 call XB_TRMP(D3DDevice_SetTransform_0) } - if (call.GetLevel() == 0) { - // Skip if this patch is called from MultiplyTransform - CxbxImpl_SetTransform(State, pMatrix); - } + CxbxImpl_SetTransform(State, pMatrix); } __declspec(naked) xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_SetTransform_0) ( ) { - D3DTRANSFORMSTATETYPE State; + xbox::X_D3DTRANSFORMSTATETYPE State; CONST D3DMATRIX *pMatrix; __asm { LTCG_PROLOGUE @@ -6461,7 +6679,7 @@ __declspec(naked) xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_SetTransform_0) // ****************************************************************** xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_SetTransform) ( - D3DTRANSFORMSTATETYPE State, + xbox::X_D3DTRANSFORMSTATETYPE State, CONST D3DMATRIX *pMatrix ) { @@ -6470,14 +6688,11 @@ xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_SetTransform) LOG_FUNC_ARG(pMatrix) LOG_FUNC_END; - NestedPatchCounter call(setTransformCount); + setTransformCount++; // Trampoline to guest code to remove the need for a GetTransform patch XB_TRMP(D3DDevice_SetTransform)(State, pMatrix); - if (call.GetLevel() == 0) { - // Skip if this patch is called from MultiplyTransform - CxbxImpl_SetTransform(State, pMatrix); - } + CxbxImpl_SetTransform(State, pMatrix); } // ****************************************************************** @@ -6485,7 +6700,7 @@ xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_SetTransform) // ****************************************************************** xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_MultiplyTransform) ( - D3DTRANSFORMSTATETYPE State, + xbox::X_D3DTRANSFORMSTATETYPE State, CONST D3DMATRIX *pMatrix ) { @@ -6494,15 +6709,17 @@ xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_MultiplyTransform) LOG_FUNC_ARG(pMatrix) LOG_FUNC_END; - NestedPatchCounter call(setTransformCount); + setTransformCount = 0; - // Trampoline to guest code to remove the need for a GetTransform patch + // Trampoline to guest code, which we expect to call SetTransform + // If we find a case where the trampoline doesn't call SetTransform + // (or we can't detect the call) we will need to implement this XB_TRMP(D3DDevice_MultiplyTransform)(State, pMatrix); - State = EmuXB2PC_D3DTS(State); + if (setTransformCount == 0) { + LOG_TEST_CASE("MultiplyTransform did not appear to call SetTransform"); + } - HRESULT hRet = g_pD3DDevice->MultiplyTransform(State, pMatrix); - DEBUG_D3DRESULT(hRet, "g_pD3DDevice->MultiplyTransform"); } // ****************************************************************** @@ -7118,8 +7335,6 @@ void CxbxUpdateHostTextures() void CxbxUpdateHostTextureScaling() { - extern xbox::X_VERTEXATTRIBUTEFORMAT* GetXboxVertexAttributeFormat(); // TMP glue - // Xbox works with "Linear" and "Swizzled" texture formats // Linear formats are not addressed with normalized coordinates (similar to https://www.khronos.org/opengl/wiki/Rectangle_Texture?) // We want to use normalized coordinates in our shaders, so need to be able to scale the coordinates back @@ -7141,7 +7356,7 @@ void CxbxUpdateHostTextureScaling() // Texcoord index. Just the texture stage unless fixed function mode int texCoordIndex = stage; - if (g_Xbox_VertexShader_IsFixedFunction) { + if (g_Xbox_VertexShaderMode == VertexShaderMode::FixedFunction) { // Get TEXCOORDINDEX for the current texture stage's state // Stores both the texture stage index and information for generating coordinates // See D3DTSS_TEXCOORDINDEX @@ -7191,18 +7406,10 @@ extern float* HLE_get_NV2A_vertex_constant_float4_ptr(unsigned const_index); // // remove our patches on D3DDevice_SetVertexShaderConstant (and CxbxImpl_SetVertexShaderConstant) void CxbxUpdateHostVertexShaderConstants() { - // Transfer all constants that have been flagged dirty to host - auto nv2a = g_NV2A->GetDeviceState(); - for (int i = 0; i < X_D3DVS_CONSTREG_COUNT; i++) { - if (nv2a->pgraph.vsh_constants_dirty[i]) { - nv2a->pgraph.vsh_constants_dirty[i] = false; - - float *constant_floats = HLE_get_NV2A_vertex_constant_float4_ptr(i); - // Note : If host SetVertexShaderConstantF has high overhead (unlikely), - // we could combine multiple adjacent constants into one call. - g_pD3DDevice->SetVertexShaderConstantF(i, constant_floats, 1); - } - } + // Copy all constants (as they may have been overwritten with fixed-function mode) + // Though we should only have to copy overwritten or dirty constants + float* constant_floats = HLE_get_NV2A_vertex_constant_float4_ptr(0); + g_pD3DDevice->SetVertexShaderConstantF(0, constant_floats, X_D3DVS_CONSTREG_COUNT); // FIXME our viewport constants don't match Xbox values // If we write them to pgraph constants, like we do with constants set by the title, @@ -7232,7 +7439,7 @@ void CxbxUpdateHostViewport() { aaScaleX *= g_RenderTargetUpscaleFactor; aaScaleY *= g_RenderTargetUpscaleFactor; - if (g_Xbox_VertexShader_IsFixedFunction) { + if (g_Xbox_VertexShaderMode == VertexShaderMode::FixedFunction) { // Set viewport D3DVIEWPORT hostViewport = g_Xbox_Viewport; hostViewport.X *= aaScaleX; @@ -7293,6 +7500,11 @@ void CxbxUpdateNativeD3DResources() CxbxUpdateHostVertexShaderConstants(); CxbxUpdateHostViewport(); + + // Update fixed function vertex shader state + if (g_Xbox_VertexShaderMode == VertexShaderMode::FixedFunction && g_UseFixedFunctionVertexShader) { + UpdateFixedFunctionVertexShaderState(); + } // NOTE: Order is important here // Some Texture States depend on RenderState values (Point Sprites) @@ -7886,6 +8098,8 @@ xbox::hresult_xt WINAPI xbox::EMUPATCH(D3DDevice_SetLight) XB_TRMP(D3DDevice_SetLight)(Index, pLight); + d3d8LightState.Lights[Index] = *pLight; + HRESULT hRet = g_pD3DDevice->SetLight(Index, pLight); DEBUG_D3DRESULT(hRet, "g_pD3DDevice->SetLight"); @@ -7902,6 +8116,12 @@ xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_SetMaterial) { LOG_FUNC_ONE_ARG(pMaterial); + ffShaderState.Materials[0].Ambient = toVector(pMaterial->Ambient); + ffShaderState.Materials[0].Diffuse = toVector(pMaterial->Diffuse); + ffShaderState.Materials[0].Specular = toVector(pMaterial->Specular); + ffShaderState.Materials[0].Emissive = toVector(pMaterial->Emissive); + ffShaderState.Materials[0].Power = pMaterial->Power; + HRESULT hRet = g_pD3DDevice->SetMaterial(pMaterial); DEBUG_D3DRESULT(hRet, "g_pD3DDevice->SetMaterial"); } @@ -7922,6 +8142,8 @@ xbox::hresult_xt WINAPI xbox::EMUPATCH(D3DDevice_LightEnable) XB_TRMP(D3DDevice_LightEnable)(Index, bEnable); + d3d8LightState.EnableLight(Index, bEnable); + HRESULT hRet = g_pD3DDevice->LightEnable(Index, bEnable); DEBUG_D3DRESULT(hRet, "g_pD3DDevice->LightEnable"); diff --git a/src/core/hle/D3D8/Direct3D9/Direct3D9.h b/src/core/hle/D3D8/Direct3D9/Direct3D9.h index 8f3c92459..6874a8a5f 100644 --- a/src/core/hle/D3D8/Direct3D9/Direct3D9.h +++ b/src/core/hle/D3D8/Direct3D9/Direct3D9.h @@ -1306,7 +1306,7 @@ xbox::void_xt WINAPI EMUPATCH(D3DDevice_SetRenderState_ShadowFunc) // ****************************************************************** xbox::void_xt WINAPI EMUPATCH(D3DDevice_SetTransform) ( - D3DTRANSFORMSTATETYPE State, + xbox::X_D3DTRANSFORMSTATETYPE State, CONST D3DMATRIX *pMatrix ); @@ -1317,7 +1317,7 @@ xbox::void_xt WINAPI EMUPATCH(D3DDevice_SetTransform_0)(); // ****************************************************************** xbox::void_xt WINAPI EMUPATCH(D3DDevice_MultiplyTransform) ( - D3DTRANSFORMSTATETYPE State, + xbox::X_D3DTRANSFORMSTATETYPE State, CONST D3DMATRIX *pMatrix ); diff --git a/src/core/hle/D3D8/Direct3D9/FixedFunctionVertexShader.hlsl b/src/core/hle/D3D8/Direct3D9/FixedFunctionVertexShader.hlsl new file mode 100644 index 000000000..cdab93f0f --- /dev/null +++ b/src/core/hle/D3D8/Direct3D9/FixedFunctionVertexShader.hlsl @@ -0,0 +1,518 @@ +#include "FixedFunctionVertexShaderState.hlsli" + +// Default values for vertex registers, and whether to use them +uniform float4 vRegisterDefaultValues[16] : register(c192); +uniform float4 vRegisterDefaultFlagsPacked[4] : register(c208); +static bool vRegisterDefaultFlags[16]; + +uniform FixedFunctionVertexShaderState state : register(c0); + +uniform float4 xboxTextureScale[4] : register(c214); + +#undef CXBX_ALL_TEXCOORD_INPUTS // Enable this to disable semantics in VS_INPUT (instead, we'll use an array of generic TEXCOORD's) + +// Input registers +struct VS_INPUT +{ +#ifdef CXBX_ALL_TEXCOORD_INPUTS + float4 v[16] : TEXCOORD; +#else + float4 pos : POSITION; + float4 bw : BLENDWEIGHT; + float4 color[2] : COLOR; + float4 backColor[2] : TEXCOORD4; + float4 normal : NORMAL; + float4 texcoord[4] : TEXCOORD; +#endif +}; + +// Input register indices (also known as attributes, as given in VS_INPUT.v array) +// TODO : Convert FVF codes on CPU to a vertex declaration with these standardized register indices: +// NOTE : Converting FVF vertex indices must also consider NV2A vertex attribute 'slot mapping', +// as set in NV2A_VTXFMT/NV097_SET_VERTEX_DATA_ARRAY_FORMAT! +// TODO : Rename these into SLOT_POSITION, SLOT_WEIGHT, SLOT_TEXTURE0, SLOT_TEXTURE3, etc : +static const uint position = 0; // See X_D3DFVF_XYZ / X_D3DVSDE_POSITION was float4 pos : POSITION; +static const uint weight = 1; // See X_D3DFVF_XYZB1-4 / X_D3DVSDE_BLENDWEIGHT was float4 bw : BLENDWEIGHT; +static const uint normal = 2; // See X_D3DFVF_NORMAL / X_D3DVSDE_NORMAL was float4 normal : NORMAL; // Note : Only normal.xyz is used. +static const uint diffuse = 3; // See X_D3DFVF_DIFFUSE / X_D3DVSDE_DIFFUSE was float4 color[2] : COLOR; +static const uint specular = 4; // See X_D3DFVF_SPECULAR / X_D3DVSDE_SPECULAR +static const uint fogCoord = 5; // Has no X_D3DFVF_* ! See X_D3DVSDE_FOG Note : Only fog.x is used. +static const uint pointSize = 6; // Has no X_D3DFVF_* ! See X_D3DVSDE_POINTSIZE +static const uint backDiffuse = 7; // Has no X_D3DFVF_* ! See X_D3DVSDE_BACKDIFFUSE was float4 backColor[2] : TEXCOORD4; +static const uint backSpecular = 8; // Has no X_D3DFVF_* ! See X_D3DVSDE_BACKSPECULAR +static const uint texcoord0 = 9; // See X_D3DFVF_TEX1 / X_D3DVSDE_TEXCOORD0 was float4 texcoord[4] : TEXCOORD; +static const uint texcoord1 = 10; // See X_D3DFVF_TEX2 / X_D3DVSDE_TEXCOORD1 +static const uint texcoord2 = 11; // See X_D3DFVF_TEX3 / X_D3DVSDE_TEXCOORD2 +static const uint texcoord3 = 12; // See X_D3DFVF_TEX4 / X_D3DVSDE_TEXCOORD3 +static const uint reserved0 = 13; // Has no X_D3DFVF_* / X_D3DVSDE_* +static const uint reserved1 = 14; // Has no X_D3DFVF_* / X_D3DVSDE_* +static const uint reserved2 = 15; // Has no X_D3DFVF_* / X_D3DVSDE_* + +float4 Get(const VS_INPUT xIn, const uint index) +{ +#ifdef CXBX_ALL_TEXCOORD_INPUTS + return xIn.v[index]; +#else + // switch statements inexplicably don't work here + if(index == position) return xIn.pos; + if(index == weight) return xIn.bw; + if(index == normal) return xIn.normal; + if(index == diffuse) return xIn.color[0]; + if(index == specular) return xIn.color[1]; + if(index == backDiffuse) return xIn.backColor[0]; + if(index == backSpecular) return xIn.backColor[1]; + if(index == texcoord0) return xIn.texcoord[0]; + if(index == texcoord1) return xIn.texcoord[1]; + if(index == texcoord2) return xIn.texcoord[2]; + if(index == texcoord3) return xIn.texcoord[3]; + return 1; +#endif +} + +// Output registers +struct VS_OUTPUT +{ + float4 oPos : POSITION; // Homogeneous clip space position + float4 oD0 : COLOR0; // Primary color (front-facing) + float4 oD1 : COLOR1; // Secondary color (front-facing) + float oFog : FOG; // Fog coordinate + float oPts : PSIZE; // Point size + float4 oB0 : TEXCOORD4; // Back-facing primary color + float4 oB1 : TEXCOORD5; // Back-facing secondary color + float4 oT0 : TEXCOORD0; // Texture coordinate set 0 + float4 oT1 : TEXCOORD1; // Texture coordinate set 1 + float4 oT2 : TEXCOORD2; // Texture coordinate set 2 + float4 oT3 : TEXCOORD3; // Texture coordinate set 3 +}; + +struct TransformInfo +{ + float4 Position; + float3 Normal; +}; + +static TransformInfo View; // Vertex transformed to viewspace +static TransformInfo Projection; // Vertex transformed to projection space + +static const int LIGHT_TYPE_NONE = 0; +static const int LIGHT_TYPE_POINT = 1; +static const int LIGHT_TYPE_SPOT = 2; +static const int LIGHT_TYPE_DIRECTIONAL = 3; + +// Final lighting output +struct LightingOutput +{ + TwoSidedColor Diffuse; + TwoSidedColor Specular; +}; + +// useful reference https://drivers.amd.com/misc/samples/dx9/FixedFuncShader.pdf +LightingOutput DoLight(const Light l, const float2 powers) +{ + LightingOutput o; + o.Diffuse.Front = o.Diffuse.Back = float3(0, 0, 0); + o.Specular.Front = o.Specular.Back = float3(0, 0, 0); + + float3 toLight; + float3 toLightN; + float attenuation = 1; + float spotIntensity = 1; + + if (l.Type == LIGHT_TYPE_DIRECTIONAL) { + toLight = toLightN = -l.DirectionVN; + } + else { + toLight = l.PositionV - View.Position.xyz; + toLightN = normalize(toLight); + } + + if (l.Type == LIGHT_TYPE_SPOT) { + // Spotlight factors + // https://docs.microsoft.com/en-us/windows/win32/direct3d9/light-types + float3 toVertexN = -toLightN; + float cosAlpha = dot(l.DirectionVN, toVertexN); + // I = ( cos(a) - cos(phi/2) ) / ( cos(theta/2) - cos(phi/2) ) + float spotBase = saturate((cosAlpha - l.CosHalfPhi) / l.SpotIntensityDivisor); + float spotIntensity = pow(spotBase, l.Falloff); + } + + if (l.Type == LIGHT_TYPE_POINT || l.Type == LIGHT_TYPE_SPOT) { + float lightDist = length(toLight); + + // A(Constant) + A(Linear) * dist + A(Exp) * dist^2 + attenuation = + 1 / (l.Attenuation[0] + + l.Attenuation[1] * lightDist + + l.Attenuation[2] * lightDist * lightDist); + + // Range cutoff + if (lightDist > l.Range) + attenuation = 0; + } + + // Diffuse lighting calculation + const float NdotLFront = dot(View.Normal, toLightN); + const float NdotLBack = dot(-View.Normal, toLightN); + o.Diffuse.Front = max(NdotLFront, 0) * l.Diffuse.rgb * attenuation * spotIntensity; + o.Diffuse.Back = max(NdotLBack, 0) * l.Diffuse.rgb * attenuation * spotIntensity; + + // Specular lighting calculation + float3 toViewerN = state.Modes.LocalViewer + ? normalize(-View.Position.xyz) // Strip sample + : float3(0, 0, -1); // DoA 3 character select + + // Note : if X_D3DRS_SPECULARENABLE is false then all light specular colours should have been zeroed out + // Blinn-Phong + // https://learnopengl.com/Advanced-Lighting/Advanced-Lighting + const float3 halfway = normalize(toViewerN + toLightN); + const float NdotHFront = dot(View.Normal, halfway); + const float NdotHBack = dot(-View.Normal, halfway); + + o.Specular.Front = pow(max(NdotHFront, 0), powers[0]) * l.Specular.rgb * attenuation * spotIntensity; + o.Specular.Back = pow(max(NdotHBack, 0), powers[1]) * l.Specular.rgb * attenuation * spotIntensity; + + return o; +} + +LightingOutput CalcLighting(const float2 powers) +{ + LightingOutput totalLightOutput; + totalLightOutput.Diffuse.Front = float3(0, 0, 0); + totalLightOutput.Diffuse.Back = float3(0, 0, 0); + totalLightOutput.Specular.Front = float3(0, 0, 0); + totalLightOutput.Specular.Back = float3(0, 0, 0); + + for (uint i = 0; i < 8; i++) + { + const Light currentLight = state.Lights[i]; + LightingOutput currentLightOutput; + + if (currentLight.Type != LIGHT_TYPE_NONE) { + currentLightOutput = DoLight(currentLight, powers); + + totalLightOutput.Diffuse.Front += currentLightOutput.Diffuse.Front; + totalLightOutput.Diffuse.Back += currentLightOutput.Diffuse.Back; + totalLightOutput.Specular.Front += currentLightOutput.Specular.Front; + totalLightOutput.Specular.Back += currentLightOutput.Specular.Back; + } + } + + return totalLightOutput; +} + +TransformInfo DoTransform(const float4 position, const float3 normal, const float4 blendWeights) +{ + TransformInfo output; + output.Position = float4(0, 0, 0, 0); + output.Normal = float3(0, 0, 0); + + // The number of matrices to blend (always in the range [1..4]) + const int matrices = state.Modes.VertexBlend_NrOfMatrices; + + // Initialize the final matrix its blend weight at 1, from which all preceding blend weights will be deducted : + float lastBlend = 1; + for (int i = 0; i < matrices; i++) + { + // Do we have to calculate the last blend value (never happens when there's already 4 matrices) ? + const bool bCalcFinalWeight = (state.Modes.VertexBlend_CalcLastWeight > 0) && (i == (matrices - 1)); + // Note : In case of X_D3DVBF_DISABLE, no prior weights have been deducted from lastBlend, so it will still be 1. + // The number of matrices will also be 1, which effectively turns this into non-weighted single-matrix multiplications : + const float blendWeight = bCalcFinalWeight ? lastBlend : blendWeights[i]; + // Reduce the blend weight for the final matrix : + lastBlend -= blendWeights[i]; + // Add this matrix (multiplied by its blend weight) to the output : + output.Position += mul(position, state.Transforms.WorldView[i]) * blendWeight; + output.Normal += mul(normal, (float3x3) state.Transforms.WorldViewInverseTranspose[i]) * blendWeight; + } + + return output; +} + +Material DoMaterial(const uint index, const uint diffuseReg, const uint specularReg, const VS_INPUT xIn) +{ + // Get the material from material state + Material material = state.Materials[index]; + + // Note : if (state.Modes.ColorVertex) no longer required because when disabled, CPU sets all MaterialSource's to D3DMCS_MATERIAL + { + // https://docs.microsoft.com/en-us/windows/win32/direct3d9/d3dmaterialcolorsource + static const int D3DMCS_MATERIAL = 0; + static const int D3DMCS_COLOR1 = 1; + static const int D3DMCS_COLOR2 = 2; + + // If COLORVERTEX mode, AND the desired diffuse or specular colour is defined in the vertex declaration + // Then use the vertex colour instead of the material + + if (!vRegisterDefaultFlags[diffuseReg]) { + const float4 diffuseVertexColour = Get(xIn, diffuseReg); + if (state.Modes.AmbientMaterialSource == D3DMCS_COLOR1) material.Ambient = diffuseVertexColour; + if (state.Modes.DiffuseMaterialSource == D3DMCS_COLOR1) material.Diffuse = diffuseVertexColour; + if (state.Modes.SpecularMaterialSource == D3DMCS_COLOR1) material.Specular = diffuseVertexColour; + if (state.Modes.EmissiveMaterialSource == D3DMCS_COLOR1) material.Emissive = diffuseVertexColour; + } + + if (!vRegisterDefaultFlags[specularReg]) { + const float4 specularVertexColour = Get(xIn, specularReg); + if (state.Modes.AmbientMaterialSource == D3DMCS_COLOR2) material.Ambient = specularVertexColour; + if (state.Modes.DiffuseMaterialSource == D3DMCS_COLOR2) material.Diffuse = specularVertexColour; + if (state.Modes.SpecularMaterialSource == D3DMCS_COLOR2) material.Specular = specularVertexColour; + if (state.Modes.EmissiveMaterialSource == D3DMCS_COLOR2) material.Emissive = specularVertexColour; + } + } + + return material; +} + +float DoFog(const VS_INPUT xIn) +{ + // TODO implement properly + // Until we have pixel shader HLSL we are still leaning on D3D renderstates for fogging + // So we are not doing any fog density calculations here + // http://developer.download.nvidia.com/assets/gamedev/docs/Fog2.pdf + + float fogDepth; + + if (state.Fog.DepthMode == FixedFunctionVertexShader::FOG_DEPTH_NONE) + fogDepth = Get(xIn, specular).a; // In fixed-function mode, fog is passed in the specular alpha + if (state.Fog.DepthMode == FixedFunctionVertexShader::FOG_DEPTH_RANGE) + fogDepth = length(View.Position.xyz); + if (state.Fog.DepthMode == FixedFunctionVertexShader::FOG_DEPTH_Z) + fogDepth = abs(Projection.Position.z); + if (state.Fog.DepthMode == FixedFunctionVertexShader::FOG_DEPTH_W) + fogDepth = Projection.Position.w; + + return fogDepth; +} + +float4 DoTexCoord(const uint stage, const VS_INPUT xIn) +{ + // Texture transform flags + // https://docs.microsoft.com/en-gb/windows/win32/direct3d9/d3dtexturetransformflags + static const int D3DTTFF_DISABLE = 0; + static const int D3DTTFF_COUNT1 = 1; + static const int D3DTTFF_COUNT2 = 2; + static const int D3DTTFF_COUNT3 = 3; + static const int D3DTTFF_COUNT4 = 4; + static const int D3DTTFF_PROJECTED = 256; // This is the only real flag + + // https://docs.microsoft.com/en-us/windows/win32/direct3d9/d3dtss-tci + // Pre-shifted + static const int TCI_PASSTHRU = 0; + static const int TCI_CAMERASPACENORMAL = 1; + static const int TCI_CAMERASPACEPOSITION = 2; + static const int TCI_CAMERASPACEREFLECTIONVECTOR = 3; + static const int TCI_OBJECT = 4; // Xbox + static const int TCI_SPHERE = 5; // Xbox + + const TextureState tState = state.TextureStates[stage]; + + // Extract transform flags + const int countFlag = tState.TextureTransformFlagsCount; + const bool projected = tState.TextureTransformFlagsProjected; + + // Get texture coordinates + // Coordinates are either from the vertex texcoord data + // Or generated + float4 texCoord = float4(0, 0, 0, 0); + if (tState.TexCoordIndexGen == TCI_PASSTHRU) + { + // Get from vertex data + const uint texCoordIndex = abs(tState.TexCoordIndex); // Note : abs() avoids error X3548 : in vs_3_0 uints can only be used with known - positive values, use int if possible + texCoord = Get(xIn, texcoord0+texCoordIndex); + + // Make coordinates homogenous + // For example, if a title supplies (u, v) + // We need to make transform (u, v, 1) to allow translation with a 3x3 matrix + // We'll need to get this from the current FVF or VertexDeclaration + // Test case: JSRF scrolling texture effect. + // Test case: Madagascar shadows + // Test case: Modify pixel shader sample + + // TODO move alongside the texture transformation when it stops angering the HLSL compiler + const float componentCount = state.TexCoordComponentCount[texCoordIndex]; + if (componentCount == 1) + texCoord.yzw = float3(1, 0, 0); + if (componentCount == 2) + texCoord.zw = float2(1, 0); + if (componentCount == 3) + texCoord.w = 1; + } // Generate texture coordinates + else if (tState.TexCoordIndexGen == TCI_CAMERASPACENORMAL) + texCoord = float4(View.Normal, 1); + else if (tState.TexCoordIndexGen == TCI_CAMERASPACEPOSITION) + texCoord = View.Position; + else + { + const float3 reflected = reflect(normalize(View.Position.xyz), View.Normal); + + if (tState.TexCoordIndexGen == TCI_CAMERASPACEREFLECTIONVECTOR) + texCoord.xyz = reflected; + // else if TCI_OBJECT TODO is this just model position? + else if (tState.TexCoordIndexGen == TCI_SPHERE) + { + // TODO verify + // http://www.bluevoid.com/opengl/sig99/advanced99/notes/node177.html + const float3 R = reflected; + const float p = sqrt(pow(R.x, 2) + pow(R.y, 2) + pow(R.z + 1, 2)); + texCoord.x = R.x / 2 * p + 0.5f; + texCoord.y = R.y / 2 * p + 0.5f; + } + } + + // Transform the texture coordinates if requested + if (countFlag != D3DTTFF_DISABLE) + texCoord = mul(texCoord, state.Transforms.Texture[stage]); + + // We always send four coordinates + // If we are supposed to send less than four + // then copy the last coordinate to the remaining coordinates + // For D3DTTFF_PROJECTED, the value of the *last* coordinate is important + // Test case: ProjectedTexture sample, which uses 3 coordinates + // We'll need to implement the divide when D3D stops handling it for us? + // https://docs.microsoft.com/en-us/windows/win32/direct3d9/d3dtexturetransformflags + if (projected) + { + if (countFlag == 1) + texCoord.yzw = texCoord.x; + if (countFlag == 2) + texCoord.zw = texCoord.y; + if (countFlag == 3) + texCoord.w = texCoord.z; + } + + return texCoord; +} + +// Point size for Point Sprites +// https://docs.microsoft.com/en-us/windows/win32/direct3d9/point-sprites +// Test case: Point sprite sample +float DoPointSpriteSize() +{ + const PointSprite ps = state.PointSprite; + float pointSize = ps.PointSize; + float A = ps.ScaleABC.x; + float B = ps.ScaleABC.y; + float C = ps.ScaleABC.z; + + // Note : if (ps.PointScaleEnable) not required because when disabled, CPU sets RenderTargetHeight and ScaleA to 1, and ScaleB and ScaleC to 0 + { + const float eyeDistance = length(View.Position); + const float factor = A + (B * eyeDistance) + (C * (eyeDistance * eyeDistance)); + + pointSize *= ps.XboxRenderTargetHeight * sqrt(1 / factor); + } + + return clamp(pointSize, ps.PointSizeMin, ps.PointSizeMax) * ps.RenderUpscaleFactor; +} + +VS_INPUT InitializeInputRegisters(const VS_INPUT xInput) +{ + VS_INPUT xIn; + + // Initialize input registers from the vertex buffer data + // Or use the register's default value (which can be changed by the title) + for (uint i = 0; i < 16; i++) + { + const float4 value = lerp(Get(xInput, i), vRegisterDefaultValues[i], vRegisterDefaultFlags[i]); + #ifdef CXBX_ALL_TEXCOORD_INPUTS + xIn.v[i] = value; + #else + // switch statements inexplicably don't work here + if(i == position) xIn.pos = value; + if(i == weight) xIn.bw = value; + if(i == normal) xIn.normal = value; + if(i == diffuse) xIn.color[0] = value; + if(i == specular) xIn.color[1] = value; + if(i == backDiffuse) xIn.backColor[0] = value; + if(i == backSpecular) xIn.backColor[1] = value; + if(i == texcoord0) xIn.texcoord[0] = value; + if(i == texcoord1) xIn.texcoord[1] = value; + if(i == texcoord2) xIn.texcoord[2] = value; + if(i == texcoord3) xIn.texcoord[3] = value; + #endif + } + + return xIn; +} + +VS_OUTPUT main(const VS_INPUT xInput) +{ + VS_OUTPUT xOut; + + // Unpack 16 bool flags from 4 float4 constant registers + vRegisterDefaultFlags = (bool[16]) vRegisterDefaultFlagsPacked; + + // TODO make sure this goes fast + + // Map default values + VS_INPUT xIn = InitializeInputRegisters(xInput); + + // World + View transform with vertex blending + View = DoTransform(Get(xIn, position), Get(xIn, normal).xyz, Get(xIn, weight)); + + // Optionally normalize camera-space normals + if (state.Modes.NormalizeNormals) + View.Normal = normalize(View.Normal); + + // Projection transform + Projection.Position = mul(View.Position, state.Transforms.Projection); + // Normal unused... + + // Projection transform - final position + xOut.oPos = Projection.Position; + + // Diffuse and specular for when lighting is disabled + xOut.oD0 = Get(xIn, diffuse); + xOut.oD1 = Get(xIn, specular); + xOut.oB0 = Get(xIn, backDiffuse); + xOut.oB1 = Get(xIn, backSpecular); + + // Vertex lighting + if (state.Modes.Lighting) // TODO : Remove this check by incorporating this boolean into the variables used below (set DiffuseMaterialSource to D3DMCS_COLOR1, SpecularMaterialSource to D3DMCS_COLOR2, all other to D3DMCS_MATERIAL and their colors and TotalLightsAmbient to zero, etc) + { + // Materials + Material material = DoMaterial(0, diffuse, specular, xIn); + Material backMaterial = DoMaterial(1, backDiffuse, backSpecular, xIn); + + // Compute each lighting component + const float2 powers = float2(material.Power, backMaterial.Power); + const LightingOutput lighting = CalcLighting(powers); + + // Frontface + material.Specular.rgb *= lighting.Specular.Front; + material.Diffuse.rgb *= lighting.Diffuse.Front; + material.Ambient.rgb *= state.TotalLightsAmbient.Front; + xOut.oD0 = float4(material.Diffuse.rgb + material.Ambient.rgb + material.Emissive.rgb, material.Diffuse.a); + xOut.oD1 = float4(material.Specular.rgb, 0); + + if(state.Modes.TwoSidedLighting) // TODO : Same as above, for backface lighting variables + { + // Backface + backMaterial.Specular.rgb *= lighting.Specular.Back; + backMaterial.Diffuse.rgb *= lighting.Diffuse.Back; + backMaterial.Ambient.rgb *= state.TotalLightsAmbient.Back; + xOut.oB0 = float4(backMaterial.Diffuse.rgb + backMaterial.Ambient.rgb + backMaterial.Emissive.rgb, backMaterial.Diffuse.a); + xOut.oB1 = float4(backMaterial.Specular.rgb, 0); + } + } + + // Colour + xOut.oD0 = saturate(xOut.oD0); + xOut.oD1 = saturate(xOut.oD1); + xOut.oB0 = saturate(xOut.oB0); + xOut.oB1 = saturate(xOut.oB1); + + // Fog + xOut.oFog = DoFog(xIn); + + // Point Sprite + xOut.oPts = DoPointSpriteSize(); + + // Texture coordinates + xOut.oT0 = DoTexCoord(0, xIn) / xboxTextureScale[0]; + xOut.oT1 = DoTexCoord(1, xIn) / xboxTextureScale[1]; + xOut.oT2 = DoTexCoord(2, xIn) / xboxTextureScale[2]; + xOut.oT3 = DoTexCoord(3, xIn) / xboxTextureScale[3]; + + return xOut; +} diff --git a/src/core/hle/D3D8/Direct3D9/FixedFunctionVertexShaderState.hlsli b/src/core/hle/D3D8/Direct3D9/FixedFunctionVertexShaderState.hlsli new file mode 100644 index 000000000..b909b97e4 --- /dev/null +++ b/src/core/hle/D3D8/Direct3D9/FixedFunctionVertexShaderState.hlsli @@ -0,0 +1,157 @@ +// C++ / HLSL shared state block for fixed function support +#ifdef __cplusplus +#pragma once + +#include +#include // for D3DFORMAT, D3DLIGHT9, etc +#include // for D3DXVECTOR4, etc +#include + +#define float4x4 D3DMATRIX +#define float4 D3DXVECTOR4 +#define float3 D3DVECTOR +#define float2 D3DXVECTOR2 +#define arr(name, type, length) std::array name + +#else +// HLSL +#define arr(name, type, length) type name[length] +#define alignas(x) +#define const static +#endif // __cplusplus + +namespace FixedFunctionVertexShader { + // Fog depth is taken from the vertex, rather than generated + const float FOG_DEPTH_NONE = 0; + // Fog depth is the output Z coordinate + const float FOG_DEPTH_Z = 1; + // Fog depth is based on the output W coordinate (1 / W) + const float FOG_DEPTH_W = 2; + // Fog depth is based distance of the vertex from the eye position + const float FOG_DEPTH_RANGE = 3; +} + +// Shared HLSL structures +// Taking care with packing rules +// In VS_3_0, packing works in mysterious ways +// * Structs inside arrays are not packed +// * Floats can't be packed at all (?) +// We don't get documented packing until vs_4_0 + +struct Transforms { + float4x4 View; // 0 + float4x4 Projection; // 1 + arr(Texture, float4x4, 4); // 2, 3, 4, 5 + // World matrices are 6, 7, 8, 9 + // But we use combined WorldView matrices in the shader + arr(WorldView, float4x4, 4); + arr(WorldViewInverseTranspose, float4x4, 4); +}; + +// See D3DLIGHT +struct Light { + // TODO in vs_4_0+ when floats are packable + // Change colour values to float3 + // And put something useful in the alpha slot + float4 Diffuse; + float4 Specular; + + // Viewspace light position + alignas(16) float3 PositionV; + alignas(16) float Range; + + // Viewspace light direction (normalized) + alignas(16) float3 DirectionVN; + alignas(16) float Type; // 1=Point, 2=Spot, 3=Directional + + alignas(16) float3 Attenuation; + alignas(16) float Falloff; + + alignas(16) float CosHalfPhi; + // cos(theta/2) - cos(phi/2) + alignas(16) float SpotIntensityDivisor; +}; + +struct Material { + float4 Diffuse; + float4 Ambient; + float4 Specular; + float4 Emissive; + + alignas(16) float Power; +}; + +struct Modes { + alignas(16) float AmbientMaterialSource; + alignas(16) float DiffuseMaterialSource; + alignas(16) float SpecularMaterialSource; + alignas(16) float EmissiveMaterialSource; + + alignas(16) float BackAmbientMaterialSource; + alignas(16) float BackDiffuseMaterialSource; + alignas(16) float BackSpecularMaterialSource; + alignas(16) float BackEmissiveMaterialSource; + + alignas(16) float Lighting; + alignas(16) float TwoSidedLighting; +// alignas(16) float SpecularEnable; + alignas(16) float LocalViewer; + +/// alignas(16) float ColorVertex; + alignas(16) float VertexBlend_NrOfMatrices; + alignas(16) float VertexBlend_CalcLastWeight; // Could be a bool in higer shader models + alignas(16) float NormalizeNormals; +}; + +struct PointSprite { + alignas(16) float PointSize; + alignas(16) float PointSizeMin; + alignas(16) float PointSizeMax; +// alignas(16) float PointScaleEnable; + alignas(16) float XboxRenderTargetHeight; + alignas(16) float3 ScaleABC; + alignas(16) float RenderUpscaleFactor; +}; + +struct TextureState { + alignas(16) float TextureTransformFlagsCount; + alignas(16) float TextureTransformFlagsProjected; + alignas(16) float TexCoordIndex; + alignas(16) float TexCoordIndexGen; +}; + +struct Fog { + alignas(16) float DepthMode; +}; + +// Vertex lighting +// Both frontface and backface lighting can be calculated +struct TwoSidedColor +{ + alignas(16) float3 Front; + alignas(16) float3 Back; +}; + +struct FixedFunctionVertexShaderState { + alignas(16) Transforms Transforms; + alignas(16) arr(Lights, Light, 8); + alignas(16) TwoSidedColor TotalLightsAmbient; + alignas(16) arr(Materials, Material, 2); + alignas(16) Modes Modes; + alignas(16) Fog Fog; + alignas(16) arr(TextureStates, TextureState, 4); + alignas(16) PointSprite PointSprite; + alignas(16) float4 TexCoordComponentCount; +}; + +#ifdef __cplusplus +#undef float4x4 +#undef float4 +#undef float3 +#undef float2 +#undef arr +#else // HLSL +#undef arr +#undef alignas +#undef const +#endif // __cplusplus diff --git a/src/core/hle/D3D8/Direct3D9/RenderStates.cpp b/src/core/hle/D3D8/Direct3D9/RenderStates.cpp index 9183e7df1..9ecd3f4d9 100644 --- a/src/core/hle/D3D8/Direct3D9/RenderStates.cpp +++ b/src/core/hle/D3D8/Direct3D9/RenderStates.cpp @@ -396,14 +396,13 @@ void XboxRenderStateConverter::ApplyComplexRenderState(uint32_t State, uint32_t switch (State) { case xbox::X_D3DRS_VERTEXBLEND: - // convert from Xbox direct3d to PC direct3d enumeration - if (Value <= 1) { - Value = Value; - } else if (Value == 3) { - Value = 2; - } else if (Value == 5) { - Value = 3; - } else { + // convert from Xbox X_D3DVERTEXBLENDFLAGS to PC D3DVERTEXBLENDFLAGS enumeration + switch (Value) { + case xbox::X_D3DVBF_DISABLE: Value = D3DVBF_DISABLE; break; + case xbox::X_D3DVBF_1WEIGHTS: Value = D3DVBF_1WEIGHTS; break; + case xbox::X_D3DVBF_2WEIGHTS: Value = D3DVBF_2WEIGHTS; break; + case xbox::X_D3DVBF_3WEIGHTS: Value = D3DVBF_3WEIGHTS; break; + default: LOG_TEST_CASE("Unsupported D3DVERTEXBLENDFLAGS (%d)"); return; } diff --git a/src/core/hle/D3D8/Direct3D9/VertexShader.cpp b/src/core/hle/D3D8/Direct3D9/VertexShader.cpp index 0f8753914..d6b8d1047 100644 --- a/src/core/hle/D3D8/Direct3D9/VertexShader.cpp +++ b/src/core/hle/D3D8/Direct3D9/VertexShader.cpp @@ -4,6 +4,7 @@ #include "core\kernel\init\CxbxKrnl.h" #include "core\kernel\support\Emu.h" +#include #include extern const char* g_vs_model = vs_model_2_a; @@ -266,24 +267,25 @@ HRESULT CompileHlsl(const std::string& hlsl, ID3DBlob** ppHostShader, const char if (FAILED(hRet)) { LOG_TEST_CASE("Couldn't assemble vertex shader"); - //EmuLog(LOG_LEVEL::WARNING, "Couldn't assemble recompiled vertex shader"); } } // Determine the log level if (pErrors) { - // Log HLSL compiler errors + // Log errors from the initial compilation EmuLog(hlslErrorLogLevel, "%s", (char*)(pErrors->GetBufferPointer())); pErrors->Release(); pErrors = nullptr; - if (pErrorsCompatibility != nullptr) { - pErrorsCompatibility->Release(); - pErrorsCompatibility = nullptr; - } } - LOG_CHECK_ENABLED(LOG_LEVEL::DEBUG) - if (g_bPrintfOn) + // Failure to recompile in compatibility mode ignored for now + if (pErrorsCompatibility != nullptr) { + pErrorsCompatibility->Release(); + pErrorsCompatibility = nullptr; + } + + LOG_CHECK_ENABLED(LOG_LEVEL::DEBUG) { + if (g_bPrintfOn) { if (!FAILED(hRet)) { // Log disassembly hRet = D3DDisassemble( @@ -298,6 +300,8 @@ HRESULT CompileHlsl(const std::string& hlsl, ID3DBlob** ppHostShader, const char pErrors->Release(); } } + } + } return hRet; } @@ -329,6 +333,31 @@ extern HRESULT EmuCompileShader return CompileHlsl(hlsl_str, ppHostShader, "CxbxVertexShaderTemplate.hlsl"); } +extern void EmuCompileFixedFunction(ID3DBlob** ppHostShader) +{ + static ID3DBlob* pShader = nullptr; + + // TODO does this need to be thread safe? + if (pShader == nullptr) { + // Determine the filename and directory for the fixed function shader + auto hlslDir = std::filesystem::path(szFilePath_CxbxReloaded_Exe) + .parent_path() + .append("hlsl"); + + auto sourceFile = hlslDir.append("FixedFunctionVertexShader.hlsl").string(); + + // Load the shader into a string + std::ifstream hlslStream(sourceFile); + std::stringstream hlsl; + hlsl << hlslStream.rdbuf(); + + // Compile the shader + CompileHlsl(hlsl.str(), &pShader, sourceFile.c_str()); + } + + *ppHostShader = pShader; +}; + static ID3DBlob* pPassthroughShader = nullptr; extern HRESULT EmuCompileXboxPassthrough(ID3DBlob** ppHostShader) diff --git a/src/core/hle/D3D8/Direct3D9/VertexShader.h b/src/core/hle/D3D8/Direct3D9/VertexShader.h index e2f6ca24b..29d8cc57c 100644 --- a/src/core/hle/D3D8/Direct3D9/VertexShader.h +++ b/src/core/hle/D3D8/Direct3D9/VertexShader.h @@ -3,6 +3,7 @@ #define DIRECT3D9VERTEXSHADER_H #include "core\hle\D3D8\XbVertexShader.h" +#include "FixedFunctionVertexShaderState.hlsli" enum class ShaderType { Empty = 0, @@ -20,6 +21,8 @@ extern HRESULT EmuCompileShader ID3DBlob** ppHostShader ); +extern void EmuCompileFixedFunction(ID3DBlob** ppHostShader); + extern HRESULT EmuCompileXboxPassthrough(ID3DBlob** ppHostShader); #endif diff --git a/src/core/hle/D3D8/FixedFunctionState.cpp b/src/core/hle/D3D8/FixedFunctionState.cpp new file mode 100644 index 000000000..c11f76fa2 --- /dev/null +++ b/src/core/hle/D3D8/FixedFunctionState.cpp @@ -0,0 +1,157 @@ +#define LOG_PREFIX CXBXR_MODULE::D3D8 + +#include "FixedFunctionState.h" +#include "Logging.h" +#include "core/kernel/init/CxbxKrnl.h" + +D3DCOLORVALUE colorValue(float r, float g, float b, float a) { + auto value = D3DCOLORVALUE(); + value.r = r; + value.g = g; + value.b = b; + value.a = a; + return value; +} + +D3DVECTOR toVector(float x, float y, float z) { + auto value = D3DVECTOR(); + value.x = x; + value.y = y; + value.z = z; + return value; +} + +D3D8LightState::D3D8LightState() { + // Define the default light + // When unset lights are enabled, they're set to the default light + auto defaultLight = xbox::X_D3DLIGHT8(); + defaultLight.Type = D3DLIGHT_DIRECTIONAL; + defaultLight.Diffuse = colorValue(1, 1, 1, 0); + defaultLight.Specular = colorValue(0, 0, 0, 0); + defaultLight.Ambient = colorValue(0, 0, 0, 0); + defaultLight.Position = toVector(0, 0, 0); + defaultLight.Direction = toVector(0, 0, 1); + defaultLight.Range = 0; + defaultLight.Falloff = 0; + defaultLight.Attenuation0 = 0; + defaultLight.Attenuation1 = 0; + defaultLight.Attenuation2 = 0; + defaultLight.Theta = 0; + defaultLight.Phi = 0; + + // We'll just preset every light to the default light + Lights.fill(defaultLight); + EnabledLights.fill(-1); +} + +void D3D8LightState::EnableLight(uint32_t index, bool enable) { + // Since Xbox only supports 8 lights, we keep track of the 8 most recently enabled lights + // Lights are ordered oldest to newest, with disabled lights at the end + + // Check to see if the light is already enabled + for (size_t i = 0; i < EnabledLightCount; i++) { + + // If the light is already in the enabled lights + if (EnabledLights[i] == index) { + // Either way we move this light to the end + std::rotate(std::begin(EnabledLights) + i, std::begin(EnabledLights) + i + 1, std::begin(EnabledLights) + EnabledLightCount); + + if (enable) { + // Don't need to do anything + EmuLog(LOG_LEVEL::INFO, "Enabled light %d but it was already enabled", index); + } + else { + // Disable the light + EnabledLights[EnabledLightCount - 1] = -1; + EnabledLightCount--; + } + return; + } + } + + if (enable) { + // The light was not in the enabled lights. Let's add it + if (EnabledLightCount < EnabledLights.size()) { + EnabledLights[EnabledLightCount] = index; // add it to the end + EnabledLightCount++; + } + else { + // Replace the oldest element and move to end + EmuLog(LOG_LEVEL::INFO, "Can't enable any more lights. Replacing the oldest light %i", EnabledLights[0]); + EnabledLights[0] = index; + std::rotate(std::begin(EnabledLights), std::begin(EnabledLights) + 1, std::end(EnabledLights)); + } + } + else { + EmuLog(LOG_LEVEL::INFO, "Could not disable light %d because it wasn't enabled", index); + } +} + +D3D8TransformState::D3D8TransformState() { + D3DMATRIX identity; + D3DXMatrixIdentity((D3DXMATRIX*)&identity); + + this->Transforms.fill(identity); + this->WorldView.fill(identity); + this->WorldViewInverseTranspose.fill(identity); + bWorldViewDirty.fill(true); +} + +void D3D8TransformState::SetTransform(xbox::X_D3DTRANSFORMSTATETYPE state, const D3DMATRIX* pMatrix) +{ + using namespace xbox; + + LOG_INIT + + if (state >= this->Transforms.size()) { + LOG_TEST_CASE("Transform state was not in expected range"); + return; + } + + // Update transform state + this->Transforms[state] = *pMatrix; + + if (state == X_D3DTS_VIEW) { + bWorldViewDirty.fill(true); + } + if ((X_D3DTS_WORLD <= state) && (state <= X_D3DTS_WORLD3)) { + bWorldViewDirty[state - X_D3DTS_WORLD] = true; + } +} + +void D3D8TransformState::RecalculateDependentMatrices(unsigned i) +{ + auto worldState = xbox::X_D3DTS_WORLD + i; + D3DXMATRIX worldView; + D3DXMatrixMultiply(&worldView, (D3DXMATRIX*)&Transforms[worldState], (D3DXMATRIX*)&Transforms[xbox::X_D3DTS_VIEW]); + this->WorldView[i] = worldView; + + D3DXMATRIX worldViewInverseTranspose; + D3DXMatrixInverse(&worldViewInverseTranspose, nullptr, &worldView); + D3DXMatrixTranspose(&worldViewInverseTranspose, &worldViewInverseTranspose); + this->WorldViewInverseTranspose[i] = worldViewInverseTranspose; +} + +D3DMATRIX* D3D8TransformState::GetWorldView(unsigned i) +{ + assert(i < 4); + + if (bWorldViewDirty[i]) { + RecalculateDependentMatrices(i); + bWorldViewDirty[i] = false; + } + + return &WorldView[i]; +} + +D3DMATRIX* D3D8TransformState::GetWorldViewInverseTranspose(unsigned i) +{ + assert(i < 4); + + if (bWorldViewDirty[i]) { + RecalculateDependentMatrices(i); + bWorldViewDirty[i] = false; + } + + return &WorldViewInverseTranspose[i]; +} diff --git a/src/core/hle/D3D8/FixedFunctionState.h b/src/core/hle/D3D8/FixedFunctionState.h new file mode 100644 index 000000000..d2dfee9e2 --- /dev/null +++ b/src/core/hle/D3D8/FixedFunctionState.h @@ -0,0 +1,46 @@ +#ifndef FIXEDFUNCTIONSTATE_H +#define FIXEDFUNCTIONSTATE_H + +#include "XbD3D8Types.h" +#include + +class D3D8LightState { +public: + std::array Lights; + + // The indices of last 8 enabled lights + // From least recently to most recently enabled + // -1 represents empty light slots + // which always appear after enabled lights + std::array EnabledLights; + + // The number of enabled lights + uint32_t EnabledLightCount = 0; + + D3D8LightState(); + + // Enable a light + void EnableLight(uint32_t index, bool enable); +}; + +class D3D8TransformState { +public: + D3D8TransformState(); + void SetTransform(xbox::X_D3DTRANSFORMSTATETYPE state, const D3DMATRIX* pMatrix); + D3DMATRIX* GetWorldView(unsigned i); + D3DMATRIX* GetWorldViewInverseTranspose(unsigned i); + + // The transforms set by the Xbox title + std::array Transforms; + +private: + void RecalculateDependentMatrices(unsigned i); + + std::array bWorldViewDirty; + // Combines world/view matrices + std::array WorldView; + // World/view inverse transpose for lighting calculations + std::array WorldViewInverseTranspose; +}; + +#endif diff --git a/src/core/hle/D3D8/XbConvert.h b/src/core/hle/D3D8/XbConvert.h index 08deee031..57cd45c72 100644 --- a/src/core/hle/D3D8/XbConvert.h +++ b/src/core/hle/D3D8/XbConvert.h @@ -94,25 +94,25 @@ else //*/ // convert from xbox to pc texture transform state types -inline D3DTRANSFORMSTATETYPE EmuXB2PC_D3DTS(D3DTRANSFORMSTATETYPE State) +inline D3DTRANSFORMSTATETYPE EmuXB2PC_D3DTS(xbox::X_D3DTRANSFORMSTATETYPE State) { // Handle Xbox -> D3D State mapping switch (State) { - case 0: return (D3DTRANSFORMSTATETYPE)D3DTS_VIEW; - case 1: return (D3DTRANSFORMSTATETYPE)D3DTS_PROJECTION; - case 2: return (D3DTRANSFORMSTATETYPE)D3DTS_TEXTURE0; - case 3: return (D3DTRANSFORMSTATETYPE)D3DTS_TEXTURE1; - case 4: return (D3DTRANSFORMSTATETYPE)D3DTS_TEXTURE2; - case 5: return (D3DTRANSFORMSTATETYPE)D3DTS_TEXTURE3; - case 6: return (D3DTRANSFORMSTATETYPE)D3DTS_WORLD; - case 7: return (D3DTRANSFORMSTATETYPE)D3DTS_WORLD1; - case 8: return (D3DTRANSFORMSTATETYPE)D3DTS_WORLD2; - case 9: return (D3DTRANSFORMSTATETYPE)D3DTS_WORLD3; + case xbox::X_D3DTS_VIEW: return D3DTS_VIEW; + case xbox::X_D3DTS_PROJECTION: return D3DTS_PROJECTION; + case xbox::X_D3DTS_TEXTURE0: return D3DTS_TEXTURE0; + case xbox::X_D3DTS_TEXTURE1: return D3DTS_TEXTURE1; + case xbox::X_D3DTS_TEXTURE2: return D3DTS_TEXTURE2; + case xbox::X_D3DTS_TEXTURE3: return D3DTS_TEXTURE3; + case xbox::X_D3DTS_WORLD: return D3DTS_WORLD; + case xbox::X_D3DTS_WORLD1: return D3DTS_WORLD1; + case xbox::X_D3DTS_WORLD2: return D3DTS_WORLD2; + case xbox::X_D3DTS_WORLD3: return D3DTS_WORLD3; } // Handle World Matrix offsets if (State >= 256 && State <= 511) { - return D3DTS_WORLDMATRIX(State - 256); + return D3DTS_WORLDMATRIX(State - 256); } CxbxKrnlCleanupEx(LOG_PREFIX_D3DCVT, "Unknown Transform State Type (%d)", State); @@ -1796,7 +1796,7 @@ typedef enum _TXBType { xtD3DTEXTUREOP, // Used for TextureStageState X_D3DTSS_COLOROP and X_D3DTSS_ALPHAOP xtD3DTEXTURESTAGESTATETYPE, xtD3DTRANSFORMSTATETYPE, - xtD3DVERTEXBLENDFLAGS, + xtD3DVERTEXBLENDFLAGS, // Used for X_D3DRS_VERTEXBLEND xtD3DVSDE, xtD3DWRAP, xtDWORD, diff --git a/src/core/hle/D3D8/XbD3D8Types.h b/src/core/hle/D3D8/XbD3D8Types.h index 6c062f7c0..c00a1fbbe 100644 --- a/src/core/hle/D3D8/XbD3D8Types.h +++ b/src/core/hle/D3D8/XbD3D8Types.h @@ -1007,6 +1007,18 @@ constexpr DWORD X_D3DCOLORWRITEENABLE_ALL = 0x01010101; // Xbox ext. // deferred texture stage state "unknown" flag #define X_D3DTSS_UNK 0x7fffffff +typedef enum _D3DVERTEXBLENDFLAGS +{ + X_D3DVBF_DISABLE = 0, // 1 matrix, 0 weights => final weight effectively 1 (Disable vertex blending) + X_D3DVBF_1WEIGHTS = 1, // 2 matrices, 1 weights => final weight calculated + X_D3DVBF_2WEIGHTS = 3, // 3 matrices, 2 weights => final weight calculated + X_D3DVBF_3WEIGHTS = 5, // 4 matrices, 3 weights => final weight calculated + X_D3DVBF_2WEIGHTS2MATRICES = 2, // 2 matrices, 2 weights (Xbox ext.) + X_D3DVBF_3WEIGHTS3MATRICES = 4, // 3 matrices, 3 weights (Xbox ext.) + X_D3DVBF_4WEIGHTS4MATRICES = 6, // 4 matrices, 4 weights (Xbox ext.) + X_D3DVBF_FORCE_DWORD = 0x7fffffff +} X_D3DVERTEXBLENDFLAGS; + typedef DWORD X_VERTEXSHADERCONSTANTMODE; // Xbox vertex shader constant modes @@ -1305,6 +1317,22 @@ typedef enum _X_D3DVSD_TOKENTYPE #define X_D3DTSS_TCI_OBJECT 0x00040000 // Warning! Collides with host Direct3D 9 D3DTSS_TCI_SPHEREMAP #define X_D3DTSS_TCI_SPHEREMAP 0x00050000 +enum X_D3DTRANSFORMSTATETYPE { + X_D3DTS_VIEW = 0, + X_D3DTS_PROJECTION = 1, + X_D3DTS_TEXTURE0 = 2, + X_D3DTS_TEXTURE1 = 3, + X_D3DTS_TEXTURE2 = 4, + X_D3DTS_TEXTURE3 = 5, + X_D3DTS_WORLD = 6, + X_D3DTS_WORLD1 = 7, + X_D3DTS_WORLD2 = 8, + X_D3DTS_WORLD3 = 9, + + X_D3DTS_MAX = 10, + X_D3DTS_FORCE_DWORD = 0x7FFFFFFF, +}; + typedef DWORD NV2AMETHOD; // diff --git a/src/core/hle/D3D8/XbVertexBuffer.cpp b/src/core/hle/D3D8/XbVertexBuffer.cpp index e67fdf6ca..23ed872d7 100644 --- a/src/core/hle/D3D8/XbVertexBuffer.cpp +++ b/src/core/hle/D3D8/XbVertexBuffer.cpp @@ -844,7 +844,7 @@ void CxbxImpl_End() // Arrange for g_InlineVertexBuffer_AttributeFormat to be returned in CxbxGetVertexDeclaration, // so that our above composed declaration will be used for the next draw : g_InlineVertexBuffer_DeclarationOverride = true; - // Note, that g_Xbox_VertexShader_IsFixedFunction should be left untouched, + // Note, that g_Xbox_VertexShaderMode should be left untouched, // because except for the declaration override, the Xbox shader (either FVF // or a program, or even passthrough shaders) should still be in effect! diff --git a/src/core/hle/D3D8/XbVertexShader.cpp b/src/core/hle/D3D8/XbVertexShader.cpp index b884a9ffa..d9918f1ae 100644 --- a/src/core/hle/D3D8/XbVertexShader.cpp +++ b/src/core/hle/D3D8/XbVertexShader.cpp @@ -58,9 +58,9 @@ extern XboxRenderStateConverter XboxRenderStates; // Declared in Direct3D9.cpp xbox::X_STREAMINPUT g_Xbox_SetVertexShaderInput_Data[X_VSH_MAX_STREAMS] = { 0 }; // Active when g_Xbox_SetVertexShaderInput_Count > 0 xbox::X_VERTEXATTRIBUTEFORMAT g_Xbox_SetVertexShaderInput_Attributes = { 0 }; // Read by GetXboxVertexAttributes when g_Xbox_SetVertexShaderInput_Count > 0 -// Variables set by [D3DDevice|CxbxImpl]_SetVertexShader() and [D3DDevice|CxbxImpl]_SelectVertexShader() : - bool g_Xbox_VertexShader_IsFixedFunction = true; - bool g_Xbox_VertexShader_IsPassthrough = false; +VertexShaderMode g_Xbox_VertexShaderMode = VertexShaderMode::FixedFunction; +bool g_UseFixedFunctionVertexShader = true; + xbox::dword_xt g_Xbox_VertexShader_Handle = 0; #ifdef CXBX_USE_GLOBAL_VERTEXSHADER_POINTER // TODO : Would this be more accurate / simpler? xbox::X_D3DVertexShader *g_Xbox_VertexShader_Ptr = nullptr; @@ -323,6 +323,23 @@ static DWORD* CxbxGetVertexShaderTokens(xbox::X_D3DVertexShader* pXboxVertexShad return &pXboxVertexShader->ProgramAndConstants[0]; } +int GetXboxVertexDataComponentCount(int d3dvsdt) { + using namespace xbox; + switch (d3dvsdt) { + case X_D3DVSDT_NORMPACKED3: + return 3; + case X_D3DVSDT_FLOAT2H: + LOG_TEST_CASE("Attempting to use component count for X_D3DVSDT_FLOAT2H, which uses an odd (value, value, 0, value) layout"); + // This is a bit of an odd case. Will call it 4 since it writes a value to the 4th component... + return 4; + default: + // Most data types have a representation consistent with the number of components + const int countMask = 0x7; + const int countShift = 4; + return (d3dvsdt >> countShift) & countMask; + } +} + extern bool g_InlineVertexBuffer_DeclarationOverride; // TMP glue extern xbox::X_VERTEXATTRIBUTEFORMAT g_InlineVertexBuffer_AttributeFormat; // TMP glue @@ -1075,7 +1092,9 @@ VertexDeclarationKey GetXboxVertexAttributesKey(xbox::X_VERTEXATTRIBUTEFORMAT* p auto attributeHash = ComputeHash((void*)pXboxVertexAttributeFormat, sizeof(xbox::X_VERTEXATTRIBUTEFORMAT)); // For now, we use different declarations depending on if the fixed function pipeline // is in use, even if the attributes are the same - return attributeHash ^ (VertexDeclarationKey)g_Xbox_VertexShader_IsFixedFunction; + return g_Xbox_VertexShaderMode == VertexShaderMode::FixedFunction + ? attributeHash + : attributeHash ^ 1; } std::unordered_map g_CxbxVertexDeclarations; @@ -1122,18 +1141,27 @@ void CxbxUpdateHostVertexShader() LOG_INIT; // Allows use of DEBUG_D3DRESULT - if (g_Xbox_VertexShader_IsFixedFunction) { - HRESULT hRet = g_pD3DDevice->SetVertexShader(nullptr); - DEBUG_D3DRESULT(hRet, "g_pD3DDevice->SetVertexShader"); - // TODO : Once available, start using host Fixed Function HLSL shader - // instead of using deprecated host fixed function (by setting a null - // vertex shader). - // As for the required host vertex declaration : - // CxbxUpdateHostVertexDeclaration already been - // called, which sets host vertex declaration based on the - // declaration that XboxVertexShaderFromFVF generated. + if (g_Xbox_VertexShaderMode == VertexShaderMode::FixedFunction) { + IDirect3DVertexShader* fixedFunctionShader = nullptr; + HRESULT hRet; + + if (g_UseFixedFunctionVertexShader) { + static IDirect3DVertexShader* ffHlsl = nullptr; + if (ffHlsl == nullptr) { + ID3DBlob* pBlob = nullptr; + EmuCompileFixedFunction(&pBlob); + if (pBlob) { + hRet = g_pD3DDevice->CreateVertexShader((DWORD*)pBlob->GetBufferPointer(), &ffHlsl); + if (FAILED(hRet)) CxbxKrnlCleanup("Failed to create fixed-function shader"); + } + } + fixedFunctionShader = ffHlsl; + } + + hRet = g_pD3DDevice->SetVertexShader(fixedFunctionShader); + if (FAILED(hRet)) CxbxKrnlCleanup("Failed to set fixed-function shader"); } - else if (g_Xbox_VertexShader_IsPassthrough && g_bUsePassthroughHLSL) { + else if (g_Xbox_VertexShaderMode == VertexShaderMode::Passthrough && g_bUsePassthroughHLSL) { if (passthroughshader == nullptr) { ID3DBlob* pBlob = nullptr; EmuCompileXboxPassthrough(&pBlob); @@ -1247,7 +1275,7 @@ CxbxVertexDeclaration* CxbxGetVertexDeclaration() // Convert Xbox vertex attributes towards host Direct3D 9 vertex element D3DVERTEXELEMENT* pRecompiledVertexElements = EmuRecompileVshDeclaration( pXboxVertexAttributeFormat, - g_Xbox_VertexShader_IsFixedFunction, + g_Xbox_VertexShaderMode == VertexShaderMode::FixedFunction, pCxbxVertexDeclaration); // Create the vertex declaration @@ -1337,6 +1365,8 @@ void CxbxImpl_SelectVertexShader(DWORD Handle, DWORD Address) // Either way, the given address slot is selected as the start of the current vertex shader program g_Xbox_VertexShader_FunctionSlots_StartAddress = Address; + g_Xbox_VertexShaderMode = VertexShaderMode::ShaderProgram; + if (Handle) { if (!VshHandleIsVertexShader(Handle)) LOG_TEST_CASE("Non-zero handle must be a VertexShader!"); @@ -1345,8 +1375,6 @@ void CxbxImpl_SelectVertexShader(DWORD Handle, DWORD Address) g_Xbox_VertexShader_Ptr = VshHandleToXboxVertexShader(Handle); #endif g_Xbox_VertexShader_Handle = Handle; - g_Xbox_VertexShader_IsFixedFunction = false; - g_Xbox_VertexShader_IsPassthrough = false; } } @@ -1417,6 +1445,37 @@ void CxbxImpl_LoadVertexShader(DWORD Handle, DWORD Address) } } +// Set default values for attributes missing from vertex declaration +void SetFixedFunctionDefaultVertexAttributes(DWORD vshFlags) { + // Test case: Mechassault (skybox) + // Test case: KOTOR (overlay) + auto decl = CxbxGetVertexDeclaration(); + for (int i = 0; i < xbox::X_D3DVSDE_TEXCOORD3; i++) { + if (decl->vRegisterInDeclaration[i]) { + continue; // only reset missing attributes + } + + const float white[4] = { 1, 1, 1, 1 }; + const float black[4] = { 0, 0, 0, 0 }; + const float unset[4] = { 0, 0, 0, 1 }; + const float* value = unset; + + // Account for flags that override this reset behaviour + if (i == xbox::X_D3DVSDE_DIFFUSE && !(vshFlags & X_VERTEXSHADER_FLAG_HASDIFFUSE) || + i == xbox::X_D3DVSDE_BACKDIFFUSE && !(vshFlags & X_VERTEXSHADER_FLAG_HASBACKDIFFUSE)) { + value = white; + } + else if (i == xbox::X_D3DVSDE_SPECULAR && !(vshFlags & X_VERTEXSHADER_FLAG_HASSPECULAR) || + i == xbox::X_D3DVSDE_BACKSPECULAR && !(vshFlags & X_VERTEXSHADER_FLAG_HASBACKSPECULAR)) { + value = black; + } + + // Note : We avoid calling CxbxImpl_SetVertexData4f here, as that would + // start populating g_InlineVertexBuffer_Table, which is not our intent here. + CxbxSetVertexAttribute(i, value[0], value[1], value[2], value[3]); + } +} + void CxbxImpl_SetVertexShader(DWORD Handle) { LOG_INIT; // Allows use of DEBUG_D3DRESULT @@ -1433,7 +1492,6 @@ void CxbxImpl_SetVertexShader(DWORD Handle) HRESULT hRet = D3D_OK; xbox::X_D3DVertexShader* pXboxVertexShader = CxbxGetXboxVertexShaderForHandle(Handle); - g_Xbox_VertexShader_IsPassthrough = false; if ((pXboxVertexShader->Flags & g_X_VERTEXSHADER_FLAG_VALID_MASK) != pXboxVertexShader->Flags) { LOG_TEST_CASE("Unknown vertex shader flag"); @@ -1453,8 +1511,8 @@ void CxbxImpl_SetVertexShader(DWORD Handle) LOG_TEST_CASE("g_Xbox_VertexShader_FunctionSlots_StartAddress != 0"); bHackCallSelectAgain = true; } - if (g_Xbox_VertexShader_IsFixedFunction != false) { - LOG_TEST_CASE("g_Xbox_VertexShader_IsFixedFunction != false"); + if (g_Xbox_VertexShaderMode != VertexShaderMode::ShaderProgram) { + LOG_TEST_CASE("Not in shader program mode after SetVertexShader trampoline"); bHackCallSelectAgain = true; } @@ -1463,7 +1521,6 @@ void CxbxImpl_SetVertexShader(DWORD Handle) // _SelectVertexShader isn't applied; // 'solve' that by calling it here instead. CxbxImpl_SelectVertexShader(Handle, 0); - g_Xbox_VertexShader_IsFixedFunction = false; } #endif } else { @@ -1475,31 +1532,16 @@ void CxbxImpl_SetVertexShader(DWORD Handle) g_Xbox_VertexShader_Handle = Handle; g_Xbox_VertexShader_FunctionSlots_StartAddress = 0; - // Only when there's no program, set default values for attributes missing from vertex shader - // Note : We avoid calling CxbxImpl_SetVertexData4f here, as that would - // start populating g_InlineVertexBuffer_Table, which is not our intend here. - if (!(pXboxVertexShader->Flags & X_VERTEXSHADER_FLAG_HASDIFFUSE)) { - CxbxSetVertexAttribute(xbox::X_D3DVSDE_DIFFUSE, 1, 1, 1, 1); - } - if (!(pXboxVertexShader->Flags & X_VERTEXSHADER_FLAG_HASSPECULAR)) { - CxbxSetVertexAttribute(xbox::X_D3DVSDE_SPECULAR, 0, 0, 0, 0); - } - if (!(pXboxVertexShader->Flags & X_VERTEXSHADER_FLAG_HASBACKDIFFUSE)) { - CxbxSetVertexAttribute(xbox::X_D3DVSDE_BACKDIFFUSE, 1, 1, 1, 1); - } - if (!(pXboxVertexShader->Flags & X_VERTEXSHADER_FLAG_HASBACKSPECULAR)) { - CxbxSetVertexAttribute(xbox::X_D3DVSDE_BACKSPECULAR, 0, 0, 0, 0); - } + SetFixedFunctionDefaultVertexAttributes(pXboxVertexShader->Flags); // Switch to passthrough program, if so required if (pXboxVertexShader->Flags & X_VERTEXSHADER_FLAG_PASSTHROUGH) { CxbxSetVertexShaderPassthroughProgram(); - g_Xbox_VertexShader_IsFixedFunction = false; - g_Xbox_VertexShader_IsPassthrough = true; + g_Xbox_VertexShaderMode = VertexShaderMode::Passthrough; } else { // Test-case : Many XDK samples, Crazy taxi 3 //LOG_TEST_CASE("Other or no vertex shader flags"); - g_Xbox_VertexShader_IsFixedFunction = true; + g_Xbox_VertexShaderMode = VertexShaderMode::FixedFunction; } } } diff --git a/src/core/hle/D3D8/XbVertexShader.h b/src/core/hle/D3D8/XbVertexShader.h index e25ab0a65..5b82c28a3 100644 --- a/src/core/hle/D3D8/XbVertexShader.h +++ b/src/core/hle/D3D8/XbVertexShader.h @@ -80,6 +80,17 @@ typedef struct _CxbxVertexDeclaration } CxbxVertexDeclaration; +enum class VertexShaderMode { + FixedFunction, + // When titles use Xbox fixed function with pre-transformed vertices + // it actually uses a special "passthrough" shader program + Passthrough, + ShaderProgram +}; + +extern VertexShaderMode g_Xbox_VertexShaderMode; +extern bool g_UseFixedFunctionVertexShader; + // Intermediate vertex shader structures enum VSH_OREG_NAME { @@ -200,6 +211,9 @@ extern size_t GetVshFunctionSize(const xbox::dword_xt* pXboxFunction); inline boolean VshHandleIsVertexShader(DWORD Handle) { return (Handle & X_D3DFVF_RESERVED0) ? TRUE : FALSE; } inline xbox::X_D3DVertexShader *VshHandleToXboxVertexShader(DWORD Handle) { return (xbox::X_D3DVertexShader *)(Handle & ~X_D3DFVF_RESERVED0);} +// Get the number of components represented by the given xbox vertex data type +extern int GetXboxVertexDataComponentCount(int d3dvsdt); + extern bool g_Xbox_VertexShader_IsFixedFunction; extern CxbxVertexDeclaration* CxbxGetVertexDeclaration(); @@ -214,4 +228,5 @@ extern void CxbxImpl_SetVertexShaderInput(DWORD Handle, UINT StreamCount, xbox:: extern void CxbxImpl_SetVertexShaderConstant(INT Register, PVOID pConstantData, DWORD ConstantCount); extern void CxbxImpl_DeleteVertexShader(DWORD Handle); extern void CxbxVertexShaderSetFlags(); +extern HRESULT SetVertexShader(IDirect3DVertexShader* pShader); #endif