From 3bfad0327f7a1bf906de94c7cf7a1930116bd3df Mon Sep 17 00:00:00 2001 From: Anthony Miles Date: Tue, 27 Oct 2020 22:07:12 +1300 Subject: [PATCH] Xbox fixed function vertex shader (not yet enabled) The shader should behave similarly to D3D9's fixed function pipeline But supports Xbox extensions and helps to move off D3D9 Co-authored with PatrickVL --- CMakeLists.txt | 3 + projects/cxbx/CMakeLists.txt | 11 + projects/cxbxr-emu/CMakeLists.txt | 11 + projects/misc/batch.cmake | 9 + src/core/hle/D3D8/Direct3D9/Direct3D9.cpp | 197 +++++- .../Direct3D9/FixedFunctionVertexShader.hlsl | 635 ++++++++++++++++++ .../FixedFunctionVertexShaderState.hlsli | 145 ++++ src/core/hle/D3D8/Direct3D9/VertexShader.cpp | 26 + src/core/hle/D3D8/Direct3D9/VertexShader.h | 3 + src/core/hle/D3D8/XbVertexShader.cpp | 17 + src/core/hle/D3D8/XbVertexShader.h | 3 + 11 files changed, 1058 insertions(+), 2 deletions(-) create mode 100644 src/core/hle/D3D8/Direct3D9/FixedFunctionVertexShader.hlsl create mode 100644 src/core/hle/D3D8/Direct3D9/FixedFunctionVertexShaderState.hlsli diff --git a/CMakeLists.txt b/CMakeLists.txt index 295102ab3..8ca950a86 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -117,7 +117,10 @@ 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" diff --git a/projects/cxbx/CMakeLists.txt b/projects/cxbx/CMakeLists.txt index 49c9b8884..731de24b4 100644 --- a/projects/cxbx/CMakeLists.txt +++ b/projects/cxbx/CMakeLists.txt @@ -194,3 +194,14 @@ 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(HlslHeaders ${CXBXR_HEADER_EMU}) +list(FILTER HlslHeaders INCLUDE REGEX ".*\\.hlsl$") +set_source_files_properties( + ${HlslHeaders} + PROPERTIES + HEADER_FILE_ONLY TRUE + VS_TOOL_OVERRIDE "None" +) \ No newline at end of file diff --git a/projects/cxbxr-emu/CMakeLists.txt b/projects/cxbxr-emu/CMakeLists.txt index 569b0d4b2..06f66f0b0 100644 --- a/projects/cxbxr-emu/CMakeLists.txt +++ b/projects/cxbxr-emu/CMakeLists.txt @@ -166,3 +166,14 @@ 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(HlslHeaders ${CXBXR_HEADER_EMU}) +list(FILTER HlslHeaders INCLUDE REGEX ".*\\.hlsl$") +set_source_files_properties( + ${HlslHeaders} + PROPERTIES + HEADER_FILE_ONLY TRUE + VS_TOOL_OVERRIDE "None" +) \ No newline at end of file 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 facb4944e..bc63cd09b 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,9 @@ XboxRenderStateConverter XboxRenderStates; XboxTextureStateConverter XboxTextureStates; +D3D8LightState d3d8LightState = D3D8LightState(); +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; @@ -6263,6 +6267,179 @@ 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, D3DXMATRIX viewTransform) { + if (d3dLightIndex == -1) { + pShaderLight->Type = 0; // Disable the light + return; + } + + auto d3dLight = &d3d8LightState.Lights[d3dLightIndex]; + + // 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); + + // Map D3D light to state struct + pShaderLight->Type = (float)((int)d3dLight->Type); + pShaderLight->Diffuse = toVector(d3dLight->Diffuse); + pShaderLight->Specular = toVector(d3dLight->Specular); + 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; + + // Lighting + ffShaderState.Modes.Lighting = (float)XboxRenderStates.GetXboxRenderState(X_D3DRS_LIGHTING); + ffShaderState.Modes.TwoSidedLighting = (float)XboxRenderStates.GetXboxRenderState(X_D3DRS_TWOSIDEDLIGHTING); + ffShaderState.Modes.SpecularEnable = (float)XboxRenderStates.GetXboxRenderState(X_D3DRS_SPECULARENABLE); + ffShaderState.Modes.LocalViewer = (float)XboxRenderStates.GetXboxRenderState(X_D3DRS_LOCALVIEWER); + ffShaderState.Modes.ColorVertex = (float)XboxRenderStates.GetXboxRenderState(X_D3DRS_COLORVERTEX); + + D3DXVECTOR4 Ambient = toVector(XboxRenderStates.GetXboxRenderState(X_D3DRS_AMBIENT)); + D3DXVECTOR4 BackAmbient = toVector(XboxRenderStates.GetXboxRenderState(X_D3DRS_BACKAMBIENT)); + + // Material sources + ffShaderState.Modes.AmbientMaterialSource = (float)XboxRenderStates.GetXboxRenderState(X_D3DRS_AMBIENTMATERIALSOURCE); + ffShaderState.Modes.DiffuseMaterialSource = (float)XboxRenderStates.GetXboxRenderState(X_D3DRS_DIFFUSEMATERIALSOURCE); + ffShaderState.Modes.SpecularMaterialSource = (float)XboxRenderStates.GetXboxRenderState(X_D3DRS_SPECULARMATERIALSOURCE); + ffShaderState.Modes.EmissiveMaterialSource = (float)XboxRenderStates.GetXboxRenderState(X_D3DRS_EMISSIVEMATERIALSOURCE); + ffShaderState.Modes.BackAmbientMaterialSource = (float)XboxRenderStates.GetXboxRenderState(X_D3DRS_BACKAMBIENTMATERIALSOURCE); + ffShaderState.Modes.BackDiffuseMaterialSource = (float)XboxRenderStates.GetXboxRenderState(X_D3DRS_BACKDIFFUSEMATERIALSOURCE); + ffShaderState.Modes.BackSpecularMaterialSource = (float)XboxRenderStates.GetXboxRenderState(X_D3DRS_BACKSPECULARMATERIALSOURCE); + ffShaderState.Modes.BackEmissiveMaterialSource = (float)XboxRenderStates.GetXboxRenderState(X_D3DRS_BACKEMISSIVEMATERIALSOURCE); + + // Point sprites + auto pointSize = XboxRenderStates.GetXboxRenderState(X_D3DRS_POINTSIZE); + ffShaderState.PointSprite.PointSize = *reinterpret_cast(&pointSize); + ffShaderState.PointSprite.PointScaleEnable = (float)XboxRenderStates.GetXboxRenderState(X_D3DRS_POINTSCALEENABLE); + ffShaderState.PointSprite.RenderTargetHeight = GetPixelContainerHeight(g_pXbox_RenderTarget); + auto scaleA = XboxRenderStates.GetXboxRenderState(X_D3DRS_POINTSCALE_A); + ffShaderState.PointSprite.ScaleA = *reinterpret_cast(&scaleA); + auto scaleB = XboxRenderStates.GetXboxRenderState(X_D3DRS_POINTSCALE_B); + ffShaderState.PointSprite.ScaleB = *reinterpret_cast(&scaleB); + auto scaleC = XboxRenderStates.GetXboxRenderState(X_D3DRS_POINTSCALE_C); + ffShaderState.PointSprite.ScaleC = *reinterpret_cast(&scaleC); + + // 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); + } + + // Misc flags + ffShaderState.Modes.VertexBlend = (float)XboxRenderStates.GetXboxRenderState(X_D3DRS_VERTEXBLEND); + ffShaderState.Modes.NormalizeNormals = (float)XboxRenderStates.GetXboxRenderState(X_D3DRS_NORMALIZENORMALS); + + // Update lights + auto LightAmbient = D3DXVECTOR4(0.f, 0.f, 0.f, 0.f); + D3DXMATRIX rowMajorViewTransform; + D3DXMatrixTranspose(&rowMajorViewTransform, (D3DXMATRIX*)&ffShaderState.Transforms.View); + for (size_t i = 0; i < ffShaderState.Lights.size(); i++) { + UpdateFixedFunctionShaderLight(d3d8LightState.EnabledLights[i], &ffShaderState.Lights[i], &LightAmbient, rowMajorViewTransform); + } + + ffShaderState.AmbientPlusLightAmbient = Ambient + LightAmbient; + ffShaderState.BackAmbientPlusLightAmbient = BackAmbient + LightAmbient; + + // 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 // ****************************************************************** @@ -6405,9 +6582,15 @@ void CxbxImpl_SetTransform { LOG_INIT - State = EmuXB2PC_D3DTS(State); + // Transpose row major to column major for HLSL + D3DXMATRIX hlslMatrix; + D3DXMatrixTranspose(&hlslMatrix, (D3DXMATRIX*)pMatrix); + // Save to vertex shader state + ((D3DXMATRIX*)&ffShaderState.Transforms)[State] = hlslMatrix; - 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"); } @@ -7897,6 +8080,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"); @@ -7913,6 +8098,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"); } @@ -7933,6 +8124,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/FixedFunctionVertexShader.hlsl b/src/core/hle/D3D8/Direct3D9/FixedFunctionVertexShader.hlsl new file mode 100644 index 000000000..a5c848610 --- /dev/null +++ b/src/core/hle/D3D8/Direct3D9/FixedFunctionVertexShader.hlsl @@ -0,0 +1,635 @@ +#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 World; // Vertex worldspace transform +static TransformInfo View; // Vertex viewspace/cameraspace transform +static TransformInfo Projection; // Vertex projection transform + +// Vertex lighting +// Both frontface and backface lighting can be calculated +struct LightingInfo +{ + float3 Front; + float3 Back; +}; + +// Final lighting output +struct LightingOutput +{ + LightingInfo Diffuse; + LightingInfo Specular; +}; + +LightingInfo DoSpecular(const float3 toLightVN, const float3 toViewerVN, const float2 powers, const float4 lightSpecular) +{ + LightingInfo o; + o.Front = o.Back = float3(0, 0, 0); + + // Specular + if (state.Modes.SpecularEnable) + { + // Blinn-Phong + // https://learnopengl.com/Advanced-Lighting/Advanced-Lighting + float3 halfway = normalize(toViewerVN + toLightVN); + float NdotH = dot(View.Normal, halfway); + + float3 frontSpecular = pow(abs(NdotH), powers[0]) * lightSpecular.rgb; + float3 backSpecular = pow(abs(NdotH), powers[1]) * lightSpecular.rgb; + + if (NdotH >= 0) + o.Front = frontSpecular; + else + o.Back = backSpecular; + } + + return o; +} + +// useful reference https://drivers.amd.com/misc/samples/dx9/FixedFuncShader.pdf + +LightingOutput DoPointLight(const Light l, const float3 toViewerVN, 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); + + // Diffuse + float3 toLightV = l.PositionV - View.Position.xyz; + float lightDist = length(toLightV); + float3 toLightVN = normalize(toLightV); + + // A(Constant) + A(Linear) * dist + A(Exp) * dist^2 + float attenuation = + 1 / (l.Attenuation[0] + + l.Attenuation[1] * lightDist + + l.Attenuation[2] * lightDist * lightDist); + + // Range cutoff + if (lightDist > l.Range) + attenuation = 0; + + float NdotL = dot(View.Normal, toLightVN); + float3 lightDiffuse = abs(NdotL) * attenuation * l.Diffuse.rgb; + + if (NdotL >= 0) + o.Diffuse.Front = lightDiffuse; + else + o.Diffuse.Back = lightDiffuse; + + // Specular + o.Specular = DoSpecular(toLightVN, toViewerVN, powers, l.Specular); + o.Specular.Front *= attenuation; + o.Specular.Back *= attenuation; + + return o; +} + +LightingOutput DoSpotLight(const Light l, const float3 toViewerVN, 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); + + // Diffuse + float3 toLightV = l.PositionV - View.Position.xyz; + float lightDist = length(toLightV); + float3 toLightVN = normalize(toLightV); + float3 toVertexVN = -toLightVN; + + // https://docs.microsoft.com/en-us/windows/win32/direct3d9/light-types + float cosAlpha = dot(l.DirectionVN, toVertexVN); + // 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); + + // A(Constant) + A(Linear) * dist + A(Exp) * dist^2 + float attenuation = + 1 / (l.Attenuation[0] + + l.Attenuation[1] * lightDist + + l.Attenuation[2] * lightDist * lightDist); + + // Range cutoff + if (lightDist > l.Range) + attenuation = 0; + + float NdotL = dot(View.Normal, toLightVN); + float3 lightDiffuse = abs(NdotL) * attenuation * l.Diffuse.rgb * spotIntensity; + + if (NdotL >= 0) + o.Diffuse.Front = lightDiffuse; + else + o.Diffuse.Back = lightDiffuse; + + // Specular + o.Specular = DoSpecular(toLightVN, toViewerVN, powers, l.Specular); + o.Specular.Front *= attenuation; + o.Specular.Back *= attenuation; + + return o; +} + +LightingOutput DoDirectionalLight(const Light l, const float3 toViewerVN, 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); + + // Diffuse + + // Intensity from N . L + float3 toLightVN = -l.DirectionVN; + float NdotL = dot(View.Normal, toLightVN); + float3 lightDiffuse = abs(NdotL * l.Diffuse.rgb); + + // Apply light contribution to front or back face + // as the case may be + if (NdotL >= 0) + o.Diffuse.Front = lightDiffuse; + else + o.Diffuse.Back = lightDiffuse; + + // Specular + o.Specular = DoSpecular(toLightVN, toViewerVN, powers, l.Specular); + + return o; +} + + +LightingOutput CalcLighting(const float2 powers) +{ + const int LIGHT_TYPE_NONE = 0; + const int LIGHT_TYPE_POINT = 1; + const int LIGHT_TYPE_SPOT = 2; + const int LIGHT_TYPE_DIRECTIONAL = 3; + + 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); + + float3 toViewerVN = state.Modes.LocalViewer + ? float3(0, 0, 1) + : normalize(-View.Position.xyz); + + for (uint i = 0; i < 8; i++) + { + const Light currentLight = state.Lights[i]; + LightingOutput currentLightOutput; + + if(currentLight.Type == LIGHT_TYPE_POINT) + currentLightOutput = DoPointLight(currentLight, toViewerVN, powers); + else if(currentLight.Type == LIGHT_TYPE_SPOT) + currentLightOutput = DoSpotLight(currentLight, toViewerVN, powers); + else if (currentLight.Type == LIGHT_TYPE_DIRECTIONAL) + currentLightOutput = DoDirectionalLight(currentLight, toViewerVN, powers); + else + continue; + + 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 DoWorldTransform(const float4 position, const float3 normal, const float4 blendWeights) +{ + TransformInfo output; + output.Position = float4(0, 0, 0, 0); + output.Normal = float3(0, 0, 0); + + // D3D + const int _BLEND_OFF = 0; + const int _1WEIGHT_2MAT = 1; + const int _2WEIGHT_3MAT = 3; + const int _3WEIGHT_4MAT = 5; + // Xbox + const int _2WEIGHT_2MAT = 2; + const int _3WEIGHT_3MAT = 4; + const int _4WEIGHT_4MAT = 6; + + if (state.Modes.VertexBlend == _BLEND_OFF) { + output.Position = mul(position, state.Transforms.World[0]); + output.Normal = mul(normal, (float3x3)state.Transforms.World[0]); + return output; + } + + // The number of matrices to blend + int mats = floor((state.Modes.VertexBlend - 1) / 2 + 2); + // If we have to calculate the last blend value + bool calcLastBlend = fmod(state.Modes.VertexBlend, 2) == 1; + + float lastBlend = 1; + for (int i = 0; i < mats - 1; i++) + { + output.Position += mul(position, state.Transforms.World[i]) * blendWeights[i]; + output.Normal += mul(normal, (float3x3) state.Transforms.World[i]) * blendWeights[i]; + lastBlend -= blendWeights[i]; + } + + if (calcLastBlend) + { + output.Position += mul(position, state.Transforms.World[mats-1]) * lastBlend; + output.Normal += mul(normal, (float3x3) state.Transforms.World[mats-1]) * lastBlend; + } + else + { + output.Position += mul(position, state.Transforms.World[mats-1]) * blendWeights[mats-1]; + output.Normal += mul(normal, (float3x3) state.Transforms.World[mats-1]) * blendWeights[mats-1]; + } + + 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]; + + if (state.Modes.ColorVertex) + { + // https://docs.microsoft.com/en-us/windows/win32/direct3d9/d3dmaterialcolorsource + const int D3DMCS_MATERIAL = 0; + const int D3DMCS_COLOR1 = 1; + const int D3DMCS_COLOR2 = 2; + + // TODO preprocess on the CPU + // 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]) { + 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]) { + 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(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 = xIn.color[1].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 + const int D3DTTFF_DISABLE = 0; + const int D3DTTFF_COUNT1 = 1; + const int D3DTTFF_COUNT2 = 2; + const int D3DTTFF_COUNT3 = 3; + const int D3DTTFF_COUNT4 = 4; + const int D3DTTFF_PROJECTED = 256; // This is the only real flag + + // https://docs.microsoft.com/en-us/windows/win32/direct3d9/d3dtss-tci + // Pre-shifted + const int TCI_PASSTHRU = 0; + const int TCI_CAMERASPACENORMAL = 1; + const int TCI_CAMERASPACEPOSITION = 2; + const int TCI_CAMERASPACEREFLECTIONVECTOR = 3; + const int TCI_OBJECT = 4; // Xbox + const int TCI_SPHERE = 5; // Xbox + + const TextureState tState = state.TextureStates[stage]; + + // Extract transform flags + int countFlag = tState.TextureTransformFlagsCount; + 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 + 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 + 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; + } + else + { + // Generate texture coordinates + float3 reflected = reflect(normalize(View.Position.xyz), View.Normal); + + if (tState.TexCoordIndexGen == TCI_CAMERASPACENORMAL) + texCoord = float4(View.Normal, 1); + else if (tState.TexCoordIndexGen == TCI_CAMERASPACEPOSITION) + texCoord = View.Position; + else 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 + float3 R = reflected; + 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() +{ + PointSprite ps = state.PointSprite; + float pointSize = ps.PointSize; + if (ps.PointScaleEnable) + { + float eyeDistance = length(View.Position); + float factor = ps.ScaleA + ps.ScaleB * eyeDistance + ps.ScaleC * (eyeDistance * eyeDistance); + pointSize *= ps.RenderTargetHeight * sqrt(1 / factor); + } + + return pointSize; +} + +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++) { + 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 transform with vertex blending + World = DoWorldTransform(Get(xIn, position), Get(xIn, normal).xyz, Get(xIn, weight)); + + // View transform + View.Position = mul(World.Position, state.Transforms.View); + View.Normal = mul(World.Normal, (float3x3) state.Transforms.View); + + // 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; + + // Vertex lighting + if (state.Modes.Lighting || state.Modes.TwoSidedLighting) + { + // Materials + Material material = DoMaterial(0, diffuse, specular, xIn); + Material backMaterial = DoMaterial(1, backDiffuse, backSpecular, xIn); + + float2 powers = float2(material.Power, backMaterial.Power); + + LightingOutput lighting = CalcLighting(powers); + + // Compute each lighting component + float3 ambient = material.Ambient.rgb * state.AmbientPlusLightAmbient.rgb; + float3 backAmbient = backMaterial.Ambient.rgb * state.BackAmbientPlusLightAmbient.rgb; + + float3 diffuse = material.Diffuse.rgb * lighting.Diffuse.Front; + float3 backDiffuse = backMaterial.Diffuse.rgb * lighting.Diffuse.Back; + + float3 specular = material.Specular.rgb * lighting.Specular.Front; + float3 backSpecular = backMaterial.Specular.rgb * lighting.Specular.Back; + + float3 emissive = material.Emissive.rgb; + float3 backEmissive = backMaterial.Emissive.rgb; + + // Frontface + xOut.oD0 = float4(ambient + diffuse + emissive, material.Diffuse.a); + xOut.oD1 = float4(specular, 0); + // Backface + xOut.oB0 = float4(backAmbient + backDiffuse + backEmissive, backMaterial.Diffuse.a); + xOut.oB1 = float4(backSpecular, 0); + } + + // TODO does TwoSidedLighting imply Lighting? Verify if TwoSidedLighting can be enabled independently of Lighting + // Diffuse and specular for when lighting is disabled + if (!state.Modes.Lighting) + { + xOut.oD0 = Get(xIn, diffuse); + xOut.oD1 = Get(xIn, specular); + } + + if(!state.Modes.TwoSidedLighting) + { + xOut.oB0 = Get(xIn, backDiffuse); + xOut.oB1 = Get(xIn, backSpecular); + } + + // 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..b684a04e9 --- /dev/null +++ b/src/core/hle/D3D8/Direct3D9/FixedFunctionVertexShaderState.hlsli @@ -0,0 +1,145 @@ +// 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 + arr(World, float4x4, 4); // 6, 7, 8, 9 +}; + +// 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; + alignas(16) float NormalizeNormals; +}; + +struct PointSprite { + alignas(16) float PointSize; + alignas(16) float PointScaleEnable; + alignas(16) float RenderTargetHeight; + alignas(16) float ScaleA; + alignas(16) float ScaleB; + alignas(16) float ScaleC; +}; + +struct TextureState { + alignas(16) float TextureTransformFlagsCount; + alignas(16) float TextureTransformFlagsProjected; + alignas(16) float TexCoordIndex; + alignas(16) float TexCoordIndexGen; +}; + +struct Fog { + alignas(16) float DepthMode; +}; + +struct FixedFunctionVertexShaderState { + alignas(16) Transforms Transforms; + alignas(16) arr(Lights, Light, 8); + alignas(16) float4 AmbientPlusLightAmbient; + alignas(16) float4 BackAmbientPlusLightAmbient; + 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/VertexShader.cpp b/src/core/hle/D3D8/Direct3D9/VertexShader.cpp index caaabef8d..10353ca5b 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_3_0; @@ -304,6 +305,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/XbVertexShader.cpp b/src/core/hle/D3D8/XbVertexShader.cpp index b884a9ffa..db62e5df5 100644 --- a/src/core/hle/D3D8/XbVertexShader.cpp +++ b/src/core/hle/D3D8/XbVertexShader.cpp @@ -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 diff --git a/src/core/hle/D3D8/XbVertexShader.h b/src/core/hle/D3D8/XbVertexShader.h index e25ab0a65..29a5ade50 100644 --- a/src/core/hle/D3D8/XbVertexShader.h +++ b/src/core/hle/D3D8/XbVertexShader.h @@ -200,6 +200,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();