diff --git a/.gitmodules b/.gitmodules index 8d4a72ff9..47cf2fe5d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -39,3 +39,6 @@ path = import/libusb url = https://github.com/libusb/libusb shallow = true +[submodule "import/nv2a_vsh_cpu"] + path = import/nv2a_vsh_cpu + url = https://github.com/abaire/nv2a_vsh_cpu.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 141d77afb..4d10741a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,9 @@ add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/projects/imgui") add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/projects/libusb") +set(nv2a_vsh_cpu_UNIT_TEST OFF) +add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/import/nv2a_vsh_cpu" EXCLUDE_FROM_ALL) + # Split the files into group for which project is likely # going to be used for both header and source files. # Then move only specific project files into their @@ -437,6 +440,10 @@ set_target_properties(cxbx cxbxr-ldr cxbxr-emu misc-batch SDL2 subhook libXbSymb PROPERTIES FOLDER Cxbx-Reloaded ) +set_target_properties(nv2a_vsh_emulator nv2a_vsh_disassembler nv2a_vsh_cpu + PROPERTIES FOLDER Cxbx-Reloaded/nv2a_vsh +) + if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") # Configure startup project set_property(DIRECTORY "${CMAKE_CURRENT_LIST_DIR}" PROPERTY VS_STARTUP_PROJECT cxbx) diff --git a/import/nv2a_vsh_cpu b/import/nv2a_vsh_cpu new file mode 160000 index 000000000..ead0af5de --- /dev/null +++ b/import/nv2a_vsh_cpu @@ -0,0 +1 @@ +Subproject commit ead0af5dee49e408c005151393f8a7dbcd27026c diff --git a/projects/cxbxr-emu/CMakeLists.txt b/projects/cxbxr-emu/CMakeLists.txt index de66678c6..adb1888bc 100644 --- a/projects/cxbxr-emu/CMakeLists.txt +++ b/projects/cxbxr-emu/CMakeLists.txt @@ -171,6 +171,7 @@ target_link_libraries(cxbxr-emu SDL2 imgui libusb + nv2a_vsh_emulator ${WINS_LIB} ) diff --git a/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp b/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp index 38ce76540..fe0877296 100644 --- a/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp +++ b/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp @@ -78,6 +78,9 @@ #include #include + +#include "nv2a_vsh_emulator.h" + using namespace Microsoft::WRL; XboxRenderStateConverter XboxRenderStates; @@ -8660,6 +8663,8 @@ xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_SetVertexShaderInput) XB_TRMP(D3DDevice_SetVertexShaderInput)(Handle, StreamCount, pStreamInputs); } +extern xbox::dword_xt* GetCxbxVertexShaderSlotPtr(const DWORD SlotIndexAddress); // tmp glue + // ****************************************************************** // * patch: D3DDevice_RunVertexStateShader // ****************************************************************** @@ -8676,8 +8681,41 @@ xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_RunVertexStateShader) // If pData is assigned, pData[0..3] is pushed towards nv2a transform data registers // then sends the nv2a a command to launch the vertex shader function located at Address + if (Address >= NV2A_MAX_TRANSFORM_PROGRAM_LENGTH) { + LOG_TEST_CASE("Address out of bounds"); + return; + } - LOG_UNIMPLEMENTED(); + NV2AState* dev = g_NV2A->GetDeviceState(); + PGRAPHState* pg = &(dev->pgraph); + + Nv2aVshProgram program = {}; // Note: This nulls program.steps + // TODO : Retain program globally and perform nv2a_vsh_parse_program only when + // the address-range we're about to emulate was modified since last parse. + // TODO : As a suggestion for this, parse all NV2A_MAX_TRANSFORM_PROGRAM_LENGTH slots, + // and here just point program.steps to global vsh_program_steps[Address]. + Nv2aVshParseResult result = nv2a_vsh_parse_program( + &program, // Note : program.steps will be malloc'ed + GetCxbxVertexShaderSlotPtr(Address), // TODO : At some point, use pg->program_data[Address] here instead + NV2A_MAX_TRANSFORM_PROGRAM_LENGTH - Address); + if (result != NV2AVPR_SUCCESS) { + LOG_TEST_CASE("nv2a_vsh_parse_program failed"); + // TODO : Dump Nv2aVshParseResult as string and program for debugging purposes + return; + } + + Nv2aVshCPUXVSSExecutionState state_linkage; + Nv2aVshExecutionState state = nv2a_vsh_emu_initialize_xss_execution_state( + &state_linkage, (float*)pg->vsh_constants); // Note : This wil memset(state_linkage, 0) + if (pData != nullptr) + //if pData != nullptr, then it contains v0.xyzw, we shall copy the binary content directly. + memcpy(state_linkage.input_regs, pData, sizeof(state_linkage.input_regs)); + + nv2a_vsh_emu_execute_track_context_writes(&state, &program, pg->vsh_constants_dirty); + // Note: Above emulation's primary purpose is to update pg->vsh_constants and pg->vsh_constants_dirty + // therefor, nothing else needs to be done here, other than to cleanup + + nv2a_vsh_program_destroy(&program); // Note: program.steps will be free'ed } // ******************************************************************