Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f71a483a00 |
No files matched your search
@@ -15,9 +15,6 @@
|
||||
/traces/
|
||||
/build/
|
||||
/dist/
|
||||
/.tools/
|
||||
/external/
|
||||
/test-results/
|
||||
*.trace
|
||||
*.dump
|
||||
*.log
|
||||
|
||||
-252
@@ -1,252 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.24)
|
||||
project(d4r_windows_diagnostics LANGUAGES C CXX)
|
||||
if(NOT WIN32 OR NOT CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
message(FATAL_ERROR "These diagnostics require native Windows x64")
|
||||
endif()
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(D4R_HIP_ROOT "$ENV{HIP_PATH}" CACHE PATH "One stable HIP SDK or TheRock installation")
|
||||
if(NOT D4R_HIP_ROOT AND EXISTS "C:/Program Files/AMD/ROCm/7.2")
|
||||
set(D4R_HIP_ROOT "C:/Program Files/AMD/ROCm/7.2")
|
||||
endif()
|
||||
if(NOT EXISTS "${D4R_HIP_ROOT}/include/hip/hip_runtime_api.h")
|
||||
message(FATAL_ERROR "Set D4R_HIP_ROOT to a Windows HIP SDK (headers and bin/clang++.exe required)")
|
||||
endif()
|
||||
find_program(D4R_HIP_CLANG NAMES clang++.exe PATHS "${D4R_HIP_ROOT}/bin" "${D4R_HIP_ROOT}/lib/llvm/bin" NO_DEFAULT_PATH REQUIRED)
|
||||
set(probe_object "${CMAKE_CURRENT_BINARY_DIR}/probe_gfx1201.hsaco")
|
||||
add_custom_command(OUTPUT "${probe_object}"
|
||||
COMMAND "${D4R_HIP_CLANG}" -x hip --offload-arch=gfx1201
|
||||
--offload-device-only --no-gpu-bundle-output -mno-wavefrontsize64
|
||||
-nogpuinc -nogpulib -O2 "${CMAKE_CURRENT_SOURCE_DIR}/tools/windows/probe_kernel.hip"
|
||||
-o "${probe_object}"
|
||||
DEPENDS tools/windows/probe_kernel.hip VERBATIM)
|
||||
add_custom_target(d4r_probe_code_object ALL DEPENDS "${probe_object}")
|
||||
set(pixel_object "${CMAKE_CURRENT_BINARY_DIR}/pixel_convert_gfx1201.hsaco")
|
||||
add_custom_command(OUTPUT "${pixel_object}"
|
||||
COMMAND "${D4R_HIP_CLANG}" -x hip -std=c++17 --offload-arch=gfx1201
|
||||
--offload-device-only --no-gpu-bundle-output -mno-wavefrontsize64
|
||||
-nogpuinc -nogpulib -O2 "${CMAKE_CURRENT_SOURCE_DIR}/tools/windows/pixel_convert_gfx1201.hip"
|
||||
-o "${pixel_object}"
|
||||
DEPENDS tools/windows/pixel_convert_gfx1201.hip tools/windows/pixel_format.h kernels/common/hip_device_minimal.h VERBATIM)
|
||||
add_custom_target(d4r_pixel_code_object ALL DEPENDS "${pixel_object}")
|
||||
set(wmma_object "${CMAKE_CURRENT_BINARY_DIR}/wmma_gfx1201.hsaco")
|
||||
add_custom_command(OUTPUT "${wmma_object}"
|
||||
COMMAND "${D4R_HIP_CLANG}" -x hip --offload-arch=gfx1201
|
||||
--offload-device-only --no-gpu-bundle-output -mno-wavefrontsize64
|
||||
-nogpuinc -nogpulib -O2 "${CMAKE_CURRENT_SOURCE_DIR}/tools/windows/wmma_gfx1201.hip"
|
||||
-o "${wmma_object}"
|
||||
DEPENDS tools/windows/wmma_gfx1201.hip kernels/common/wmma_backend.h
|
||||
kernels/common/wmma_layout.h VERBATIM)
|
||||
add_custom_target(d4r_wmma_code_object ALL DEPENDS "${wmma_object}")
|
||||
# K/M objects stay in experimental/, outside the ZLUDA native override
|
||||
# directory, until each layer and then the complete network pass replay.
|
||||
# M uses the existing FP16 widening baseline; native FP8 is not enabled here.
|
||||
set(native_objects)
|
||||
option(D4R_K_FP16_BASELINE "Round every K16 accumulation step like NVIDIA f16 WMMA" ON)
|
||||
option(D4R_M_FP16_BASELINE "Preserve M e4m3 quantisation and f16 accumulation per K32" ON)
|
||||
foreach(family k m)
|
||||
file(GLOB native_sources CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/kernels/${family}/*.hip")
|
||||
file(GLOB native_headers CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/kernels/${family}/*.h")
|
||||
foreach(source IN LISTS native_sources)
|
||||
get_filename_component(kernel_name "${source}" NAME_WE)
|
||||
set(native_object "${CMAKE_CURRENT_BINARY_DIR}/${kernel_name}_gfx1201.hsaco")
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${source}")
|
||||
file(READ "${source}" kernel_source)
|
||||
string(REGEX MATCH "// d4r-build-flags: ([^\r\n]+)" flags_line "${kernel_source}")
|
||||
set(native_flags)
|
||||
if(family STREQUAL "k" AND D4R_K_FP16_BASELINE)
|
||||
list(APPEND native_flags -DD4R_K_FP16_BASELINE)
|
||||
endif()
|
||||
if(family STREQUAL "m" AND D4R_M_FP16_BASELINE)
|
||||
list(APPEND native_flags -DSWIN_EXACT -DSWIN_EXACT_PV)
|
||||
endif()
|
||||
if(flags_line)
|
||||
separate_arguments(source_flags NATIVE_COMMAND "${CMAKE_MATCH_1}")
|
||||
list(APPEND native_flags ${source_flags})
|
||||
endif()
|
||||
add_custom_command(OUTPUT "${native_object}"
|
||||
COMMAND "${D4R_HIP_CLANG}" -x hip -std=c++17 --offload-arch=gfx1201
|
||||
--offload-device-only --no-gpu-bundle-output -mno-wavefrontsize64
|
||||
-nogpuinc -nogpulib -O3 -DD4R_DEVICE_ONLY_MINIMAL ${native_flags}
|
||||
"${source}" -o "${native_object}"
|
||||
DEPENDS "${source}" ${native_headers} kernels/common/wmma_layout.h
|
||||
kernels/common/hip_device_minimal.h VERBATIM)
|
||||
list(APPEND native_objects "${native_object}")
|
||||
install(FILES "${native_object}" DESTINATION "experimental/${family}")
|
||||
endforeach()
|
||||
endforeach()
|
||||
add_custom_target(d4r_native_gfx1201_compile ALL DEPENDS ${native_objects})
|
||||
foreach(probe hip_gfx1201_probe gfx12_wmma_probe k_module_probe native_replay_probe cuda_driver_probe cuda_mma_probe cuda_image_probe d3d12_command_probe d3d12_pixel_probe d3d12_hip_interop_probe hip_stream_lifecycle_probe ngx_cuda_init_probe d3d12_dlss_probe)
|
||||
add_executable(d4r_${probe} tools/windows/${probe}.cpp)
|
||||
target_compile_definitions(d4r_${probe} PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX __HIP_PLATFORM_AMD__)
|
||||
target_include_directories(d4r_${probe} PRIVATE "${D4R_HIP_ROOT}/include")
|
||||
target_link_libraries(d4r_${probe} PRIVATE version dbghelp)
|
||||
if(MINGW)
|
||||
target_link_options(d4r_${probe} PRIVATE -static)
|
||||
endif()
|
||||
if(MSVC)
|
||||
target_compile_options(d4r_${probe} PRIVATE /W4 /EHsc)
|
||||
set_property(TARGET d4r_${probe} PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
|
||||
else()
|
||||
target_compile_options(d4r_${probe} PRIVATE -Wall -Wextra)
|
||||
endif()
|
||||
install(TARGETS d4r_${probe} RUNTIME DESTINATION bin)
|
||||
endforeach()
|
||||
set(D4R_MINHOOK_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/external/MinHook" CACHE PATH "MinHook 1.3.4 source checkout")
|
||||
if(NOT EXISTS "${D4R_MINHOOK_ROOT}/include/MinHook.h")
|
||||
message(FATAL_ERROR "Clone MinHook v1.3.4 into external/MinHook or set D4R_MINHOOK_ROOT")
|
||||
endif()
|
||||
add_subdirectory("${D4R_MINHOOK_ROOT}" "${CMAKE_CURRENT_BINARY_DIR}/minhook" EXCLUDE_FROM_ALL)
|
||||
add_library(d4r_nvngx_windows SHARED tools/windows/d4r_nvngx_windows.cpp tools/windows/d3d12_command_hooks.cpp)
|
||||
add_dependencies(d4r_nvngx_windows d4r_pixel_code_object)
|
||||
set_target_properties(d4r_nvngx_windows PROPERTIES PREFIX "" OUTPUT_NAME "d4r_nvngx")
|
||||
target_compile_definitions(d4r_nvngx_windows PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX __HIP_PLATFORM_AMD__)
|
||||
target_include_directories(d4r_nvngx_windows PRIVATE "${D4R_HIP_ROOT}/include")
|
||||
target_link_libraries(d4r_nvngx_windows PRIVATE version dbghelp d3d12 dxguid minhook)
|
||||
install(FILES "${D4R_MINHOOK_ROOT}/LICENSE.txt" DESTINATION licenses RENAME MinHook.txt)
|
||||
if(MINGW)
|
||||
target_link_options(d4r_nvngx_windows PRIVATE -static)
|
||||
endif()
|
||||
install(TARGETS d4r_nvngx_windows RUNTIME DESTINATION bin)
|
||||
if(MSVC)
|
||||
target_sources(d4r_ngx_cuda_init_probe PRIVATE tools/d4r_ngx_param_msvc.cpp tools/d4r_ngx_param_host.cpp)
|
||||
target_sources(d4r_d3d12_dlss_probe PRIVATE tools/d4r_ngx_param_msvc.cpp tools/d4r_ngx_param_host.cpp)
|
||||
target_sources(d4r_nvngx_windows PRIVATE tools/d4r_ngx_param_msvc.cpp tools/d4r_ngx_param_host.cpp)
|
||||
else()
|
||||
find_program(D4R_CLANG_CL NAMES clang-cl.exe PATHS "${D4R_HIP_ROOT}/bin" "${D4R_HIP_ROOT}/lib/llvm/bin" NO_DEFAULT_PATH REQUIRED)
|
||||
set(ngx_parameter_object "${CMAKE_CURRENT_BINARY_DIR}/ngx_parameter_msvc.obj")
|
||||
add_custom_command(OUTPUT "${ngx_parameter_object}"
|
||||
COMMAND "${D4R_CLANG_CL}" /c /O2 /Zl /GS- /GR- /EHs-c-
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/tools/d4r_ngx_param_msvc.cpp" "/Fo${ngx_parameter_object}"
|
||||
DEPENDS tools/d4r_ngx_param_msvc.cpp VERBATIM)
|
||||
set_source_files_properties("${ngx_parameter_object}" PROPERTIES GENERATED TRUE EXTERNAL_OBJECT TRUE)
|
||||
target_sources(d4r_ngx_cuda_init_probe PRIVATE "${ngx_parameter_object}" tools/d4r_ngx_param_host.cpp)
|
||||
target_sources(d4r_d3d12_dlss_probe PRIVATE "${ngx_parameter_object}" tools/d4r_ngx_param_host.cpp)
|
||||
target_sources(d4r_nvngx_windows PRIVATE "${ngx_parameter_object}" tools/d4r_ngx_param_host.cpp)
|
||||
endif()
|
||||
add_library(d4r_nvapi_trace SHARED tools/windows/nvapi_trace.cpp)
|
||||
set_target_properties(d4r_nvapi_trace PROPERTIES PREFIX "" OUTPUT_NAME "nvapi64"
|
||||
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/nvapi-trace"
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/nvapi-trace")
|
||||
target_compile_definitions(d4r_nvapi_trace PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX)
|
||||
target_link_libraries(d4r_nvapi_trace PRIVATE version dbghelp)
|
||||
if(MINGW)
|
||||
target_link_options(d4r_nvapi_trace PRIVATE -static)
|
||||
endif()
|
||||
install(TARGETS d4r_nvapi_trace RUNTIME DESTINATION nvapi-trace)
|
||||
add_library(d4r_nvapi_compat SHARED tools/windows/nvapi_trace.cpp)
|
||||
set_target_properties(d4r_nvapi_compat PROPERTIES PREFIX "" OUTPUT_NAME "nvapi64"
|
||||
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/nvapi-compat"
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/nvapi-compat")
|
||||
target_compile_definitions(d4r_nvapi_compat PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX __HIP_PLATFORM_AMD__ D4R_NVAPI_COMPAT)
|
||||
target_include_directories(d4r_nvapi_compat PRIVATE "${D4R_HIP_ROOT}/include")
|
||||
target_link_libraries(d4r_nvapi_compat PRIVATE version dbghelp)
|
||||
if(MINGW)
|
||||
target_link_options(d4r_nvapi_compat PRIVATE -static)
|
||||
endif()
|
||||
install(TARGETS d4r_nvapi_compat RUNTIME DESTINATION nvapi-compat)
|
||||
target_link_libraries(d4r_d3d12_hip_interop_probe PRIVATE d3d12 dxgi dxguid)
|
||||
target_link_libraries(d4r_d3d12_dlss_probe PRIVATE d3d12 dxgi dxguid d3dcompiler)
|
||||
add_executable(d4r_debug_launcher tools/windows/debug_launcher.cpp)
|
||||
target_include_directories(d4r_debug_launcher PRIVATE tools/windows)
|
||||
target_link_libraries(d4r_debug_launcher PRIVATE version dbghelp)
|
||||
install(TARGETS d4r_debug_launcher RUNTIME DESTINATION bin)
|
||||
target_link_libraries(d4r_d3d12_command_probe PRIVATE d3d12 dxgi dxguid)
|
||||
target_link_libraries(d4r_d3d12_pixel_probe PRIVATE d3d12 dxgi dxguid)
|
||||
add_dependencies(d4r_d3d12_pixel_probe d4r_pixel_code_object)
|
||||
add_dependencies(d4r_d3d12_command_probe d4r_nvngx_windows)
|
||||
set(D4R_AGILITY_ROOT "" CACHE PATH "Optional unpacked Microsoft.Direct3D.D3D12 1.619.5 debug SDK")
|
||||
if(EXISTS "${D4R_AGILITY_ROOT}/build/native/bin/x64/d3d12SDKLayers.dll")
|
||||
add_executable(d4r_d3d12_command_debug_probe EXCLUDE_FROM_ALL tools/windows/d3d12_command_probe.cpp)
|
||||
target_compile_definitions(d4r_d3d12_command_debug_probe PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX __HIP_PLATFORM_AMD__ D4R_DIAGNOSTIC_AGILITY)
|
||||
target_include_directories(d4r_d3d12_command_debug_probe PRIVATE "${D4R_HIP_ROOT}/include")
|
||||
target_link_libraries(d4r_d3d12_command_debug_probe PRIVATE version dbghelp d3d12 dxgi dxguid)
|
||||
if(MINGW)
|
||||
target_link_options(d4r_d3d12_command_debug_probe PRIVATE -static)
|
||||
endif()
|
||||
foreach(dll D3D12Core.dll d3d12SDKLayers.dll)
|
||||
configure_file("${D4R_AGILITY_ROOT}/build/native/bin/x64/${dll}" "${CMAKE_CURRENT_BINARY_DIR}/debug-d3d12/${dll}" COPYONLY)
|
||||
endforeach()
|
||||
configure_file("${D4R_AGILITY_ROOT}/LICENSE.txt" "${CMAKE_CURRENT_BINARY_DIR}/debug-d3d12/LICENSE.txt" COPYONLY)
|
||||
endif()
|
||||
add_dependencies(d4r_hip_gfx1201_probe d4r_probe_code_object)
|
||||
add_dependencies(d4r_gfx12_wmma_probe d4r_wmma_code_object)
|
||||
add_dependencies(d4r_k_module_probe d4r_native_gfx1201_compile)
|
||||
install(FILES "${probe_object}" DESTINATION bin)
|
||||
install(FILES "${pixel_object}" DESTINATION bin)
|
||||
install(FILES "${wmma_object}" DESTINATION bin)
|
||||
install(FILES scripts/windows/test-windows-rdna4.ps1 DESTINATION .)
|
||||
install(FILES scripts/windows/test-k-replay.ps1 DESTINATION .)
|
||||
install(FILES scripts/windows/test-k-captures.ps1 DESTINATION .)
|
||||
install(FILES scripts/windows/test-native-overrides.ps1 DESTINATION .)
|
||||
install(FILES tools/windows/k_layer_validate.py DESTINATION .)
|
||||
install(FILES tools/windows/k_replay_validate.py DESTINATION .)
|
||||
install(FILES tools/windows/k_ptx_validate.py DESTINATION .)
|
||||
install(FILES tools/windows/frame_compare.py DESTINATION .)
|
||||
install(FILES tools/windows/m_replay_validate.py DESTINATION .)
|
||||
install(FILES tools/windows/m_ptx_validate.py DESTINATION .)
|
||||
install(FILES tools/windows/m_temporal_validate.py tools/windows/m_capture_compare.py DESTINATION .)
|
||||
install(FILES scripts/windows/test-m-replay.ps1 DESTINATION .)
|
||||
install(FILES scripts/windows/test-m-captures.ps1 DESTINATION .)
|
||||
install(FILES kernels/tools/pwin_model.py kernels/tools/pwin_check.py kernels/tools/ptxsim.py
|
||||
kernels/tools/extract_dlss_ptx.py kernels/tools/kernel_manifest.py DESTINATION reference)
|
||||
install(FILES kernels/tools/model_enc3.py kernels/tools/swin_model.py kernels/tools/swin_check.py DESTINATION reference)
|
||||
install(FILES docs/windows-rdna4-port.md DESTINATION docs)
|
||||
include(CTest)
|
||||
add_test(NAME d3d12_gpu_pixel_formats COMMAND d4r_d3d12_pixel_probe
|
||||
--hip-root "${D4R_HIP_ROOT}" --module "${pixel_object}" --iterations 2)
|
||||
set_tests_properties(d3d12_gpu_pixel_formats PROPERTIES TIMEOUT 120 LABELS "hardware;gfx1201;formats")
|
||||
add_test(NAME d3d12_gpu_enhanced_formats COMMAND d4r_d3d12_pixel_probe
|
||||
--hip-root "${D4R_HIP_ROOT}" --module "${pixel_object}" --interop-mode enhanced --iterations 2)
|
||||
set_tests_properties(d3d12_gpu_enhanced_formats PROPERTIES TIMEOUT 120 LABELS "hardware;gfx1201;formats;enhanced_barriers")
|
||||
add_test(NAME d3d12_command_backend COMMAND d4r_d3d12_command_probe
|
||||
--hip-root "${D4R_HIP_ROOT}" --module "$<TARGET_FILE:d4r_nvngx_windows>" --interop-mode indirect --iterations 32)
|
||||
set_tests_properties(d3d12_command_backend PROPERTIES TIMEOUT 120 LABELS "hardware;d3d12;command_lists;lifetime")
|
||||
add_test(NAME d3d12_command_initial_pso COMMAND d4r_d3d12_command_probe
|
||||
--hip-root "${D4R_HIP_ROOT}" --module "$<TARGET_FILE:d4r_nvngx_windows>" --interop-mode initial-pso --iterations 32)
|
||||
set_tests_properties(d3d12_command_initial_pso PROPERTIES TIMEOUT 120 LABELS "hardware;d3d12;command_lists;lifetime")
|
||||
add_test(NAME d3d12_command_root_reset_warp COMMAND d4r_d3d12_command_probe
|
||||
--hip-root "${D4R_HIP_ROOT}" --module "$<TARGET_FILE:d4r_nvngx_windows>" --interop-mode indirect-root-reset --iterations 32)
|
||||
set_tests_properties(d3d12_command_root_reset_warp PROPERTIES TIMEOUT 120 LABELS "software;d3d12;command_lists;lifetime"
|
||||
ENVIRONMENT "D4R_COMMAND_PROBE_WARP=1;D4R_COMMAND_PROBE_NO_HIP=1")
|
||||
add_test(NAME hip_gfx1201 COMMAND d4r_hip_gfx1201_probe
|
||||
--hip-root "${D4R_HIP_ROOT}" --module "${probe_object}")
|
||||
set_tests_properties(hip_gfx1201 PROPERTIES TIMEOUT 90 LABELS "hardware;gfx1201")
|
||||
add_test(NAME gfx12_wmma COMMAND d4r_gfx12_wmma_probe
|
||||
--hip-root "${D4R_HIP_ROOT}" --module "${wmma_object}" --iterations 16)
|
||||
set_tests_properties(gfx12_wmma PROPERTIES TIMEOUT 90 LABELS "hardware;gfx1201;wmma")
|
||||
foreach(layer enc1 enc2)
|
||||
add_test(NAME k_${layer}_execution COMMAND d4r_k_module_probe
|
||||
--hip-root "${D4R_HIP_ROOT}"
|
||||
--module "${CMAKE_CURRENT_BINARY_DIR}/dltss_pwin_${layer}_layer_gfx1201.hsaco"
|
||||
--kernel-name ${layer})
|
||||
set_tests_properties(k_${layer}_execution PROPERTIES TIMEOUT 90 LABELS "hardware;gfx1201;k_execution")
|
||||
endforeach()
|
||||
add_test(NAME d3d12_hip_interop COMMAND d4r_d3d12_hip_interop_probe
|
||||
--hip-root "${D4R_HIP_ROOT}" --module "${probe_object}" --iterations 32)
|
||||
set_tests_properties(d3d12_hip_interop PROPERTIES TIMEOUT 120 LABELS "hardware;interop")
|
||||
add_test(NAME d3d12_hip_map_lifetime COMMAND d4r_d3d12_hip_interop_probe
|
||||
--hip-root "${D4R_HIP_ROOT}" --interop-mode map --iterations 64)
|
||||
set_tests_properties(d3d12_hip_map_lifetime PROPERTIES TIMEOUT 120 LABELS "hardware;interop;lifetime")
|
||||
set(D4R_ZLUDA_ROOT "" CACHE PATH "Local Windows ZLUDA directory (nvcuda.dll)")
|
||||
if(EXISTS "${D4R_ZLUDA_ROOT}/nvcuda.dll")
|
||||
foreach(context primary created)
|
||||
add_test(NAME cuda_${context} COMMAND d4r_cuda_driver_probe
|
||||
--hip-root "${D4R_HIP_ROOT}" --cuda-dll "${D4R_ZLUDA_ROOT}/nvcuda.dll"
|
||||
--context ${context} --iterations 32)
|
||||
set_tests_properties(cuda_${context} PROPERTIES TIMEOUT 180 LABELS "hardware;zluda")
|
||||
endforeach()
|
||||
if(EXISTS "${D4R_ZLUDA_ROOT}/build-info.json")
|
||||
add_test(NAME cuda_f16_mma_reference COMMAND d4r_cuda_mma_probe
|
||||
--hip-root "${D4R_HIP_ROOT}" --cuda-dll "${D4R_ZLUDA_ROOT}/nvcuda.dll")
|
||||
set_tests_properties(cuda_f16_mma_reference PROPERTIES TIMEOUT 120
|
||||
LABELS "hardware;zluda;mma;reference"
|
||||
ENVIRONMENT "D4R_ZLUDA_WMMA=1;D4R_ZLUDA_WMMA_F16_REFERENCE=1")
|
||||
add_test(NAME cuda_images COMMAND d4r_cuda_image_probe
|
||||
--hip-root "${D4R_HIP_ROOT}" --cuda-dll "${D4R_ZLUDA_ROOT}/nvcuda.dll" --iterations 32)
|
||||
set_tests_properties(cuda_images PROPERTIES TIMEOUT 90 LABELS "hardware;zluda;descriptors")
|
||||
add_test(NAME d3d12_cuda_images COMMAND d4r_d3d12_dlss_probe
|
||||
--hip-root "${D4R_HIP_ROOT}" --cuda-dll "${D4R_ZLUDA_ROOT}/nvcuda.dll" --interop-mode images --iterations 1)
|
||||
set_tests_properties(d3d12_cuda_images PROPERTIES TIMEOUT 90 LABELS "hardware;interop;zluda;descriptors")
|
||||
endif()
|
||||
endif()
|
||||
@@ -2,28 +2,10 @@
|
||||
|
||||
d4r runs NVIDIA's official DLSS Super Resolution library (`nvngx_dlss.dll`) in Windows games on AMD Radeon GPUs under Linux and Proton. The game asks for DLSS as usual; the DLSS network runs on the AMD GPU through [ZLUDA](https://github.com/vosen/ZLUDA) (CUDA on ROCm/HIP), with the heaviest DLSS kernels replaced by hand-written RDNA3 and RDNA4 code.
|
||||
|
||||
**Supported DLSS models:** DLSS 3 CNN (E), DLSS 4 transformer (K, default), and DLSS 4.5 transformer (M, plus experimental L). DLSS 5 is PURPOSELY not supported.
|
||||
|
||||
Preset L is NVIDIA's DLSS 4.5 model for Ultra Performance, especially at 4K. Set `[DLSS] Model = L` in `d4r.ini` and select Ultra Performance in game or OptiScaler; selecting L alone does not change the input resolution. L shares M's native Swin layers and has its own texture-kernel build. Its unfolded input/output arithmetic remains translated, so performance and image quality need game testing; the M benchmarks below do not apply to L.
|
||||
**Supported DLSS models:** DLSS 3 CNN (E), DLSS 4 transformer (K, default), and DLSS 4.5 transformer (M). DLSS 5 is PURPOSELY not supported.
|
||||
|
||||
**Proof of concept:** d4r shows that DLSS can run on an AMD GPU, but it is not really that practical for everyday use yet. It has been tested on one GPU in a handful of games and depends on unreleased patches to ZLUDA and vkd3d-proton.
|
||||
|
||||
**Native Windows development:** an experimental D3D12 backend runs K and M
|
||||
transformer networks on Windows 11 / RX 9070 XT (gfx1201), using patched
|
||||
ZLUDA/HIP and VRAM buffers/shared fences directly. Inputs and output belong to
|
||||
the current frame; this path does not use Wine, Proton or CPU image staging.
|
||||
The Windows backend, command-list hooks and diagnostics live in `tools/windows`.
|
||||
All sixteen Windows hardware gates pass, and queued-frame/feature-recreation K
|
||||
outputs match synchronous controls exactly. A five-minute K/4K Silent Hill 2
|
||||
run completes 16941 finite frames without backend errors. Performance remains
|
||||
under development; asynchronous interop is opt-in and requires one queue.
|
||||
M retains the FP16-equivalent baseline; native FP8 has not established a
|
||||
full-network speedup. Windows preset L is not validated. See
|
||||
[Windows build and validation](docs/windows-rdna4-port.md),
|
||||
[OptiScaler installation](docs/windows-game.md) and
|
||||
[Windows measurements](docs/windows-performance.md). NVIDIA DLLs are supplied
|
||||
locally and excluded from packages.
|
||||
|
||||
See [supported games](SUPPORTED_GAMES.md) for the tested games and DLSS models.
|
||||
|
||||
> **Not affiliated with NVIDIA or AMD.**
|
||||
@@ -63,7 +45,7 @@ d4r builds for RDNA3 and RDNA4. A newly built release compiles native DLSS 4 and
|
||||
| RX 7900 GRE / XT / XTX, Radeon PRO W7800 / W7900 | gfx1100 | widened to f16 | RX 7900 XTX: [external video](https://www.youtube.com/watch?v=_GLjJ2Dn5pU) (DLSS 4.5); other cards untested |
|
||||
| RX 7600 / 7600 XT, RX 7650 GRE | gfx1102 | widened to f16 | untested |
|
||||
| RDNA3 integrated GPUs | gfx1103 | widened to f16 | untested |
|
||||
| RX 9070 XT / 9070 / 9070 GRE, Radeon AI PRO R9700 | gfx1201 | native FP8 (`NativeFp8`, default on) | RX 9070 XT: [external video](https://www.youtube.com/watch?v=lZ0BLYqAtCs), other cards untested |
|
||||
| RX 9070 XT / 9070 / 9070 GRE, Radeon AI PRO R9700 | gfx1201 | native FP8 (`NativeFp8`, default on) | emulator only |
|
||||
| RX 9060 XT / 9060 | gfx1200 | native FP8 (`NativeFp8`, default on) | compile only |
|
||||
| RDNA2 and older | | | unsupported |
|
||||
|
||||
@@ -102,8 +84,6 @@ The release zip works like an OptiScaler release: its contents go into the folde
|
||||
- an `d4r` folder with the shim, the CUDA bridge, ZLUDA, the ROCm 7.2.4 runtime, the native kernels, this game's `d4r.ini`, and NVIDIA's `nvngx_dlss.dll` (310.7) and `_nvngx.dll`.
|
||||
2. In Steam, select GE-Proton 11 for the game and set these launch options: `PROTON_FORCE_NVAPI=1 DXVK_NVAPI_GPU_ARCH=AD100 %command%`.
|
||||
|
||||
When updating, replace `d3d12.dll` and `d3d12core.dll` together with `d4r/nvngx.dll`. The shim requires their matching resource-lifetime extension.
|
||||
|
||||
ROCm does not need to be installed: the zip includes its runtime (from AMD's Ubuntu 22.04 packages, which run under Steam's container runtime on any distribution). The zip's NVIDIA files and the kernels built from NVIDIA's code are not covered by this repository's license (see [NOTICE](NOTICE)). The Proton prefix and the system are not changed. [packaging/D4R_README.txt](packaging/D4R_README.txt) is the full guide that ships in the zip; `scripts/package_release.sh` builds the zip (see [docs/building.md](docs/building.md#7-package-a-release)).
|
||||
|
||||
## Requirements (building from source)
|
||||
|
||||
+14
-30
@@ -17,20 +17,14 @@ CompatDataDir =
|
||||
GameExe =
|
||||
|
||||
[DLSS]
|
||||
; DLSS model recommendations:
|
||||
;
|
||||
; Model | DLSS | Recommended modes | Notes
|
||||
; ------+-------+---------------------------------+----------------------------------
|
||||
; K | 4 | DLAA / Quality / Balanced | Default; faster alternative to M/L
|
||||
; M | 4.5 | Performance | More demanding on RDNA3
|
||||
; L | 4.5 | Ultra Performance | Especially 4K; experimental
|
||||
; E | 3 CNN | Quality / Balanced / Performance | Legacy faster fallback
|
||||
; F | 3 CNN | DLAA / Ultra Performance | Deprecated; prefer K or L
|
||||
;
|
||||
; Select the upscaling mode separately in game/OptiScaler. These are recommendations;
|
||||
; other modes are allowed. L requires the L texture build.
|
||||
; Aliases: CNN = E, DLSS4 = K, DLSS4.5 = M. A/B/C/D/G/J use K in this DLL version.
|
||||
; auto = use the game/OptiScaler choice. Benchmarks: docs/performance.md.
|
||||
; Which DLSS network ("preset") to run. fps measured on an RX 7700 XT, Townfall 2560x1440, same street
|
||||
; walk, for Quality / Balanced / Performance / Ultra Performance (FSR 4: 76 / 85 / 94 / 107):
|
||||
; E (or CNN) = DLSS 3 CNN, the fastest at Quality..Performance 72 / 81 / 88 / 89
|
||||
; K (or DLSS4) = DLSS 4 transformer, sharper; best at Ultra Perf 69 / 77 / 84 / 95
|
||||
; M (or DLSS4.5) = DLSS 4.5 transformer, best image, slowest 52 / 60 / 70 / 90
|
||||
; F = the other CNN (same speed as E); A, B, C, D, G, J run the K network in this DLSS version;
|
||||
; L (DLSS 4.5 Ultra Performance model) is not accelerated here and runs very slowly.
|
||||
; auto = whatever the game/OptiScaler asks for
|
||||
Model = K
|
||||
[Latency]
|
||||
; How many frames old the upscaled image the game presents may be.
|
||||
@@ -48,11 +42,6 @@ FrameAge = 0
|
||||
; directory directly). Missing accuracy kernels use translated PTX, never the fast set. Slower; this
|
||||
; aims for fidelity, but RTX image parity is not proven. (D4R_PREFER_ACCURACY)
|
||||
PreferAccuracy = false
|
||||
; false: use original translated enc1/enc2 layers for L/M, retaining other native kernels.
|
||||
; Reduces the native encoders' motion trails seen in Townfall; best paired with PreferAccuracy.
|
||||
; Costs some GPU time. Manifest required for selective fallback; flat sets fall back entirely.
|
||||
; (D4R_NATIVE_SWIN_ENCODERS)
|
||||
NativeSwinEncoders = true
|
||||
; Native RDNA3 / RDNA4 kernels for the DLSS 4 and 4.5 network layers (replace ZLUDA's translated code).
|
||||
; fast = native kernels with relaxed numerics (unless PreferAccuracy is true)
|
||||
; exact = a separately built directory with conservative FP8/f16 rounding; not an RTX parity guarantee
|
||||
@@ -71,12 +60,6 @@ IgnoreDenormals = true
|
||||
ImplicitMaxBlock = 256
|
||||
|
||||
[Interop]
|
||||
; Experimental foreground motion coverage for thin geometry. 0 = original vectors;
|
||||
; 1/2 = opaque-surface dilation radius in render pixels; both automatically fill sky gaps
|
||||
; from the nearest foreground within 8 render pixels. Needs render- or display-resolution, unjittered motion vectors
|
||||
; and same-frame linear VRAM inputs; unsupported features retain original vectors.
|
||||
; Default off. May affect disocclusion edges. (D4R_MOTION_DILATION)
|
||||
MotionVectorDilation = 0
|
||||
; Keep DLSS inputs and output in VRAM (vkd3d-proton Vulkan interop) instead of copying through
|
||||
; system memory. Falls back automatically when the game's resources do not qualify.
|
||||
VramInterop = true
|
||||
@@ -84,18 +67,19 @@ VramInterop = true
|
||||
InputSync = true
|
||||
; Faster hand-offs between the game and DLSS (same frame, same result). Need FrameAge = 0 and VRAM
|
||||
; interop, and a runtime built from this checkout (scripts/install_d4r_runtime.sh).
|
||||
; Input readiness is checked with CPU sleeps; recorded-but-discarded lists never queue GPU waits.
|
||||
; LinearInputs: DLSS reads the game's inputs in place once the input marker is ready
|
||||
; GpuWait: DLSS is queued ahead and starts on the GPU the moment the game's inputs are ready
|
||||
; LinearInputs: DLSS reads the game's inputs in place (no input copies; needs GpuWait)
|
||||
; DirectOutput: the result is written straight into the game-side buffer (preset K, and only while
|
||||
; NGX runs the native output kernel; drops NVIDIA's "DLSS SDK" watermark)
|
||||
; ElideNgxSync: removes NGX's internal CPU waits between its GPU stages
|
||||
; EvalSync = false: no CPU wait between DLSS and the output copy
|
||||
; MarkerPollUs: how often (microseconds) the CPU checks for the game's inputs
|
||||
; MarkerPollUs: how often (microseconds) the CPU checks for the game's inputs without GpuWait
|
||||
GpuWait = true
|
||||
LinearInputs = true
|
||||
DirectOutput = true
|
||||
ElideNgxSync = true
|
||||
EvalSync = false
|
||||
MarkerPollUs = 200
|
||||
MarkerPollUs = 20
|
||||
|
||||
[Paths]
|
||||
; auto = defaults of scripts/d4r_proton_env.sh
|
||||
@@ -149,4 +133,4 @@ LogDir = auto
|
||||
[Env]
|
||||
; Anything else, passed through as environment variables (one per line):
|
||||
; D4R_SHIM_SPLIT_TIMEOUT_MS = 200
|
||||
; D4R_SHIM_BLOCKING_SYNC = 0 ; A/B only: restore context waits instead of default event queries/sleeps
|
||||
; D4R_SHIM_BLOCKING_SYNC = 1 ; yield CPU while waiting for DLSS output
|
||||
+13
-11
@@ -12,7 +12,7 @@ d4r keeps NVIDIA's DLSS library unmodified and gives it what it expects: an NGX
|
||||
| CUDA bridge `nvcuda.dll` | `tools/wine_nvcuda_bridge.c` | A Wine builtin whose Windows exports forward to ZLUDA's Linux `libcuda.so`, plus d4r helpers (Vulkan memory import, asynchronous 2D copies, GPU-side waits, output redirect, linear textures) |
|
||||
| ZLUDA | `patches/zluda` | CUDA driver API on HIP; compiles NVIDIA's PTX to AMDGPU code; serves native kernels in place of PTX kernels |
|
||||
| Native kernels | `kernels/` | RDNA3 implementations of the DLSS network layers and parts of texture kernels |
|
||||
| vkd3d-proton patch | `patches/vkd3d-proton` | Splits a D3D12 command list around DLSS and retains external resources through command allocator completion |
|
||||
| vkd3d-proton patch | `patches/vkd3d-proton` | Lets the shim split a D3D12 command list around the DLSS call |
|
||||
| NVAPI identity | `tools/d4r_nvapi_identity.c` | Reports an Ada (sm_89) GPU so NGX selects the network weights matching the PTX ZLUDA compiles |
|
||||
|
||||
## One frame
|
||||
@@ -24,22 +24,23 @@ With VRAM interop and split frames (the defaults when the patched vkd3d-proton i
|
||||
- A frame marker.
|
||||
- A split. vkd3d-proton submits everything the game records after the DLSS call separately, gated on a timeline semaphore reaching this frame's number.
|
||||
- At the start of that second part, a copy of the DLSS result into the game's output texture. Linear R11G11B10, RGB10A2, and RGBA8/BGRA8 outputs are converted from RGBA16F on the GPU. sRGB formats continue through host staging to preserve their existing byte interpretation.
|
||||
2. **The CUDA worker queues DLSS.** The prep thread checks the GPU-written input marker with short CPU sleeps. It drops recorded-but-unsubmitted frames when they time out or their feature retires, before any DLSS work is queued. The CUDA worker then queues:
|
||||
2. **The CUDA worker queues DLSS.** A worker thread owns the CUDA context. For each frame it queues on the GPU, without waiting on the CPU:
|
||||
- a wait for the marker (`GpuWait`);
|
||||
- the input copies into CUDA arrays, or none when NGX samples the buffers in place (`LinearInputs`);
|
||||
- NGX's evaluation (NGX's own CPU synchronisations are elided with `ElideNgxSync`);
|
||||
- the result, written straight into the output buffer by the native output kernel (`DirectOutput`, preset K) or copied there.
|
||||
3. **Release.** When the GPU finishes, the worker signals the timeline semaphore and the rest of the game's frame runs, now with this frame's result.
|
||||
|
||||
The watchdog reports delayed output but never signals an unfinished producer as ready. Retiring features remain registered until their CPU/CUDA jobs drain. The lifetime-aware vkd3d extension retains their staging buffers, imported buffers, conversion images and split semaphore through command-allocator reset or destruction after GPU completion. Discarded recordings are covered too; cleanup no longer relies on a 50 ms delay.
|
||||
A watchdog releases any frame that never completes (for example a command list that is recorded but never executed), so the game cannot stall on DLSS.
|
||||
|
||||
The shim requires the matching lifetime-aware d3d12.dll and d3d12core.dll, even for host staging. It rejects older runtimes rather than recording references whose lifetime it cannot protect. FrameAge > 0 shows the newest finished result; without VRAM interop, inputs and output are staged through host memory.
|
||||
Without the vkd3d-proton patch the shim shows the newest finished result instead (one or more frames old). Without VRAM interop, inputs and output are staged through host memory.
|
||||
|
||||
## Why each piece exists
|
||||
|
||||
- **CUDA NGX instead of D3D12 NGX.** NVIDIA's D3D12 path needs NVIDIA's driver. The CUDA path only needs a CUDA driver API, which ZLUDA provides.
|
||||
- **The Ada identity.** NGX picks network weights by GPU architecture. The weights have to match the PTX that ZLUDA compiles, which is sm_89 for DLSS 310.
|
||||
- **Native kernels.** Translating NVIDIA's warp-level matrix code is slow (register pressure, lane shuffles, per-instruction mode switches). Earlier translated K layers also produced non-finite outputs that the final kernel masked. Native layers avoid that path; the compiler now preserves FP16 denormal requirements too, which fixes the translated failure in recorded captures. Numerical and image-quality parity with RTX hardware remains unverified.
|
||||
- **Sleeping readiness checks.** Waiting for input submission before queueing DLSS avoids an uncancellable GPU wait. Explicit output event queries with short sleeps avoid HIP event/context waits that can consume a CPU core. Submillisecond waits use NtDelayExecution because MinGW std::sleep_for can truncate them to zero. Linear inputs and direct output remain available.
|
||||
- **The GPU-side hand-offs.** With DLSS itself fast, most of the remaining gap to FSR 4 was the GPU idling while CPU threads waited on each other. Moving the waits onto the GPU raised GPU utilisation from about 95% to 99%.
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -52,6 +53,7 @@ The shim requires the matching lifetime-aware d3d12.dll and d3d12core.dll, even
|
||||
| `[Kernels] NativeKernels` | `D4R_ZLUDA_NATIVE_DIR` | native kernel directory (`fast` / `exact` / `off`) |
|
||||
| `[Kernels] PreferAccuracy` | `D4R_PREFER_ACCURACY` | default off; accuracy variants for every native family and conservative translation/synchronization settings |
|
||||
| `[Interop] VramInterop` | `D4R_SHIM_VRAM_INTEROP` | keep inputs and output in VRAM |
|
||||
| `[Interop] GpuWait` | `D4R_SHIM_GPU_WAIT` | GPU-side wait for the game's inputs |
|
||||
| `[Interop] LinearInputs` | `D4R_SHIM_LINEAR_INPUTS` | NGX samples the input buffers in place |
|
||||
| `[Interop] DirectOutput` | `D4R_SHIM_OUTPUT_DIRECT` | the output kernel writes the game-side buffer directly (preset K) |
|
||||
| `[Interop] ElideNgxSync` | `D4R_ELIDE_NGX_SYNC` | skip NGX's internal CPU waits |
|
||||
@@ -88,11 +90,11 @@ The release zip (`scripts/package_release.sh`) is unpacked into the folder that
|
||||
|
||||
The launcher writes each run's logs to `~/.cache/d4r-dlss-captures/game-<exe>-<time>/`. `d4r_nvngx.log` holds the shim's log. With `[Debug] Profile = true` it also records one `D4R_PROFILE` line per frame, with the stage timings.
|
||||
|
||||
Output completion uses event queries with 200 us NtDelayExecution sleeps by default. `D4R_SHIM_BLOCKING_SYNC = 0`
|
||||
under `[Env]` restores context synchronization for an A/B check. The profile records
|
||||
`output_sync_wall`, `output_sync_cpu`, and `output_sync_method=event_query_sleep` for the new path.
|
||||
An unavailable or failed event falls back to context synchronization. The old `GpuWait` setting
|
||||
is no longer used: input readiness is checked on the CPU before CUDA work is queued.
|
||||
|
||||
For a CPU-use A/B check, set `D4R_SHIM_BLOCKING_SYNC = 1` under `[Env]` in the game's `d4r.ini`.
|
||||
The VRAM path then requests a blocking event wait for its null-stream output. If ZLUDA and HIP
|
||||
honour the event flag, the waiting thread yields the CPU; an unavailable or failed event falls back
|
||||
to the original context sync. This is opt-in because it changes the wait scope and may add wakeup
|
||||
latency. The profile reports `output_sync_wall`, `output_sync_cpu`, and `output_sync_blocking`;
|
||||
compare CPU use and frame rate in the same scene.
|
||||
The `d2h` field on this path includes the output synchronization wait, even when direct output
|
||||
skips the array-to-buffer copy.
|
||||
+3
-3
@@ -5,7 +5,7 @@ These steps produce the pieces the launcher needs: a patched ZLUDA, a patched vk
|
||||
| Component | Tested version |
|
||||
|---|---|
|
||||
| ZLUDA | `ee2f25a` (upstream), plus `patches/zluda/0002` through `0007` in order |
|
||||
| vkd3d-proton | `3dfc6f07` (the base GE-Proton11-3 ships), plus `patches/vkd3d-proton/0001` and `0002` |
|
||||
| vkd3d-proton | `3dfc6f07` (the base GE-Proton11-3 ships), plus `patches/vkd3d-proton/0001` |
|
||||
| ROCm | 7.2 (HIP runtime, clang, device libraries) |
|
||||
| Proton | GE-Proton11-3 (with its OptiScaler integration) |
|
||||
| DLSS | `nvngx_dlss.dll` 310.7.0 |
|
||||
@@ -81,7 +81,7 @@ D4R_ZLUDA_EMIT=/path/to/zluda/target/release/examples/d4r_emit \
|
||||
kernels/build.sh all kernels/out/native
|
||||
```
|
||||
|
||||
`kernels/out/native` then holds one code object per replaced DLSS kernel; set `NativeKernelDirFast` in d4r.ini (or `D4R_ZLUDA_NATIVE_DIR`) to it. `kernels/build.sh k` or `m` builds only the network layers and needs neither the DLL nor ZLUDA. `kernels/build.sh l` builds L's shared network layers and unfolded texture variants; it needs the DLL and emitter. The texture kernels (`tex`, including L) extract PTX from your DLL into `kernels/extracted/` and compile it offline for `D4R_GPU_ARCH` through `D4R_ZLUDA_EMIT`; that directory and the build output are git-ignored and must not be redistributed. On gfx12, build a second variant with `D4R_NATIVE_FP8=1` and place it in an `<arch>-fp8` folder.
|
||||
`kernels/out/native` then holds one code object per replaced DLSS kernel; set `NativeKernelDirFast` in d4r.ini (or `D4R_ZLUDA_NATIVE_DIR`) to it. `kernels/build.sh k` or `m` builds only the network layers and needs neither the DLL nor ZLUDA. The texture kernels (`tex`) extract PTX from your DLL into `kernels/extracted/` and compile it offline for `D4R_GPU_ARCH` through `D4R_ZLUDA_EMIT`; that directory and the build output are git-ignored and must not be redistributed. On gfx12, build a second variant with `D4R_NATIVE_FP8=1` and place it in an `<arch>-fp8` folder.
|
||||
|
||||
To add the accuracy option to a developer set, repeat the build with `D4R_PREFER_ACCURACY=1` and output `kernels/out/native/accuracy` (multi-target builds: `kernels/accuracy/<arch>` and `<arch>-fp8`). Use a separate empty directory; successfully built accuracy sets receive `d4r-accuracy.txt`. Enable `[Kernels] PreferAccuracy = true` and restart. The option defaults to false and never substitutes the fast set when accuracy binaries are absent.
|
||||
|
||||
@@ -132,7 +132,7 @@ Mount the repository at `/work`, ROCm's compiler/headers/device libraries at `/o
|
||||
|---|---|
|
||||
| `D4R_RELEASE_BUILD_ROOT` | writable directory under `/work/build/` |
|
||||
| `D4R_ZLUDA_SRC` | isolated checkout inside that directory, with both pinned LLVM and HiGHS submodules, the real OCKL LFS payload, and patches `0002`–`0007` applied |
|
||||
| `D4R_VKD3D_SRC` | isolated vkd3d-proton checkout inside that directory, with its submodules and patches `0001` and `0002` applied |
|
||||
| `D4R_VKD3D_SRC` | isolated vkd3d-proton checkout inside that directory, with its submodules and patch `0001` applied |
|
||||
| `CARGO_HOME` | writable build-local Cargo cache; prefetch the locked dependencies for an offline build |
|
||||
| `D4R_ROCM_DIR` | `/opt/rocm` |
|
||||
| `D4R_ROCM_LINK_STUBS` | optional directory for additional ROCm link libraries |
|
||||
|
||||
+1
-16
@@ -35,18 +35,12 @@ The release is built this way, with hashes from DLSS 310.7.0 and 310.9.1. The PT
|
||||
- `pwin_wide.h`: the deep layers, which have only 6–77 windows per frame. It uses 4·NG waves per window, split by token tile and channel group, so the GPU stays busy.
|
||||
- **Weights:** prep kernels expand the weights into WMMA operand images once.
|
||||
|
||||
**DLSS 4.5, presets L/M (`kernels/m`, `rrlite_*`).**
|
||||
**DLSS 4.5, preset M (`kernels/m`, `rrlite_*`).**
|
||||
- **Network:** the Swin blocks of DLSS 4.5, whose weights are FP8. Their prep kernels expand the weights to f16 WMMA operands, once per weight set.
|
||||
- **Template:** `swin_block.h` covers encoders, the tube-shaped enc3 and decoders.
|
||||
- **Output encoding:** the FP8 output is encoded two values at a time with packed 16-bit operations (`enc8x2`, exhaustively equal to the scalar encoder), and the 2×2 patch merge reads the rounded f16 values the codes decode to, from a row layout without LDS bank conflicts, instead of decoding the bytes again in every wave.
|
||||
- **Weight loads:** enc1 forms each weight tile's address in scalar registers (`SWIN_SCALAR_BLOAD`); the 8-wave layers are faster without it.
|
||||
|
||||
**Preset L texture variants.** L uses unfolded `rrlite_enc0_4x4_*`, `rrlite_dec0_4x4`, and `rrlite_post_3_*` kernels. The L build keeps their full neural arithmetic and replaces surface stores with the existing native format-conversion functions. M's folded enc0 tail and dec0 head cannot be used for these variants. All four input flag combinations and all eight post variants are built, along with the shared downsample kernels. `all` and `tex` include these variants; `l` builds the shared Swin layers plus only the L texture set. L remains experimental; these replacements do not establish RTX parity or game performance.
|
||||
|
||||
**Motion mitigation.** `[Kernels] NativeSwinEncoders = false` (`D4R_NATIVE_SWIN_ENCODERS=0`) bypasses only `rrlite_enc1_4x4` and `rrlite_enc2_4x4` in the native manifest. L/M then use the original translated PTX for those encoders, retaining native tube, decoder and texture kernels. Keep `PreferAccuracy = true` for the tested conservative path. It reduced the extra building-edge trails in a 64-frame moving Townfall capture at 853×480 → 2560×1440 on gfx1101; it does not recover detail absent from the low-resolution input or establish RTX parity. The default remains `true` for speed. Flat developer sets without manifests fall back entirely when this switch is off; add a manifest to keep selective acceleration.
|
||||
|
||||
Initial validation: a gfx1101 fast L build completed two Ultra Performance harness frames (192×108 → 576×324, DLSS 310.7). Its finite, nonzero RGBA16F output matched the original translated L texture kernels byte for byte with the same native Swin layers. A gfx1201 FP8 accuracy L set compiled successfully; RDNA4 hardware and 4K game output have not been tested. Full packaging rebuilds missing L variants when given an older texture bundle.
|
||||
|
||||
## RDNA4
|
||||
|
||||
`kernels/common/wmma_layout.h` selects the WMMA operand and accumulator layout. gfx12 uses eight f16 values per lane in each K half; the accumulator rows are `i + 8·half`. The gfx11 path retains its original layout. `D4R_WMMA_LAYOUT=12` on gfx11 is a test shim: it exercises gfx12 indexing but executes gfx11 WMMA, so its replay output can be compared byte for byte with the existing gfx11 build.
|
||||
@@ -85,7 +79,6 @@ This mode aims to preserve NVIDIA's arithmetic, not to promise bit-identical RTX
|
||||
```sh
|
||||
kernels/build.sh k # DLSS 4 layers (ROCm clang only)
|
||||
kernels/build.sh m # DLSS 4.5 layers
|
||||
kernels/build.sh l # L shared layers + unfolded textures (needs DLL and D4R_ZLUDA_EMIT)
|
||||
kernels/build.sh tex # texture kernels (needs D4R_DLSS_DLL and D4R_ZLUDA_BUILD)
|
||||
kernels/build.sh all DIR # everything into DIR (default kernels/out/native)
|
||||
D4R_PREFER_ACCURACY=1 kernels/build.sh all DIR/accuracy # conservative versions of every family
|
||||
@@ -126,11 +119,3 @@ Median GPU time per frame at Quality (1706×960 → 2560×1440, RX 7700 XT), fro
|
||||
| **total** | **3.10** | **2.97** | **total** | **9.04** | **8.23** |
|
||||
|
||||
Runs of the same build vary by about ±3% per kernel. M additionally skips its output copy now (direct output through the downsample kernel), which is not a kernel and not in the table. Games whose flags differ from Townfall's (motion vectors at render resolution, LDR input, regular depth) gain more: those kernel variants used to run as plain ZLUDA compiles (K 3.6–4.6 ms → 2.8–3.0 ms, M −0.5 to −0.7 ms per frame).
|
||||
|
||||
## Experimental thin-feature motion coverage
|
||||
|
||||
`[Interop] MotionVectorDilation = 1` or `2` (`D4R_MOTION_DILATION`) extends nearby foreground motion into pixels whose depth otherwise belongs to the background. The value selects the opaque-surface radius in render pixels. Both enabled values also fill sky gaps using the nearest foreground pixel within an eight-render-pixel circle (far depth 0 for reversed Z, 1 for normal Z). This is the wider sky fill from the comparison; it can strengthen trails in some frames and remains opt-in through `MotionVectorDilation`. This is a depth-guided approximation to better thin-feature motion coverage, not engine-side conservative rasterization. It improved the captured Townfall rooftop where the earlier native-encoder fallback did not resolve the artifact.
|
||||
|
||||
The d4r-owned GPU kernel preserves the original depth/motion buffers and writes a separate RG16F velocity texture. The worker waits for its completion before NGX evaluates. No per-frame CPU readback is needed. It requires same-frame linear VRAM inputs, full-resolution unjittered motion vectors and zero subrect bases. Unsupported features or unavailable kernels keep the original vectors. The default is `0` (off); larger radii can affect disocclusion and unrelated foreground edges. The method does not recreate geometry missing from every input frame or establish RTX parity.
|
||||
|
||||
`scripts/build_wine_nvcuda_bridge.sh` embeds the support kernels when `D4R_ROCM_DIR` points to ROCm clang. All six gfx110x/gfx120x targets compile; physical validation is currently gfx1101 only. A bridge built without the support kernels reports the feature unavailable rather than changing inputs. Optional GPU regressions cover forward/reversed depth, two radii, jitter, padded rows and preservation of input buffers; see `tests/test_motion_kernel_gpu.py`.
|
||||
@@ -1,115 +0,0 @@
|
||||
# Native Windows RDNA4 game development package
|
||||
|
||||
Validated hardware: Windows 11 x64 / Radeon RX 9070 XT / gfx1201. Native K and
|
||||
M transformer kernels pass standalone D3D12 + OptiScaler checks. Silent Hill 2
|
||||
K now renders real menu frames and M renders a saved gameplay level with
|
||||
GPU NaN/Inf checks. Both K and M pass 3840x2160 output on this machine.
|
||||
The user reports 49-51 FPS for K in the measured scene after the command-hook
|
||||
fix and locally built output-store optimization. This is a local
|
||||
development package; no NVIDIA proprietary DLL is included.
|
||||
|
||||
Build the shim/diagnostics, corrected ZLUDA, K/M native objects and patched
|
||||
OptiScaler using the scripts in `scripts/windows`, then run:
|
||||
|
||||
```powershell
|
||||
.\scripts\windows\package-windows-game.ps1 -ArchivePath "$PWD\dist\windows-rdna4-game.zip"
|
||||
```
|
||||
|
||||
The package pins TheRock 10.2.0a20260929 because its Windows external-memory
|
||||
mapping lifetime passes the repeated import/release test. Stable HIP 7.2 has
|
||||
a reproduced mapped-view leak; see `docs/windows-rdna4-port.md` for the source
|
||||
fix, exact dependency versions, validation and complete build commands.
|
||||
The ZIP contains only manifest-listed files, including a snapshot of committed
|
||||
d4r source. Private NVIDIA-derived objects, caches and diagnostic captures are
|
||||
excluded. Original FP8 M and the optional hardware-packing variant pass strict
|
||||
replay/network tests but do not establish a full-network speedup, so the game
|
||||
package retains its FP16-equivalent M baseline.
|
||||
|
||||
Run a D3D12 game without anti-cheat using your locally supplied NVIDIA DLLs:
|
||||
|
||||
```powershell
|
||||
.\dist\windows-rdna4-game\windows-game.ps1 -GameExe "D:\Games\SILENT HILL 2\SHProto\Binaries\Win64\SHProto-Win64-Shipping.exe" -NgxCore "C:\Users\Administrator\d4r\_nvngx.dll" -DlssDll "C:\Users\Administrator\d4r\nvngx_dlss.dll" -Preset 11
|
||||
```
|
||||
|
||||
Use `-Preset 13` for M. The validated manifest requires DLSS 310.9.1 with the
|
||||
recorded SHA256; other feature DLLs require regenerating and validating the
|
||||
native manifest. Select an upscaler supported by OptiScaler in the game.
|
||||
The script installs `dxgi.dll`, an explicit DLSS configuration and a private
|
||||
`d4r` runtime directory, and captures stdout/stderr, exit code, loaded DLL paths,
|
||||
driver information and OptiScaler logs. Ordinary runs use a process job without
|
||||
an attached debugger. Add `-CaptureExceptions` to capture exception events and
|
||||
minidumps when reproducing a crash. A local
|
||||
ZIP bundle is printed at exit. `-RunSeconds 120` bounds a diagnostic run;
|
||||
the normal default allows you to play until you close the game.
|
||||
|
||||
For the Silent Hill 2 startup black-screen investigation, add
|
||||
`-DiagnosticResolution 1280x720`. This backs up its two graphics configuration
|
||||
files, temporarily selects a window at that resolution, and restores the
|
||||
original bytes after the test. It does not change progression saves. First-use
|
||||
PTX compilation can be slow; `-CacheDirectory <existing-ZLUDA-cache>` reuses
|
||||
modules with matching GPU, codegen switches and ZLUDA binary fingerprint.
|
||||
|
||||
Add `-ValidateOutput` to scan the entire RGBA16F DLSS output for NaN/Inf on
|
||||
the GPU before returning it to D3D12. Only two diagnostic counters (8 bytes)
|
||||
are read by the CPU; image data remains in VRAM. Validation failures are
|
||||
reported explicitly. Disable this diagnostic when measuring production speed.
|
||||
|
||||
`-ProfileStages` records CPU completion time for seven interop/NGX stages,
|
||||
command-list split/replay, submitted state publication and suffix completion.
|
||||
The recording interval measures NGX calls on the recording thread, not Present.
|
||||
`-ProfileKernels` enables synchronizing HIP-event kernel profiling and changes
|
||||
frame scheduling; use it separately from a production FPS measurement.
|
||||
`-ProfileCommandHooks` samples one in 64 D3D12 interceptions to distinguish
|
||||
tracking/lock waits from driver calls. It is disabled in ordinary runs.
|
||||
`-ProfileCudaApi` aggregates existing CUDA API host durations without adding
|
||||
HIP events or synchronization. `-UncachedInteropLists` restores per-frame
|
||||
copy-list allocation for comparison with the normal safely reused lists.
|
||||
`-ProfileGpuBoundary` requires `-AsyncInterop` and measures D3D12 GPU intervals
|
||||
around the input/output copies and external fence. It reads 32 timestamp bytes
|
||||
only after existing completion, with no extra CPU wait. Its external interval
|
||||
includes HIP and scheduling; it is not an isolated transformer kernel time.
|
||||
The runner drains stdout/stderr concurrently as raw bytes; the earlier
|
||||
line-oriented PowerShell collector imposed large per-frame delays. The optional
|
||||
ZLUDA kernel profiler is for diagnostics only. Measurements and reproduction
|
||||
commands are in [windows-performance.md](windows-performance.md).
|
||||
|
||||
`-AsyncInterop` enables the experimental K scheduling path. Submission queues
|
||||
the current frame's input/output dependencies and returns while a CUDA worker
|
||||
executes DLSS. Owned command memory is retained until GPU completion; feature
|
||||
release and resource reconfiguration wait for pending consumers. It passes
|
||||
the standalone queued-frame and Release/CreateFeature exact-output regression,
|
||||
but remains opt-in while game stability and performance are checked. It
|
||||
currently requires one D3D12 command queue. This switch is independent of
|
||||
profiling and output scans; omit those diagnostics for a production FPS run.
|
||||
|
||||
Ordinary runs use OptiScaler info logging and suppress successful HIP/CUDA API
|
||||
messages. Errors, GPU identity, imports, frame records and native/translated
|
||||
kernel launches remain logged. `-VerboseRuntime` enables all API and frontend
|
||||
trace messages when investigating a failure. Numerical relaxation flags are
|
||||
cleared in the child process; FP16 rounding and subnormals stay enabled.
|
||||
|
||||
The optional K output-store optimization is built locally from your NVIDIA DLL
|
||||
with `build-native-texture.ps1` and checked by `test-native-texture.ps1`;
|
||||
commands and measurements are in [windows-performance.md](windows-performance.md).
|
||||
Add `-LocalTextureKernels "C:\Users\Administrator\d4r\build\private-textures-gfx1201"`
|
||||
to the game command to install it. The object/manifest hashes and DLSS identity
|
||||
must match validation. These generated objects contain NVIDIA code, remain
|
||||
private, and are excluded from the distributable package. Ordinary launches
|
||||
inherit the game's resolution/window settings; the default argument is `-dx12`.
|
||||
|
||||
Original files are backed up before replacement. Restore with:
|
||||
|
||||
```powershell
|
||||
.\dist\windows-rdna4-game\windows-game.ps1 -Action restore -GameExe "D:\Games\SILENT HILL 2\SHProto\Binaries\Win64\SHProto-Win64-Shipping.exe"
|
||||
```
|
||||
|
||||
Restore refuses to overwrite files changed since installation. Backups and
|
||||
diagnostic results are retained. The script does not change progression saves,
|
||||
system DLLs, driver settings or security settings. All runtime work is native
|
||||
Windows. Colour, depth, motion, exposure and output cross APIs through VRAM
|
||||
and shared D3D12 fences; the backend waits for the current frame and never
|
||||
uses a previous-frame fallback.
|
||||
|
||||
OptiScaler source modifications are GPL-3.0 and exported under
|
||||
`source-patches/optiscaler`. ZLUDA source modifications are exported under
|
||||
`source-patches/zluda`. Dependency license texts and build hashes are included.
|
||||
@@ -1,348 +0,0 @@
|
||||
# Windows RDNA4 measurements
|
||||
|
||||
Hardware: RX 9070 XT / gfx1201, Windows 11, display driver 32.0.31041.1004.
|
||||
Runtime: TheRock 10.2.0a20260929, HIP 7.17.26386; ZLUDA b0161a4 with
|
||||
`D4R_ZLUDA_WMMA=1`, FP8 widening, native FP8 disabled, f16 reference rounding.
|
||||
|
||||
The optional profiler uses HIP events on the actual launch stream, including
|
||||
separate measurements of one-time weight preparation. It synchronizes each
|
||||
sampled launch and changes scheduling: these numbers are not application FPS.
|
||||
HIP occupancy predictions are not measured hardware occupancy. The native
|
||||
objects' actual wave32, LDS, VGPR/SGPR and spill counts are available in
|
||||
`llvm-readobj --notes` and can be attached to the JSON report. Memory bandwidth
|
||||
and WMMA utilization were not measured; no utilization is inferred from timing.
|
||||
|
||||
The identical four-frame standalone D3D12/OptiScaler workloads output 512x288.
|
||||
Summing the measured main-kernel event times and dividing by four gives:
|
||||
|
||||
| Preset | Fully translated, ms/frame | Native FP16-equivalent, ms/frame |
|
||||
| --- | ---: | ---: |
|
||||
| K | 157.745441 | 0.684675 |
|
||||
| M | 7.428538 | 1.566500 |
|
||||
|
||||
Both rows include translated input/output tails. Native overrides cover all
|
||||
11 K layers and five M layers; M's tube layer executes six times per frame.
|
||||
M native and translated RGB are bit-exact, with max absolute/relative error 0.
|
||||
K native remains bit-exact against its independently validated native control;
|
||||
fully translated K has the previously documented arithmetic differences.
|
||||
All outputs are finite. These are reference-correct codegen settings, not
|
||||
measurements of a relaxed fast-math translated configuration.
|
||||
|
||||
M's first profiled Evaluate includes about 0.9 seconds of host initialization;
|
||||
the four-sample CPU mean must not be treated as steady-state latency. Its
|
||||
median Evaluate host completion is 4.855 ms. GPU events and prep phases are
|
||||
reported separately in the raw JSON.
|
||||
|
||||
The initial game runner used PowerShell line-oriented output redirection.
|
||||
It heavily throttled verbose stdout/stderr. The runner now concurrently drains
|
||||
raw byte streams using the same approach as the standalone diagnostics.
|
||||
|
||||
| Silent Hill 2 stage, CPU mean | Initial capture, ms | Raw-byte capture with HIP profiler, ms |
|
||||
| --- | ---: | ---: |
|
||||
| Input fence wait | 5.641 | 0.833 |
|
||||
| Input conversion and CUDA array upload | 13.196 | 0.668 |
|
||||
| NGX Evaluate host call | 25.715 | 3.350 |
|
||||
| NGX all-stream completion | 1.846 | 0.033 |
|
||||
| Output CUDA array download | 4.288 | 0.167 |
|
||||
| Output conversion and shared fence signal | 1.500 | 0.033 |
|
||||
|
||||
The first run additionally scanned every output (10.142 ms in that throttled
|
||||
capture); the kernel-profiling run disables that scan. Both use a temporary
|
||||
1280x720 window and current-frame VRAM interop. These are separate menu/game
|
||||
runs, not a controlled whole-game FPS comparison. No synchronization was
|
||||
removed to obtain the improvement. The raw-byte profiling run completed
|
||||
2345 K frames, with no d4r failure, CPU image copy or previous-frame output.
|
||||
|
||||
Reproduce native or translated measurements with locally supplied NVIDIA DLLs
|
||||
at the repository root and the separately built profiling runtime:
|
||||
|
||||
```powershell
|
||||
.\scripts\windows\profile-windows-rdna4.ps1 -Preset 11
|
||||
.\scripts\windows\profile-windows-rdna4.ps1 -Preset 11 -Translated
|
||||
.\scripts\windows\profile-windows-rdna4.ps1 -Preset 13
|
||||
.\scripts\windows\profile-windows-rdna4.ps1 -Preset 13 -Translated
|
||||
```
|
||||
|
||||
`profile_report.py <results-directory> --output profile.json` summarizes raw
|
||||
CPU stages and every kernel's event time, launch geometry, registers, private
|
||||
memory, LDS and predicted blocks per multiprocessor. Add
|
||||
`--metadata-directory <directory-of-llvm-readobj-notes>` for native wave sizes
|
||||
and compiler spill counts. Reports and proprietary workload inputs remain local.
|
||||
|
||||
Native FP8 remains a separate optional experiment; the package uses the
|
||||
validated FP16-equivalent baseline. Its later checks and timings are below.
|
||||
|
||||
## K host synchronization and asynchronous submission
|
||||
|
||||
Current priority is K performance. The user's synchronous 4K reading is
|
||||
49–51 FPS / 53% GPU utilization, versus approximately 80+ FPS / 100% with
|
||||
FSR4. These readings do not identify a CPU hardware limit or provide a
|
||||
controlled same-scene comparison. M/FP8 optimization is deferred.
|
||||
|
||||
ZLUDA candidate `a1c506f` adds `D4R_ZLUDA_PROFILE_API`, enabled by the game
|
||||
runner's `-ProfileCudaApi`. It measures existing API host durations, aggregates
|
||||
per thread and logs every two seconds without HIP events or additional waits.
|
||||
The synchronous control shows five `cuCtxSynchronize` calls per frame with
|
||||
about 3.9 ms aggregate host time. CUDA launch host overhead is approximately
|
||||
0.46 ms per frame. Cached copy-list setup is only 0.011 ms median. These host
|
||||
measurements include GPU waits and must not be added to GPU kernel times.
|
||||
|
||||
| K/4K command submission, CPU median | Synchronous | First async | Async without CUDA mutex |
|
||||
| --- | ---: | ---: | ---: |
|
||||
| Entire submission, ms | 10.512 | 2.676 | 0.181 |
|
||||
| DLSS boundary, ms | 10.083 | 2.604 | 0.124 |
|
||||
| Consumer suffix completion, ms | 0.350 | 0.005 | 0.004 |
|
||||
|
||||
The first async game run completes 9481 finite frames; the user reports
|
||||
60 FPS / 63% GPU. The mutex-free run crashes during feature recreation after
|
||||
366 finite frames, so its submission timing is not a successful gameplay
|
||||
benchmark. The standalone burst/recreate reproducer subsequently identifies
|
||||
caller-owned GPU backing freed after ReleaseFeature returns. Waiting for
|
||||
pending D3D12 consumers in ReleaseFeature fixes the reproducer: all twelve
|
||||
sync/async test frames pass, and corresponding RGB outputs are exact. Game
|
||||
stability and FPS must be checked separately on the fixed build.
|
||||
|
||||
The fixed game retest on 2026-10-02 completes 16941 K/4K frames, all finite,
|
||||
including feature recreation and color-format reimports, with zero backend
|
||||
failures. The user reports 62 FPS / 63% GPU. Median CPU submission is
|
||||
0.197 ms, NGX host completion on the worker is 4.400 ms and output validation
|
||||
is 0.380 ms. The 10.162 ms median worker input wait includes queued game
|
||||
rendering. These results establish a stable five-minute test of this fix,
|
||||
not a conclusion about the remaining GPU idle time or FSR4 performance.
|
||||
|
||||
`-AsyncInterop` is opt-in. It queues input/output fences on the submission
|
||||
thread, executes CUDA on a worker and retires owned command memory by GPU
|
||||
completion. Three copy slots bound outstanding ownership. The queue consumes
|
||||
the output of the same frame, with no CPU image copy. The worker's input wait
|
||||
includes its lead behind submitted rendering and is not a CPU submission
|
||||
stall. Neither that duration nor the NGX recording interval measures Present
|
||||
latency. Reconfiguration waits for all consumers; ordinary submission does not
|
||||
hold the CUDA mutex. The experimental runtime currently supports one queue.
|
||||
|
||||
```powershell
|
||||
.\scripts\windows\test-async-k.ps1
|
||||
```
|
||||
|
||||
The test generates fresh synchronous controls, submits three frames without
|
||||
intermediate CPU completion, and repeats while releasing/recreating the DLSS
|
||||
feature with work pending. Results include exact RGB comparisons, finite GPU
|
||||
scans, stdout/stderr, exceptions and runtime provenance. To include the local
|
||||
validated output-store optimization, pass its combined native directory with
|
||||
`-NativeRoot`; proprietary objects remain local.
|
||||
|
||||
`-ProfileGpuBoundary` (requires `-AsyncInterop`) adds D3D12 timestamps before
|
||||
and after the VRAM input/output copies. Resolved results are read only once
|
||||
the copy slot's existing fence completes; 32 diagnostic bytes per frame are
|
||||
read and no new completion wait is added. `external_span_ms` spans HIP and
|
||||
host/driver scheduling across the external fence. `between_boundaries_ms`
|
||||
also includes game rendering, and `input_interval_ms` is not Present timing.
|
||||
These queries obey the queue's timestamp ordering and add GPU commands; they
|
||||
are disabled for an ordinary FPS run. GPU clock behavior during idle can
|
||||
affect intervals; no hardware occupancy is inferred. Timestamp semantics:
|
||||
[Microsoft D3D12 timing](https://learn.microsoft.com/en-us/windows/win32/direct3d12/timing).
|
||||
|
||||
The timestamp-enabled 4K burst/recreation gate completes twelve frames, all
|
||||
finite; all six async RGB outputs still match their fresh controls exactly.
|
||||
Each async mode emits all three timing records, including the final frame
|
||||
at feature release. Results: `test-results/k-async-gpu-boundary-4k`.
|
||||
All sixteen CTest gates pass on the rebuilt shim. Reproduce with:
|
||||
|
||||
```powershell
|
||||
.\scripts\windows\test-async-k.ps1 -ProfileGpuBoundary
|
||||
```
|
||||
|
||||
The 2026-10-02 game timestamp run completes 9384 K/4K frames without backend
|
||||
errors (`test-results/silent-hill2-k-gpu-boundary-4k`). Median GPU intervals:
|
||||
input copies 0.061 ms, external span 5.586 ms, output copy 0.208 ms, entire
|
||||
boundary 5.856 ms, between boundaries 9.512 ms. Median input interval is
|
||||
15.383 ms. Output scans and serializing kernel events are off. This locates
|
||||
the remaining time outside the small D3D12 copies; it does not prove whether
|
||||
that time is game GPU work, CPU waits or context scheduling.
|
||||
|
||||
### Present and GPU-busy capture
|
||||
|
||||
`capture-presentmon.ps1` attaches the official PresentMon 2.6.0 x64 CLI to
|
||||
one running game process. It verifies SHA256
|
||||
`b2a706bc6ad475749e3b7e3409263aa1e6906d45bdcf993f6dbc0f660188f1af`,
|
||||
uses a unique ETW session and stops at its time bound/process exit, without
|
||||
replacing any existing trace or installing a service. It saves CSV, console
|
||||
output, driver version, target PID and the HWS registry setting. The default
|
||||
downloads the pinned CLI into `.tools` if needed. Run elevated when ETW access
|
||||
requires it; no driver or security setting is changed.
|
||||
|
||||
```powershell
|
||||
.\scripts\windows\capture-presentmon.ps1 -Seconds 30
|
||||
python .\tools\windows\profile_report.py <capture-directory> --output profile.json
|
||||
```
|
||||
|
||||
Keep the game foreground and use the same scene/quality setting for comparison.
|
||||
The report separates swapchains and preserves missing metrics. Present FPS
|
||||
comes from present intervals, rather than NGX recording timestamps. GPU
|
||||
busy/wait are ETW estimates, not a hardware sensor percentage; CPU busy is
|
||||
frame work/wait attribution before Present, not physical CPU utilization.
|
||||
HWS and cross-API context attribution can affect accuracy. Metric definitions:
|
||||
[PresentMon console documentation](https://github.com/GameTechDev/PresentMon/blob/v2.6.0/README-ConsoleApplication.md).
|
||||
|
||||
The initial 60-second trace captures 2172 presents during a changing
|
||||
Independent Flip / Composed Flip session, with mean present interval
|
||||
27.593 ms (36.241 FPS), mean GPU busy 18.759 ms and GPU wait 2.303 ms.
|
||||
This is not a controlled capture of the user's 62 FPS scene and is excluded
|
||||
from speedup conclusions. Its containing game run completes 9627 K/4K frames
|
||||
without backend errors; ordinary recording intervals later return to about
|
||||
16 ms. A stable foreground-scene capture is needed before optimizing from
|
||||
the ETW metrics. Raw files: `test-results/silent-hill2-k-presentmon-4k`.
|
||||
|
||||
Further game coverage (HIP kernel profiling disabled, GPU output checks enabled):
|
||||
M at 1920x1080 completes 4833 frames, all finite. K at 3840x2160 completes
|
||||
4685 frames, all finite. Neither has a backend failure, CPU image copy or an
|
||||
aged-frame fallback. Their mean interop/NGX CPU stages total about 13.6 and
|
||||
10.1 ms respectively; these are different scenes, not a K/M comparison.
|
||||
|
||||
The no-debugger K/4K run completes 5774 frames, with 5774 finite output scans
|
||||
and 63514 native transformer launches. The user reports 38-42 FPS. Additional
|
||||
CPU measurements exclude replay/publication as the main cause of low FPS:
|
||||
|
||||
| CPU measurement | Mean, ms | Median, ms |
|
||||
| --- | ---: | ---: |
|
||||
| Command split setup | 0.284 | 0.275 |
|
||||
| State replay within split setup | 0.025 | 0.023 |
|
||||
| Submitted texture state publication | 0.029 | 0.029 |
|
||||
| Consumer suffix completion | 0.405 | 0.409 |
|
||||
| NGX recording interval | 27.474 | 28.466 |
|
||||
|
||||
Recording intervals are between NGX recording calls on the same thread, not
|
||||
DXGI Present times. The interval includes rendering and application scheduling.
|
||||
A second K/4K run with the attached debugger completes 3567 frames without
|
||||
failure, with median recording interval 30.091 ms. The runs include different
|
||||
menu/game states; this does not establish a precise debugger FPS speedup.
|
||||
Its sparse debugger events also rule out a per-kernel OutputDebugString storm.
|
||||
An earlier control that stayed on the startup screen completed zero DLSS frames
|
||||
and is excluded. Normal game launches now omit the attached debugger; crash
|
||||
diagnostics remain available with `-CaptureExceptions`.
|
||||
|
||||
The 2961-frame 4K K profile identifies the translated output tail as its largest
|
||||
kernel: 4.401670 ms mean GPU time, versus about 2.875 ms for all eleven native
|
||||
transformer layers combined. Ported upstream's texture-store recipe to native
|
||||
Windows. It retains accuracy mode, wave32, FP16 subnormals and f16 reference
|
||||
accumulation; native FP8 remains disabled.
|
||||
|
||||
Both output variants execute on every checked frame and match the previous
|
||||
RGB output bit-exactly: eight consecutive 512x288 frames per variant, then
|
||||
three consecutive 3840x2160 frames per variant. All RGBA components are finite.
|
||||
Identical synthetic inputs and public NGX flags give these 4K event times
|
||||
(three samples each; host initialization and readback are outside the event):
|
||||
|
||||
| K output variant | Translated mean, ms | Native stores mean, ms |
|
||||
| --- | ---: | ---: |
|
||||
| LDR, regular depth, high-resolution MV | 2.907823 | 1.803233 |
|
||||
| HDR, inverted depth, low-resolution MV | 2.978547 | 2.088347 |
|
||||
|
||||
A subsequent game run executes 6221 frames with the native HDR output tail,
|
||||
zero backend failures and mean tail GPU time 1.801918 ms. The earlier game
|
||||
profile's 4.401670 ms is from a different scene/scheduling state; use the
|
||||
identical-input table for the controlled comparison, not an inferred FPS
|
||||
multiplier. This profiled game run did not enable the NaN scan.
|
||||
|
||||
Reproduce the private texture build and full-frame comparisons:
|
||||
|
||||
```powershell
|
||||
.\scripts\windows\build-native-texture.ps1 -DlssDll "$PWD\nvngx_dlss.dll"
|
||||
.\scripts\windows\build-native-texture.ps1 -DlssDll "$PWD\nvngx_dlss.dll" -Kernel hiluma_engine_output_depthreg_mvhi_ldr_max_v2_rel
|
||||
.\scripts\windows\test-native-texture.ps1 -OutputResolution 3840x2160 -Iterations 3
|
||||
```
|
||||
|
||||
These objects contain code derived from the locally supplied NVIDIA PTX. They
|
||||
stay in ignored local directories and are excluded from the public package.
|
||||
The installer accepts them with `-LocalTextureKernels` only after validation,
|
||||
checking the exact DLL identity, object and manifest hashes. M retains its
|
||||
independently validated FP16-equivalent baseline.
|
||||
|
||||
The following no-debugger K/4K run completes 9982 frames and 9982 finite GPU
|
||||
scans. Kernel event profiling is disabled; the validated private output tail
|
||||
executes natively. The user reports 43-47 FPS. Mean NGX recording interval is
|
||||
21.867 ms. No backend failure, CPU image copy or previous-frame output occurs.
|
||||
|
||||
Random one-in-64 hook samples then identify contention in the shared routing
|
||||
table used by every intercepted D3D12 command. Per-thread weak lookup caches
|
||||
now bypass that table on repeated calls to a list. Table mutations invalidate
|
||||
cache generations; Reset/address reuse cannot route to a previous suffix, and
|
||||
thread caches do not retain game resources. The per-list and submission locks
|
||||
remain. A GPU regression resets and re-splits the exact same list with changed
|
||||
root constants twice per iteration, checking both prefix and suffix results.
|
||||
|
||||
| Sampled command-hook access | Shared table | Per-thread weak cache |
|
||||
| --- | ---: | ---: |
|
||||
| Busiest recording thread, mean us/call | 0.203 | 0.066 |
|
||||
| Estimated access + capture across threads, ms/s | 977.528 | 233.606 |
|
||||
|
||||
The latter estimate includes lock waits summed across concurrently running
|
||||
threads, not CPU execution time or serial frame latency. The two game runs
|
||||
have different thread populations (31 versus 29); there is no identical frame
|
||||
trace. Generated forwarding methods have separate driver/capture markers;
|
||||
special-case hooks contribute access samples but lack those body markers.
|
||||
|
||||
With the cache, 4770 complete K/4K frames and finite GPU scans pass, with zero
|
||||
backend failures, CPU image copies or previous-frame output. Median NGX
|
||||
recording interval is 19.473 ms. The user reports 49-51 FPS and 53% GPU usage
|
||||
in the previously measured scene. These user readings are separate from the
|
||||
sampled hook estimates and do not establish isolated WMMA utilization.
|
||||
All sixteen CTest gates pass, including 64 same-object Reset/split checks in
|
||||
each physical command-backend test and the software indirect-root regression.
|
||||
Use `-ProfileCommandHooks -ProfileStages` to collect samples; the sampler is
|
||||
disabled for ordinary play.
|
||||
|
||||
Native M FP8 was subsequently compiled and tested on this gfx1201. ISA confirms
|
||||
`v_wmma_f32_16x16x16_fp8_fp8`, with packed e4m3 weight images; exact FP16
|
||||
attention/PV remains. All logical outputs and merges in forty recorded launches
|
||||
match the previously NumPy-validated FP16 baseline byte-for-byte. Full DLSS RGB
|
||||
also matches exactly: four 512x288 LDR frames and four 3840x2160 HDR frames,
|
||||
all RGBA finite. This establishes correctness for those fixtures, not a speedup.
|
||||
|
||||
The original software operand packing makes the FP8 network slower in the
|
||||
four-frame 4K test: sum of five main-kernel event means (tube weighted six times)
|
||||
is 27.789640 ms versus FP16's 23.550875 ms. A paired in-process replay confirms
|
||||
the regression without relying solely on separate process timings. Both modules
|
||||
and prepared weights remain resident, 512 launches warm the GPU, then sixty-four
|
||||
AB/BA pairs time the same restored input using HIP events. Diagnostic CPU input
|
||||
restoration is outside the timed event and is unrelated to game VRAM interop.
|
||||
|
||||
Added opt-in `D4R_FP8_HW_PACK`: native packed conversion of already quantised
|
||||
operands replaces software exponent/subnormal reconstruction. It preserves all
|
||||
254 finite OCP e4m3 encodings, including signed zero, in an exhaustive GPU check.
|
||||
All forty recorded launches still match exactly. Four complete 4K HDR frames
|
||||
also match FP16 RGB exactly with finite RGBA. ISA confirms both packed converts
|
||||
and FP8 WMMA. The baseline build's sixteen objects remain byte-identical.
|
||||
|
||||
| Paired replay median, ms | FP16 (software-packing test) | FP8 software packing | FP16 (hardware-packing test) | FP8 hardware packing |
|
||||
| --- | ---: | ---: | ---: | ---: |
|
||||
| enc1 | 0.175450 | 0.217050 | 0.175100 | 0.177700 |
|
||||
| enc2 | 0.141350 | 0.164550 | 0.132550 | 0.131550 |
|
||||
| enc3 tube | 0.108000 | 0.112000 | 0.107600 | 0.099850 |
|
||||
| dec2 | 0.129900 | 0.146000 | 0.143250 | 0.140850 |
|
||||
| dec1 | 0.169650 | 0.194450 | 0.154250 | 0.156550 |
|
||||
|
||||
These are two paired tests on the captured 512x288 workload. Medians avoid large
|
||||
isolated scheduling outliers; raw per-pair ratios are retained. They identify a
|
||||
substantial cost in software packing but do not establish an overall FPS gain.
|
||||
The separately run four-frame 4K hardware-packing network mean is 24.603463 ms;
|
||||
it still does not beat the measured FP16 total. Default packaging retains FP16.
|
||||
Hardware counters for bandwidth or WMMA utilization were not measured.
|
||||
|
||||
Reproduce the isolated build, encoding/replay checks and paired timings using
|
||||
the existing private validated captures; no source editing is required:
|
||||
|
||||
```powershell
|
||||
.\scripts\windows\build-native-m-fp8.ps1 -DlssDll "$PWD\nvngx_dlss.dll" -HardwarePacking
|
||||
.\scripts\windows\test-native-m-fp8.ps1 -ModuleDirectory "$PWD\build\native-m-fp8-hardware-pack" -CaptureDirectory "$PWD\test-results\ngx-m-exact-pv-capture\captures" -BaselineValidation "$PWD\test-results\m-native-exact-pv-temporal-reference\summary.json" -Benchmark
|
||||
```
|
||||
|
||||
Omit `-HardwarePacking` to compile the original FP8 experiment. The replay
|
||||
validator stops at the first mismatch, retains GPU stdout/stderr/exit code and
|
||||
hashes every compared logical output. `profile_report.py` accepts replay logs
|
||||
and reports per-pair GPU time and candidate/control ratios. This experiment is
|
||||
not installed by the game runner or substituted silently for the baseline.
|
||||
The conversion builtin is listed in the official
|
||||
[Clang AMDGPU builtin reference](https://clang.llvm.org/docs/AMDGPUBuiltinReference.html#builtin-amdgcn-cvt-pk-fp8-f32).
|
||||
|
||||
References: [HIP events](https://rocmdocs.amd.com/projects/HIP/en/develop/doxygen/html/group___event.html),
|
||||
[HIP occupancy API](https://rocm.docs.amd.com/projects/HIP/en/latest/doxygen/html/group___occupancy.html).
|
||||
File diff suppressed because it is too large.
Load diff
+14
-26
@@ -2,7 +2,7 @@
|
||||
# Builds the native replacements for DLSS kernels into one directory that ZLUDA serves them from
|
||||
# (D4R_ZLUDA_NATIVE_DIR, or NativeKernelDirFast in d4r.ini). See docs/native-kernels.md.
|
||||
#
|
||||
# usage: kernels/build.sh [all|k|l|m|tex] [OUT_DIR] (default: all, kernels/out/native)
|
||||
# usage: kernels/build.sh [all|k|m|tex] [OUT_DIR] (default: all, kernels/out/native)
|
||||
#
|
||||
# D4R_ROCM_DIR ROCm installation with clang and the HIP device libraries (default /opt/rocm)
|
||||
# D4R_GPU_ARCH target GPU (default gfx1101): an RDNA3 (gfx110x) or RDNA4 (gfx120x) target; the kernels
|
||||
@@ -37,7 +37,7 @@ esac
|
||||
CLANG="$ROCM/lib/llvm/bin/clang++"
|
||||
[[ -x "$CLANG" ]] || { echo "clang++ not found in $ROCM/lib/llvm/bin (set D4R_ROCM_DIR)" >&2; exit 2; }
|
||||
mkdir -p "$OUT"
|
||||
case "$WHAT" in all|k|l|m|tex) ;; *) echo "unknown kernel family: $WHAT" >&2; exit 2 ;; esac
|
||||
case "$WHAT" in all|k|m|tex) ;; *) echo "unknown kernel family: $WHAT" >&2; exit 2 ;; esac
|
||||
# Never certify a directory containing older fast binaries as an accuracy set.
|
||||
if [[ "$ACCURACY" == 1 ]] && compgen -G "$OUT/*.hsaco" >/dev/null &&
|
||||
{ [[ ! -f "$OUT/d4r-accuracy.txt" ]] || [[ "$(cat "$OUT/d4r-accuracy.txt")" != 1 ]]; }; then
|
||||
@@ -67,11 +67,11 @@ if [[ "$WHAT" == all || "$WHAT" == k ]]; then
|
||||
echo "== DLSS 4 (preset K) transformer layers"
|
||||
for src in "$HERE"/k/dltss_pwin_*.hip; do build_hip "$src"; done
|
||||
fi
|
||||
if [[ "$WHAT" == all || "$WHAT" == m || "$WHAT" == l ]]; then
|
||||
echo "== DLSS 4.5 (presets L/M) shared Swin layers"
|
||||
if [[ "$WHAT" == all || "$WHAT" == m ]]; then
|
||||
echo "== DLSS 4.5 (preset M) Swin layers"
|
||||
for src in "$HERE"/m/rrlite_*.hip; do build_hip "$src" "$([[ "$FP8" == 1 ]] && echo -DD4R_FP8_WMMA)"; done
|
||||
fi
|
||||
if [[ "$WHAT" == all || "$WHAT" == tex || "$WHAT" == l ]]; then
|
||||
if [[ "$WHAT" == all || "$WHAT" == tex ]]; then
|
||||
echo "== texture kernels (NVIDIA PTX with native parts)"
|
||||
: "${D4R_DLSS_DLL:?set D4R_DLSS_DLL to nvngx_dlss.dll (310.7)}"
|
||||
: "${D4R_ZLUDA_EMIT:?set D4R_ZLUDA_EMIT to d4r_emit from a ZLUDA build with patches/zluda}"
|
||||
@@ -86,29 +86,17 @@ if [[ "$WHAT" == all || "$WHAT" == tex || "$WHAT" == l ]]; then
|
||||
# post 0.89 -> 0.77 ms, hiluma output 0.905 -> 0.887 ms (downsample was slower as wave64).]
|
||||
# Every flag combination DLSS selects (motion vectors hi/lo, HDR/LDR, depth inverted/regular, ...) gets
|
||||
# the same treatment, so games other than the one measured run the native versions too.
|
||||
specs=()
|
||||
if [[ "$WHAT" != l ]]; then
|
||||
specs+=(rrlite_dec0_4x4_folded:dec0_head)
|
||||
for mv in mvhi mvlo; do
|
||||
for range in hdr ldr; do
|
||||
specs+=("rrlite_enc0_4x4_${mv}_${range}_folded:enc0_tail")
|
||||
for v in 3_1 3_2; do specs+=("rrlite_post_${v}_${mv}_${range}_folded:sust_only:w64"); done
|
||||
for depth in depthinv depthreg; do
|
||||
for kind in "" _max; do
|
||||
[[ -z "$kind" ]] && specs+=("hiluma_engine_output_${depth}_${mv}_${range}_v1_rel:sust_only:w64")
|
||||
specs+=("hiluma_engine_output_${depth}_${mv}_${range}${kind}_v2_rel:sust_only:w64")
|
||||
done
|
||||
done
|
||||
done
|
||||
done
|
||||
fi
|
||||
# L's unfolded variants keep their complete neural arithmetic. The folded M
|
||||
# GEMM heads/tails are incompatible; substitute only the surface stores.
|
||||
specs+=(rrlite_dec0_4x4:sust_only)
|
||||
specs=(rrlite_dec0_4x4_folded:dec0_head)
|
||||
for mv in mvhi mvlo; do
|
||||
for range in hdr ldr; do
|
||||
specs+=("rrlite_enc0_4x4_${mv}_${range}:sust_only")
|
||||
for v in 3_1 3_2; do specs+=("rrlite_post_${v}_${mv}_${range}:sust_only:w64"); done
|
||||
specs+=("rrlite_enc0_4x4_${mv}_${range}_folded:enc0_tail")
|
||||
for v in 3_1 3_2; do specs+=("rrlite_post_${v}_${mv}_${range}_folded:sust_only:w64"); done
|
||||
for depth in depthinv depthreg; do
|
||||
for kind in "" _max; do
|
||||
[[ -z "$kind" ]] && specs+=("hiluma_engine_output_${depth}_${mv}_${range}_v1_rel:sust_only:w64")
|
||||
specs+=("hiluma_engine_output_${depth}_${mv}_${range}${kind}_v2_rel:sust_only:w64")
|
||||
done
|
||||
done
|
||||
done
|
||||
done
|
||||
for mode in static dynamic; do
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
// Device-only Windows HIP build shim. The diagnostic/kernel code objects are
|
||||
// compiled with -nogpuinc/-nogpulib; no host HIP or C++ standard library is
|
||||
// needed. Definitions below mirror public HIP qualifiers and LLVM AMDGPU
|
||||
// work-item/grid builtins. The normal Linux HIP include path is untouched.
|
||||
#define __device__ __attribute__((device))
|
||||
#define __host__ __attribute__((host))
|
||||
#define __global__ __attribute__((global))
|
||||
#define __shared__ __attribute__((shared))
|
||||
#define __constant__ __attribute__((constant))
|
||||
#define __forceinline__ __attribute__((always_inline)) inline
|
||||
#define __launch_bounds__(n) __attribute__((amdgpu_flat_work_group_size(1, n)))
|
||||
|
||||
struct d4r_device_dim3 { unsigned x, y, z; };
|
||||
// Public HIP vector storage ABI used by M's packed global/LDS accesses.
|
||||
struct alignas(8) uint2 { unsigned x, y; };
|
||||
struct alignas(16) uint4 { unsigned x, y, z, w; };
|
||||
static_assert(sizeof(uint2) == 8 && alignof(uint2) == 8, "HIP uint2 ABI");
|
||||
static_assert(sizeof(uint4) == 16 && alignof(uint4) == 16, "HIP uint4 ABI");
|
||||
__device__ __forceinline__ d4r_device_dim3 d4r_thread_idx() {
|
||||
return {__builtin_amdgcn_workitem_id_x(), __builtin_amdgcn_workitem_id_y(),
|
||||
__builtin_amdgcn_workitem_id_z()};
|
||||
}
|
||||
__device__ __forceinline__ d4r_device_dim3 d4r_block_idx() {
|
||||
return {__builtin_amdgcn_workgroup_id_x(), __builtin_amdgcn_workgroup_id_y(),
|
||||
__builtin_amdgcn_workgroup_id_z()};
|
||||
}
|
||||
__device__ __forceinline__ d4r_device_dim3 d4r_grid_dim() {
|
||||
return {__builtin_amdgcn_grid_size_x() / __builtin_amdgcn_workgroup_size_x(),
|
||||
__builtin_amdgcn_grid_size_y() / __builtin_amdgcn_workgroup_size_y(),
|
||||
__builtin_amdgcn_grid_size_z() / __builtin_amdgcn_workgroup_size_z()};
|
||||
}
|
||||
#define threadIdx d4r_thread_idx()
|
||||
#define blockIdx d4r_block_idx()
|
||||
#define gridDim d4r_grid_dim()
|
||||
__device__ __attribute__((convergent, always_inline)) inline void __syncthreads() {
|
||||
// Match HIP __work_group_barrier: execution rendezvous alone does not
|
||||
// publish LDS stores or prevent loads moving across the barrier.
|
||||
__builtin_amdgcn_fence(__ATOMIC_RELEASE, "workgroup");
|
||||
__builtin_amdgcn_s_barrier();
|
||||
__builtin_amdgcn_fence(__ATOMIC_ACQUIRE, "workgroup");
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
// d4r's gfx11 interface: each lane supplies 16 FP16 K operands. The 8 FP32
|
||||
// outputs have logical row 2*i + lane/16 and logical column lane%16.
|
||||
// gfx12 consumes only 8 FP16 operands per lane and emits 8 consecutive rows
|
||||
// per lane group. Keep the old interface for K/M/texture call sites until their
|
||||
// surrounding fragments are ported and validated individually.
|
||||
using d4r_wmma_h16 = _Float16 __attribute__((ext_vector_type(16)));
|
||||
using d4r_wmma_h8 = _Float16 __attribute__((ext_vector_type(8)));
|
||||
using d4r_wmma_f8 = float __attribute__((ext_vector_type(8)));
|
||||
|
||||
__attribute__((device, always_inline)) static inline float d4r_wmma_other_half(float value)
|
||||
{
|
||||
const uint32_t bits = __builtin_bit_cast(uint32_t, value);
|
||||
const uint32_t swapped = __builtin_amdgcn_permlanex16(
|
||||
bits, bits, 0x76543210u, 0xfedcba98u, false, false);
|
||||
return __builtin_bit_cast(float, swapped);
|
||||
}
|
||||
|
||||
__attribute__((device, always_inline)) static inline d4r_wmma_f8
|
||||
d4r_wmma_legacy_layout(d4r_wmma_h16 a, d4r_wmma_h16 b, d4r_wmma_f8 c)
|
||||
{
|
||||
#if defined(__gfx1200__) || defined(__gfx1201__)
|
||||
const uint32_t group = __builtin_amdgcn_mbcnt_lo(~0u, 0u) >> 4;
|
||||
d4r_wmma_h8 a12, b12;
|
||||
d4r_wmma_f8 c12;
|
||||
d4r_wmma_f8 remote_c;
|
||||
#pragma unroll
|
||||
for (int i = 0; i < 8; ++i)
|
||||
remote_c[i] = d4r_wmma_other_half(c[i]);
|
||||
#pragma unroll
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
a12[i] = a[8u * group + i];
|
||||
b12[i] = b[8u * group + i];
|
||||
const uint32_t row = 8u * group + i;
|
||||
const uint32_t old_index = row >> 1;
|
||||
c12[i] = (row & 1u) == group ? c[old_index] : remote_c[old_index];
|
||||
}
|
||||
const d4r_wmma_f8 d12 = __builtin_amdgcn_wmma_f32_16x16x16_f16_w32_gfx12(a12, b12, c12);
|
||||
d4r_wmma_f8 result, remote_d;
|
||||
#pragma unroll
|
||||
for (int i = 0; i < 8; ++i)
|
||||
remote_d[i] = d4r_wmma_other_half(d12[i]);
|
||||
#pragma unroll
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
const uint32_t row = 2u * i + group;
|
||||
const uint32_t new_index = row & 7u;
|
||||
result[i] = (row >> 3) == group ? d12[new_index] : remote_d[new_index];
|
||||
}
|
||||
return result;
|
||||
#elif defined(__gfx1100__) || defined(__gfx1101__) || defined(__gfx1102__) || defined(__gfx1103__)
|
||||
return __builtin_amdgcn_wmma_f32_16x16x16_f16_w32(a, b, c);
|
||||
#else
|
||||
#error "d4r WMMA backend requires a validated gfx11 or gfx12 target"
|
||||
#endif
|
||||
}
|
||||
+1
-48
@@ -7,11 +7,7 @@
|
||||
// output channel wm_acc_row(i) of the 16-channel tile ("D^T"); ../common/wmma_layout.h has the per-target
|
||||
// register layouts (gfx11: channel 2i + (l >> 4); gfx12: channel i + 8 (l >> 4)).
|
||||
#pragma once
|
||||
#ifdef D4R_DEVICE_ONLY_MINIMAL
|
||||
#include "../common/hip_device_minimal.h"
|
||||
#else
|
||||
#include <hip/hip_runtime.h>
|
||||
#endif
|
||||
#include <stdint.h>
|
||||
#include "../common/wmma_layout.h"
|
||||
#if defined(D4R_ACCURACY) && defined(PWIN_F32ACC)
|
||||
@@ -33,55 +29,12 @@ __device__ __forceinline__ uint32_t lane_id()
|
||||
|
||||
typedef wm_op op_t; // WMMA operand: u8v (gfx11) or u4v (gfx12)
|
||||
|
||||
// The CUDA fragment reduction rounds products and every tree node to f16.
|
||||
// A float sum of the squared halves changes rsqrt and can amplify differences
|
||||
// through attention. The strict baseline preserves its channel grouping.
|
||||
template <int C>
|
||||
__device__ __forceinline__ half_t l2_sum(const half_t* row)
|
||||
{
|
||||
#ifdef D4R_K_FP16_BASELINE
|
||||
static_assert(C % 32 == 0 && C <= 160, "audited K channel counts");
|
||||
hv2 partial[4];
|
||||
#pragma unroll
|
||||
for (int t = 0; t < 4; ++t)
|
||||
{
|
||||
hv2 halves[2];
|
||||
#pragma unroll
|
||||
for (int parity = 0; parity < 2; ++parity)
|
||||
{
|
||||
hv2 pairs[C / 32];
|
||||
#pragma unroll
|
||||
for (int group = 0; group < C / 32; ++group)
|
||||
{
|
||||
const hv2 a = *(const hv2*)(row + 2 * t + 8 * parity + 32 * group);
|
||||
const hv2 b = *(const hv2*)(row + 2 * t + 8 * parity + 32 * group + 16);
|
||||
pairs[group] = (a * a) + (b * b);
|
||||
}
|
||||
hv2 value = pairs[0];
|
||||
if constexpr (C >= 64) value = value + pairs[1];
|
||||
if constexpr (C == 96) value = value + pairs[2];
|
||||
if constexpr (C >= 128) value = value + (pairs[2] + pairs[3]);
|
||||
if constexpr (C == 160) value = value + pairs[4];
|
||||
halves[parity] = value;
|
||||
}
|
||||
partial[t] = halves[0] + halves[1];
|
||||
}
|
||||
const hv2 sum = (partial[0] + partial[1]) + (partial[2] + partial[3]);
|
||||
return sum[0] + sum[1];
|
||||
#else
|
||||
float sum = 0;
|
||||
#pragma unroll
|
||||
for (int c = 0; c < C; ++c) sum += (float)(half_t)(row[c] * row[c]);
|
||||
return (half_t)sum;
|
||||
#endif
|
||||
}
|
||||
|
||||
// one k16 step with the f16 accumulator of NVIDIA's f16 wmma (rounded after the step)
|
||||
// PWIN_F32ACC: keep the accumulator in f32 through the chain (rounded to f16 where the values are used)
|
||||
__device__ __forceinline__ f8v mma16(const op_t& a, const op_t& b, f8v c)
|
||||
{
|
||||
f8v d = wm_mma(a, b, c);
|
||||
#if !defined(PWIN_F32ACC) || defined(D4R_K_FP16_BASELINE)
|
||||
#ifndef PWIN_F32ACC
|
||||
#pragma unroll
|
||||
for (int i = 0; i < 8; ++i)
|
||||
d[i] = (float)(half_t)d[i];
|
||||
|
||||
+11
-1
@@ -259,7 +259,17 @@ __device__ void pwin_core(const PwinParams& p, const u8v* __restrict__ img, cons
|
||||
|
||||
STAMP(1);
|
||||
// ---- L2 norm: h1 = x0 * (rsqrt(sum x0^2) * gamma1) -> hb
|
||||
const half_t ss = l2_sum<C>(arow);
|
||||
float ss = 0.0f;
|
||||
for (int kt = 0; kt < KT; ++kt)
|
||||
{
|
||||
const u8v xv = lds_row16(arow + 16 * kt);
|
||||
#pragma unroll
|
||||
for (int c = 0; c < 16; ++c)
|
||||
{
|
||||
const half_t v = op_get(xv, c);
|
||||
ss += (float)(half_t)(v * v);
|
||||
}
|
||||
}
|
||||
const half_t r16 = (half_t)__builtin_amdgcn_rsqf((float)(half_t)ss);
|
||||
for (int kt = (int)hf; kt < KT; kt += 2)
|
||||
{
|
||||
|
||||
+12
-1
@@ -19,7 +19,18 @@ __device__ void pos_core(const PwinParams& p, const u8v* __restrict__ img, const
|
||||
{
|
||||
const half_t* arow = act[16 * (wv * MT + mi) + m];
|
||||
half_t* hrow = hb[16 * (wv * MT + mi) + m];
|
||||
const half_t ss = l2_sum<C>(arow);
|
||||
float ss = 0.0f;
|
||||
#pragma unroll
|
||||
for (int kt = 0; kt < KT; ++kt)
|
||||
{
|
||||
const u8v xv = lds_row16(arow + 16 * kt);
|
||||
#pragma unroll
|
||||
for (int c = 0; c < 16; ++c)
|
||||
{
|
||||
const half_t v = op_get(xv, c);
|
||||
ss += (float)(half_t)(v * v);
|
||||
}
|
||||
}
|
||||
const half_t r16 = (half_t)__builtin_amdgcn_rsqf((float)(half_t)ss);
|
||||
for (int kt = (int)hf; kt < KT; kt += 2)
|
||||
{
|
||||
|
||||
+11
-1
@@ -38,7 +38,17 @@ __device__ void wide_core(const PwinParams& p, const u8v* __restrict__ img, cons
|
||||
half_t* hrow = hb[tok];
|
||||
|
||||
// ---- L2 norm: h1 = x0 * (rsqrt(sum x0^2) * gamma1) -> hb (all groups of a token tile compute the sum)
|
||||
const half_t ss = l2_sum<C>(arow);
|
||||
float ss = 0.0f;
|
||||
for (int kt = 0; kt < KT; ++kt)
|
||||
{
|
||||
const u8v xv = lds_row16(arow + 16 * kt);
|
||||
#pragma unroll
|
||||
for (int c = 0; c < 16; ++c)
|
||||
{
|
||||
const half_t v = op_get(xv, c);
|
||||
ss += (float)(half_t)(v * v);
|
||||
}
|
||||
}
|
||||
const half_t r16 = (half_t)__builtin_amdgcn_rsqf((float)(half_t)ss);
|
||||
for (int kt = 2 * g + (int)hf; kt < KT; kt += 2 * NG)
|
||||
{
|
||||
|
||||
+8
-72
@@ -5,24 +5,7 @@
|
||||
// CIN patch-expand input channels (0 = none): decoder input = f16(q8(expand(low-res CIN)) + skip)
|
||||
#pragma once
|
||||
#include "swin_common.h"
|
||||
#ifdef D4R_SWIN_DIAGNOSTICS
|
||||
extern "C" {
|
||||
__device__ int d4r_swin_debug_block[2] = {0, 4};
|
||||
__device__ half_t d4r_swin_debug_values[4 * 64 * 128];
|
||||
}
|
||||
template <int C, int AST> __device__ __forceinline__ void swin_snapshot(
|
||||
const half_t* activation, int stage, int bx, int by, int tid, bool pair_order) {
|
||||
if (bx == d4r_swin_debug_block[0] && by == d4r_swin_debug_block[1] && tid < 64)
|
||||
for (int c = 0; c < C; ++c)
|
||||
d4r_swin_debug_values[(stage * 64 + tid) * 128 + c] =
|
||||
activation[tid * AST + (pair_order ? gperm(c) : apos(c))];
|
||||
__syncthreads();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Immediate lane masks need only a compile-time integer, not the host C++
|
||||
// standard library (which is absent from device-only Windows HIP builds).
|
||||
template <int Mask> struct SwinLaneMask { static constexpr int value = Mask; };
|
||||
#include <type_traits>
|
||||
|
||||
template <int C, int NW, int NPM, int CIN = 0> struct SwinLayout
|
||||
{
|
||||
@@ -93,12 +76,7 @@ __device__ __forceinline__ void swin_block(const CommonParams& p, const TubePara
|
||||
static_assert(NW % NH == 0 && NW / NH <= 4, "waves per head");
|
||||
constexpr int NPL = L::NPL, NT = L::NT, INNER = L::INNER;
|
||||
constexpr int AST = (C > INNER ? C : INNER) + 8;
|
||||
constexpr int GST = 40, PST = 24;
|
||||
#ifdef SWIN_EXACT_PV
|
||||
constexpr int QST = 56;
|
||||
#else
|
||||
constexpr int QST = 40;
|
||||
#endif
|
||||
constexpr int GST = 40, QST = 40, PST = 24;
|
||||
#ifndef SWIN_MLP_R
|
||||
#define SWIN_MLP_R(NW) ((NW) / 2)
|
||||
#endif
|
||||
@@ -313,9 +291,6 @@ __device__ __forceinline__ void swin_block(const CommonParams& p, const TubePara
|
||||
__syncthreads();
|
||||
|
||||
// ------------------------------------------------ stage 2: Q/V projection + attention (wave = head)
|
||||
#ifdef D4R_SWIN_DIAGNOSTICS
|
||||
swin_snapshot<C, AST>(A, 0, bx, by, tid, false);
|
||||
#endif
|
||||
{
|
||||
constexpr int MW = 4 / (NW / NH); // windows per wave
|
||||
const int h = NW == NH ? wv : wv % NH, m0 = NW == NH ? 0 : MW * (wv / NH);
|
||||
@@ -412,23 +387,16 @@ __device__ __forceinline__ void swin_block(const CommonParams& p, const TubePara
|
||||
auto xr = [](hv2 v, auto mask) {
|
||||
return __builtin_bit_cast(hv2, xor_lane<decltype(mask)::value>(__builtin_bit_cast(uint32_t, v)));
|
||||
};
|
||||
const hv2 v1 = wg[k] + xr(wg[k], SwinLaneMask<8>{});
|
||||
const hv2 v2 = v1 + xr(v1, SwinLaneMask<2>{});
|
||||
const hv2 v3 = v2 + xr(v2, SwinLaneMask<4>{});
|
||||
const hv2 sum = v3 + xr(v3, SwinLaneMask<1>{});
|
||||
const hv2 v1 = wg[k] + xr(wg[k], std::integral_constant<int, 8>{});
|
||||
const hv2 v2 = v1 + xr(v1, std::integral_constant<int, 2>{});
|
||||
const hv2 v3 = v2 + xr(v2, std::integral_constant<int, 4>{});
|
||||
const hv2 sum = v3 + xr(v3, std::integral_constant<int, 1>{});
|
||||
const hv2 rinv = {f16(1.0f / (float)sum[0]), f16(1.0f / (float)sum[1])};
|
||||
const hv2 pw = wg[k] * rinv;
|
||||
Qs[mrow(2 * k) * PST + l16] = pw[0];
|
||||
Qs[mrow(2 * k + 1) * PST + l16] = pw[1];
|
||||
}
|
||||
wave_sync();
|
||||
#ifdef SWIN_EXACT_PV
|
||||
half_t* Vs = Qs + 16 * PST;
|
||||
for (int nt = 0; nt < 2; ++nt)
|
||||
for (int i = 0; i < 8; ++i)
|
||||
Vs[mrow(i) * 32 + 16 * nt + l16] = vp[m][nt][i / 2][i & 1];
|
||||
wave_sync();
|
||||
#endif
|
||||
const sop pa = lds16(Qs + l16 * PST);
|
||||
#pragma unroll
|
||||
for (int nt = 0; nt < 2; ++nt)
|
||||
@@ -451,19 +419,7 @@ __device__ __forceinline__ void swin_block(const CommonParams& p, const TubePara
|
||||
}
|
||||
const h16 vb = __builtin_bit_cast(h16, vw);
|
||||
#endif
|
||||
f8v o = wmma(pa, vb, splat(0.0f));
|
||||
#ifdef SWIN_EXACT_PV
|
||||
// FP16 P/V products need more than 24 significant bits in a
|
||||
// cancellation-heavy dot. An f32 WMMA can round to the wrong
|
||||
// FP16 neighbour at a midpoint, then flip the FP8 output code.
|
||||
// This baseline sums the 16 FP16 products before one FP16 round.
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
double dot = 0.0;
|
||||
for (int k = 0; k < 16; ++k)
|
||||
dot += (double)Qs[mrow(i) * PST + k] * (double)Vs[k * 32 + 16 * nt + l16];
|
||||
o[i] = (float)(half_t)dot;
|
||||
}
|
||||
#endif
|
||||
const f8v o = wmma(pa, vb, splat(0.0f));
|
||||
const int ocol = apos(32 * h + ginv(16 * nt + l16));
|
||||
#pragma unroll
|
||||
for (int i = 0; i < 8; i += 2)
|
||||
@@ -479,9 +435,6 @@ __device__ __forceinline__ void swin_block(const CommonParams& p, const TubePara
|
||||
__syncthreads();
|
||||
|
||||
// ------------------------------------------------ stage 3: output projection + residual
|
||||
#ifdef D4R_SWIN_DIAGNOSTICS
|
||||
swin_snapshot<INNER, AST>(A, 3, bx, by, tid, false);
|
||||
#endif
|
||||
#pragma unroll
|
||||
for (int kc = 0; kc < NH; ++kc)
|
||||
{
|
||||
@@ -515,11 +468,6 @@ __device__ __forceinline__ void swin_block(const CommonParams& p, const TubePara
|
||||
if (tid < 64)
|
||||
{
|
||||
// the row as 128-bit LDS loads: element 8 j + c of n8 tile j
|
||||
#ifdef D4R_SWIN_DIAGNOSTICS
|
||||
if (bx == d4r_swin_debug_block[0] && by == d4r_swin_debug_block[1])
|
||||
for (int c = 0; c < C; ++c)
|
||||
d4r_swin_debug_values[(64 + tid) * 128 + c] = A[tid * AST + gperm(c)];
|
||||
#endif
|
||||
h8 row[L::NT8];
|
||||
#pragma unroll
|
||||
for (int j = 0; j < L::NT8; ++j)
|
||||
@@ -578,9 +526,6 @@ __device__ __forceinline__ void swin_block(const CommonParams& p, const TubePara
|
||||
__syncthreads();
|
||||
|
||||
// ------------------------------------------------ stage 4: MLP, R chunks per round
|
||||
#ifdef D4R_SWIN_DIAGNOSTICS
|
||||
swin_snapshot<C, AST>(A, 2, bx, by, tid, false);
|
||||
#endif
|
||||
{
|
||||
constexpr int WPC = NW / R, MPW = 4 / WPC; // waves per chunk, fc1 m tiles per wave
|
||||
const int cw = wv / WPC, mb = MPW * (wv % WPC);
|
||||
@@ -770,16 +715,7 @@ __device__ __forceinline__ void swin_block(const CommonParams& p, const TubePara
|
||||
if (MX < 0 || MX >= W2 || MY < 0 || MY >= H2)
|
||||
continue;
|
||||
p.p48[((size_t)((ch >> 5) * H2 + MY) * W2 + MX) * 32 + (ch & 31)] =
|
||||
(uint8_t)enc8(
|
||||
#ifdef SWIN_EXACT
|
||||
// The audited 310.9 patch-merge path bounds the f16
|
||||
// result to +/- 2*pi before e4m3 quantisation.
|
||||
__builtin_elementwise_max(__builtin_elementwise_min(y[j].get(i), (half_t)6.283185307179586f),
|
||||
(half_t)-6.283185307179586f)
|
||||
#else
|
||||
y[j].get(i)
|
||||
#endif
|
||||
);
|
||||
(uint8_t)enc8(y[j].get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,7 @@
|
||||
// D4R_FP8_WMMA (gfx12 layout only): the GEMMs whose operands are both e4m3 values run as native FP8 WMMAs on
|
||||
// e4m3 bytes (weights prepared as bytes, activations encoded when loaded); P V stays f16.
|
||||
#pragma once
|
||||
#ifdef D4R_DEVICE_ONLY_MINIMAL
|
||||
#include "../common/hip_device_minimal.h"
|
||||
#else
|
||||
#include <hip/hip_runtime.h>
|
||||
#endif
|
||||
#include <stdint.h>
|
||||
#include "../common/wmma_layout.h"
|
||||
#ifdef D4R_ACCURACY
|
||||
@@ -273,25 +269,11 @@ __device__ __forceinline__ sop lds16(const half_t* p)
|
||||
// e4m3 bytes of an f16 operand whose values are e4m3 values (K order kept: byte j = element j)
|
||||
__device__ __forceinline__ gop to_fp8(sop v)
|
||||
{
|
||||
#if defined(D4R_FP8_HW_PACK) && defined(__GFX12__)
|
||||
// Inputs already contain exact e4m3 values. gfx12's native packed convert
|
||||
// avoids the software subnormal/sign/exponent reconstruction below. Keep
|
||||
// opt-in until all finite encodings and layer/network outputs pass replay.
|
||||
gop result;
|
||||
#pragma unroll
|
||||
for (int word = 0; word < 2; ++word) {
|
||||
int packed = __builtin_amdgcn_cvt_pk_fp8_f32((float)v[4 * word], (float)v[4 * word + 1], 0, false);
|
||||
packed = __builtin_amdgcn_cvt_pk_fp8_f32((float)v[4 * word + 2], (float)v[4 * word + 3], packed, true);
|
||||
result[word] = (uint32_t)packed;
|
||||
}
|
||||
return result;
|
||||
#else
|
||||
uint32_t c[4];
|
||||
#pragma unroll
|
||||
for (int j = 0; j < 4; ++j)
|
||||
c[j] = codes8x2((hv2){v[2 * j], v[2 * j + 1]});
|
||||
return (u2v){__builtin_amdgcn_perm(c[1], c[0], 0x06040200u), __builtin_amdgcn_perm(c[3], c[2], 0x06040200u)};
|
||||
#endif
|
||||
}
|
||||
// f32 += A B over 16 K for e4m3 operands
|
||||
__device__ __forceinline__ f8v wmma8(gop a, gop b, f8v c)
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
#include "../../tools/d4r_motion_dilation.h"
|
||||
#pragma clang fp contract(off)
|
||||
|
||||
__attribute__((device)) static int clamp_index(int x, uint32_t limit)
|
||||
{
|
||||
return x < 0 ? 0 : x >= (int)limit ? (int)limit - 1 : x;
|
||||
}
|
||||
|
||||
extern "C" __attribute__((global, amdgpu_flat_work_group_size(128, 128)))
|
||||
void d4r_motion_dilate(D4rMotionDilationParams p)
|
||||
{
|
||||
const uint32_t id = __builtin_amdgcn_workgroup_id_x() * 128 + __builtin_amdgcn_workitem_id_x();
|
||||
if ((uint64_t)id >= (uint64_t)p.motion_width * p.motion_height)
|
||||
return;
|
||||
const uint32_t x = id % p.motion_width, y = id / p.motion_width;
|
||||
const int cx = clamp_index((int)__builtin_floorf((x + 0.5f) * p.render_width / p.motion_width + p.jitter_x), p.render_width);
|
||||
const int cy = clamp_index((int)__builtin_floorf((y + 0.5f) * p.render_height / p.motion_height + p.jitter_y), p.render_height);
|
||||
const uint8_t* depth = (const uint8_t*)p.depth;
|
||||
const uint8_t* motion = (const uint8_t*)p.motion;
|
||||
uint8_t* output = (uint8_t*)p.output;
|
||||
const float current = *(const float*)(depth + (uint64_t)cy * p.depth_pitch + 4 * cx);
|
||||
float best = current;
|
||||
int bx = cx, by = cy;
|
||||
for (int dy = -(int)p.radius; dy <= (int)p.radius; ++dy)
|
||||
for (int dx = -(int)p.radius; dx <= (int)p.radius; ++dx)
|
||||
{
|
||||
const int nx = clamp_index(cx + dx, p.render_width), ny = clamp_index(cy + dy, p.render_height);
|
||||
const float candidate = *(const float*)(depth + (uint64_t)ny * p.depth_pitch + 4 * nx);
|
||||
if (p.reversed_depth ? candidate > best : candidate < best)
|
||||
best = candidate, bx = nx, by = ny;
|
||||
}
|
||||
// Sky gaps need wider coverage than opaque edges. Select the closest
|
||||
// foreground pixel within eight render pixels, rather than the closest
|
||||
// depth (which can borrow a distant roof/chimney's velocity).
|
||||
const bool sky = p.reversed_depth ? current <= 1.e-7f : current >= 1.0f - 1.e-7f;
|
||||
bool sky_filled = false;
|
||||
if (sky)
|
||||
{
|
||||
int best_distance = 65;
|
||||
for (int dy = -8; dy <= 8; ++dy)
|
||||
for (int dx = -8; dx <= 8; ++dx)
|
||||
{
|
||||
const int nx = cx + dx, ny = cy + dy;
|
||||
const int distance = dx * dx + dy * dy;
|
||||
if (nx < 0 || ny < 0 || nx >= (int)p.render_width || ny >= (int)p.render_height ||
|
||||
distance > 64 || distance > best_distance)
|
||||
continue;
|
||||
const float candidate = *(const float*)(depth + (uint64_t)ny * p.depth_pitch + 4 * nx);
|
||||
const bool foreground = p.reversed_depth ? candidate > 1.e-7f : candidate < 1.0f - 1.e-7f;
|
||||
// Stable coordinate tie-break matches nearest-foreground fill.
|
||||
if (foreground && (distance < best_distance || nx < bx || (nx == bx && ny < by)))
|
||||
best_distance = distance, best = candidate, bx = nx, by = ny, sky_filled = true;
|
||||
}
|
||||
}
|
||||
uint32_t value = *(const uint32_t*)(motion + (uint64_t)y * p.motion_pitch + 4 * x);
|
||||
const bool nearer = p.reversed_depth ? best > current * 1.02f + 1.e-7f : best < current * 0.98f - 1.e-7f;
|
||||
if (sky_filled || nearer)
|
||||
{
|
||||
const int mx = clamp_index((int)__builtin_floorf((bx + 0.5f - p.jitter_x) * p.motion_width / p.render_width), p.motion_width);
|
||||
const int my = clamp_index((int)__builtin_floorf((by + 0.5f - p.jitter_y) * p.motion_height / p.render_height), p.motion_height);
|
||||
value = *(const uint32_t*)(motion + (uint64_t)my * p.motion_pitch + 4 * mx);
|
||||
}
|
||||
*(uint32_t*)(output + (uint64_t)y * p.motion_pitch + 4 * x) = value;
|
||||
}
|
||||
@@ -211,9 +211,7 @@ def replace_rz_round(lines):
|
||||
ENC0_REF = "rrlite_enc0_4x4_mvhi_hdr_folded"
|
||||
# the other flag combinations of the kernels whose only native part is the surface-store rewrite
|
||||
SUST_ONLY_RE = re.compile(r"^(hiluma_engine_output_depth(inv|reg)_mv(hi|lo)_(hdr|ldr)(_max)?_v[12]_rel|"
|
||||
r"rrlite_post_3_[12]_mv(hi|lo)_(hdr|ldr)(_folded)?|"
|
||||
r"rrlite_enc0_4x4_mv(hi|lo)_(hdr|ldr)|rrlite_dec0_4x4|"
|
||||
r"rrlite_downsample_kernel_(static|dynamic)_(hdr|ldr))$")
|
||||
r"rrlite_post_3_[12]_mv(hi|lo)_(hdr|ldr)_folded|rrlite_downsample_kernel_(static|dynamic)_(hdr|ldr))$")
|
||||
ENC0_RE = re.compile(r"^rrlite_enc0_4x4_mv(hi|lo)_(hdr|ldr)_folded$")
|
||||
|
||||
|
||||
|
||||
+8
-14
@@ -96,8 +96,7 @@ def split_args(s):
|
||||
|
||||
|
||||
def parse_kernel(path, name):
|
||||
with open(path) as file:
|
||||
src = file.read()
|
||||
src = open(path).read()
|
||||
i = src.index('.entry ' + name + '(')
|
||||
j = src.find('\n.visible', i + 10)
|
||||
j = len(src) if j < 0 else j
|
||||
@@ -341,11 +340,12 @@ class Block:
|
||||
steps = 0
|
||||
while pc < len(stmts):
|
||||
st = stmts[pc]
|
||||
if pc in pending:
|
||||
active |= pending.pop(pc)
|
||||
for l in st.labels:
|
||||
if l in pending:
|
||||
active |= pending.pop(l)
|
||||
if not active.any():
|
||||
# Reconverge at either a forward target or a loop fallthrough.
|
||||
targets = sorted(pending)
|
||||
# jump to the nearest label someone waits at
|
||||
targets = sorted(self.labels[l] for l in pending)
|
||||
if not targets:
|
||||
break
|
||||
pc = targets[0]
|
||||
@@ -365,16 +365,10 @@ class Block:
|
||||
if not taking.any():
|
||||
pc += 1
|
||||
continue
|
||||
# Lanes finishing a loop wait at its fallthrough while the
|
||||
# other lanes execute their remaining iterations. Their
|
||||
# registers and memory writes stay masked until reconvergence.
|
||||
pending[pc + 1] = pending.get(pc + 1, np.zeros(self.T, bool)) | (active & ~taking)
|
||||
active = taking.copy()
|
||||
pc = target
|
||||
continue
|
||||
raise NotImplementedError('divergent backward branch at ' + st.text)
|
||||
active = active & ~taking
|
||||
if taking.any():
|
||||
pending[target] = pending.get(target, np.zeros(self.T, bool)) | taking
|
||||
pending[st.args[0]] = pending.get(st.args[0], np.zeros(self.T, bool)) | taking
|
||||
pc += 1
|
||||
continue
|
||||
if mask.any() or st.op.startswith(('mma', 'shfl', 'movmatrix', 'bar')):
|
||||
|
||||
@@ -6,38 +6,6 @@ import numpy as np
|
||||
|
||||
F16, F32, F64 = np.float16, np.float32, np.float64
|
||||
|
||||
# Legacy replay reports retain their original approximate norm by default.
|
||||
# Windows strict K validation selects the PTX fragment's rounded reduction.
|
||||
NORM_FRAGMENT_ORDER = False
|
||||
|
||||
|
||||
def norm_rows(x):
|
||||
if not NORM_FRAGMENT_ORDER:
|
||||
return f16(np.sum(x.astype(F32).astype(F64) ** 2, axis=1))
|
||||
tokens, channels = x.shape
|
||||
assert channels in (32, 64, 96, 128, 160)
|
||||
square = f16(x.astype(F64) ** 2)
|
||||
partial = []
|
||||
for lane in range(4):
|
||||
halves = []
|
||||
for parity in range(2):
|
||||
pairs = [add16(square[:, 2 * lane + 8 * parity + 32 * g:2 * lane + 8 * parity + 32 * g + 2],
|
||||
square[:, 2 * lane + 8 * parity + 32 * g + 16:2 * lane + 8 * parity + 32 * g + 18])
|
||||
for g in range(channels // 32)]
|
||||
value = pairs[0]
|
||||
if channels >= 64:
|
||||
value = add16(value, pairs[1])
|
||||
if channels == 96:
|
||||
value = add16(value, pairs[2])
|
||||
if channels >= 128:
|
||||
value = add16(value, add16(pairs[2], pairs[3]))
|
||||
if channels == 160:
|
||||
value = add16(value, pairs[4])
|
||||
halves.append(value)
|
||||
partial.append(add16(halves[0], halves[1]))
|
||||
total = add16(add16(partial[0], partial[1]), add16(partial[2], partial[3]))
|
||||
return add16(total[:, 0], total[:, 1])
|
||||
|
||||
|
||||
class Dump:
|
||||
def __init__(self, d):
|
||||
@@ -213,7 +181,7 @@ def pwin_enc2(P, bx, by, pm_order=((0, 0), (0, 1), (1, 0), (1, 1)), b2_in_c0=Tru
|
||||
"""logical enc2 layer (H 2, C 64 -> merged 96) for one window; returns (y [64 x 64], merged [16 x 96])"""
|
||||
C, H, Dh = 64, 2, 32
|
||||
x0 = load_window(P, bx, by, C)
|
||||
s = norm_rows(x0)
|
||||
s = f16(np.sum(x0.astype(F32).astype(F64) ** 2, axis=1))
|
||||
r = rsqrt16(s)
|
||||
h1 = mul16(x0, mul16(r[:, None], vec(P, 0, C)[None, :]))
|
||||
Os = []
|
||||
@@ -280,7 +248,7 @@ def swin_core(P, L, x0, dbg=None, posattn=False):
|
||||
"""norm -> attention -> Wo + residual -> MLP (+ residual): the block output y [64 x C]"""
|
||||
C, H = L.C, L.H
|
||||
NCH = C // 32
|
||||
s = norm_rows(x0)
|
||||
s = f16(np.sum(x0.astype(F32).astype(F64) ** 2, axis=1))
|
||||
r = rsqrt16(s)
|
||||
h1 = mul16(x0, mul16(r[:, None], vec(P, L.G1, C)[None, :]))
|
||||
Os = []
|
||||
|
||||
@@ -23,10 +23,6 @@ import swin_model as sm # noqa: E402
|
||||
LAYERS = {'enc1': (64, 2, 96, 0), 'enc2': (96, 4, 128, 0), 'enc3_tube': (128, 4, 0, 0), 'dec2': (96, 4, 0, 128),
|
||||
'dec1': (64, 2, 0, 96)}
|
||||
|
||||
# Preserve the existing Linux oracle by default; Windows strict replay enables
|
||||
# the audited 310.9 f16 patch-merge bound before final e4m3 quantisation.
|
||||
CLAMP_PATCH_MERGE = False
|
||||
|
||||
|
||||
def blocks(grid, n, seed=1):
|
||||
gx, gy = grid[0], grid[1]
|
||||
@@ -51,11 +47,7 @@ def model_block(P, layer, bx, by):
|
||||
Wg = me.E4M3[P.wbuf[sm.woff_table(PM0 + g * 128 * C, 512, 64 * C, 4 * C, 32)]]
|
||||
bias = P.f16vec(PM0 + 4 * C * NPM + 64 * g, 32)
|
||||
cols.append(me.mma_chain(A, Wg, C0=np.broadcast_to(bias, (16, 32)).copy()))
|
||||
merged = sm.pair_to_nat(np.concatenate(cols, axis=1))
|
||||
if CLAMP_PATCH_MERGE:
|
||||
limit = me.f16(2 * np.pi)
|
||||
merged = np.clip(merged, -limit, limit)
|
||||
merged = me.q8(merged)
|
||||
merged = me.q8(sm.pair_to_nat(np.concatenate(cols, axis=1)))
|
||||
return out, merged
|
||||
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ def layout(C, heads, pre=0):
|
||||
L['end'] = M0 + 66 * C + 64 + (64 * C + 64) * (NC - 1)
|
||||
return L
|
||||
|
||||
def swin_block(P, x0, C, heads, pre=0, stages=None):
|
||||
def swin_block(P, x0, C, heads, pre=0):
|
||||
"""x0: 64 x C natural (f16 values). Returns (x2 pair order, out natural fp8 values)."""
|
||||
fp8 = lambda T: E4M3[P.wbuf[T]]
|
||||
L = layout(C, heads, pre)
|
||||
@@ -116,8 +116,6 @@ def swin_block(P, x0, C, heads, pre=0, stages=None):
|
||||
Wo[32 * s:32 * s + 32] = woff_table(L['head'][s] + 64 * C + 512, 0, 512, 32, C)
|
||||
x1 = mma_chain(pair_to_nat(q8(O)), fp8(Wo), C0=f16(nat_to_pair(x0) + P.f16vec(L['bo'], C)[None, :]))
|
||||
h2 = pair_to_nat(rms_from_sum(x1, tree_norm2(x1), P.f16vec(L['g2'], C)))
|
||||
if stages is not None:
|
||||
stages.update(h1=h1, x1=pair_to_nat(x1), h2=h2, O=pair_to_nat(q8(O)))
|
||||
x2 = f16(x1 + P.f16vec(L['b2'], C)[None, :])
|
||||
idx = gp(np.arange(32))
|
||||
for c, B1 in enumerate(L['b1']):
|
||||
|
||||
@@ -37,7 +37,6 @@ Install
|
||||
1. Extract everything in this zip into the folder that holds the game's main .exe. For Unreal
|
||||
Engine games that is <game>/<Project>/Binaries/Win64/, next to <Project>-Win64-Shipping.exe.
|
||||
You get dxgi.dll, OptiScaler.ini, d3d12.dll, d3d12core.dll, this file and an d4r folder.
|
||||
When updating, replace both d3d12 DLLs with the shim: it requires their resource-lifetime extension.
|
||||
If the folder already has a dxgi.dll or OptiScaler.ini (another OptiScaler install), move those
|
||||
out of the way first. Also remove PROTON_USE_OPTISCALER from the game's launch options if you
|
||||
used GE-Proton's built-in OptiScaler: d4r brings its own.
|
||||
@@ -66,16 +65,10 @@ Settings
|
||||
start may compile a separate cache. Missing accuracy variants use translated kernels, never the
|
||||
fast native set. The mode aims to match NVIDIA's output, but 1:1 RTX image quality is not proven.
|
||||
- d4r/d4r.ini: d4r's settings for this game. The main one is the DLSS model:
|
||||
K (DLSS 4, default), E (DLSS 3 CNN, a little faster), M (DLSS 4.5), or L (DLSS 4.5
|
||||
for Ultra Performance, especially 4K; experimental). Set Model = L and select Ultra Performance
|
||||
in game or OptiScaler. Choosing the model does not change the render resolution.
|
||||
K (DLSS 4, default), E (DLSS 3 CNN, a little faster) or M (DLSS 4.5, slowest).
|
||||
- On RDNA4, [Kernels] NativeFp8 is on by default. It selects native FP8 WMMA and the matching
|
||||
gfx12-fp8 kernel folder; set it to false to use f16 widening. The FP8 path has not been tested
|
||||
on a real RDNA4 GPU.
|
||||
- If L/M shows extra trails in motion, try [Kernels] NativeSwinEncoders = false with
|
||||
PreferAccuracy = true. This uses original translated enc1/enc2 layers while retaining other
|
||||
native acceleration. It reduced trails in a captured Townfall sequence on RX 7700 XT and costs
|
||||
some GPU time. Restart after changing it; other scenes and RDNA4 hardware still need testing.
|
||||
- OptiScaler.ini: OptiScaler's settings, such as its fps overlay and the render resolution of each
|
||||
quality mode. Press Insert in game for OptiScaler's menu.
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ Third-party software, unmodified or patched by d4r:
|
||||
Licenses: d4r/licenses/ZLUDA-LICENSE-*.txt, d4r/licenses/LLVM-LICENSE.txt
|
||||
d3d12.dll, d3d12core.dll
|
||||
vkd3d-proton (GNU LGPL 2.1), https://github.com/HansKristian-Work/vkd3d-proton
|
||||
commit @VKD3D_COMMIT@ with d4r/source/patches/vkd3d-proton/0001 and 0002 applied,
|
||||
commit @VKD3D_COMMIT@ with d4r/source/patches/vkd3d-proton/0001 applied,
|
||||
built with its build-win64.txt cross file (meson --buildtype release).
|
||||
Licenses: d4r/licenses/vkd3d-proton-*.txt
|
||||
d4r/rocm/lib/ ROCm 7.2.4 runtime, unmodified, from AMD's Ubuntu 22.04 packages
|
||||
|
||||
+11
-24
@@ -3,19 +3,12 @@
|
||||
; that config/d4r.ini.default in the source repository lists for it, e.g. in the game's launch options.
|
||||
|
||||
[DLSS]
|
||||
; DLSS model recommendations:
|
||||
;
|
||||
; Model | DLSS | Recommended modes | Notes
|
||||
; ------+-------+---------------------------------+----------------------------------
|
||||
; K | 4 | DLAA / Quality / Balanced | Default; faster alternative to M/L
|
||||
; M | 4.5 | Performance | More demanding on RDNA3
|
||||
; L | 4.5 | Ultra Performance | Especially 4K; experimental
|
||||
; E | 3 CNN | Quality / Balanced / Performance | Legacy faster fallback
|
||||
; F | 3 CNN | DLAA / Ultra Performance | Deprecated; prefer K or L
|
||||
;
|
||||
; Select the upscaling mode separately in game/OptiScaler. These are recommendations;
|
||||
; other modes are allowed. L requires the L texture build.
|
||||
; game = use the game/OptiScaler choice. Benchmarks: D4R_README.txt.
|
||||
; Which DLSS network runs:
|
||||
; K DLSS 4 transformer (default)
|
||||
; E DLSS 3 CNN: a little faster, softer image
|
||||
; M DLSS 4.5 transformer: best image, much slower on RDNA3
|
||||
; game whatever the game (or OptiScaler's preset override) asks for
|
||||
; Frame rates measured on an RX 7700 XT at 2560x1440, Quality mode: see D4R_README.txt.
|
||||
Model = K
|
||||
; NVIDIA's library marks the image "DLSS SDK - DO NOT DISTRIBUTE" in this setup; d4r turns that off.
|
||||
; true shows it.
|
||||
@@ -32,9 +25,6 @@ FrameAge = 0
|
||||
; intermediate FP8/f16 rounding and denormal handling, and keeps NGX's synchronization calls.
|
||||
; Slower but aims to match RTX DLSS.
|
||||
PreferAccuracy = false
|
||||
; false reduces L/M motion trails by using original enc1/enc2 layers; other native kernels stay on.
|
||||
; Best paired with PreferAccuracy = true; costs some GPU time. Restart after changing this.
|
||||
NativeSwinEncoders = true
|
||||
; Hand-written RDNA3 and RDNA4 kernels for the DLSS 4 and 4.5 networks (d4r\kernels). Each one is used
|
||||
; only when your DLSS library's code for that kernel matches the code it was written for; otherwise
|
||||
; that kernel is compiled from NVIDIA's code instead, which is slower but still correct.
|
||||
@@ -45,13 +35,10 @@ NativeKernels = on
|
||||
NativeFp8 = on
|
||||
|
||||
[Interop]
|
||||
; Experimental thin-feature tracking: 0 = off; 1/2 = opaque-surface dilation radius.
|
||||
; Both enabled values fill sky gaps from the nearest foreground within 8 render pixels.
|
||||
; Requires render- or display-resolution, unjittered motion vectors and same-frame linear VRAM inputs. Restart to change.
|
||||
; Unsupported features keep original vectors; larger radii may affect disocclusion edges.
|
||||
MotionVectorDilation = 0
|
||||
; Keep inputs and output in video memory. Input and output readiness waits use CPU sleeps.
|
||||
; These keep DLSS's inputs and output in video memory and move the waits between the game and DLSS
|
||||
; onto the GPU. Leave them on unless you are chasing a problem.
|
||||
VramInterop = true
|
||||
GpuWait = true
|
||||
LinearInputs = true
|
||||
ElideNgxSync = true
|
||||
; Lets DLSS's last kernel write straight into the game's output buffer (DLSS 4 / K only). It takes
|
||||
@@ -72,5 +59,5 @@ Profile = false
|
||||
|
||||
[Env]
|
||||
; Extra environment variables for d4r, NAME = value.
|
||||
; Sleeping event queries are the default; 0 restores context waits for an A/B comparison.
|
||||
; D4R_SHIM_BLOCKING_SYNC = 0
|
||||
; Use a blocking CUDA event while waiting for DLSS output.
|
||||
; D4R_SHIM_BLOCKING_SYNC = 1
|
||||
@@ -1,328 +0,0 @@
|
||||
From 33bbac250ac7e2ca4eff67e8817ada9dad578208 Mon Sep 17 00:00:00 2001
|
||||
From: Codex <codex@localhost>
|
||||
Date: Thu, 1 Oct 2026 16:10:08 +0300
|
||||
Subject: [PATCH] Support explicitly selected Windows d4r DLSS backend on
|
||||
physical AMD adapters
|
||||
|
||||
---
|
||||
OptiScaler/Config.cpp | 3 ++
|
||||
OptiScaler/Config.h | 1 +
|
||||
OptiScaler/OptiScaler.vcxproj | 4 +--
|
||||
OptiScaler/State.h | 1 +
|
||||
OptiScaler/Util.cpp | 12 +++++--
|
||||
OptiScaler/dllmain.cpp | 33 +++++++++++++++++--
|
||||
OptiScaler/hooks/LibraryLoad_Hooks.cpp | 17 ++++++++++
|
||||
OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp | 17 ++++++++++
|
||||
OptiScaler/misc/IdentifyGpu.cpp | 20 +++++++++++
|
||||
OptiScaler/proxies/NVNGX_Proxy.h | 5 ++-
|
||||
OptiScaler/upscalers/FeatureProvider_Dx12.cpp | 10 ++++++
|
||||
11 files changed, 115 insertions(+), 8 deletions(-)
|
||||
|
||||
diff --git a/OptiScaler/Config.cpp b/OptiScaler/Config.cpp
|
||||
index d8c62d3..f444387 100644
|
||||
--- a/OptiScaler/Config.cpp
|
||||
+++ b/OptiScaler/Config.cpp
|
||||
@@ -323,6 +323,7 @@ bool Config::Reload(std::filesystem::path iniPath)
|
||||
{
|
||||
// Don't enable again if set false because of no nvngx found
|
||||
DLSSEnabled.set_from_config(readBool("DLSS", "Enabled"));
|
||||
+ DLSSAllowExternalBackend.set_from_config(readBool("DLSS", "AllowExternalBackend"));
|
||||
UseGenericAppIdWithDlss.set_from_config(readBool("DLSS", "UseGenericAppIdWithDlss"));
|
||||
|
||||
RenderPresetOverride.set_from_config(readBool("DLSS", "RenderPresetOverride"));
|
||||
@@ -1168,6 +1169,8 @@ bool Config::SaveIni()
|
||||
// DLSS
|
||||
{
|
||||
ini.SetValue("DLSS", "Enabled", GetBoolValue(Instance()->DLSSEnabled.value_for_config()).c_str());
|
||||
+ ini.SetValue("DLSS", "AllowExternalBackend",
|
||||
+ GetBoolValue(Instance()->DLSSAllowExternalBackend.value_for_config()).c_str());
|
||||
ini.SetValue("DLSS", "RenderPresetOverride",
|
||||
GetBoolValue(Instance()->RenderPresetOverride.value_for_config()).c_str());
|
||||
ini.SetValue("DLSS", "RenderPresetForAll",
|
||||
diff --git a/OptiScaler/Config.h b/OptiScaler/Config.h
|
||||
index 4ab62a4..0caf8c8 100644
|
||||
--- a/OptiScaler/Config.h
|
||||
+++ b/OptiScaler/Config.h
|
||||
@@ -275,6 +275,7 @@ class Config
|
||||
|
||||
// DLSS
|
||||
CustomOptional<bool> DLSSEnabled { true };
|
||||
+ CustomOptional<bool> DLSSAllowExternalBackend { false };
|
||||
CustomOptional<bool> RenderPresetOverride { false };
|
||||
CustomOptional<uint32_t> RenderPresetForAll { 0 };
|
||||
CustomOptional<uint32_t> RenderPresetDLAA { 0 };
|
||||
diff --git a/OptiScaler/OptiScaler.vcxproj b/OptiScaler/OptiScaler.vcxproj
|
||||
index 24ca80b..dfbfc97 100644
|
||||
--- a/OptiScaler/OptiScaler.vcxproj
|
||||
+++ b/OptiScaler/OptiScaler.vcxproj
|
||||
@@ -236,7 +236,7 @@
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
- <GenerateDebugInformation>false</GenerateDebugInformation>
|
||||
+ <GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableUAC>false</EnableUAC>
|
||||
<AdditionalDependencies>winhttp.lib;WindowsApp.lib;Dbghelp.lib;dxgi.lib;d3d11.lib;d3d12.lib;vulkan-1.lib;dxguid.lib;freetype.lib;ffx_fsr2_api_x64.lib;ffx_fsr2_api_dx11_x64.lib;ffx_fsr2_api_dx12_x64.lib;ffx_fsr2_api_vk_x64.lib;ffx_fsr2_212_api_dx12_x64.lib;ffx_fsr2_212_api_vk_x64.lib;ffx_fsr2_212_api_x64.lib;d3dcompiler.lib;ffx_backend_dx11_x64.lib;ffx_fsr3_x64.lib;ffx_frameinterpolation_x64.lib;ffx_fsr3upscaler_x64.lib;ffx_opticalflow_x64.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<ModuleDefinitionFile>Source.def</ModuleDefinitionFile>
|
||||
@@ -873,4 +873,4 @@ copy NUL "$(SolutionDir)x64\Release\a\!! EXTRACT ALL FILES TO GAME FOLDER !!" /Y
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
-</Project>
|
||||
\ No newline at end of file
|
||||
+</Project>
|
||||
diff --git a/OptiScaler/State.h b/OptiScaler/State.h
|
||||
index 42164de..84f8263 100644
|
||||
--- a/OptiScaler/State.h
|
||||
+++ b/OptiScaler/State.h
|
||||
@@ -265,6 +265,7 @@ class State
|
||||
int dlssgDetectedInterpolationCount = 0;
|
||||
|
||||
// DLSS
|
||||
+ bool dlssExternalBackend = false;
|
||||
bool dlssPresetsOverriddenExternally = false;
|
||||
bool dlssPresetsOverridenByOpti = false;
|
||||
uint32_t dlssRenderPresetExternal = 0;
|
||||
diff --git a/OptiScaler/Util.cpp b/OptiScaler/Util.cpp
|
||||
index f7ac6fa..7367a91 100644
|
||||
--- a/OptiScaler/Util.cpp
|
||||
+++ b/OptiScaler/Util.cpp
|
||||
@@ -486,8 +486,16 @@ bool Util::GetFileVersion(std::wstring dllPath, version_t* fileVersionOut, versi
|
||||
|
||||
bool Util::IsSubpath(const std::filesystem::path& path, const std::filesystem::path& base)
|
||||
{
|
||||
- auto rel = std::filesystem::relative(path, base);
|
||||
- auto first = *rel.begin();
|
||||
+ // Called by LdrLoadDll hooks: relative()/weakly_canonical() perform file
|
||||
+ // queries, can re-enter the hooks, and throw when an optional directory or
|
||||
+ // a bare DLL search name cannot be resolved. This is a lexical routing
|
||||
+ // check, not a filesystem access control decision.
|
||||
+ auto normalizedPath = std::filesystem::path(ToLower(path.lexically_normal().wstring()));
|
||||
+ auto normalizedBase = std::filesystem::path(ToLower(base.lexically_normal().wstring()));
|
||||
+ auto rel = normalizedPath.lexically_relative(normalizedBase);
|
||||
+ if (rel.empty())
|
||||
+ return false;
|
||||
+ const auto first = *rel.begin();
|
||||
return first != "." && first != "..";
|
||||
}
|
||||
|
||||
diff --git a/OptiScaler/dllmain.cpp b/OptiScaler/dllmain.cpp
|
||||
index cba14b1..b4b786e 100644
|
||||
--- a/OptiScaler/dllmain.cpp
|
||||
+++ b/OptiScaler/dllmain.cpp
|
||||
@@ -948,7 +948,7 @@ static void CheckWorkingMode()
|
||||
|
||||
// NVAPI
|
||||
// Doesn't seem to like GetModuleHandle for some reason, so call our load to make sure
|
||||
- if (GetDllNameWModule(&nvapiNamesW) != nullptr)
|
||||
+ if (!State::Instance().dlssExternalBackend && GetDllNameWModule(&nvapiNamesW) != nullptr)
|
||||
{
|
||||
// This hooks nvapi as well when possible
|
||||
auto nvapi64 = LibraryLoadHooks::LoadNvApi();
|
||||
@@ -1958,7 +1958,33 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserv
|
||||
return nvidiaDetected;
|
||||
};
|
||||
|
||||
- bool possibleNvidia = isNvidiaViaNvapi();
|
||||
+ // Opt in only to a configured, versioned Windows d4r backend. The
|
||||
+ // marker performs no HIP/NGX initialization under DLL_PROCESS_ATTACH.
|
||||
+ // GPU support is still checked by its public NGX requirements API.
|
||||
+ if (Config::Instance()->DLSSEnabled.value_or_default() &&
|
||||
+ Config::Instance()->DLSSAllowExternalBackend.value_or_default() &&
|
||||
+ Config::Instance()->NvngxPath.has_value())
|
||||
+ {
|
||||
+ auto backendPath = std::filesystem::path(Config::Instance()->NvngxPath.value()) / L"_nvngx.dll";
|
||||
+ if (backendPath.is_absolute())
|
||||
+ {
|
||||
+ auto module = NtdllProxy::LoadLibraryExW_Ldr(backendPath.c_str(), nullptr,
|
||||
+ LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
|
||||
+ if (module)
|
||||
+ {
|
||||
+ using BackendVersion = unsigned (*)();
|
||||
+ auto version = reinterpret_cast<BackendVersion>(
|
||||
+ KernelBaseProxy::GetProcAddress_()(module, "d4r_WindowsBackendVersion"));
|
||||
+ State::Instance().dlssExternalBackend = version && version() == 1;
|
||||
+ NtdllProxy::FreeLibrary_Ldr(module);
|
||||
+ }
|
||||
+ }
|
||||
+ if (State::Instance().dlssExternalBackend)
|
||||
+ spdlog::info("Validated external Windows d4r DLSS backend; retaining physical GPU identity");
|
||||
+ else
|
||||
+ spdlog::error("AllowExternalBackend requires an absolute NvngxPath containing Windows d4r ABI 1");
|
||||
+ }
|
||||
+ bool possibleNvidia = !State::Instance().dlssExternalBackend && isNvidiaViaNvapi();
|
||||
|
||||
spdlog::info("");
|
||||
spdlog::info("Check for DLSS files");
|
||||
@@ -1992,7 +2018,8 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserv
|
||||
State::Instance().NVNGX_DLSSG_Path = Util::FindFilePath(exePath, "nvngx_dlssg.dll");
|
||||
|
||||
// Not 100% accurate for Nvidia cards without DLSS
|
||||
- if (Config::Instance()->DLSSEnabled.value_or_default() && possibleNvidia)
|
||||
+ if (Config::Instance()->DLSSEnabled.value_or_default() &&
|
||||
+ (possibleNvidia || State::Instance().dlssExternalBackend))
|
||||
{
|
||||
if (State::Instance().NVNGX_DLSS_Path.has_value())
|
||||
{
|
||||
diff --git a/OptiScaler/hooks/LibraryLoad_Hooks.cpp b/OptiScaler/hooks/LibraryLoad_Hooks.cpp
|
||||
index 90bdb89..108ea5b 100644
|
||||
--- a/OptiScaler/hooks/LibraryLoad_Hooks.cpp
|
||||
+++ b/OptiScaler/hooks/LibraryLoad_Hooks.cpp
|
||||
@@ -46,6 +46,18 @@ HMODULE LibraryLoadHooks::LoadLibraryCheckW(std::wstring libName, LPCWSTR lpLibF
|
||||
{
|
||||
auto libNameA = wstring_to_string(libName);
|
||||
|
||||
+ // The external D3D12 shim itself uses a locally supplied CUDA NGX core.
|
||||
+ // Keep that exact absolute path distinct from game-facing NGX interception.
|
||||
+ if (State::Instance().dlssExternalBackend)
|
||||
+ {
|
||||
+ wchar_t corePath[32768] {};
|
||||
+ const auto length = GetEnvironmentVariableW(L"D4R_NGX_CORE", corePath, 32768);
|
||||
+ if (length && length < 32768 &&
|
||||
+ Util::ToLower(std::filesystem::path(libName).lexically_normal().wstring()) ==
|
||||
+ Util::ToLower(std::filesystem::path(corePath).lexically_normal().wstring()))
|
||||
+ return nullptr;
|
||||
+ }
|
||||
+
|
||||
#ifdef LOG_LIB_OPERATIONS
|
||||
LOG_TRACE("{}", libNameA);
|
||||
#endif
|
||||
@@ -132,6 +144,11 @@ HMODULE LibraryLoadHooks::LoadLibraryCheckW(std::wstring libName, LPCWSTR lpLibF
|
||||
// NvApi64.dll
|
||||
if (CheckDllNameW(&libName, &nvapiNamesW))
|
||||
{
|
||||
+ // An explicit external CUDA backend owns its NVAPI topology provider.
|
||||
+ // Returning OptiScaler's fake-NVAPI would replace that provider and can
|
||||
+ // recurse into DXGI while the NGX loader holds the loader lock.
|
||||
+ if (State::Instance().dlssExternalBackend)
|
||||
+ return nullptr; // Continue through the original loader.
|
||||
LOG_INFO("{} call!", libNameA);
|
||||
|
||||
return LibraryLoadHooks::LoadNvApi();
|
||||
diff --git a/OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp b/OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp
|
||||
index 68e1b42..89c9ba8 100644
|
||||
--- a/OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp
|
||||
+++ b/OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp
|
||||
@@ -687,6 +687,7 @@ static NVSDK_NGX_Result TryCreateOptiFeature(ID3D12GraphicsCommandList* InCmdLis
|
||||
}
|
||||
|
||||
// Assign handle
|
||||
+ const bool allocatedHandle = *OutHandle == nullptr;
|
||||
if (*OutHandle == nullptr)
|
||||
*OutHandle = new NVSDK_NGX_Handle { handleId };
|
||||
else
|
||||
@@ -705,6 +706,16 @@ static NVSDK_NGX_Result TryCreateOptiFeature(ID3D12GraphicsCommandList* InCmdLis
|
||||
}
|
||||
else
|
||||
{
|
||||
+ if (state.dlssExternalBackend && upscalerBackend == Upscaler::DLSS)
|
||||
+ {
|
||||
+ LOG_ERROR("External DLSS initialization failed; refusing FSR fallback");
|
||||
+ if (shouldRestoreSigs)
|
||||
+ D3D12Hooks::RestoreRoot(InCmdList);
|
||||
+ D3D12Hooks::SetRootSignatureTracking(true);
|
||||
+ Dx12Contexts.erase(handleId);
|
||||
+ if (allocatedHandle) { delete *OutHandle; *OutHandle = nullptr; }
|
||||
+ return NVSDK_NGX_Result_Fail;
|
||||
+ }
|
||||
LOG_ERROR("Feature '{}' initialization failed falling back to FSR 2.1.2", UpscalerDisplayName(upscalerBackend));
|
||||
state.newBackend = Upscaler::FSR21;
|
||||
state.changeBackend[handleId] = true;
|
||||
@@ -1027,6 +1038,12 @@ static NVSDK_NGX_Result TryEvaluateOptiFeature(ID3D12GraphicsCommandList* InCmdL
|
||||
// Fallback to FSR 2.1.2 if feature failed to initialize and user didn't explicitly request it
|
||||
if (!feature->IsInited() && cfg.Dx12Upscaler.value_or_default() != Upscaler::FSR21)
|
||||
{
|
||||
+ if (state.dlssExternalBackend && cfg.Dx12Upscaler.value_or_default() == Upscaler::DLSS)
|
||||
+ {
|
||||
+ LOG_ERROR("External DLSS feature is not initialized; refusing FSR fallback");
|
||||
+ D3D12Hooks::SetRootSignatureTracking(true);
|
||||
+ return NVSDK_NGX_Result_Fail;
|
||||
+ }
|
||||
LOG_WARN("Feature '{}' failed to initialize. Falling back to FSR 2.1.2", feature->Name());
|
||||
ImGui::InsertNotification({ ImGuiToastType::Warning, 10000, "Falling back to FSR 2.1.2" });
|
||||
|
||||
diff --git a/OptiScaler/misc/IdentifyGpu.cpp b/OptiScaler/misc/IdentifyGpu.cpp
|
||||
index 3877f9f..6c05904 100644
|
||||
--- a/OptiScaler/misc/IdentifyGpu.cpp
|
||||
+++ b/OptiScaler/misc/IdentifyGpu.cpp
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <proxies/D3d12_Proxy.h>
|
||||
#include "nvapi/NvApiTypes.h"
|
||||
#include <hooks/Amdxc64_Hooks.h>
|
||||
+#include <proxies/NVNGX_Proxy.h>
|
||||
|
||||
using Microsoft::WRL::ComPtr;
|
||||
|
||||
@@ -187,6 +188,25 @@ std::vector<GpuInformation> IdentifyGpu::checkGpuInfo()
|
||||
|
||||
for (auto& gpuInfo : localCachedInfo)
|
||||
{
|
||||
+ if (State::Instance().dlssExternalBackend && !gpuInfo.softwareAdapter)
|
||||
+ {
|
||||
+ // Query the configured D3D12 backend outside DLL_PROCESS_ATTACH.
|
||||
+ // Preserve vendor/device/architecture data obtained from DXGI.
|
||||
+ auto requirements = NVNGXProxy::D3D12_GetFeatureRequirements();
|
||||
+ ComPtr<IDXGIAdapter1> physicalAdapter;
|
||||
+ if (requirements && SUCCEEDED(factory->EnumAdapterByLuid(gpuInfo.luid, IID_PPV_ARGS(&physicalAdapter))))
|
||||
+ {
|
||||
+ NVSDK_NGX_FeatureDiscoveryInfo discovery {};
|
||||
+ discovery.SDKVersion = NVSDK_NGX_Version_API;
|
||||
+ discovery.FeatureID = NVSDK_NGX_Feature_SuperSampling;
|
||||
+ NVSDK_NGX_FeatureRequirement supported {};
|
||||
+ const auto result = requirements(physicalAdapter.Get(), &discovery, &supported);
|
||||
+ gpuInfo.dlssCapable = result == NVSDK_NGX_Result_Success &&
|
||||
+ supported.FeatureSupported == NVSDK_NGX_FeatureSupportResult_Supported;
|
||||
+ LOG_INFO("External D3D12 DLSS backend: {} support={} result={:X}",
|
||||
+ gpuInfo.name, gpuInfo.dlssCapable, (unsigned) result);
|
||||
+ }
|
||||
+ }
|
||||
if (gpuInfo.vendorId == VendorId::Nvidia)
|
||||
{
|
||||
queryNvapi(gpuInfo);
|
||||
diff --git a/OptiScaler/proxies/NVNGX_Proxy.h b/OptiScaler/proxies/NVNGX_Proxy.h
|
||||
index f6c08e5..7892009 100644
|
||||
--- a/OptiScaler/proxies/NVNGX_Proxy.h
|
||||
+++ b/OptiScaler/proxies/NVNGX_Proxy.h
|
||||
@@ -500,7 +500,10 @@ class NVNGXProxy
|
||||
{
|
||||
LOG_INFO("Getting nvngx method addresses");
|
||||
|
||||
- HookNgxApi(_module.dll);
|
||||
+ // The external bridge reports physical adapter support itself.
|
||||
+ // Do not overwrite its unsupported result with NVIDIA spoofing.
|
||||
+ if (!State::Instance().dlssExternalBackend)
|
||||
+ HookNgxApi(_module.dll);
|
||||
|
||||
_module.D3D11_Init =
|
||||
(PFN_D3D11_Init) KernelBaseProxy::GetProcAddress_()(_module.dll, "NVSDK_NGX_D3D11_Init");
|
||||
diff --git a/OptiScaler/upscalers/FeatureProvider_Dx12.cpp b/OptiScaler/upscalers/FeatureProvider_Dx12.cpp
|
||||
index d738498..1191b29 100644
|
||||
--- a/OptiScaler/upscalers/FeatureProvider_Dx12.cpp
|
||||
+++ b/OptiScaler/upscalers/FeatureProvider_Dx12.cpp
|
||||
@@ -42,6 +42,11 @@ bool FeatureProvider_Dx12::GetFeature(Upscaler upscaler, UINT handleId, NVSDK_NG
|
||||
break;
|
||||
|
||||
case Upscaler::DLSS:
|
||||
+ if (state.dlssExternalBackend && (!primaryGpu.dlssCapable || !state.NVNGX_DLSS_Path.has_value()))
|
||||
+ {
|
||||
+ LOG_ERROR("Configured external D3D12 DLSS backend is unavailable; refusing FSR fallback");
|
||||
+ return false;
|
||||
+ }
|
||||
if (primaryGpu.dlssCapable && state.NVNGX_DLSS_Path.has_value())
|
||||
{
|
||||
*feature = std::make_unique<DLSSFeatureDx12>(handleId, parameters);
|
||||
@@ -77,6 +82,11 @@ bool FeatureProvider_Dx12::GetFeature(Upscaler upscaler, UINT handleId, NVSDK_NG
|
||||
|
||||
if (!loaded)
|
||||
{
|
||||
+ if (state.dlssExternalBackend && upscaler == Upscaler::DLSS)
|
||||
+ {
|
||||
+ LOG_ERROR("External D3D12 DLSS module failed to load; refusing FSR fallback");
|
||||
+ return false;
|
||||
+ }
|
||||
// Fail after the constructor
|
||||
ImGui::InsertNotification({ ImGuiToastType::Warning, 10000, "Falling back to FSR 2.1.2" });
|
||||
*feature = std::make_unique<FSR2FeatureDx12_212>(handleId, parameters);
|
||||
--
|
||||
2.55.0.windows.5
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
From 7ed1f1d66b430ba35a197fbd9880cd6a76af8f06 Mon Sep 17 00:00:00 2001
|
||||
From: Codex <codex@localhost>
|
||||
Date: Thu, 1 Oct 2026 16:45:01 +0300
|
||||
Subject: [PATCH] Install the external d4r command backend before NGX
|
||||
initialization
|
||||
|
||||
---
|
||||
OptiScaler/hooks/D3D12_Hooks.cpp | 14 ++++++++++++++
|
||||
1 file changed, 14 insertions(+)
|
||||
|
||||
diff --git a/OptiScaler/hooks/D3D12_Hooks.cpp b/OptiScaler/hooks/D3D12_Hooks.cpp
|
||||
index 29c0bec..3b757be 100644
|
||||
--- a/OptiScaler/hooks/D3D12_Hooks.cpp
|
||||
+++ b/OptiScaler/hooks/D3D12_Hooks.cpp
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <proxies/IGDExt_Proxy.h>
|
||||
#include <proxies/Streamline_Proxy.h>
|
||||
#include <proxies/KernelBase_Proxy.h>
|
||||
+#include <proxies/NVNGX_Proxy.h>
|
||||
|
||||
#include <detours/detours.h>
|
||||
|
||||
@@ -2230,6 +2231,19 @@ static void HookToDevice(ID3D12Device* InDevice)
|
||||
if (Util::CheckForRealObject(__FUNCTION__, InDevice, (IUnknown**) &realDevice))
|
||||
pVTable = *(PVOID**) realDevice;
|
||||
|
||||
+ if (State::Instance().dlssExternalBackend)
|
||||
+ {
|
||||
+ // Capture command signatures/resources created before the game's NGX
|
||||
+ // Init. Install before adding OptiScaler's own method trampolines.
|
||||
+ using InstallCommandBackend = unsigned (*)(ID3D12Device*);
|
||||
+ auto install = reinterpret_cast<InstallCommandBackend>(KernelBaseProxy::GetProcAddress_()(
|
||||
+ NVNGXProxy::NVNGXModule(), "d4r_D3D12_InstallCommandBackend"));
|
||||
+ auto result = install ? install(realDevice ? realDevice : InDevice) : 0;
|
||||
+ LOG_INFO("External d4r command backend installed at device discovery: result={:X}", result);
|
||||
+ if (result != 1)
|
||||
+ LOG_ERROR("External d4r early command tracking failed; DLSS initialization must be checked");
|
||||
+ }
|
||||
+
|
||||
// hudless
|
||||
o_D3D12DeviceRelease = (PFN_Release) pVTable[2];
|
||||
o_CreateSampler = (PFN_CreateSampler) pVTable[22];
|
||||
--
|
||||
2.55.0.windows.5
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
From 09488c47855aadbf85f68ab8c72c0f3ac5f915e9 Mon Sep 17 00:00:00 2001
|
||||
From: Codex <codex@localhost>
|
||||
Date: Thu, 1 Oct 2026 17:01:32 +0300
|
||||
Subject: [PATCH] Preserve legacy ProjectID initialization in the external
|
||||
backend
|
||||
|
||||
---
|
||||
OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp | 6 ++++++
|
||||
1 file changed, 6 insertions(+)
|
||||
|
||||
diff --git a/OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp b/OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp
|
||||
index 89c9ba8..1f723f1 100644
|
||||
--- a/OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp
|
||||
+++ b/OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp
|
||||
@@ -313,6 +313,8 @@ NVSDK_NGX_API NVSDK_NGX_Result NVSDK_NGX_D3D12_Init_ProjectID(const char* InProj
|
||||
|
||||
if (result == NVSDK_NGX_Result_Success)
|
||||
NVNGXProxy::SetDx12Inited(true);
|
||||
+ else if (State::Instance().dlssExternalBackend)
|
||||
+ return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -343,6 +345,10 @@ NVSDK_NGX_API NVSDK_NGX_Result NVSDK_NGX_D3D12_Init_with_ProjectID(
|
||||
{
|
||||
LOG_FUNC();
|
||||
|
||||
+ if (State::Instance().dlssExternalBackend)
|
||||
+ return NVSDK_NGX_D3D12_Init_ProjectID(InProjectId, InEngineType, InEngineVersion,
|
||||
+ InApplicationDataPath, InDevice, InSDKVersion, InFeatureInfo);
|
||||
+
|
||||
LOG_INFO("InProjectId: {0}", InProjectId);
|
||||
LOG_INFO("InEngineType: {0}", (int) InEngineType);
|
||||
LOG_INFO("InEngineVersion: {0}", InEngineVersion);
|
||||
--
|
||||
2.55.0.windows.5
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
# Explicit Windows d4r backend
|
||||
|
||||
Base: OptiScaler `45a2001303ddff632e279f77aef85ceede5832cb` (2026-09-30).
|
||||
The modifications to OptiScaler are GPL-3.0, matching its upstream license.
|
||||
No NVIDIA DLL is supplied or patched.
|
||||
|
||||
`0001` introduces `[DLSS] AllowExternalBackend=true`, default false. Set
|
||||
`[Libraries] NvngxPath` to the absolute directory containing the Windows d4r
|
||||
shim as `_nvngx.dll`. Discovery requires its GPU-free ABI-1 marker. Physical
|
||||
adapter support is queried through the bridge's D3D12 requirements API after
|
||||
DLL startup; vendor, LUID and HIP architecture are retained.
|
||||
|
||||
The configured `D4R_NGX_CORE` absolute path bypasses game-facing NGX DLL
|
||||
redirection. The external backend loads its own NVAPI provider. An explicitly
|
||||
selected external DLSS backend reports initialization/load errors instead of
|
||||
silently switching to FSR. The patch fixes the upstream loader's filesystem
|
||||
exception by making IsSubpath a lexical check, and retains a PDB for diagnosis.
|
||||
|
||||
`0002` installs the command backend at D3D12 device discovery, before NGX Init
|
||||
and before OptiScaler's own method hooks. Command signatures created during
|
||||
game startup therefore retain their public argument descriptions for replay.
|
||||
The standalone runner's `-EarlyIndirectProbe` exercises this startup order.
|
||||
|
||||
`0003` preserves the Project ID and engine identity through OptiScaler's
|
||||
older `Init_with_ProjectID` entry point used by its XeSS/FSR inputs. It also
|
||||
propagates an explicit external backend initialization failure. Reproduce
|
||||
with the D3D12 runner's `-NgxAbi project-legacy`.
|
||||
|
||||
Build with Microsoft Build Tools 2022, v143 and Windows SDK 10.0.26100:
|
||||
|
||||
```powershell
|
||||
.\scripts\windows\build-optiscaler-windows.ps1
|
||||
```
|
||||
|
||||
The script clones the pinned source if absent, checks/applies the patch,
|
||||
initializes pinned submodules, invokes MSBuild and writes the DLL, PDB, license
|
||||
and build metadata into `dist/optiscaler-windows-d4r`. Existing source is not
|
||||
reset. The upstream packaging post-build event is disabled for this build.
|
||||
The official bootstrapper is https://aka.ms/vs/17/release/vs_buildtools.exe;
|
||||
install the `Microsoft.VisualStudio.Workload.VCTools`,
|
||||
`Microsoft.VisualStudio.Component.VC.Tools.x86.x64` and
|
||||
`Microsoft.VisualStudio.Component.Windows11SDK.26100` components. The audited
|
||||
local build uses MSVC 14.44.35207 / Build Tools 17.14.
|
||||
|
||||
For standalone verification add
|
||||
`-OptiScalerDll <absolute-dist-path>/OptiScaler.dll -CaptureExceptions`
|
||||
to the documented K/M runner. Its private INI selects DLSS, disables frame
|
||||
generation and keeps RestoreComputeSignature, RestoreGraphicSignature and
|
||||
ExtendedStateRestore false. Use `-RequireNativeNetwork`; finite output alone
|
||||
cannot distinguish DLSS from an unwanted FSR replacement.
|
||||
@@ -1,25 +0,0 @@
|
||||
From: d4r Windows port
|
||||
Subject: [PATCH] Backport AMD external-memory view ownership fix to ROCm 7.2
|
||||
|
||||
Upstream fix: ROCm/clr 529f6b1641de436dafbb095d5438b0fbf773765d
|
||||
(ROCm/rocm-systems #3528). Already present in current upstream.
|
||||
The constructor owns reference 1. MemObjMap does not retain the view;
|
||||
hipFree removes the entry and releases reference 1. The extra retain
|
||||
leaks the view and its parent imported allocation.
|
||||
|
||||
Hardware regression: d4r_d3d12_hip_interop_probe --interop-mode map
|
||||
--iterations 64. Windows SDK 7.2.60201 leaks one HANDLE / 256 KiB each
|
||||
cycle; tested TheRock 10.2.0a20260929 (HIP 7.17.26386) stays bounded.
|
||||
This patch is for rebuilding old runtime sources, never binary patching.
|
||||
|
||||
diff --git a/hipamd/src/hip_memory.cpp b/hipamd/src/hip_memory.cpp
|
||||
--- a/hipamd/src/hip_memory.cpp
|
||||
+++ b/hipamd/src/hip_memory.cpp
|
||||
@@ -179,7 +179,6 @@ hipError_t hipExternalMemoryGetMappedBuffer(void** devPtr, hipExternalMemory_t extMem,
|
||||
// Map the device memory to the user pointer
|
||||
*devPtr = reinterpret_cast<void*>(devMem->virtualAddress());
|
||||
amd::MemObjMap::AddMemObj(*devPtr, view);
|
||||
- view->retain();
|
||||
|
||||
HIP_RETURN(hipSuccess);
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
diff --git a/include/vkd3d_device_vkd3d_ext.idl b/include/vkd3d_device_vkd3d_ext.idl
|
||||
--- a/include/vkd3d_device_vkd3d_ext.idl
|
||||
+++ b/include/vkd3d_device_vkd3d_ext.idl
|
||||
@@ -120,6 +120,19 @@
|
||||
}
|
||||
|
||||
[
|
||||
+ uuid(4f8c9462-8197-4dc1-a584-cf44d9706db2),
|
||||
+ object,
|
||||
+ local,
|
||||
+ pointer_default(unique)
|
||||
+]
|
||||
+interface ID3D12DXVKInteropDeviceD4R2 : ID3D12DXVKInteropDeviceD4R
|
||||
+{
|
||||
+ /* Retain external resources until the allocator can safely reset or is
|
||||
+ * destroyed. Pending submissions already retain the allocator. */
|
||||
+ HRESULT RetainExternalResources(ID3D12CommandList *list, IUnknown *resources);
|
||||
+}
|
||||
+
|
||||
+[
|
||||
uuid(f3112584-41f9-348d-a59b-00b7e1d285d6),
|
||||
object,
|
||||
local,
|
||||
diff --git a/libs/vkd3d/command.c b/libs/vkd3d/command.c
|
||||
--- a/libs/vkd3d/command.c
|
||||
+++ b/libs/vkd3d/command.c
|
||||
@@ -2791,6 +2791,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
+HRESULT d3d12_command_list_retain_external_resources(struct d3d12_command_list *list, IUnknown *resources)
|
||||
+{
|
||||
+ struct d3d12_command_allocator *allocator = list->allocator;
|
||||
+ size_t i;
|
||||
+
|
||||
+ if (!list->is_recording || !allocator)
|
||||
+ return E_INVALIDARG;
|
||||
+ for (i = 0; i < allocator->external_resources_count; i++)
|
||||
+ if (allocator->external_resources[i] == resources)
|
||||
+ return S_OK;
|
||||
+ if (!vkd3d_array_reserve((void **)&allocator->external_resources, &allocator->external_resources_size,
|
||||
+ allocator->external_resources_count + 1, sizeof(*allocator->external_resources)))
|
||||
+ return E_OUTOFMEMORY;
|
||||
+ IUnknown_AddRef(resources);
|
||||
+ allocator->external_resources[allocator->external_resources_count++] = resources;
|
||||
+ return S_OK;
|
||||
+}
|
||||
+
|
||||
static bool d3d12_command_allocator_add_view(struct d3d12_command_allocator *allocator,
|
||||
struct vkd3d_view *view)
|
||||
{
|
||||
@@ -2858,6 +2876,10 @@
|
||||
struct d3d12_device *device = allocator->device;
|
||||
const struct vkd3d_vk_device_procs *vk_procs = &device->vk_procs;
|
||||
unsigned int i;
|
||||
+
|
||||
+ for (i = 0; i < allocator->external_resources_count; ++i)
|
||||
+ IUnknown_Release(allocator->external_resources[i]);
|
||||
+ allocator->external_resources_count = 0;
|
||||
|
||||
for (i = 0; i < allocator->buffer_view_count; ++i)
|
||||
{
|
||||
@@ -2993,6 +3015,7 @@
|
||||
d3d12_command_list_allocator_destroyed(allocator->current_command_list);
|
||||
|
||||
d3d12_command_allocator_free_resources(allocator);
|
||||
+ vkd3d_free(allocator->external_resources);
|
||||
vkd3d_free(allocator->buffer_views);
|
||||
vkd3d_free(allocator->views);
|
||||
vkd3d_free(allocator->pipelines);
|
||||
diff --git a/libs/vkd3d/device.c b/libs/vkd3d/device.c
|
||||
--- a/libs/vkd3d/device.c
|
||||
+++ b/libs/vkd3d/device.c
|
||||
@@ -4658,7 +4658,8 @@
|
||||
|| IsEqualGUID(riid, &IID_ID3D12DXVKInteropDevice1)
|
||||
|| IsEqualGUID(riid, &IID_ID3D12DXVKInteropDevice2)
|
||||
|| IsEqualGUID(riid, &IID_ID3D12DXVKInteropDevice3)
|
||||
- || IsEqualGUID(riid, &IID_ID3D12DXVKInteropDeviceD4R))
|
||||
+ || IsEqualGUID(riid, &IID_ID3D12DXVKInteropDeviceD4R)
|
||||
+ || IsEqualGUID(riid, &IID_ID3D12DXVKInteropDeviceD4R2))
|
||||
{
|
||||
d3d12_dxvk_interop_device_AddRef(&device->ID3D12DXVKInteropDevice_iface);
|
||||
*object = &device->ID3D12DXVKInteropDevice_iface;
|
||||
@@ -11380,7 +11381,7 @@
|
||||
}
|
||||
|
||||
extern CONST_VTBL struct ID3D12DeviceExt5Vtbl d3d12_device_vkd3d_ext_vtbl;
|
||||
-extern CONST_VTBL struct ID3D12DXVKInteropDeviceD4RVtbl d3d12_dxvk_interop_device_vtbl;
|
||||
+extern CONST_VTBL struct ID3D12DXVKInteropDeviceD4R2Vtbl d3d12_dxvk_interop_device_vtbl;
|
||||
extern CONST_VTBL struct ID3DLowLatencyDeviceVtbl d3d_low_latency_device_vtbl;
|
||||
extern CONST_VTBL struct IAmdExtAntiLagApiVtbl d3d_amd_ext_anti_lag_vtbl;
|
||||
|
||||
diff --git a/libs/vkd3d/device_vkd3d_ext.c b/libs/vkd3d/device_vkd3d_ext.c
|
||||
--- a/libs/vkd3d/device_vkd3d_ext.c
|
||||
+++ b/libs/vkd3d/device_vkd3d_ext.c
|
||||
@@ -1157,7 +1157,16 @@
|
||||
return d3d12_command_list_split_for_external_wait(cmd_list, (VkSemaphore)vk_semaphore, value);
|
||||
}
|
||||
|
||||
-CONST_VTBL struct ID3D12DXVKInteropDeviceD4RVtbl d3d12_dxvk_interop_device_vtbl =
|
||||
+static HRESULT STDMETHODCALLTYPE d3d12_dxvk_interop_device_RetainExternalResources(
|
||||
+ d3d12_dxvk_interop_device_iface *iface, ID3D12CommandList *list, IUnknown *resources)
|
||||
+{
|
||||
+ struct d3d12_command_list *cmd_list = d3d12_command_list_from_iface(list);
|
||||
+ if (!cmd_list || !resources)
|
||||
+ return E_INVALIDARG;
|
||||
+ return d3d12_command_list_retain_external_resources(cmd_list, resources);
|
||||
+}
|
||||
+
|
||||
+CONST_VTBL struct ID3D12DXVKInteropDeviceD4R2Vtbl d3d12_dxvk_interop_device_vtbl =
|
||||
{
|
||||
/* IUnknown methods */
|
||||
d3d12_dxvk_interop_device_QueryInterface,
|
||||
@@ -1192,6 +1201,9 @@
|
||||
|
||||
/* ID3D12DXVKInteropDeviceD4R methods */
|
||||
d3d12_dxvk_interop_device_SplitCommandListForExternalWait,
|
||||
+
|
||||
+ /* ID3D12DXVKInteropDeviceD4R2 methods */
|
||||
+ d3d12_dxvk_interop_device_RetainExternalResources,
|
||||
};
|
||||
|
||||
static inline struct d3d12_device *d3d12_device_from_ID3DLowLatencyDevice(d3d_low_latency_device_iface *iface)
|
||||
diff --git a/libs/vkd3d/vkd3d_private.h b/libs/vkd3d/vkd3d_private.h
|
||||
--- a/libs/vkd3d/vkd3d_private.h
|
||||
+++ b/libs/vkd3d/vkd3d_private.h
|
||||
@@ -2758,6 +2758,10 @@
|
||||
LONG refcount;
|
||||
LONG internal_refcount;
|
||||
|
||||
+ IUnknown **external_resources;
|
||||
+ size_t external_resources_size;
|
||||
+ size_t external_resources_count;
|
||||
+
|
||||
D3D12_COMMAND_LIST_TYPE type;
|
||||
|
||||
struct vkd3d_view **views;
|
||||
@@ -5474,7 +5478,9 @@
|
||||
HRESULT d3d12_command_list_split_for_external_wait(struct d3d12_command_list *list,
|
||||
VkSemaphore semaphore, uint64_t value);
|
||||
|
||||
-typedef ID3D12DXVKInteropDeviceD4R d3d12_dxvk_interop_device_iface;
|
||||
+HRESULT d3d12_command_list_retain_external_resources(struct d3d12_command_list *list, IUnknown *resources);
|
||||
+
|
||||
+typedef ID3D12DXVKInteropDeviceD4R2 d3d12_dxvk_interop_device_iface;
|
||||
|
||||
/* ID3DLowLatencyDevice */
|
||||
typedef ID3DLowLatencyDevice d3d_low_latency_device_iface;
|
||||
@@ -1,203 +0,0 @@
|
||||
Windows LLVM-MinGW build, portable helper assertions and optional diagnostic UI.
|
||||
Apply after upstream d4r patches 0002 through 0007, on ZLUDA ee2f25a.
|
||||
|
||||
diff --git a/ext/highs-sys/build.rs b/ext/highs-sys/build.rs
|
||||
--- a/ext/highs-sys/build.rs
|
||||
+++ b/ext/highs-sys/build.rs
|
||||
@@ -66,11 +66,12 @@
|
||||
let linux = target.contains("linux");
|
||||
let mingw = target.contains("pc-windows-gnu");
|
||||
if apple {
|
||||
println!("cargo:rustc-link-lib=dylib=c++");
|
||||
} else if linux || mingw {
|
||||
- println!("cargo:rustc-link-lib=dylib=stdc++");
|
||||
+ let cxx_library = env::var("CXXSTDLIB").unwrap_or_else(|_| "stdc++".to_owned());
|
||||
+ println!("cargo:rustc-link-lib=dylib={cxx_library}");
|
||||
}
|
||||
println!("cargo:rerun-if-changed=../HiGHS/highs/interfaces/highs_c_api.h");
|
||||
|
||||
true
|
||||
}
|
||||
diff --git a/ext/hip_runtime-sys/build.rs b/ext/hip_runtime-sys/build.rs
|
||||
--- a/ext/hip_runtime-sys/build.rs
|
||||
+++ b/ext/hip_runtime-sys/build.rs
|
||||
@@ -1,8 +1,12 @@
|
||||
fn main() {
|
||||
if cfg!(windows) {
|
||||
- println!("cargo:rustc-link-arg=/DELAYLOAD:amdhip64_7.dll");
|
||||
+ if std::env::var("CARGO_CFG_TARGET_ENV").as_deref() == Ok("gnu") {
|
||||
+ println!("cargo:rustc-link-arg=-Wl,--delayload=amdhip64_7.dll");
|
||||
+ } else {
|
||||
+ println!("cargo:rustc-link-arg=/DELAYLOAD:amdhip64_7.dll");
|
||||
+ }
|
||||
} else {
|
||||
println!("cargo:rustc-link-lib=dylib=amdhip64");
|
||||
println!("cargo:rustc-link-search=native=/opt/rocm/lib/");
|
||||
}
|
||||
}
|
||||
diff --git a/llvm_zluda/build.rs b/llvm_zluda/build.rs
|
||||
--- a/llvm_zluda/build.rs
|
||||
+++ b/llvm_zluda/build.rs
|
||||
@@ -19,10 +19,18 @@
|
||||
|
||||
fn main() {
|
||||
let bc_path1 = "src/device-libs/ockl.bc";
|
||||
println!("cargo:rerun-if-changed={}", bc_path1);
|
||||
check_lfs_file(bc_path1);
|
||||
+
|
||||
+ println!("cargo:rerun-if-env-changed=ZLUDA_LLVM_BUILD_DIR");
|
||||
+ if let Some(directory) = env::var_os("ZLUDA_LLVM_BUILD_DIR") {
|
||||
+ let llvm_build_path = PathBuf::from(directory);
|
||||
+ println!("cargo:build-path={}", llvm_build_path.display());
|
||||
+ link_prebuilt_llvm(&llvm_build_path);
|
||||
+ return;
|
||||
+ }
|
||||
|
||||
let mut cmake = Config::new(r"../ext/llvm-project/llvm");
|
||||
try_use_sccache(&mut cmake);
|
||||
try_use_ninja(&mut cmake);
|
||||
cmake
|
||||
@@ -57,16 +65,31 @@
|
||||
cmake.build();
|
||||
}
|
||||
let cmake_profile = cmake.get_profile();
|
||||
let llvm_build_path = get_llvm_build_path(&llvm_dir, cmake_profile);
|
||||
println!("cargo:build-path={}", llvm_build_path.display());
|
||||
+ link_prebuilt_llvm(&llvm_build_path);
|
||||
+}
|
||||
+
|
||||
+fn link_prebuilt_llvm(llvm_build_path: &PathBuf) {
|
||||
let (cxxflags, ldflags, libdir, lib_names, system_libs) =
|
||||
llvm_config(&llvm_build_path.join("bin/llvm-config")).unwrap();
|
||||
println!("cargo:rustc-link-arg={ldflags}");
|
||||
println!("cargo:rustc-link-search=native={libdir}");
|
||||
for lib in system_libs.split_ascii_whitespace() {
|
||||
- println!("cargo:rustc-link-arg={lib}");
|
||||
+ if let Some(name) = lib.strip_prefix("-l") {
|
||||
+ println!("cargo:rustc-link-lib={name}");
|
||||
+ } else {
|
||||
+ println!("cargo:rustc-link-arg={lib}");
|
||||
+ }
|
||||
+ }
|
||||
+ // llvm-config omits system libraries on Windows. Propagate these through
|
||||
+ // this rlib to its final cdylib consumer (link-arg alone cannot do that).
|
||||
+ if env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("windows") {
|
||||
+ for library in ["psapi", "shell32", "ole32", "uuid", "advapi32", "ws2_32", "ntdll"] {
|
||||
+ println!("cargo:rustc-link-lib={library}");
|
||||
+ }
|
||||
}
|
||||
link_lld_components();
|
||||
link_llvm_components(lib_names);
|
||||
compile_cxx_lib(cxxflags, &llvm_build_path);
|
||||
}
|
||||
diff --git a/zluda/build.rs b/zluda/build.rs
|
||||
--- a/zluda/build.rs
|
||||
+++ b/zluda/build.rs
|
||||
@@ -1,11 +1,15 @@
|
||||
use vergen_gix::{Emitter, Gix};
|
||||
|
||||
fn main() {
|
||||
if cfg!(windows) {
|
||||
- println!("cargo:rustc-link-arg=delayimp.lib");
|
||||
- println!("cargo:rustc-link-arg=/DELAYLOAD:amdhip64_7.dll");
|
||||
+ if std::env::var("CARGO_CFG_TARGET_ENV").as_deref() == Ok("gnu") {
|
||||
+ println!("cargo:rustc-link-arg=-Wl,--delayload=amdhip64_7.dll");
|
||||
+ } else {
|
||||
+ println!("cargo:rustc-link-arg=delayimp.lib");
|
||||
+ println!("cargo:rustc-link-arg=/DELAYLOAD:amdhip64_7.dll");
|
||||
+ }
|
||||
let dll_path = "bin/nvcudart_hybrid64.dll";
|
||||
println!("cargo:rerun-if-changed={}", dll_path);
|
||||
check_lfs_file(dll_path);
|
||||
}
|
||||
let git = Gix::builder().sha(false).build();
|
||||
diff --git a/zluda_windows/src/lib.rs b/zluda_windows/src/lib.rs
|
||||
--- a/zluda_windows/src/lib.rs
|
||||
+++ b/zluda_windows/src/lib.rs
|
||||
@@ -11,27 +11,27 @@
|
||||
use uuid::uuid;
|
||||
use widestring::{u16str, U16Str, U16String};
|
||||
use windows::{
|
||||
core::{w, HRESULT, PCWSTR},
|
||||
Win32::{
|
||||
- Foundation::{HANDLE, HINSTANCE, HMODULE, HWND, LPARAM, S_OK, WPARAM},
|
||||
+ Foundation::{FreeLibrary, HANDLE, HINSTANCE, HMODULE, HWND, LPARAM, S_OK, WPARAM},
|
||||
System::{
|
||||
JobObjects::{
|
||||
AssignProcessToJobObject, JobObjectExtendedLimitInformation,
|
||||
SetInformationJobObject, JOBOBJECT_EXTENDED_LIMIT_INFORMATION,
|
||||
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE,
|
||||
},
|
||||
LibraryLoader::{
|
||||
- GetModuleFileNameA, GetModuleFileNameW, GetModuleHandleExW, LoadLibraryExW,
|
||||
+ GetModuleFileNameA, GetModuleFileNameW, GetModuleHandleExW, GetProcAddress, LoadLibraryExW,
|
||||
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
|
||||
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS,
|
||||
- LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR,
|
||||
+ LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR, LOAD_LIBRARY_SEARCH_SYSTEM32,
|
||||
},
|
||||
},
|
||||
UI::{
|
||||
Controls::{
|
||||
- TaskDialogIndirect, TASKDIALOGCONFIG, TASKDIALOGCONFIG_0, TASKDIALOGCONFIG_1,
|
||||
+ TASKDIALOGCONFIG, TASKDIALOGCONFIG_0, TASKDIALOGCONFIG_1,
|
||||
TASKDIALOG_NOTIFICATIONS, TDCBF_CLOSE_BUTTON, TDF_ENABLE_HYPERLINKS,
|
||||
TDN_HYPERLINK_CLICKED, TD_ERROR_ICON,
|
||||
},
|
||||
WindowsAndMessaging::IDCLOSE,
|
||||
},
|
||||
@@ -487,11 +487,23 @@
|
||||
pszFooter: PCWSTR::null(),
|
||||
pfCallback: Some(task_dialog_callback),
|
||||
lpCallbackData: 0,
|
||||
cxWidth: 0,
|
||||
};
|
||||
- TaskDialogIndirect(&config, None, None, None).ok();
|
||||
+ // A CUDA DLL may be loaded by an executable with no Common Controls
|
||||
+ // v6 activation manifest. Do not make its diagnostic UI an eager DLL
|
||||
+ // import: comctl32 v5 has no TaskDialogIndirect export.
|
||||
+ if let Ok(controls) = LoadLibraryExW(w!("comctl32.dll"), None, LOAD_LIBRARY_SEARCH_SYSTEM32) {
|
||||
+ if let Some(raw) = GetProcAddress(controls, windows::core::s!("TaskDialogIndirect")) {
|
||||
+ let show: unsafe extern "system" fn(*const TASKDIALOGCONFIG, *mut i32, *mut i32, *mut i32) -> HRESULT =
|
||||
+ mem::transmute(raw);
|
||||
+ let _ = show(&config, ptr::null_mut(), ptr::null_mut(), ptr::null_mut());
|
||||
+ } else {
|
||||
+ eprintln!("ZLUDA failed to load {}; set HIP_PATH to the selected HIP SDK", redirect_names[0]);
|
||||
+ }
|
||||
+ let _ = FreeLibrary(controls);
|
||||
+ }
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
unsafe extern "system" fn task_dialog_callback(
|
||||
diff --git a/ptx/lib/zluda_ptx_impl.cpp b/ptx/lib/zluda_ptx_impl.cpp
|
||||
--- a/ptx/lib/zluda_ptx_impl.cpp
|
||||
+++ b/ptx/lib/zluda_ptx_impl.cpp
|
||||
@@ -405,11 +405,26 @@
|
||||
uint32_t line,
|
||||
uint64_t function,
|
||||
uint64_t char_size)
|
||||
{
|
||||
(void)char_size;
|
||||
+#if defined(_WIN32) || defined(_WIN64)
|
||||
+ // HIP on Windows only supplies the wide _wassert entry point. Keep the
|
||||
+ // CUDA assertion's ASCII diagnostics and trap through public OCKL APIs.
|
||||
+ auto output = __ockl_fprintf_stderr_begin();
|
||||
+ const char* parts[] = {"CUDA assertion: ", (const char*)message, " at ", (const char*)file,
|
||||
+ " in ", (const char*)function, "\n"};
|
||||
+ for (const char* part : parts) {
|
||||
+ const char* end = part;
|
||||
+ while (*end) ++end;
|
||||
+ output = __ockl_fprintf_append_string_n(output, part, end - part, 0);
|
||||
+ }
|
||||
+ __ockl_fprintf_append_args(output, 1, line, 0, 0, 0, 0, 0, 0, 1);
|
||||
+ __builtin_trap();
|
||||
+#else
|
||||
[[clang::noinline]] __assert_fail((const char *)message, (const char *)file, line, (const char *)function);
|
||||
+#endif
|
||||
}
|
||||
|
||||
// * Smallest denormal is 1.4 × 10^-45
|
||||
// * Smallest normal is ~1.175494351 × 10^(-38)
|
||||
// * Now, 1.175494351×10^-38 / 1.4 × 10^-45 = 8396388 + 31/140
|
||||
@@ -1,124 +0,0 @@
|
||||
Implement the CUDA texture resource descriptor query required by NGX Evaluate.
|
||||
Regression: tools/windows/cuda_image_probe.cpp in the d4r Windows port.
|
||||
|
||||
diff --git a/zluda/src/lib.rs b/zluda/src/lib.rs
|
||||
--- a/zluda/src/lib.rs
|
||||
+++ b/zluda/src/lib.rs
|
||||
@@ -67,12 +67,14 @@
|
||||
cuda_macros::cuda_function_declarations!(
|
||||
unimplemented,
|
||||
implemented
|
||||
<= [
|
||||
cuArray3DCreate_v2,
|
||||
+ cuArray3DGetDescriptor_v2,
|
||||
cuArrayCreate_v2,
|
||||
cuArrayDestroy,
|
||||
+ cuArrayGetDescriptor_v2,
|
||||
cuCtxCreate_v2,
|
||||
cuCtxCreate_v4,
|
||||
cuCtxDestroy_v2,
|
||||
cuCtxGetApiVersion,
|
||||
cuCtxGetCurrent,
|
||||
@@ -203,10 +205,11 @@
|
||||
cuSurfObjectCreate,
|
||||
cuSurfObjectDestroy,
|
||||
cuSurfObjectGetResourceDesc,
|
||||
cuTexObjectCreate,
|
||||
cuTexObjectDestroy,
|
||||
+ cuTexObjectGetResourceDesc,
|
||||
cuTexRefSetAddress_v2,
|
||||
cuTexRefSetAddressMode,
|
||||
cuTexRefSetArray,
|
||||
cuTexRefSetFilterMode,
|
||||
cuTexRefSetFlags,
|
||||
diff --git a/zluda/src/impl/tex.rs b/zluda/src/impl/tex.rs
|
||||
--- a/zluda/src/impl/tex.rs
|
||||
+++ b/zluda/src/impl/tex.rs
|
||||
@@ -1,19 +1,58 @@
|
||||
use crate::r#impl::hipfix;
|
||||
use hip_runtime_sys::*;
|
||||
+use std::collections::HashMap;
|
||||
+use std::sync::{Mutex, OnceLock};
|
||||
+
|
||||
+fn object_resources() -> &'static Mutex<HashMap<usize, Vec<u8>>> {
|
||||
+ static RESOURCES: OnceLock<Mutex<HashMap<usize, Vec<u8>>>> = OnceLock::new();
|
||||
+ RESOURCES.get_or_init(|| Mutex::new(HashMap::new()))
|
||||
+}
|
||||
|
||||
pub(crate) unsafe fn object_create(
|
||||
p_tex_object: *mut hipTextureObject_t,
|
||||
p_res_desc: *const HIP_RESOURCE_DESC,
|
||||
p_tex_desc: *const HIP_TEXTURE_DESC,
|
||||
p_res_view_desc: *const HIP_RESOURCE_VIEW_DESC,
|
||||
) -> hipError_t {
|
||||
- hipTexObjectCreate(p_tex_object, p_res_desc, p_tex_desc, p_res_view_desc)
|
||||
+ let output = p_tex_object.as_mut().ok_or(hipErrorCode_t::InvalidValue)?;
|
||||
+ let descriptor = p_res_desc.as_ref().ok_or(hipErrorCode_t::InvalidValue)?;
|
||||
+ hipTexObjectCreate(output, descriptor, p_tex_desc, p_res_view_desc)?;
|
||||
+ let bytes = std::slice::from_raw_parts(p_res_desc.cast::<u8>(), std::mem::size_of::<HIP_RESOURCE_DESC>()).to_vec();
|
||||
+ match object_resources().lock() {
|
||||
+ Ok(mut resources) => {
|
||||
+ resources.insert(*output as usize, bytes);
|
||||
+ Ok(())
|
||||
+ }
|
||||
+ Err(_) => {
|
||||
+ let _ = hipDestroyTextureObject(*output);
|
||||
+ *output = std::ptr::null_mut();
|
||||
+ Err(hipErrorCode_t::Unknown)
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn object_destroy(tex_object: hipTextureObject_t) -> hipError_t {
|
||||
- hipDestroyTextureObject(tex_object)
|
||||
+ let mut resources = object_resources().lock().map_err(|_| hipErrorCode_t::Unknown)?;
|
||||
+ if !resources.contains_key(&(tex_object as usize)) { return Err(hipErrorCode_t::InvalidValue); }
|
||||
+ hipDestroyTextureObject(tex_object)?;
|
||||
+ resources.remove(&(tex_object as usize));
|
||||
+ Ok(())
|
||||
+}
|
||||
+
|
||||
+pub(crate) unsafe fn object_get_resource_desc(
|
||||
+ descriptor: &mut HIP_RESOURCE_DESC,
|
||||
+ tex_object: hipTextureObject_t,
|
||||
+) -> hipError_t {
|
||||
+ // NGX probes surface handles as textures before trying the surface API.
|
||||
+ // Passing that foreign handle to HIP dereferences invalid host metadata.
|
||||
+ // Return a CUDA error for unknown handles and preserve the complete CUDA
|
||||
+ // descriptor (HIP's query leaves the flags/reserved bytes unspecified).
|
||||
+ let resources = object_resources().lock().map_err(|_| hipErrorCode_t::Unknown)?;
|
||||
+ let bytes = resources.get(&(tex_object as usize)).ok_or(hipErrorCode_t::InvalidValue)?;
|
||||
+ std::ptr::copy_nonoverlapping(bytes.as_ptr(), (descriptor as *mut HIP_RESOURCE_DESC).cast::<u8>(), bytes.len());
|
||||
+ Ok(())
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn ref_set_array(
|
||||
texref: *mut textureReference,
|
||||
array: hipArray_t,
|
||||
diff --git a/zluda/src/impl/array.rs b/zluda/src/impl/array.rs
|
||||
--- a/zluda/src/impl/array.rs
|
||||
+++ b/zluda/src/impl/array.rs
|
||||
@@ -70,5 +70,23 @@
|
||||
handle: *mut hipArray_t,
|
||||
desc: *const HIP_ARRAY3D_DESCRIPTOR,
|
||||
) -> hipError_t {
|
||||
hipArray3DCreate(handle, desc)
|
||||
}
|
||||
+
|
||||
+pub(crate) unsafe fn get_descriptor_v2(
|
||||
+ output: &mut HIP_ARRAY_DESCRIPTOR,
|
||||
+ array: hipArray_t,
|
||||
+) -> hipError_t {
|
||||
+ hipArrayGetDescriptor(output, array)?;
|
||||
+ if let Some(format) = original_format(array)? {
|
||||
+ output.Format = hipArray_Format(format);
|
||||
+ }
|
||||
+ Ok(())
|
||||
+}
|
||||
+
|
||||
+pub(crate) unsafe fn _3d_get_descriptor_v2(
|
||||
+ output: &mut HIP_ARRAY3D_DESCRIPTOR,
|
||||
+ array: hipArray_t,
|
||||
+) -> hipError_t {
|
||||
+ hipArray3DGetDescriptor(output, array)
|
||||
+}
|
||||
@@ -1,440 +0,0 @@
|
||||
Subject: [PATCH] Guard and own native overrides; capture K replay on Windows
|
||||
|
||||
Native replacements require the upstream PTX identity manifest. Preserve native
|
||||
module/image ownership, bypass redundant JIT for completely replaced modules,
|
||||
include the loaded Windows DLL fingerprint in cache identity, and expose opt-in
|
||||
private real-weight replay captures with explicit native/translated launch logs.
|
||||
|
||||
diff --git a/zluda/src/impl/d4r_replay.rs b/zluda/src/impl/d4r_replay.rs
|
||||
new file mode 100644
|
||||
index 0000000..875b2e3
|
||||
--- /dev/null
|
||||
+++ b/zluda/src/impl/d4r_replay.rs
|
||||
@@ -0,0 +1,109 @@
|
||||
+//! Opt-in, synchronous diagnostic captures. Never enabled in the game fast path.
|
||||
+//! Captures can contain proprietary network weights; keep them out of releases.
|
||||
+use hip_runtime_sys::*;
|
||||
+use std::{collections::HashMap, ffi::{c_void, CStr}, fs, path::PathBuf, sync::{Mutex, OnceLock}};
|
||||
+
|
||||
+struct Config { directory: PathBuf, filter: String, limit: u32 }
|
||||
+static CONFIG: OnceLock<Option<Config>> = OnceLock::new();
|
||||
+static COUNTS: OnceLock<Mutex<HashMap<String, u32>>> = OnceLock::new();
|
||||
+
|
||||
+pub(crate) struct Capture { directory: PathBuf, allocations: Vec<(usize, usize)> }
|
||||
+
|
||||
+fn io_error(error: impl std::fmt::Display) -> hipErrorCode_t {
|
||||
+ eprintln!("[d4r-replay] capture failed: {error}");
|
||||
+ hipErrorCode_t::r#Unknown
|
||||
+}
|
||||
+
|
||||
+impl Capture {
|
||||
+ fn save(&self, destination: &std::path::Path) -> hipError_t {
|
||||
+ fs::create_dir_all(destination).map_err(io_error)?;
|
||||
+ for (id, &(base, size)) in self.allocations.iter().enumerate() {
|
||||
+ let mut bytes = vec![0u8; size];
|
||||
+ unsafe { hipMemcpyDtoH(bytes.as_mut_ptr().cast(), hipDeviceptr_t(base as *mut c_void), size) }?;
|
||||
+ fs::write(destination.join(format!("alloc-{id}.bin")), bytes).map_err(io_error)?;
|
||||
+ }
|
||||
+ Ok(())
|
||||
+ }
|
||||
+
|
||||
+ pub(crate) fn finish(self, stream: hipStream_t) -> hipError_t {
|
||||
+ unsafe { hipStreamSynchronize(stream) }?;
|
||||
+ self.save(&self.directory.join("gpu-output"))?;
|
||||
+ eprintln!("[d4r-replay] complete path={}", self.directory.display());
|
||||
+ Ok(())
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+pub(crate) unsafe fn begin(
|
||||
+ name: &CStr, stream: hipStream_t, parameters: *mut *mut c_void,
|
||||
+ extra: *mut *mut c_void, launch: [u32; 7],
|
||||
+) -> Result<Option<Capture>, hipErrorCode_t> {
|
||||
+ let Some(config) = CONFIG.get_or_init(|| std::env::var_os("D4R_CUDA_REPLAY_DUMP_DIR").map(|directory| Config {
|
||||
+ directory: directory.into(),
|
||||
+ filter: std::env::var("D4R_CUDA_REPLAY_DUMP_FILTER").unwrap_or_default(),
|
||||
+ limit: std::env::var("D4R_CUDA_REPLAY_DUMP_LIMIT").ok().and_then(|s| s.parse().ok()).unwrap_or(1),
|
||||
+ })) else { return Ok(None) };
|
||||
+ let name = name.to_str().map_err(io_error)?;
|
||||
+ // Only the audited, single 176-byte K argument ABI is captured here.
|
||||
+ if !name.starts_with("dltss_pwin_") || !name.ends_with("_layer") || !name.contains(&config.filter) {
|
||||
+ return Ok(None);
|
||||
+ }
|
||||
+ let mut block = std::ptr::null::<u8>();
|
||||
+ let mut bytes = None;
|
||||
+ if !extra.is_null() {
|
||||
+ for i in (0..6).step_by(2) {
|
||||
+ match *extra.add(i) as usize {
|
||||
+ 1 => block = (*extra.add(i + 1)).cast(),
|
||||
+ 2 => bytes = Some(*(*extra.add(i + 1) as *const usize)),
|
||||
+ 0 => break,
|
||||
+ _ => return Err(hipErrorCode_t::r#InvalidValue),
|
||||
+ }
|
||||
+ }
|
||||
+ } else if !parameters.is_null() {
|
||||
+ block = (*parameters).cast();
|
||||
+ bytes = Some(176);
|
||||
+ }
|
||||
+ if block.is_null() || bytes != Some(176) {
|
||||
+ return Err(io_error(format!("unexpected K parameter ABI for {name}: {bytes:?}")));
|
||||
+ }
|
||||
+ let sequence = {
|
||||
+ let mut counts = COUNTS.get_or_init(|| Mutex::new(HashMap::new())).lock().map_err(io_error)?;
|
||||
+ let count = counts.entry(name.to_owned()).or_default();
|
||||
+ if *count >= config.limit { return Ok(None); }
|
||||
+ *count += 1;
|
||||
+ *count
|
||||
+ };
|
||||
+ hipStreamSynchronize(stream)?;
|
||||
+ let arguments = std::slice::from_raw_parts(block, 176);
|
||||
+ let directory = config.directory.join(format!("replay-{sequence:06}-{name}"));
|
||||
+ fs::create_dir_all(&directory).map_err(io_error)?;
|
||||
+ fs::write(directory.join("args.bin"), arguments).map_err(io_error)?;
|
||||
+ let mut manifest = format!("kernel {name}\nlaunch {} {} {} {} {} {} {}\nargs 176\n",
|
||||
+ launch[0], launch[1], launch[2], launch[3], launch[4], launch[5], launch[6]);
|
||||
+ let mut allocations = Vec::<(usize, usize)>::new();
|
||||
+ for offset in (0..176).step_by(8) {
|
||||
+ let pointer = u64::from_le_bytes(arguments[offset..offset + 8].try_into().unwrap()) as usize;
|
||||
+ if pointer == 0 { continue; }
|
||||
+ let mut base = hipDeviceptr_t(std::ptr::null_mut());
|
||||
+ let mut size = 0;
|
||||
+ if hipMemGetAddressRange(&mut base, &mut size, hipDeviceptr_t(pointer as *mut c_void)).is_err() { continue; }
|
||||
+ let base = base.0 as usize;
|
||||
+ if pointer < base || pointer - base >= size { continue; }
|
||||
+ if size > 512 * 1024 * 1024 { return Err(io_error("allocation exceeds diagnostic 512 MiB limit")); }
|
||||
+ let id = match allocations.iter().position(|&(b, _)| b == base) {
|
||||
+ Some(id) => id,
|
||||
+ None => {
|
||||
+ let id = allocations.len();
|
||||
+ allocations.push((base, size));
|
||||
+ manifest.push_str(&format!("alloc {id} 0x{base:x} {size}\n"));
|
||||
+ id
|
||||
+ }
|
||||
+ };
|
||||
+ manifest.push_str(&format!("pointer {offset} {id} {}\n", pointer - base));
|
||||
+ }
|
||||
+ if allocations.is_empty() { return Err(io_error("no K allocation pointers resolved")); }
|
||||
+ fs::write(directory.join("manifest.txt"), manifest).map_err(io_error)?;
|
||||
+ let capture = Capture { directory, allocations };
|
||||
+ capture.save(&capture.directory)?;
|
||||
+ eprintln!("[d4r-replay] begin kernel={name} allocations={} path={}", capture.allocations.len(), capture.directory.display());
|
||||
+ Ok(Some(capture))
|
||||
+}
|
||||
diff --git a/zluda/src/impl/function.rs b/zluda/src/impl/function.rs
|
||||
index 033d9a0..7a7470a 100644
|
||||
--- a/zluda/src/impl/function.rs
|
||||
+++ b/zluda/src/impl/function.rs
|
||||
@@ -7,6 +7,8 @@ use hip_runtime_sys::*;
|
||||
use std::mem;
|
||||
|
||||
pub(crate) struct Function {
|
||||
+ pub(crate) name: std::ffi::CString,
|
||||
+ pub(crate) native_owner: Option<super::module::NativeModule>,
|
||||
pub(crate) base: hipFunction_t,
|
||||
pub(crate) sm_version: u32,
|
||||
pub(crate) explicit_args_size_align: Option<Vec<FunctionArgInfo>>,
|
||||
@@ -144,6 +146,8 @@ pub(crate) fn launch_kernel(
|
||||
}
|
||||
translated_extra_storage.as_mut_ptr()
|
||||
};
|
||||
+ let replay = unsafe { super::d4r_replay::begin(&f.name, stream, kernel_params, extra,
|
||||
+ [grid_dim_x, grid_dim_y, grid_dim_z, block_dim_x, block_dim_y, block_dim_z, shared_mem_bytes]) }?;
|
||||
// the parameter block: kernel_params[0] (a single struct parameter) or the extra buffer
|
||||
let prep_key = if let (Some(_), Some(key_offset)) = (f.prep, f.prep_key) {
|
||||
let mut block: *const u8 = std::ptr::null();
|
||||
@@ -206,7 +210,11 @@ pub(crate) fn launch_kernel(
|
||||
}
|
||||
let block_dim_z = if f.block_z != 0 { f.block_z } else { block_dim_z };
|
||||
let (grid_dim_x, grid_dim_y, grid_dim_z) = if f.grid_x != 0 { (f.grid_x, 1, 1) } else { (grid_dim_x, grid_dim_y, grid_dim_z) };
|
||||
- unsafe {
|
||||
+ if std::env::var_os("D4R_ZLUDA_VERBOSE").is_some() {
|
||||
+ eprintln!("[d4r-launch] kernel={:?} backend={} grid={grid_dim_x},{grid_dim_y},{grid_dim_z} block={block_dim_x},{block_dim_y},{block_dim_z}",
|
||||
+ f.name, if f.native_owner.is_some() { "native" } else { "translated" });
|
||||
+ }
|
||||
+ let result = unsafe {
|
||||
hipModuleLaunchKernel(
|
||||
f.base,
|
||||
grid_dim_x,
|
||||
@@ -220,7 +228,10 @@ pub(crate) fn launch_kernel(
|
||||
kernel_params,
|
||||
extra_for_hip,
|
||||
)
|
||||
- }
|
||||
+ };
|
||||
+ result?;
|
||||
+ if let Some(replay) = replay { replay.finish(stream)?; }
|
||||
+ Ok(())
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn set_attribute(
|
||||
diff --git a/zluda/src/impl/library.rs b/zluda/src/impl/library.rs
|
||||
index 98be33e..1319792 100644
|
||||
--- a/zluda/src/impl/library.rs
|
||||
+++ b/zluda/src/impl/library.rs
|
||||
@@ -21,8 +21,8 @@ impl Library {
|
||||
let module_lock = self.modules.get(device).ok_or(CUerror::INVALID_DEVICE)?;
|
||||
*module_lock.get_or_init(|| match self.data {
|
||||
LibraryData::Lazy(lib) => {
|
||||
- let (module, sm_version, meta32) = module::load_hip_module(lib)?;
|
||||
- Ok(Module::new(module, sm_version, meta32).wrap())
|
||||
+ let (module, sm_version, meta32, ptx_hash, native_image) = module::load_hip_module(lib)?;
|
||||
+ Ok(Module::new(module, sm_version, meta32, ptx_hash, native_image).wrap())
|
||||
}
|
||||
LibraryData::Eager(()) => Err(CUerror::NOT_SUPPORTED),
|
||||
})
|
||||
diff --git a/zluda/src/impl/mod.rs b/zluda/src/impl/mod.rs
|
||||
index 5329d32..14e0aee 100644
|
||||
--- a/zluda/src/impl/mod.rs
|
||||
+++ b/zluda/src/impl/mod.rs
|
||||
@@ -5,6 +5,7 @@ pub(super) mod array;
|
||||
pub(super) mod context;
|
||||
pub(super) mod device;
|
||||
pub(super) mod driver;
|
||||
+pub(super) mod d4r_replay;
|
||||
pub(super) mod event;
|
||||
pub(super) mod function;
|
||||
pub(super) mod graph;
|
||||
diff --git a/zluda/src/impl/module.rs b/zluda/src/impl/module.rs
|
||||
index 9f72f2f..5a79368 100644
|
||||
--- a/zluda/src/impl/module.rs
|
||||
+++ b/zluda/src/impl/module.rs
|
||||
@@ -19,6 +19,8 @@ pub(crate) struct Module {
|
||||
pub(crate) base: hipModule_t,
|
||||
pub(crate) sm_version: u32,
|
||||
pub(crate) bit32: Option<Metadata32Bit>,
|
||||
+ ptx_hash: Option<u64>,
|
||||
+ _native_base_image: Option<Vec<u8>>,
|
||||
mutable: Mutex<ModuleMutable>,
|
||||
}
|
||||
|
||||
@@ -33,12 +35,14 @@ impl ModuleMutable {
|
||||
sm_version: u32,
|
||||
name: CString,
|
||||
explicit_args_size_align: Option<Vec<FunctionArgInfo>>,
|
||||
+ ptx_hash: Option<u64>,
|
||||
) -> Result<&Function, CUerror> {
|
||||
Ok(match self.functions.entry(name) {
|
||||
hash_map::Entry::Occupied(entry) => &*entry.into_mut(),
|
||||
hash_map::Entry::Vacant(entry) => {
|
||||
- let (func_handle, prep, block_z, grid_x, prep_key, prep_slots) = match native_override(entry.key()) {
|
||||
- Some(native) => native,
|
||||
+ let mut native_owner = None;
|
||||
+ let (func_handle, prep, block_z, grid_x, prep_key, prep_slots) = match native_override(entry.key(), ptx_hash) {
|
||||
+ Some((native, owner)) => { native_owner = Some(owner); native },
|
||||
None => {
|
||||
let mut func_handle = unsafe { std::mem::zeroed() };
|
||||
unsafe { hipModuleGetFunction(&mut func_handle, module, entry.key().as_ptr()) }?;
|
||||
@@ -46,6 +50,8 @@ impl ModuleMutable {
|
||||
}
|
||||
};
|
||||
let func = Box::new(Function {
|
||||
+ name: entry.key().clone(),
|
||||
+ native_owner,
|
||||
base: func_handle,
|
||||
sm_version,
|
||||
explicit_args_size_align,
|
||||
@@ -69,19 +75,48 @@ impl ModuleMutable {
|
||||
/// same stream with `d4r_prep_blocks` (a u32 global in the code object) blocks of 128 threads.
|
||||
/// An optional u32 global `d4r_block_z` replaces the launch's block z dimension, and `d4r_grid_x`
|
||||
/// (persistent kernels, which derive the original grid from their parameters) the grid.
|
||||
+pub(crate) struct NativeModule {
|
||||
+ module: hipModule_t,
|
||||
+ _image: Vec<u8>,
|
||||
+}
|
||||
+
|
||||
+impl Drop for NativeModule {
|
||||
+ fn drop(&mut self) {
|
||||
+ if self.module.0.is_null() { return; }
|
||||
+ if let Err(error) = unsafe { hipModuleUnload(self.module) } {
|
||||
+ eprintln!("[d4r-native] unload failed: {error:?}");
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
fn native_override(
|
||||
name: &CStr,
|
||||
-) -> Option<(hipFunction_t, Option<(hipFunction_t, u32)>, u32, u32, Option<u32>, u32)> {
|
||||
+ ptx_hash: Option<u64>,
|
||||
+) -> Option<((hipFunction_t, Option<(hipFunction_t, u32)>, u32, u32, Option<u32>, u32), NativeModule)> {
|
||||
let dir = std::env::var_os("D4R_ZLUDA_NATIVE_DIR")?;
|
||||
+ let manifest = std::path::Path::new(&dir).join("d4r-kernels.txt");
|
||||
+ let allowed = fs::read_to_string(&manifest).ok().is_some_and(|text| text.lines().any(|line| {
|
||||
+ let mut fields = line.split_whitespace();
|
||||
+ fields.next() == name.to_str().ok()
|
||||
+ && fields.next().and_then(|s| u64::from_str_radix(s, 16).ok()) == ptx_hash
|
||||
+ && ptx_hash.is_some()
|
||||
+ }));
|
||||
+ if !allowed {
|
||||
+ eprintln!("[d4r-native] miss kernel={name:?} reason=ptx_identity hash={ptx_hash:016x?} fallback=translated");
|
||||
+ return None;
|
||||
+ }
|
||||
let path = std::path::Path::new(&dir).join(format!("{}.hsaco", name.to_str().ok()?));
|
||||
- let image = fs::read(&path).ok()?;
|
||||
- // HIP may reference the image after loading; the override lives for the whole process.
|
||||
- let image: &'static [u8] = Box::leak(image.into_boxed_slice());
|
||||
+ let image = match fs::read(&path) {
|
||||
+ Ok(image) => image,
|
||||
+ Err(error) => { eprintln!("[d4r-native] miss kernel={name:?} reason=object_missing error={error} fallback=translated"); return None; }
|
||||
+ };
|
||||
let mut native_module = unsafe { mem::zeroed() };
|
||||
if unsafe { hipModuleLoadData(&mut native_module, image.as_ptr().cast()) }.is_err() {
|
||||
eprintln!("[d4r-native] failed to load {}", path.display());
|
||||
return None;
|
||||
}
|
||||
+ // Keep the image and module owned by the CUfunction; release at module unload.
|
||||
+ let owner = NativeModule { module: native_module, _image: image };
|
||||
let mut func = unsafe { mem::zeroed() };
|
||||
if unsafe { hipModuleGetFunction(&mut func, native_module, name.as_ptr()) }.is_err() {
|
||||
eprintln!("[d4r-native] {} has no kernel {:?}", path.display(), name);
|
||||
@@ -138,7 +173,7 @@ fn native_override(
|
||||
prep_key,
|
||||
prep_slots
|
||||
);
|
||||
- Some((func, prep, block_z, grid_x, prep_key, prep_slots))
|
||||
+ Some(((func, prep, block_z, grid_x, prep_key, prep_slots), owner))
|
||||
}
|
||||
|
||||
impl ZludaObject for Module {
|
||||
@@ -229,7 +264,7 @@ pub(crate) struct Global32Bit {
|
||||
}
|
||||
|
||||
impl Module {
|
||||
- pub(crate) fn new(base: hipModule_t, sm_version: u32, bit32: Option<Metadata32Bit>) -> Self {
|
||||
+ pub(crate) fn new(base: hipModule_t, sm_version: u32, bit32: Option<Metadata32Bit>, ptx_hash: Option<u64>, native_base_image: Option<Vec<u8>>) -> Self {
|
||||
Self {
|
||||
base,
|
||||
sm_version,
|
||||
@@ -237,6 +272,8 @@ impl Module {
|
||||
functions: FxHashMap::default(),
|
||||
}),
|
||||
bit32,
|
||||
+ ptx_hash,
|
||||
+ _native_base_image: native_base_image,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,7 +285,7 @@ impl Module {
|
||||
.map(Vec::clone)
|
||||
});
|
||||
mutable
|
||||
- .get_function(self.base, self.sm_version, name.to_owned(), explicit_args)
|
||||
+ .get_function(self.base, self.sm_version, name.to_owned(), explicit_args, self.ptx_hash)
|
||||
.map(|f| unsafe { (f as *const Function).as_ref().unwrap() })
|
||||
}
|
||||
}
|
||||
@@ -256,7 +293,7 @@ impl Module {
|
||||
fn get_best_ptx_and_compile(
|
||||
global_state: &GlobalState,
|
||||
image: CodeLibraryRef<'_>,
|
||||
-) -> Result<(hipModule_t, u32, Option<Metadata32Bit>), CUerror> {
|
||||
+) -> Result<(hipModule_t, u32, Option<Metadata32Bit>, Option<u64>, Option<Vec<u8>>), CUerror> {
|
||||
let mut ptx_modules = Vec::new();
|
||||
unsafe {
|
||||
CodeLibraryRef::iterate_modules(image, |_, module| match module {
|
||||
@@ -280,7 +317,7 @@ fn get_best_ptx_and_compile(
|
||||
let dump_dir = std::path::PathBuf::from(dump_dir);
|
||||
let _ = fs::create_dir_all(&dump_dir);
|
||||
for (index, source) in ptx_modules.iter().enumerate() {
|
||||
- let stem = format!("module-{index:04}");
|
||||
+ let stem = format!("module-{:016x}-{index:04}", ptx_identity(source));
|
||||
let _ = fs::write(dump_dir.join(format!("{stem}.ptx")), source.as_bytes());
|
||||
let diagnostic = match ptx_parser::parse_module_checked(source.as_ref()) {
|
||||
Ok(ast) => format!(
|
||||
@@ -345,6 +382,32 @@ fn get_best_ptx_and_compile(
|
||||
)
|
||||
}
|
||||
};
|
||||
+ let ptx_hash = text.map(|source| ptx_identity(source));
|
||||
+ // A fully replaced, single-entry module needs no translated binary. LDS
|
||||
+ // declarations belong to the replaced kernel; exported globals do not.
|
||||
+ let only_kernel_and_lds = module.directives.iter().all(|directive| match directive {
|
||||
+ ptx_parser::Directive::Method(_, method) => method.func_directive.name.is_kernel(),
|
||||
+ ptx_parser::Directive::Variable(_, variable) => matches!(variable.info.state_space, ptx_parser::StateSpace::Shared),
|
||||
+ });
|
||||
+ let entries: Vec<_> = module.directives.iter().filter_map(|directive| match directive {
|
||||
+ ptx_parser::Directive::Method(_, method) => Some(method), _ => None,
|
||||
+ }).collect();
|
||||
+ if module.address_size == 64 && only_kernel_and_lds && entries.len() == 1 {
|
||||
+ let method = entries[0];
|
||||
+ {
|
||||
+ if method.func_directive.name.is_kernel() {
|
||||
+ if let Ok(name) = CString::new(method.func_directive.name.text()) {
|
||||
+ if let Some((_, mut owner)) = native_override(&name, ptx_hash) {
|
||||
+ let base = owner.module;
|
||||
+ owner.module = hipModule_t(std::ptr::null_mut());
|
||||
+ let image = mem::take(&mut owner._image);
|
||||
+ eprintln!("[d4r-native] module_direct kernel={name:?} translated_jit=skipped");
|
||||
+ return Ok((base, module.sm_version, None, ptx_hash, Some(image)));
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
// TODO: get this information on initialization
|
||||
let hip_properties = get_hip_properties()?;
|
||||
let gcn_arch = get_gcn_arch(&hip_properties)?;
|
||||
@@ -372,7 +435,12 @@ fn get_best_ptx_and_compile(
|
||||
.or_else(|_| compile_and_cache(gcn_arch, attributes, module, &mut cache_with_key))?;
|
||||
let mut hip_module = unsafe { mem::zeroed() };
|
||||
unsafe { hipModuleLoadData(&mut hip_module, elf_module.as_ptr().cast()) }?;
|
||||
- Ok((hip_module, sm_version, zluda32))
|
||||
+ Ok((hip_module, sm_version, zluda32, ptx_hash, None))
|
||||
+}
|
||||
+
|
||||
+fn ptx_identity(text: &str) -> u64 {
|
||||
+ text.trim_end_matches('\0').bytes().fold(0xcbf29ce484222325u64, |hash, byte|
|
||||
+ (hash ^ u64::from(byte)).wrapping_mul(0x100000001b3))
|
||||
}
|
||||
|
||||
fn cow_bytes_to_str<'a>(data: Cow<'a, [u8]>) -> Option<Cow<'a, str>> {
|
||||
@@ -384,7 +452,7 @@ fn cow_bytes_to_str<'a>(data: Cow<'a, [u8]>) -> Option<Cow<'a, str>> {
|
||||
|
||||
pub(crate) fn load_hip_module(
|
||||
library: CodeLibraryRef,
|
||||
-) -> Result<(hipModule_t, u32, Option<Metadata32Bit>), CUerror> {
|
||||
+) -> Result<(hipModule_t, u32, Option<Metadata32Bit>, Option<u64>, Option<Vec<u8>>), CUerror> {
|
||||
let global_state = driver::global_state()?;
|
||||
get_best_ptx_and_compile(global_state, library)
|
||||
}
|
||||
@@ -434,7 +502,7 @@ fn zluda_build_version() -> &'static str {
|
||||
key.starts_with("D4R_ZLUDA_")
|
||||
&& !matches!(
|
||||
key.as_str(),
|
||||
- "D4R_ZLUDA_NATIVE_DIR" | "D4R_ZLUDA_DIR" | "D4R_ZLUDA_DUMP_DIR" | "D4R_ZLUDA_DUMP_PTX_DIR" | "D4R_ZLUDA_LIBCUDA"
|
||||
+ "D4R_ZLUDA_NATIVE_DIR" | "D4R_ZLUDA_DIR" | "D4R_ZLUDA_DUMP_DIR" | "D4R_ZLUDA_DUMP_PTX_DIR" | "D4R_ZLUDA_LIBCUDA" | "D4R_ZLUDA_VERBOSE"
|
||||
| "D4R_ZLUDA_WMMA_FP8_NATIVE" // keyed by effect (ExtraCacheAttributes)
|
||||
)
|
||||
})
|
||||
@@ -469,7 +537,10 @@ fn loaded_library_fingerprint() -> Option<String> {
|
||||
|
||||
#[cfg(windows)]
|
||||
fn loaded_library_fingerprint() -> Option<String> {
|
||||
- None
|
||||
+ let path = zluda_windows::get_module_path_for_function(zluda_build_version as *const () as usize)?;
|
||||
+ let metadata = fs::metadata(path).ok()?;
|
||||
+ let timestamp = metadata.modified().ok()?.duration_since(std::time::UNIX_EPOCH).ok()?;
|
||||
+ Some(format!("{:x}.{:x}.{:x}", metadata.len(), timestamp.as_secs(), timestamp.subsec_nanos()))
|
||||
}
|
||||
|
||||
fn get_cache_key<'a, 'b>(
|
||||
@@ -577,16 +648,16 @@ pub(crate) fn load(module: &mut CUmodule, fname: &CStr) -> CUresult {
|
||||
image.push(0);
|
||||
let library = unsafe { CodeLibraryRef::try_load(image.as_ptr() as *const std::ffi::c_void) }
|
||||
.map_err(|_| CUerror::NO_BINARY_FOR_GPU)?;
|
||||
- let (hip_module, sm_version, meta32) = load_hip_module(library)?;
|
||||
- *module = Module::new(hip_module, sm_version, meta32).wrap();
|
||||
+ let (hip_module, sm_version, meta32, ptx_hash, native_image) = load_hip_module(library)?;
|
||||
+ *module = Module::new(hip_module, sm_version, meta32, ptx_hash, native_image).wrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn load_data(module: &mut CUmodule, image: &std::ffi::c_void) -> CUresult {
|
||||
let library =
|
||||
unsafe { CodeLibraryRef::try_load(image) }.map_err(|_| CUerror::NO_BINARY_FOR_GPU)?;
|
||||
- let (hip_module, sm_version, meta32) = load_hip_module(library)?;
|
||||
- *module = Module::new(hip_module, sm_version, meta32).wrap();
|
||||
+ let (hip_module, sm_version, meta32, ptx_hash, native_image) = load_hip_module(library)?;
|
||||
+ *module = Module::new(hip_module, sm_version, meta32, ptx_hash, native_image).wrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
diff --git a/zluda/src/impl/d4r_replay.rs b/zluda/src/impl/d4r_replay.rs
|
||||
index 875b2e3..d491ae2 100644
|
||||
--- a/zluda/src/impl/d4r_replay.rs
|
||||
+++ b/zluda/src/impl/d4r_replay.rs
|
||||
@@ -7,6 +7,18 @@ struct Config { directory: PathBuf, filter: String, limit: u32 }
|
||||
static CONFIG: OnceLock<Option<Config>> = OnceLock::new();
|
||||
static COUNTS: OnceLock<Mutex<HashMap<String, u32>>> = OnceLock::new();
|
||||
|
||||
+fn parameter_size(name: &str) -> Option<usize> {
|
||||
+ match name {
|
||||
+ "dltss_pwin_enc0_layer" | "dltss_pwin_enc1_layer" | "dltss_pwin_enc2_layer" |
|
||||
+ "dltss_pwin_enc3_layer" | "dltss_pwin_enc4_layer" | "dltss_pwin_dec5_layer" |
|
||||
+ "dltss_pwin_dec4_layer" | "dltss_pwin_dec3_layer" | "dltss_pwin_dec2_layer" |
|
||||
+ "dltss_pwin_dec1_layer" | "dltss_pwin_dec0_layer" => Some(176),
|
||||
+ "rrlite_enc1_4x4" | "rrlite_enc2_4x4" | "rrlite_dec2_4x4" | "rrlite_dec1_4x4" => Some(56),
|
||||
+ "rrlite_enc3_tube_4x4" => Some(88),
|
||||
+ _ => None,
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
pub(crate) struct Capture { directory: PathBuf, allocations: Vec<(usize, usize)> }
|
||||
|
||||
fn io_error(error: impl std::fmt::Display) -> hipErrorCode_t {
|
||||
@@ -43,8 +55,10 @@ pub(crate) unsafe fn begin(
|
||||
limit: std::env::var("D4R_CUDA_REPLAY_DUMP_LIMIT").ok().and_then(|s| s.parse().ok()).unwrap_or(1),
|
||||
})) else { return Ok(None) };
|
||||
let name = name.to_str().map_err(io_error)?;
|
||||
- // Only the audited, single 176-byte K argument ABI is captured here.
|
||||
- if !name.starts_with("dltss_pwin_") || !name.ends_with("_layer") || !name.contains(&config.filter) {
|
||||
+ // Only audited single-by-value K/M argument blocks. Never infer the size
|
||||
+ // from a prefix: reading a different kernel's parameters can overrun host memory.
|
||||
+ let Some(parameter_bytes) = parameter_size(name) else { return Ok(None); };
|
||||
+ if !name.contains(&config.filter) {
|
||||
return Ok(None);
|
||||
}
|
||||
let mut block = std::ptr::null::<u8>();
|
||||
@@ -60,10 +74,10 @@ pub(crate) unsafe fn begin(
|
||||
}
|
||||
} else if !parameters.is_null() {
|
||||
block = (*parameters).cast();
|
||||
- bytes = Some(176);
|
||||
+ bytes = Some(parameter_bytes);
|
||||
}
|
||||
- if block.is_null() || bytes != Some(176) {
|
||||
- return Err(io_error(format!("unexpected K parameter ABI for {name}: {bytes:?}")));
|
||||
+ if block.is_null() || bytes != Some(parameter_bytes) {
|
||||
+ return Err(io_error(format!("unexpected parameter ABI for {name}: {bytes:?}, expected {parameter_bytes}")));
|
||||
}
|
||||
let sequence = {
|
||||
let mut counts = COUNTS.get_or_init(|| Mutex::new(HashMap::new())).lock().map_err(io_error)?;
|
||||
@@ -73,14 +87,14 @@ pub(crate) unsafe fn begin(
|
||||
*count
|
||||
};
|
||||
hipStreamSynchronize(stream)?;
|
||||
- let arguments = std::slice::from_raw_parts(block, 176);
|
||||
+ let arguments = std::slice::from_raw_parts(block, parameter_bytes);
|
||||
let directory = config.directory.join(format!("replay-{sequence:06}-{name}"));
|
||||
fs::create_dir_all(&directory).map_err(io_error)?;
|
||||
fs::write(directory.join("args.bin"), arguments).map_err(io_error)?;
|
||||
- let mut manifest = format!("kernel {name}\nlaunch {} {} {} {} {} {} {}\nargs 176\n",
|
||||
+ let mut manifest = format!("kernel {name}\nlaunch {} {} {} {} {} {} {}\nargs {parameter_bytes}\n",
|
||||
launch[0], launch[1], launch[2], launch[3], launch[4], launch[5], launch[6]);
|
||||
let mut allocations = Vec::<(usize, usize)>::new();
|
||||
- for offset in (0..176).step_by(8) {
|
||||
+ for offset in (0..parameter_bytes).step_by(8) {
|
||||
let pointer = u64::from_le_bytes(arguments[offset..offset + 8].try_into().unwrap()) as usize;
|
||||
if pointer == 0 { continue; }
|
||||
let mut base = hipDeviceptr_t(std::ptr::null_mut());
|
||||
@@ -100,10 +114,23 @@ pub(crate) unsafe fn begin(
|
||||
};
|
||||
manifest.push_str(&format!("pointer {offset} {id} {}\n", pointer - base));
|
||||
}
|
||||
- if allocations.is_empty() { return Err(io_error("no K allocation pointers resolved")); }
|
||||
+ if allocations.is_empty() { return Err(io_error("no allocation pointers resolved")); }
|
||||
fs::write(directory.join("manifest.txt"), manifest).map_err(io_error)?;
|
||||
let capture = Capture { directory, allocations };
|
||||
capture.save(&capture.directory)?;
|
||||
eprintln!("[d4r-replay] begin kernel={name} allocations={} path={}", capture.allocations.len(), capture.directory.display());
|
||||
Ok(Some(capture))
|
||||
}
|
||||
+
|
||||
+#[cfg(test)]
|
||||
+mod tests {
|
||||
+ #[test]
|
||||
+ fn only_audited_argument_sizes() {
|
||||
+ assert_eq!(super::parameter_size("dltss_pwin_enc0_layer"), Some(176));
|
||||
+ assert_eq!(super::parameter_size("rrlite_dec2_4x4"), Some(56));
|
||||
+ assert_eq!(super::parameter_size("rrlite_enc3_tube_4x4"), Some(88));
|
||||
+ assert_eq!(super::parameter_size("dltss_pwin_enc0_layer_prep"), None);
|
||||
+ assert_eq!(super::parameter_size("rrlite_enc3_tube_4x4_prep"), None);
|
||||
+ assert_eq!(super::parameter_size("dltss_pwin_unknown_layer"), None);
|
||||
+ }
|
||||
+}
|
||||
@@ -1,65 +0,0 @@
|
||||
diff --git a/zluda_cache/src/lib.rs b/zluda_cache/src/lib.rs
|
||||
index acd262e..c3ac1be 100644
|
||||
--- a/zluda_cache/src/lib.rs
|
||||
+++ b/zluda_cache/src/lib.rs
|
||||
@@ -22,18 +22,36 @@ pub struct ModuleCache(SqliteConnection);
|
||||
|
||||
impl ModuleCache {
|
||||
pub fn create_cache_dir_and_get_path() -> Option<String> {
|
||||
- let mut cache_dir = dirs::cache_dir()?;
|
||||
- cache_dir.extend(["zluda", "ComputeCache"]);
|
||||
+ let override_dir = std::env::var_os("ZLUDA_CACHE_DIR");
|
||||
+ let cache_dir = match override_dir {
|
||||
+ Some(path) => {
|
||||
+ let path = std::path::PathBuf::from(path);
|
||||
+ if !path.is_absolute() {
|
||||
+ eprintln!("[zluda-cache] ZLUDA_CACHE_DIR must be absolute; cache disabled");
|
||||
+ return None;
|
||||
+ }
|
||||
+ path
|
||||
+ }
|
||||
+ None => dirs::cache_dir()?.join("zluda").join("ComputeCache"),
|
||||
+ };
|
||||
+ Self::cache_path(&cache_dir)
|
||||
+ }
|
||||
+
|
||||
+ fn cache_path(cache_dir: &std::path::Path) -> Option<String> {
|
||||
// We ensure that the cache directory exists
|
||||
- std::fs::create_dir_all(&cache_dir).ok()?;
|
||||
+ if let Err(error) = std::fs::create_dir_all(cache_dir) {
|
||||
+ eprintln!("[zluda-cache] cannot create {}: {error}; cache disabled", cache_dir.display());
|
||||
+ return None;
|
||||
+ }
|
||||
// No need to create the file, it will be created by SQLite on first access
|
||||
// zluda.db might be in use by older versions of ZLUDA
|
||||
- cache_dir.push("zluda2.db");
|
||||
- Some(cache_dir.to_string_lossy().into())
|
||||
+ Some(cache_dir.join("zluda2.db").to_string_lossy().into())
|
||||
}
|
||||
|
||||
pub fn open(file_path: &str) -> Option<Self> {
|
||||
- let mut conn = SqliteConnection::establish(file_path).ok()?;
|
||||
+ let mut conn = SqliteConnection::establish(file_path).map_err(|error| {
|
||||
+ eprintln!("[zluda-cache] cannot open {file_path}: {error}; cache disabled");
|
||||
+ }).ok()?;
|
||||
conn.batch_execute("PRAGMA journal_mode = WAL; PRAGMA synchronous = NORMAL;")
|
||||
.ok()?;
|
||||
conn.run_pending_migrations(MIGRATIONS).ok()?;
|
||||
@@ -86,6 +104,17 @@ mod tests {
|
||||
use arrayvec::ArrayString;
|
||||
use diesel::prelude::*;
|
||||
|
||||
+ #[test]
|
||||
+ fn explicit_directory_creates_persistent_database() {
|
||||
+ let directory = std::env::temp_dir().join(format!("zluda-cache-test-{}-{}", std::process::id(), ModuleCache::time_now()));
|
||||
+ let path = ModuleCache::cache_path(&directory).unwrap();
|
||||
+ assert_eq!(std::path::Path::new(&path), directory.join("zluda2.db"));
|
||||
+ let cache = ModuleCache::open(&path).unwrap();
|
||||
+ drop(cache);
|
||||
+ assert!(std::path::Path::new(&path).is_file());
|
||||
+ std::fs::remove_dir_all(directory).unwrap();
|
||||
+ }
|
||||
+
|
||||
#[derive(Queryable, Selectable)]
|
||||
#[diesel(table_name = crate::schema::modules)]
|
||||
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
|
||||
@@ -1,151 +0,0 @@
|
||||
diff --git a/llvm_zluda/src/compile.rs b/llvm_zluda/src/compile.rs
|
||||
index 5a54417..90c0034 100644
|
||||
--- a/llvm_zluda/src/compile.rs
|
||||
+++ b/llvm_zluda/src/compile.rs
|
||||
@@ -158,6 +158,10 @@ fn wmma_rewrite_flags(gcn_arch: &str) -> Option<u32> {
|
||||
let mut flags = if wmma_fp8_rewrite_requested() { 1 } else { 0 };
|
||||
if family == 12 {
|
||||
flags |= 2;
|
||||
+ // Explicit diagnostic/correctness mode; retain upstream's fast default.
|
||||
+ if std::env::var("D4R_ZLUDA_WMMA_F16_REFERENCE").is_ok_and(|value| value.trim() == "1") {
|
||||
+ flags |= 8;
|
||||
+ }
|
||||
// native FP8 needs gfx12 hardware; the layout shim on gfx11 takes it only for tests (D4R_WMMA12_SHIM)
|
||||
if flags & 1 != 0 && wmma_fp8_native_requested() {
|
||||
flags |= 4;
|
||||
diff --git a/llvm_zluda/src/lib.cpp b/llvm_zluda/src/lib.cpp
|
||||
index 07238db..d262fc6 100644
|
||||
--- a/llvm_zluda/src/lib.cpp
|
||||
+++ b/llvm_zluda/src/lib.cpp
|
||||
@@ -970,6 +970,7 @@ static unsigned widenMmaK8(Module *M)
|
||||
// Flags bit 0: also lower m16n8k32 e4m3 MMAs (D4R_ZLUDA_WMMA_FP8=1).
|
||||
// Flags bit 1: gfx12 (RDNA4) WMMA layout: the d4r_wmma12_* helpers.
|
||||
// Flags bit 2 (with bit 1): e4m3 MMAs on native FP8 WMMA: the d4r_wmma12f8_* e4m3 helpers.
|
||||
+// Flags bit 3 (with bit 1): f64 sum / one f16 round for f16 accumulator MMAs.
|
||||
// The helper sets share one contract (see zluda_ptx_impl.cpp), so only their names change here.
|
||||
unsigned LLVMZludaRewriteMmaF16Wmma(LLVMModuleRef ModuleRef, unsigned Flags)
|
||||
{
|
||||
@@ -999,14 +1000,15 @@ unsigned LLVMZludaRewriteMmaF16Wmma(LLVMModuleRef ModuleRef, unsigned Flags)
|
||||
const std::string Arch = Gfx12 ? "__zluda_ptx_impl_d4r_wmma12_" : "__zluda_ptx_impl_d4r_wmma_";
|
||||
const std::string E4m3 = NativeFp8 ? "__zluda_ptx_impl_d4r_wmma12f8_" : Arch;
|
||||
const std::string Widen = NativeFp8 ? E4m3 : "__zluda_ptx_impl_d4r_wmma_";
|
||||
+ const std::string F16 = Arch + (Gfx12 && (Flags & 8) != 0 ? "t_mma_f16_reference" : "t_mma_f16");
|
||||
Helpers H;
|
||||
H.AFromB = M->getOrInsertFunction((Arch + "t_a_from_b").c_str(), Attributes, V8, V2);
|
||||
H.BFromA = M->getOrInsertFunction((Arch + "t_b_from_a").c_str(), Attributes, V8, V4);
|
||||
- H.Mma = M->getOrInsertFunction((Arch + "t_mma_f16").c_str(), Attributes, V2, V8, V8, V2);
|
||||
+ H.Mma = M->getOrInsertFunction(F16.c_str(), Attributes, V2, V8, V8, V2);
|
||||
H.FromNv = M->getOrInsertFunction("__zluda_ptx_impl_d4r_wmma_t_from_nv", Attributes, V2, V2);
|
||||
H.ToNv = M->getOrInsertFunction("__zluda_ptx_impl_d4r_wmma_t_to_nv", Attributes, V2, V2);
|
||||
H.AFromBPair = M->getOrInsertFunction((Arch + "t_a_from_b_pair").c_str(), Attributes, V8, V2, V2);
|
||||
- H.MmaPair = M->getOrInsertFunction((Arch + "t_mma_f16_pair").c_str(), Attributes, V4, V8, V8, V2, V2);
|
||||
+ H.MmaPair = M->getOrInsertFunction((F16 + "_pair").c_str(), Attributes, V4, V8, V8, V2, V2);
|
||||
{
|
||||
Type *F = Type::getFloatTy(Context);
|
||||
Type *V4F = FixedVectorType::get(F, 4);
|
||||
diff --git a/ptx/lib/zluda_ptx_impl.cpp b/ptx/lib/zluda_ptx_impl.cpp
|
||||
index 4ff25dd..36bd6b1 100644
|
||||
--- a/ptx/lib/zluda_ptx_impl.cpp
|
||||
+++ b/ptx/lib/zluda_ptx_impl.cpp
|
||||
@@ -1801,6 +1801,78 @@ __device__ static wmma_float8 wmma12_f16(wmma_uint8 a, wmma_uint8 b, wmma_float8
|
||||
#endif
|
||||
}
|
||||
|
||||
+// Correctness baseline for f16-accumulator MMAs. f32 WMMA accumulation may
|
||||
+// reach the other f16 neighbour around cancellation-heavy rounding midpoints.
|
||||
+// Keep the existing gfx12 gathers/layout; sum their f16 operands in f64 and
|
||||
+// round directly to f16 once, matching d4r's independent NumPy/PTX reference.
|
||||
+// The e4m3 and f32-shadow paths continue using native WMMA.
|
||||
+__device__ static double wmma12_half_value(uint32_t bits)
|
||||
+{
|
||||
+ const uint32_t magnitude = bits & 0x7fffu, exponent = magnitude >> 10;
|
||||
+ const double value = exponent == 0 ? double(magnitude) * 0x1p-24 :
|
||||
+ double(std::bit_cast<float>(exponent == 31 ? (0x7f800000u | ((magnitude & 1023u) << 13)) :
|
||||
+ ((magnitude << 13) + 0x38000000u)));
|
||||
+ return bits & 0x8000u ? -value : value;
|
||||
+}
|
||||
+
|
||||
+// Preserve f16 subnormal result bits even when the target's default f16
|
||||
+// arithmetic mode flushes them. No intermediate f32 rounding is involved.
|
||||
+__device__ static uint32_t wmma12_half_round(double value)
|
||||
+{
|
||||
+ const uint64_t bits = std::bit_cast<uint64_t>(value);
|
||||
+ const uint32_t sign = uint32_t(bits >> 48) & 0x8000u;
|
||||
+ const uint32_t exponent = uint32_t((bits >> 52) & 2047u);
|
||||
+ const uint64_t fraction = bits & 0xfffffffffffffu;
|
||||
+ if (exponent == 2047u) return sign | (fraction ? 0x7e00u : 0x7c00u);
|
||||
+ const int power = int(exponent) - 1023;
|
||||
+ if (power < -25) return sign;
|
||||
+ if (power > 15) return sign | 0x7c00u;
|
||||
+ const unsigned shift = power < -14 ? unsigned(28 - power) : 42u;
|
||||
+ const uint64_t significand = fraction | 0x10000000000000ull;
|
||||
+ uint32_t code = uint32_t(significand >> shift);
|
||||
+ const uint64_t tail = significand & ((uint64_t(1) << shift) - 1);
|
||||
+ const uint64_t midpoint = uint64_t(1) << (shift - 1);
|
||||
+ code += tail > midpoint || (tail == midpoint && (code & 1u));
|
||||
+ if (power >= -14) code += uint32_t(power + 14) << 10;
|
||||
+ return sign | code;
|
||||
+}
|
||||
+
|
||||
+__device__ static wmma_uint4 wmma12_reference_c(uint2::Native_vec_ c0, uint2::Native_vec_ c1, bool paired)
|
||||
+{
|
||||
+ const bool high = wmma12_half() != 0;
|
||||
+ const uint2::Native_vec_ own = paired && high ? c1 : c0;
|
||||
+ const uint2::Native_vec_ send = paired && !high ? c1 : c0;
|
||||
+ const uint2::Native_vec_ other = {wmma12_other_half(send.x), wmma12_other_half(send.y)};
|
||||
+ const uint2::Native_vec_ even = paired && high ? other : own;
|
||||
+ const uint2::Native_vec_ odd = paired && high ? own : other;
|
||||
+ return {(even.x & 0xffffu) | (odd.x << 16), (even.x >> 16) | (odd.x & 0xffff0000u),
|
||||
+ (even.y & 0xffffu) | (odd.y << 16), (even.y >> 16) | (odd.y & 0xffff0000u)};
|
||||
+}
|
||||
+
|
||||
+__device__ static wmma_uint4 wmma12_f16_reference(wmma_uint8 a, wmma_uint8 b, wmma_uint4 c)
|
||||
+{
|
||||
+ // x/y: packed even rows, z/w: packed odd rows, in this half-wave.
|
||||
+ wmma_uint4 result = {};
|
||||
+ const uint32_t half = wmma12_half();
|
||||
+ for (uint32_t i = 0; i < 8; ++i)
|
||||
+ {
|
||||
+ const uint32_t row = i + 8 * half;
|
||||
+ double sum = wmma12_half_value((c[i / 2] >> (16 * (i & 1))) & 0xffffu);
|
||||
+ for (uint32_t khalf = 0; khalf < 2; ++khalf)
|
||||
+ for (uint32_t j = 0; j < 4; ++j)
|
||||
+ {
|
||||
+ const uint32_t av = wmma_from_lane(row + 16 * khalf, a[j]);
|
||||
+ const uint32_t other = wmma12_other_half(b[j]);
|
||||
+ const uint32_t bv = half == khalf ? b[j] : other;
|
||||
+ sum += wmma12_half_value(av & 0xffffu) * wmma12_half_value(bv & 0xffffu);
|
||||
+ sum += wmma12_half_value(av >> 16) * wmma12_half_value(bv >> 16);
|
||||
+ }
|
||||
+ // Do not round through f32: that can reintroduce the midpoint error.
|
||||
+ result[(i & 1) * 2 + i / 4] |= wmma12_half_round(sum) << (16 * ((i / 2) & 1));
|
||||
+ }
|
||||
+ return result;
|
||||
+}
|
||||
+
|
||||
// f32 D' = A' B' + C' for operands whose dwords hold two e4m3 bytes in their low 16 bits (the d4r_wmma12f8
|
||||
// e4m3 "widening" below keeps the bytes): true e4m3 values, no scaling
|
||||
__device__ static wmma_float8 wmma12_e4m3(wmma_uint8 a, wmma_uint8 b, wmma_float8 c)
|
||||
@@ -1959,6 +2031,22 @@ extern "C"
|
||||
return wmma12_t_from_d_pair(wmma12_f16(a_t, b_t, wmma12_c_from_t_pair(c0_t, c1_t)));
|
||||
}
|
||||
|
||||
+ uint2::Native_vec_ FUNC(d4r_wmma12_t_mma_f16_reference)(wmma_uint8 a_t, wmma_uint8 b_t, uint2::Native_vec_ c_t)
|
||||
+ {
|
||||
+ const wmma_uint4 rows = wmma12_f16_reference(a_t, b_t, wmma12_reference_c(c_t, c_t, false));
|
||||
+ const uint2::Native_vec_ other = {wmma12_other_half(rows.z), wmma12_other_half(rows.w)};
|
||||
+ return wmma12_half() ? other : uint2::Native_vec_{rows.x, rows.y};
|
||||
+ }
|
||||
+
|
||||
+ uint4::Native_vec_ FUNC(d4r_wmma12_t_mma_f16_reference_pair)(wmma_uint8 a_t, wmma_uint8 b_t, uint2::Native_vec_ c0_t, uint2::Native_vec_ c1_t)
|
||||
+ {
|
||||
+ const wmma_uint4 rows = wmma12_f16_reference(a_t, b_t, wmma12_reference_c(c0_t, c1_t, true));
|
||||
+ const bool high = wmma12_half() != 0;
|
||||
+ const uint32_t r0 = wmma12_other_half(high ? rows.x : rows.z);
|
||||
+ const uint32_t r1 = wmma12_other_half(high ? rows.y : rows.w);
|
||||
+ return high ? uint4::Native_vec_{r0, r1, rows.z, rows.w} : uint4::Native_vec_{rows.x, rows.y, r0, r1};
|
||||
+ }
|
||||
+
|
||||
wmma_float4 FUNC(d4r_wmma12_s_mma_f16)(wmma_uint8 a_t, wmma_uint8 b_t, wmma_float4 c, float up, float down)
|
||||
{
|
||||
return wmma12_s_from_d(wmma12_f16(a_t, b_t, wmma12_c_from_s(c * up))) * down;
|
||||
@@ -1,148 +0,0 @@
|
||||
From b0161a4166652eee86a8113a38d042b02403db6e Mon Sep 17 00:00:00 2001
|
||||
From: Codex <codex@localhost>
|
||||
Date: Thu, 1 Oct 2026 18:07:07 +0300
|
||||
Subject: [PATCH] Add opt-in Windows HIP event and occupancy diagnostics
|
||||
|
||||
---
|
||||
zluda/src/impl/d4r_profile.rs | 60 +++++++++++++++++++++++++++++++++++
|
||||
zluda/src/impl/function.rs | 9 ++++++
|
||||
zluda/src/impl/mod.rs | 1 +
|
||||
zluda/src/impl/module.rs | 2 +-
|
||||
4 files changed, 71 insertions(+), 1 deletion(-)
|
||||
create mode 100644 zluda/src/impl/d4r_profile.rs
|
||||
|
||||
diff --git a/zluda/src/impl/d4r_profile.rs b/zluda/src/impl/d4r_profile.rs
|
||||
new file mode 100644
|
||||
index 0000000..33ba5f6
|
||||
--- /dev/null
|
||||
+++ b/zluda/src/impl/d4r_profile.rs
|
||||
@@ -0,0 +1,60 @@
|
||||
+//! Optional, deliberately serializing HIP-event diagnostics. Not a production
|
||||
+//! scheduler or a claim about application FPS / measured hardware occupancy.
|
||||
+use hip_runtime_sys::*;
|
||||
+use std::{ffi::CStr, ptr, time::Instant};
|
||||
+
|
||||
+pub(super) struct Launch {
|
||||
+ start: hipEvent_t,
|
||||
+ end: hipEvent_t,
|
||||
+ host: Instant,
|
||||
+}
|
||||
+
|
||||
+impl Drop for Launch {
|
||||
+ fn drop(&mut self) {
|
||||
+ unsafe {
|
||||
+ if !self.end.is_null() { let _ = hipEventDestroy(self.end); }
|
||||
+ if !self.start.is_null() { let _ = hipEventDestroy(self.start); }
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+impl Launch {
|
||||
+ pub(super) unsafe fn begin(stream: hipStream_t) -> Result<Option<Self>, hipErrorCode_t> {
|
||||
+ if std::env::var_os("D4R_ZLUDA_PROFILE").is_none() { return Ok(None); }
|
||||
+ let mut result = Self { start: ptr::null_mut(), end: ptr::null_mut(), host: Instant::now() };
|
||||
+ hipEventCreateWithFlags(&mut result.start, 0)?;
|
||||
+ hipEventCreateWithFlags(&mut result.end, 0)?;
|
||||
+ hipEventRecord(result.start, stream)?;
|
||||
+ result.host = Instant::now();
|
||||
+ Ok(Some(result))
|
||||
+ }
|
||||
+
|
||||
+ pub(super) unsafe fn finish(self, function: hipFunction_t, name: &CStr, backend: &str,
|
||||
+ phase: &str, grid: [u32;3], block: [u32;3], shared: u32, stream: hipStream_t) -> hipError_t {
|
||||
+ let enqueue_ms = self.host.elapsed().as_secs_f64() * 1000.;
|
||||
+ hipEventRecord(self.end, stream)?;
|
||||
+ hipEventSynchronize(self.end)?;
|
||||
+ let completion_ms = self.host.elapsed().as_secs_f64() * 1000.;
|
||||
+ let mut gpu_ms = 0.;
|
||||
+ hipEventElapsedTime(&mut gpu_ms, self.start, self.end)?;
|
||||
+ // Module function handles use the Driver API attribute query. The
|
||||
+ // plural Runtime API expects a registered host function address.
|
||||
+ let mut lds = 0;
|
||||
+ let mut regs = 0;
|
||||
+ let mut private = 0;
|
||||
+ let mut max_threads = 0;
|
||||
+ hipFuncGetAttribute(&mut lds, hipFunction_attribute::HIP_FUNC_ATTRIBUTE_SHARED_SIZE_BYTES, function)?;
|
||||
+ hipFuncGetAttribute(&mut regs, hipFunction_attribute::HIP_FUNC_ATTRIBUTE_NUM_REGS, function)?;
|
||||
+ hipFuncGetAttribute(&mut private, hipFunction_attribute::HIP_FUNC_ATTRIBUTE_LOCAL_SIZE_BYTES, function)?;
|
||||
+ hipFuncGetAttribute(&mut max_threads, hipFunction_attribute::HIP_FUNC_ATTRIBUTE_MAX_THREADS_PER_BLOCK, function)?;
|
||||
+ let threads = block.iter().try_fold(1u32, |a, b| a.checked_mul(*b))
|
||||
+ .filter(|n| *n <= i32::MAX as u32).ok_or(hipErrorCode_t::InvalidValue)?;
|
||||
+ let mut blocks_per_multiprocessor = 0;
|
||||
+ hipModuleOccupancyMaxActiveBlocksPerMultiprocessor(&mut blocks_per_multiprocessor,
|
||||
+ function, threads as i32, shared as usize)?;
|
||||
+ eprintln!("D4R_KERNEL_PROFILE kernel={name:?} backend={backend} phase={phase} gpu_ms={gpu_ms:.6} enqueue_ms={enqueue_ms:.6} completion_ms={completion_ms:.6} grid={},{},{} block={},{},{} static_lds_bytes={} dynamic_lds_bytes={shared} regs={} private_bytes={} max_threads={} predicted_blocks_per_multiprocessor={blocks_per_multiprocessor} serializing=1",
|
||||
+ grid[0], grid[1], grid[2], block[0], block[1], block[2], lds,
|
||||
+ regs, private, max_threads);
|
||||
+ Ok(())
|
||||
+ }
|
||||
+}
|
||||
diff --git a/zluda/src/impl/function.rs b/zluda/src/impl/function.rs
|
||||
index 7a7470a..c456dfe 100644
|
||||
--- a/zluda/src/impl/function.rs
|
||||
+++ b/zluda/src/impl/function.rs
|
||||
@@ -191,6 +191,7 @@ pub(crate) fn launch_kernel(
|
||||
}
|
||||
};
|
||||
if let (Some((prep, blocks)), true) = (f.prep, run_prep) {
|
||||
+ let profile = unsafe { super::d4r_profile::Launch::begin(stream) }?;
|
||||
unsafe {
|
||||
hipModuleLaunchKernel(
|
||||
prep,
|
||||
@@ -206,6 +207,9 @@ pub(crate) fn launch_kernel(
|
||||
extra_for_hip,
|
||||
)
|
||||
}?;
|
||||
+ if let Some(profile) = profile {
|
||||
+ unsafe { profile.finish(prep, &f.name, "native", "prep", [blocks,1,1], [128,1,1], 0, stream) }?;
|
||||
+ }
|
||||
f.prep_last.store(prep_key, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
let block_dim_z = if f.block_z != 0 { f.block_z } else { block_dim_z };
|
||||
@@ -214,6 +218,7 @@ pub(crate) fn launch_kernel(
|
||||
eprintln!("[d4r-launch] kernel={:?} backend={} grid={grid_dim_x},{grid_dim_y},{grid_dim_z} block={block_dim_x},{block_dim_y},{block_dim_z}",
|
||||
f.name, if f.native_owner.is_some() { "native" } else { "translated" });
|
||||
}
|
||||
+ let profile = unsafe { super::d4r_profile::Launch::begin(stream) }?;
|
||||
let result = unsafe {
|
||||
hipModuleLaunchKernel(
|
||||
f.base,
|
||||
@@ -230,6 +235,10 @@ pub(crate) fn launch_kernel(
|
||||
)
|
||||
};
|
||||
result?;
|
||||
+ if let Some(profile) = profile {
|
||||
+ unsafe { profile.finish(f.base, &f.name, if f.native_owner.is_some() { "native" } else { "translated" },
|
||||
+ "main", [grid_dim_x, grid_dim_y, grid_dim_z], [block_dim_x, block_dim_y, block_dim_z], shared_mem_bytes, stream) }?;
|
||||
+ }
|
||||
if let Some(replay) = replay { replay.finish(stream)?; }
|
||||
Ok(())
|
||||
}
|
||||
diff --git a/zluda/src/impl/mod.rs b/zluda/src/impl/mod.rs
|
||||
index 14e0aee..380eb6e 100644
|
||||
--- a/zluda/src/impl/mod.rs
|
||||
+++ b/zluda/src/impl/mod.rs
|
||||
@@ -6,6 +6,7 @@ pub(super) mod context;
|
||||
pub(super) mod device;
|
||||
pub(super) mod driver;
|
||||
pub(super) mod d4r_replay;
|
||||
+pub(super) mod d4r_profile;
|
||||
pub(super) mod event;
|
||||
pub(super) mod function;
|
||||
pub(super) mod graph;
|
||||
diff --git a/zluda/src/impl/module.rs b/zluda/src/impl/module.rs
|
||||
index 5a79368..232f7d4 100644
|
||||
--- a/zluda/src/impl/module.rs
|
||||
+++ b/zluda/src/impl/module.rs
|
||||
@@ -502,7 +502,7 @@ fn zluda_build_version() -> &'static str {
|
||||
key.starts_with("D4R_ZLUDA_")
|
||||
&& !matches!(
|
||||
key.as_str(),
|
||||
- "D4R_ZLUDA_NATIVE_DIR" | "D4R_ZLUDA_DIR" | "D4R_ZLUDA_DUMP_DIR" | "D4R_ZLUDA_DUMP_PTX_DIR" | "D4R_ZLUDA_LIBCUDA" | "D4R_ZLUDA_VERBOSE"
|
||||
+ "D4R_ZLUDA_NATIVE_DIR" | "D4R_ZLUDA_DIR" | "D4R_ZLUDA_DUMP_DIR" | "D4R_ZLUDA_DUMP_PTX_DIR" | "D4R_ZLUDA_LIBCUDA" | "D4R_ZLUDA_VERBOSE" | "D4R_ZLUDA_PROFILE"
|
||||
| "D4R_ZLUDA_WMMA_FP8_NATIVE" // keyed by effect (ExtraCacheAttributes)
|
||||
)
|
||||
})
|
||||
--
|
||||
2.55.0.windows.5
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
From a1c506fc956cfc347c56c44311669700c0d500ac Mon Sep 17 00:00:00 2001
|
||||
From: Codex <codex@localhost>
|
||||
Date: Thu, 1 Oct 2026 22:16:19 +0300
|
||||
Subject: [PATCH] Profile CUDA host API duration without serializing GPU
|
||||
launches
|
||||
|
||||
---
|
||||
zluda/src/d4r_api_profile.rs | 34 ++++++++++++++++++++++++++++++++++
|
||||
zluda/src/lib.rs | 3 +++
|
||||
2 files changed, 37 insertions(+)
|
||||
create mode 100644 zluda/src/d4r_api_profile.rs
|
||||
|
||||
diff --git a/zluda/src/d4r_api_profile.rs b/zluda/src/d4r_api_profile.rs
|
||||
new file mode 100644
|
||||
index 0000000..ef2da1b
|
||||
--- /dev/null
|
||||
+++ b/zluda/src/d4r_api_profile.rs
|
||||
@@ -0,0 +1,34 @@
|
||||
+//! Host API timings only: no HIP events, stream waits, or altered scheduling.
|
||||
+use std::{cell::RefCell, collections::BTreeMap, sync::OnceLock, time::Instant};
|
||||
+
|
||||
+#[derive(Default)]
|
||||
+struct Row { calls: u64, total_ms: f64, max_ms: f64 }
|
||||
+struct Samples { since: Instant, rows: BTreeMap<&'static str, Row> }
|
||||
+thread_local! {
|
||||
+ static SAMPLES: RefCell<Samples> = RefCell::new(Samples { since: Instant::now(), rows: BTreeMap::new() });
|
||||
+}
|
||||
+pub(crate) struct Call { name: &'static str, start: Instant }
|
||||
+impl Call {
|
||||
+ pub(crate) fn begin(name: &'static str) -> Option<Self> {
|
||||
+ static ENABLED: OnceLock<bool> = OnceLock::new();
|
||||
+ if !*ENABLED.get_or_init(|| std::env::var_os("D4R_ZLUDA_PROFILE_API").is_some()) { return None; }
|
||||
+ Some(Self { name, start: Instant::now() })
|
||||
+ }
|
||||
+}
|
||||
+impl Drop for Call {
|
||||
+ fn drop(&mut self) {
|
||||
+ let ms = self.start.elapsed().as_secs_f64() * 1000.;
|
||||
+ SAMPLES.with(|samples| {
|
||||
+ let mut samples = samples.borrow_mut();
|
||||
+ let row = samples.rows.entry(self.name).or_default();
|
||||
+ row.calls += 1; row.total_ms += ms; row.max_ms = row.max_ms.max(ms);
|
||||
+ let period_ms = samples.since.elapsed().as_secs_f64() * 1000.;
|
||||
+ if period_ms < 2000. { return; }
|
||||
+ for (name, row) in &samples.rows {
|
||||
+ eprintln!("D4R_CUDA_API_PROFILE thread={:?} api={name} calls={} total_ms={:.6} max_ms={:.6} period_ms={period_ms:.6} serializing=0",
|
||||
+ std::thread::current().id(), row.calls, row.total_ms, row.max_ms);
|
||||
+ }
|
||||
+ samples.rows.clear(); samples.since = Instant::now();
|
||||
+ });
|
||||
+ }
|
||||
+}
|
||||
diff --git a/zluda/src/lib.rs b/zluda/src/lib.rs
|
||||
index ecdeb3e..31bfa42 100644
|
||||
--- a/zluda/src/lib.rs
|
||||
+++ b/zluda/src/lib.rs
|
||||
@@ -2,6 +2,7 @@ use cuda_types::cuda::CUerror;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
pub(crate) mod r#impl;
|
||||
+mod d4r_api_profile;
|
||||
#[cfg_attr(windows, path = "os_win.rs")]
|
||||
#[cfg_attr(not(windows), path = "os_unix.rs")]
|
||||
mod os;
|
||||
@@ -40,6 +41,7 @@ macro_rules! implemented {
|
||||
if !initialized() {
|
||||
return Err(CUerror::DEINITIALIZED);
|
||||
}
|
||||
+ let _profile = d4r_api_profile::Call::begin(stringify!($fn_name));
|
||||
cuda_macros::cuda_normalize_fn!( crate::r#impl::$fn_name ) ($(zluda_common::FromCuda::<_, CUerror>::from_cuda(&$arg_id)?),*)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -57,6 +59,7 @@ macro_rules! implemented_in_function {
|
||||
if !initialized() {
|
||||
return Err(CUerror::DEINITIALIZED);
|
||||
}
|
||||
+ let _profile = d4r_api_profile::Call::begin(stringify!($fn_name));
|
||||
cuda_macros::cuda_normalize_fn!( crate::r#impl::function::$fn_name ) ($(zluda_common::FromCuda::<_, CUerror>::from_cuda(&$arg_id)?),*)?;
|
||||
Ok(())
|
||||
}
|
||||
--
|
||||
2.55.0.windows.5
|
||||
|
||||
@@ -5,7 +5,7 @@ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
MINGW_CXX="${MINGW_CXX:-x86_64-w64-mingw32-g++}"
|
||||
|
||||
"$ROOT/scripts/build_d4r_nvngx_shim.sh"
|
||||
"$MINGW_CXX" -std=c++20 -O2 -Wall -Wextra -I"$ROOT/build/vulkan-include" -static -static-libgcc -static-libstdc++ \
|
||||
"$MINGW_CXX" -std=c++20 -O2 -Wall -Wextra -static -static-libgcc -static-libstdc++ \
|
||||
"$ROOT/tools/d3d12_dlss_harness.cpp" "$ROOT/tools/d4r_ngx_param_host.cpp" "$ROOT/build/d4r_ngx_param_msvc.obj" \
|
||||
-o "$ROOT/build/d3d12_dlss_harness.exe"
|
||||
printf 'Built %s\n' "$ROOT/build/d3d12_dlss_harness.exe"
|
||||
@@ -1,30 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build d4r-owned motion processing kernels and embed them in the Wine bridge.
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
OUT="${1:-$ROOT/build/wine-nvcuda/generated}"
|
||||
ROCM="${D4R_ROCM_DIR:-/opt/rocm}"
|
||||
CLANG="$ROCM/lib/llvm/bin/clang++"
|
||||
[[ -x "$CLANG" ]] || { echo "motion kernels need ROCm clang (D4R_ROCM_DIR)" >&2; exit 2; }
|
||||
mkdir -p "$OUT"
|
||||
for arch in gfx1100 gfx1101 gfx1102 gfx1103 gfx1200 gfx1201; do
|
||||
"$CLANG" -x hip -nogpuinc -nogpulib --offload-device-only --offload-arch="$arch" \
|
||||
--no-gpu-bundle-output -O3 -mno-wavefrontsize64 -o "$OUT/$arch.hsaco" \
|
||||
"$ROOT/kernels/support/motion_dilate.hip"
|
||||
done
|
||||
python3 - "$OUT" <<'PY'
|
||||
from pathlib import Path
|
||||
import sys
|
||||
root = Path(sys.argv[1])
|
||||
lines = ['/* Generated from d4r-owned HIP source; do not edit. */', '#include <string.h>']
|
||||
for p in sorted(root.glob('gfx*.hsaco')):
|
||||
data = p.read_bytes()
|
||||
lines.append(f'static const unsigned char motion_{p.stem}[] __attribute__((aligned(64))) = {{')
|
||||
lines.extend(','.join(f'0x{x:02x}' for x in data[i:i+24]) + ',' for i in range(0,len(data),24))
|
||||
lines.append('};')
|
||||
lines.append('static const void* d4r_motion_image(const char* arch) {')
|
||||
for p in sorted(root.glob('gfx*.hsaco')):
|
||||
lines.append(f' if (strcmp(arch, "{p.stem}") == 0) return motion_{p.stem};')
|
||||
lines.append(' return NULL;\n}')
|
||||
(root/'d4r_motion_kernels.h').write_text('\n'.join(lines)+'\n')
|
||||
PY
|
||||
@@ -5,7 +5,7 @@ set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
: "${D4R_RELEASE_BUILD_ROOT:?set the build output directory}"
|
||||
: "${D4R_ZLUDA_SRC:?set a build-local ZLUDA checkout with patches 0002-0007 applied}"
|
||||
: "${D4R_VKD3D_SRC:?set a build-local vkd3d-proton checkout with patches 0001 and 0002 applied}"
|
||||
: "${D4R_VKD3D_SRC:?set a build-local vkd3d-proton checkout with patch 0001 applied}"
|
||||
: "${D4R_ROCM_DIR:?set ROCm with clang and device libraries}"
|
||||
BUILD="$(realpath -m "$D4R_RELEASE_BUILD_ROOT")"
|
||||
[[ "$(getconf GNU_LIBC_VERSION)" == 'glibc 2.41' ]] || {
|
||||
|
||||
@@ -7,13 +7,7 @@ WINEBUILD="${WINEBUILD:-winebuild}"
|
||||
BRIDGE_ROOT="$ROOT/build/wine-nvcuda"
|
||||
|
||||
mkdir -p "$BRIDGE_ROOT/x86_64-unix" "$BRIDGE_ROOT/x86_64-windows"
|
||||
MOTION_FLAGS=()
|
||||
if [[ -x "${D4R_ROCM_DIR:-/opt/rocm}/lib/llvm/bin/clang++" ]]; then
|
||||
"$ROOT/scripts/build_d4r_motion_kernels.sh" "$BRIDGE_ROOT/generated"
|
||||
MOTION_FLAGS=(-DD4R_MOTION_KERNELS -I"$BRIDGE_ROOT/generated")
|
||||
fi
|
||||
"$WINEGCC" -m64 -shared -o "$BRIDGE_ROOT/x86_64-unix/nvcuda.dll" \
|
||||
"${MOTION_FLAGS[@]}" \
|
||||
"$ROOT/tools/wine_nvcuda_bridge.c" \
|
||||
-Wb,--export="$ROOT/tools/wine_nvcuda_bridge.spec" -ldl -lpthread
|
||||
"$WINEBUILD" --dll --fake-module -E "$ROOT/tools/wine_nvcuda_bridge.spec" \
|
||||
|
||||
@@ -133,24 +133,18 @@ def main(argv):
|
||||
# RDNA4's native FP8 WMMA (on unless set off; ZLUDA and the bridge ignore it on other GPUs)
|
||||
value = flag("Kernels", "NativeFp8")
|
||||
env["D4R_ZLUDA_WMMA_FP8_NATIVE"] = "0" if value is False else "1"
|
||||
value = flag("Kernels", "NativeSwinEncoders")
|
||||
env["D4R_NATIVE_SWIN_ENCODERS"] = "0" if value is False else "1"
|
||||
value = get("Kernels", "ImplicitMaxBlock")
|
||||
if value:
|
||||
env["D4R_ZLUDA_IMPLICIT_MAX_BLOCK"] = value
|
||||
|
||||
for key, var in (("VramInterop", "D4R_SHIM_VRAM_INTEROP"), ("InputSync", "D4R_SHIM_INPUT_SYNC"),
|
||||
("LinearInputs", "D4R_SHIM_LINEAR_INPUTS"),
|
||||
("GpuWait", "D4R_SHIM_GPU_WAIT"), ("LinearInputs", "D4R_SHIM_LINEAR_INPUTS"),
|
||||
("DirectOutput", "D4R_SHIM_OUTPUT_DIRECT"), ("ElideNgxSync", "D4R_ELIDE_NGX_SYNC"),
|
||||
("EvalSync", "D4R_SHIM_EVAL_SYNC")):
|
||||
value = flag("Interop", key)
|
||||
if value is not None:
|
||||
env[var] = "1" if value else "0"
|
||||
value = get("Interop", "MarkerPollUs")
|
||||
dilation = get("Interop", "MotionVectorDilation")
|
||||
if dilation is not None and dilation not in ("0", "1", "2"):
|
||||
fail("[Interop] MotionVectorDilation must be 0, 1 or 2")
|
||||
env["D4R_MOTION_DILATION"] = dilation or "0"
|
||||
if value:
|
||||
if not value.isdigit():
|
||||
fail(f"[Interop] MarkerPollUs must be a number of microseconds, not {value!r}")
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
# older builds
|
||||
# Accuracy texture sets go in accuracy/<target>/, built with D4R_PREFER_ACCURACY=1.
|
||||
# D4R_ZLUDA_EMIT d4r_emit from the patched ZLUDA build; required in full builds when an accuracy
|
||||
# texture set or L's unfolded texture variants are not supplied in D4R_BUNDLE_TEX.
|
||||
# texture set is not supplied in D4R_BUNDLE_TEX/accuracy/<target>.
|
||||
# D4R_BUNDLE_NVIDIA=0 leaves them out (d4r-VERSION-nonvidia.zip; users then add the two DLLs themselves).
|
||||
set -euo pipefail
|
||||
|
||||
@@ -126,24 +126,6 @@ for arch in $ARCHS; do
|
||||
folders+=("$arch")
|
||||
[[ "$arch" == gfx12* ]] && folders+=("$arch-fp8")
|
||||
done
|
||||
# Older texture bundles predate L. Complete them from the user's DLSS DLL rather
|
||||
# than silently shipping only M's folded variants under the new L support claim.
|
||||
ensure_l_textures() {
|
||||
local dir="$1" accuracy="$2" missing=0 mv range v name
|
||||
local names=(rrlite_dec0_4x4)
|
||||
for mv in mvhi mvlo; do
|
||||
for range in hdr ldr; do
|
||||
names+=("rrlite_enc0_4x4_${mv}_${range}")
|
||||
for v in 3_1 3_2; do names+=("rrlite_post_${v}_${mv}_${range}"); done
|
||||
done
|
||||
done
|
||||
for name in "${names[@]}"; do [[ -f "$dir/$name.hsaco" ]] || missing=1; done
|
||||
if [[ "$missing" == 1 ]]; then
|
||||
: "${D4R_ZLUDA_EMIT:?set D4R_ZLUDA_EMIT or supply the unfolded L texture variants in D4R_BUNDLE_TEX}"
|
||||
D4R_PREFER_ACCURACY="$accuracy" D4R_ROCM_DIR="$ROCM" D4R_GPU_ARCH="$arch" D4R_NATIVE_FP8="$fp8" \
|
||||
D4R_DLSS_DLL="$D4R_BUNDLE_DLSS" "$ROOT/kernels/build.sh" l "$dir" >/dev/null
|
||||
fi
|
||||
}
|
||||
build_target() {
|
||||
local folder="$1" arch fp8 tex_dir accurate accurate_tex f
|
||||
arch="${folder%-fp8}"
|
||||
@@ -166,7 +148,6 @@ build_target() {
|
||||
done
|
||||
fi
|
||||
fi
|
||||
[[ "$VARIANT" != full ]] || ensure_l_textures "$STAGE/d4r/kernels/$folder" 0
|
||||
python3 "$ROOT/kernels/tools/kernel_manifest.py" "$STAGE/d4r/kernels/$folder" "${DLLS[@]}"
|
||||
|
||||
# Every release target also gets conservative network and texture variants. Do not copy fast
|
||||
@@ -188,7 +169,6 @@ build_target() {
|
||||
fi
|
||||
fi
|
||||
if [[ "$VARIANT" == full ]]; then
|
||||
ensure_l_textures "$accurate" 1
|
||||
for f in "$STAGE/d4r/kernels/$folder"/*.hsaco; do
|
||||
[[ -f "$accurate/$(basename "$f")" ]] || {
|
||||
echo "missing accuracy variant of $(basename "$f") for $folder" >&2; exit 2;
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
[CmdletBinding()]
|
||||
param([Parameter(Mandatory=$true)][string]$DlssDll, [string]$HipRoot, [string]$OutputDirectory, [switch]$HardwarePacking)
|
||||
$ErrorActionPreference='Stop'
|
||||
$repo=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
|
||||
if (!$HipRoot) { $HipRoot=Join-Path $repo '.tools/therock-10.2.0a20260929/_rocm_sdk_core' }
|
||||
if (!$OutputDirectory) { $OutputDirectory=Join-Path $repo $(if ($HardwarePacking) { 'build/native-m-fp8-hardware-pack' } else { 'build/native-m-fp8-experimental' }) }
|
||||
$OutputDirectory=[IO.Path]::GetFullPath($OutputDirectory)
|
||||
if ($OutputDirectory -eq [IO.Path]::GetFullPath((Join-Path $repo 'build/native-m-gfx1201'))) {
|
||||
throw 'Keep the validated FP16 baseline separate from the experimental FP8 build.'
|
||||
}
|
||||
$DlssDll=(Get-Item -LiteralPath $DlssDll -ErrorAction Stop).FullName
|
||||
$compiler=Join-Path $HipRoot 'lib/llvm/bin/clang++.exe'
|
||||
if (!(Test-Path -LiteralPath $compiler)) { $compiler=Join-Path $HipRoot 'bin/clang++.exe' }
|
||||
if (!(Test-Path -LiteralPath $compiler)) { throw "HIP compiler missing under $HipRoot" }
|
||||
New-Item -ItemType Directory -Force $OutputDirectory | Out-Null
|
||||
$disassembler=Join-Path (Split-Path $compiler) 'llvm-objdump.exe'
|
||||
if (!(Test-Path -LiteralPath $disassembler)) { throw 'HIP llvm-objdump is required to confirm native FP8 instructions.' }
|
||||
foreach ($source in Get-ChildItem -LiteralPath (Join-Path $repo 'kernels/m') -Filter '*.hip' | Sort-Object Name) {
|
||||
$flags=@()
|
||||
if ((Get-Content -LiteralPath $source.FullName -Raw) -match '// d4r-build-flags: ([^\r\n]+)') { $flags=$Matches[1].Trim().Split(' ') }
|
||||
if ($HardwarePacking) { $flags+='-DD4R_FP8_HW_PACK' }
|
||||
$object=Join-Path $OutputDirectory ($source.BaseName+'.hsaco')
|
||||
$ErrorActionPreference='Continue'
|
||||
& $compiler -x hip -std=c++17 --offload-arch=gfx1201 --offload-device-only --no-gpu-bundle-output `
|
||||
-mno-wavefrontsize64 -nogpuinc -nogpulib -O3 -DD4R_DEVICE_ONLY_MINIMAL `
|
||||
-DSWIN_EXACT -DSWIN_EXACT_PV -DD4R_FP8_WMMA @flags $source.FullName `
|
||||
-o $object > (Join-Path $OutputDirectory ($source.BaseName+'.build.stdout.log')) `
|
||||
2> (Join-Path $OutputDirectory ($source.BaseName+'.build.stderr.log'))
|
||||
$ErrorActionPreference='Stop'
|
||||
if ($LASTEXITCODE) { throw "Experimental FP8 compile failed: $($source.Name)" }
|
||||
$isa=Join-Path $OutputDirectory ($source.BaseName+'.isa.txt')
|
||||
& $disassembler --disassemble --mcpu=gfx1201 $object > $isa
|
||||
if ($LASTEXITCODE -or !(Select-String -LiteralPath $isa -Pattern 'v_wmma_f32_16x16x16_fp8_fp8')) {
|
||||
throw "Native FP8 WMMA is absent from $($source.Name); no FP16 fallback is accepted."
|
||||
}
|
||||
if ($HardwarePacking -and !(Select-String -LiteralPath $isa -Pattern 'v_cvt_pk_fp8_f32')) { throw 'Native FP8 operand packing is absent from ISA.' }
|
||||
Write-Host "Compiled gfx1201 FP8 WMMA: $($source.BaseName)"
|
||||
}
|
||||
if ($HardwarePacking) {
|
||||
$probeDirectory=Join-Path $OutputDirectory 'packing-probe'
|
||||
New-Item -ItemType Directory -Force $probeDirectory | Out-Null
|
||||
& $compiler -x hip -std=c++17 --offload-arch=gfx1201 --offload-device-only --no-gpu-bundle-output `
|
||||
-mno-wavefrontsize64 -nogpuinc -nogpulib -O3 -DD4R_DEVICE_ONLY_MINIMAL `
|
||||
-DD4R_FP8_WMMA -DD4R_FP8_HW_PACK -DSWIN_EXACT -DSWIN_EXACT_PV `
|
||||
(Join-Path $repo 'tools/windows/fp8_pack_probe.hip') -o (Join-Path $probeDirectory 'fp8_pack_probe.hsaco')
|
||||
if ($LASTEXITCODE) { throw 'Native hardware packing diagnostic compile failed.' }
|
||||
}
|
||||
& python (Join-Path $repo 'kernels/tools/kernel_manifest.py') $OutputDirectory $DlssDll
|
||||
if ($LASTEXITCODE) { throw 'Experimental FP8 manifest generation failed.' }
|
||||
@{architecture='gfx1201'; family='M'; experimental=$true; nativeFp8=$true; accuracy=$true;
|
||||
numerics='SWIN_EXACT; SWIN_EXACT_PV; FP8 WMMA for e4m3 GEMMs; exact FP16 attention';
|
||||
validation='not validated; do not install before layer/full-network checks';
|
||||
compiler=$compiler; compilerVersion=(& $compiler --version | Select-Object -First 1);
|
||||
sourceCommit=(& git -C $repo rev-parse HEAD).Trim(); nativeFp8IsaVerified=$true; hardwarePacking=[bool]$HardwarePacking;
|
||||
sourceWorkingTreeDirty=[bool](& git -C $repo status --porcelain);
|
||||
dlssSha256=(Get-FileHash -LiteralPath $DlssDll -Algorithm SHA256).Hash;
|
||||
modules=@(Get-ChildItem -LiteralPath $OutputDirectory -Filter '*.hsaco' | Get-FileHash -Algorithm SHA256)} |
|
||||
ConvertTo-Json -Depth 5 | Set-Content -LiteralPath (Join-Path $OutputDirectory 'build-info.json') -Encoding UTF8
|
||||
Write-Host "Experimental native FP8 M objects: $OutputDirectory (baseline unchanged)"
|
||||
@@ -1,15 +0,0 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][string]$DlssDll,
|
||||
[string]$Kernel = 'hiluma_engine_output_depthinv_mvlo_hdr_max_v2_rel',
|
||||
[string]$HipRoot = 'C:\Program Files\AMD\ROCm\7.2',
|
||||
[string]$ZludaRoot,
|
||||
[string]$OutputDirectory
|
||||
)
|
||||
$ErrorActionPreference='Stop'
|
||||
$repo=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
|
||||
if (!$ZludaRoot) { $ZludaRoot=Join-Path $repo 'dist/zluda-windows-final' }
|
||||
if (!$OutputDirectory) { $OutputDirectory=Join-Path $repo 'build/private-textures-gfx1201' }
|
||||
& python (Join-Path $repo 'tools/windows/build_native_texture.py') --dlss-dll $DlssDll --hip-root $HipRoot `
|
||||
--zluda-root $ZludaRoot --kernel $Kernel --output-directory $OutputDirectory
|
||||
if ($LASTEXITCODE) { throw "Native texture build failed ($LASTEXITCODE); inspect the output work directory." }
|
||||
@@ -1,78 +0,0 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$SourceRoot,
|
||||
[string]$BuildToolsRoot,
|
||||
[string]$InstallRoot,
|
||||
[int]$Jobs = 6
|
||||
)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repo = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
|
||||
if (!$SourceRoot) { $SourceRoot = Join-Path $repo 'external/OptiScaler' }
|
||||
if (!$InstallRoot) { $InstallRoot = Join-Path $repo 'dist/optiscaler-windows-d4r' }
|
||||
$SourceRoot = [IO.Path]::GetFullPath($SourceRoot)
|
||||
$InstallRoot = [IO.Path]::GetFullPath($InstallRoot)
|
||||
$base = '45a2001303ddff632e279f77aef85ceede5832cb'
|
||||
$patches = @(Get-ChildItem -LiteralPath (Join-Path $repo 'patches/optiscaler') -Filter '*.patch' | Sort-Object Name)
|
||||
if (!$patches.Count) { throw 'OptiScaler source patches are missing.' }
|
||||
$previousPath = $env:PATH
|
||||
try {
|
||||
# Git for Windows' submodule helper needs its POSIX utility directory.
|
||||
$gitExe = (Get-Command git -ErrorAction Stop).Source
|
||||
$gitInstall = [IO.Path]::GetFullPath((Join-Path (Split-Path $gitExe) '..'))
|
||||
$gitUtilities = Join-Path $gitInstall 'usr/bin'
|
||||
if (Test-Path -LiteralPath $gitUtilities) { $env:PATH = "$gitUtilities;$env:PATH" }
|
||||
if (!(Test-Path -LiteralPath (Join-Path $SourceRoot '.git'))) {
|
||||
& git clone --filter=blob:none --no-checkout https://github.com/optiscaler/OptiScaler.git $SourceRoot
|
||||
if ($LASTEXITCODE) { throw 'OptiScaler clone failed.' }
|
||||
& git -C $SourceRoot checkout --detach $base
|
||||
if ($LASTEXITCODE) { throw 'Pinned OptiScaler checkout failed.' }
|
||||
}
|
||||
& git -C $SourceRoot merge-base --is-ancestor $base HEAD
|
||||
if ($LASTEXITCODE) { throw "OptiScaler must be based on audited commit $base" }
|
||||
foreach ($patch in $patches) {
|
||||
$ErrorActionPreference = 'Continue'
|
||||
& git -C $SourceRoot apply --reverse --check $patch.FullName 2>$null
|
||||
$alreadyApplied = $LASTEXITCODE -eq 0
|
||||
$ErrorActionPreference = 'Stop'
|
||||
if (!$alreadyApplied) {
|
||||
& git -C $SourceRoot apply --check $patch.FullName
|
||||
if ($LASTEXITCODE) { throw "Source patch conflicts: $($patch.Name); existing changes retained." }
|
||||
& git -C $SourceRoot apply $patch.FullName
|
||||
if ($LASTEXITCODE) { throw "Source patch failed: $($patch.Name)" }
|
||||
}
|
||||
}
|
||||
& git -C $SourceRoot submodule update --init --depth 1
|
||||
if ($LASTEXITCODE) { throw 'Pinned OptiScaler submodules failed.' }
|
||||
if (!$BuildToolsRoot -and (Test-Path -LiteralPath (Join-Path $repo '.tools/vs2022/MSBuild/Current/Bin/MSBuild.exe'))) {
|
||||
$BuildToolsRoot = Join-Path $repo '.tools/vs2022'
|
||||
}
|
||||
if (!$BuildToolsRoot) {
|
||||
$vswhere = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio/Installer/vswhere.exe'
|
||||
if (Test-Path -LiteralPath $vswhere) {
|
||||
$BuildToolsRoot = & $vswhere -latest -products '*' -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath
|
||||
}
|
||||
}
|
||||
if (!$BuildToolsRoot) { throw 'MSVC v143 Build Tools and Windows SDK 10.0.26100 are required; see patches/optiscaler/README.md.' }
|
||||
$msbuild = Join-Path $BuildToolsRoot 'MSBuild/Current/Bin/MSBuild.exe'
|
||||
if (!(Test-Path -LiteralPath $msbuild)) { throw "MSBuild missing: $msbuild" }
|
||||
New-Item -ItemType Directory -Force $InstallRoot | Out-Null
|
||||
$log = Join-Path $InstallRoot 'build.log'
|
||||
$ErrorActionPreference = 'Continue'
|
||||
& $msbuild (Join-Path $SourceRoot 'OptiScaler.sln') "/m:$Jobs" /p:Configuration=Release /p:Platform=x64 `
|
||||
/p:PostBuildEventUseInBuild=false /verbosity:quiet "/flp:logfile=$log;verbosity=normal"
|
||||
$ErrorActionPreference = 'Stop'
|
||||
if ($LASTEXITCODE) { throw "OptiScaler build failed ($LASTEXITCODE); see $log" }
|
||||
foreach ($file in @('OptiScaler.dll', 'OptiScaler.pdb')) {
|
||||
Copy-Item -LiteralPath (Join-Path $SourceRoot "x64/Release/$file") -Destination $InstallRoot -Force
|
||||
}
|
||||
Copy-Item -LiteralPath (Join-Path $SourceRoot 'LICENSE') -Destination (Join-Path $InstallRoot 'GPL-3.0.txt') -Force
|
||||
foreach ($patch in $patches) { Copy-Item -LiteralPath $patch.FullName -Destination $InstallRoot -Force }
|
||||
$metadata = @{base=$base; sourceCommit=(& git -C $SourceRoot rev-parse HEAD).Trim();
|
||||
sourceDirty=[bool](& git -C $SourceRoot status --porcelain); backendAbi=1;
|
||||
buildTools=$BuildToolsRoot; msbuildVersion=(& $msbuild -version -nologo | Select-Object -Last 1);
|
||||
patches=@($patches | ForEach-Object { @{name=$_.Name; sha256=(Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256).Hash} });
|
||||
dllSha256=(Get-FileHash -LiteralPath (Join-Path $InstallRoot 'OptiScaler.dll') -Algorithm SHA256).Hash;
|
||||
submodules=@(& git -C $SourceRoot submodule status)}
|
||||
$metadata | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath (Join-Path $InstallRoot 'build-info.json') -Encoding UTF8
|
||||
Write-Host "Built OptiScaler with external Windows d4r backend: $InstallRoot"
|
||||
} finally { $env:PATH = $previousPath }
|
||||
@@ -1,72 +0,0 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$HipRoot = $env:HIP_PATH,
|
||||
[ValidateSet('stable', 'therock')][string]$RuntimeProfile = 'stable',
|
||||
[string]$ZludaRoot,
|
||||
[string]$ToolchainRoot,
|
||||
[string]$BuildDirectory,
|
||||
[string]$InstallDirectory
|
||||
)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repo = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
|
||||
if ($RuntimeProfile -eq 'therock') {
|
||||
if (!$PSBoundParameters.ContainsKey('HipRoot')) { $HipRoot = Join-Path $repo '.tools/therock-10.2.0a20260929/_rocm_sdk_core' }
|
||||
if (!$BuildDirectory) { $BuildDirectory = Join-Path $repo 'build/windows-rdna4-therock' }
|
||||
if (!$InstallDirectory) { $InstallDirectory = Join-Path $repo 'dist/windows-rdna4-therock' }
|
||||
}
|
||||
if (!$HipRoot) { $HipRoot = 'C:\Program Files\AMD\ROCm\7.2' }
|
||||
if (!$BuildDirectory) { $BuildDirectory = Join-Path $repo 'build/windows-rdna4' }
|
||||
if (!$InstallDirectory) { $InstallDirectory = Join-Path $repo 'dist/windows-rdna4-diagnostics' }
|
||||
$HipRoot = [IO.Path]::GetFullPath($HipRoot).TrimEnd('\', '/')
|
||||
$BuildDirectory = [IO.Path]::GetFullPath($BuildDirectory).TrimEnd('\', '/')
|
||||
$InstallDirectory = [IO.Path]::GetFullPath($InstallDirectory).TrimEnd('\', '/')
|
||||
if (!$ZludaRoot -and (Test-Path (Join-Path $repo '.tools/zluda/zluda/nvcuda.dll'))) {
|
||||
$ZludaRoot = Join-Path $repo '.tools/zluda/zluda'
|
||||
}
|
||||
if (!$ToolchainRoot -and (Test-Path (Join-Path $repo '.tools/llvm-mingw-20260922-ucrt-x86_64'))) {
|
||||
$ToolchainRoot = Join-Path $repo '.tools/llvm-mingw-20260922-ucrt-x86_64'
|
||||
}
|
||||
$cmakeCommand = Get-Command cmake -ErrorAction SilentlyContinue
|
||||
$cmake = if ($cmakeCommand) { $cmakeCommand.Source } else { Join-Path $repo '.tools/python/cmake/data/bin/cmake.exe' }
|
||||
$ninjaCommand = Get-Command ninja -ErrorAction SilentlyContinue
|
||||
$ninja = if ($ninjaCommand) { $ninjaCommand.Source } else { Join-Path $repo '.tools/python/ninja/data/bin/ninja.exe' }
|
||||
if (!(Test-Path $ninja)) { $ninja = Join-Path $repo '.tools/python/bin/ninja.exe' }
|
||||
if (!(Test-Path $cmake) -or !(Test-Path $ninja)) {
|
||||
throw 'CMake >=3.24 and Ninja required. See docs/windows-rdna4-port.md for local tool setup.'
|
||||
}
|
||||
New-Item -ItemType Directory -Force $BuildDirectory | Out-Null
|
||||
$minhook = Join-Path $repo 'external/MinHook'
|
||||
if (!(Test-Path (Join-Path $minhook 'include/MinHook.h'))) {
|
||||
& git clone --depth 1 --branch v1.3.4 https://github.com/TsudaKageyu/minhook.git $minhook
|
||||
if ($LASTEXITCODE) { throw 'MinHook 1.3.4 source download failed.' }
|
||||
}
|
||||
$minhookCommit = (& git -C $minhook rev-parse HEAD).Trim()
|
||||
if ($minhookCommit -ne 'c3fcafdc10146beb5919319d0683e44e3c30d537') {
|
||||
throw "Expected audited MinHook 1.3.4 source, found $minhookCommit"
|
||||
}
|
||||
$originalPath = $env:PATH
|
||||
try {
|
||||
$configure = @('--fresh', '-S', $repo, '-B', $BuildDirectory, '-G', 'Ninja', '-DCMAKE_BUILD_TYPE=RelWithDebInfo',
|
||||
"-DCMAKE_MAKE_PROGRAM=$ninja", "-DD4R_HIP_ROOT=$HipRoot", "-DCMAKE_INSTALL_PREFIX=$InstallDirectory")
|
||||
if ($ToolchainRoot) {
|
||||
$compiler = Join-Path $ToolchainRoot 'bin/x86_64-w64-mingw32-clang++.exe'
|
||||
if (!(Test-Path $compiler)) { throw "Compiler missing: $compiler" }
|
||||
$env:PATH = "$(Join-Path $ToolchainRoot 'bin');$originalPath"
|
||||
$configure += "-DCMAKE_CXX_COMPILER=$compiler"
|
||||
$configure += "-DCMAKE_C_COMPILER=$(Join-Path $ToolchainRoot 'bin/x86_64-w64-mingw32-clang.exe')"
|
||||
}
|
||||
if ($ZludaRoot) { $configure += "-DD4R_ZLUDA_ROOT=$ZludaRoot" }
|
||||
$ErrorActionPreference = 'Continue'
|
||||
& $cmake @configure 2>&1 | Tee-Object (Join-Path $BuildDirectory 'configure.log')
|
||||
$ErrorActionPreference = 'Stop'
|
||||
if ($LASTEXITCODE) { throw "CMake configure exit=$LASTEXITCODE" }
|
||||
$ErrorActionPreference = 'Continue'
|
||||
& $cmake --build $BuildDirectory --parallel 4 2>&1 | Tee-Object (Join-Path $BuildDirectory 'build.log')
|
||||
$ErrorActionPreference = 'Stop'
|
||||
if ($LASTEXITCODE) { throw "Build exit=$LASTEXITCODE" }
|
||||
$ErrorActionPreference = 'Continue'
|
||||
& $cmake --install $BuildDirectory 2>&1 | Tee-Object (Join-Path $BuildDirectory 'install.log')
|
||||
$ErrorActionPreference = 'Stop'
|
||||
if ($LASTEXITCODE) { throw "Install exit=$LASTEXITCODE" }
|
||||
Write-Host "Built diagnostics: $InstallDirectory"
|
||||
} finally { $env:PATH = $originalPath }
|
||||
@@ -1,70 +0,0 @@
|
||||
[CmdletBinding()]
|
||||
param([string]$SourceRoot, [string]$HipRoot = 'C:\Program Files\AMD\ROCm\7.2', [string]$WorkRoot)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repo = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
|
||||
if (!$SourceRoot) { $SourceRoot = Join-Path $repo 'external/ZLUDA' }
|
||||
if (!$WorkRoot) { $WorkRoot = Join-Path $repo 'build/zluda-helpers-windows' }
|
||||
$toolchain = Join-Path $repo '.tools/llvm-mingw-20260922-ucrt-x86_64'
|
||||
$compiler = Join-Path $HipRoot 'bin/clang++.exe'
|
||||
$dis = Join-Path $HipRoot 'bin/llvm-dis.exe'
|
||||
$assembler = Join-Path $HipRoot 'bin/llvm-as.exe'
|
||||
$ocml = Join-Path $HipRoot 'amdgcn/bitcode/ocml.bc'
|
||||
foreach ($file in @($compiler,$dis,$assembler,$ocml)) {
|
||||
if (!(Test-Path $file)) { throw "HIP helper dependency missing: $file" }
|
||||
}
|
||||
New-Item -ItemType Directory -Force $WorkRoot | Out-Null
|
||||
$configInclude = Join-Path $WorkRoot 'libcxx-config'
|
||||
New-Item -ItemType Directory -Force $configInclude | Out-Null
|
||||
Copy-Item -LiteralPath (Join-Path $toolchain 'include/c++/v1/__config_site') -Destination $configInclude -Force
|
||||
Copy-Item -LiteralPath (Join-Path $SourceRoot 'ext/llvm-project/libcxx/vendor/llvm/default_assertion_handler.in') `
|
||||
-Destination (Join-Path $configInclude '__assertion_handler') -Force
|
||||
# HIP 7.2's long shuffle overloads assume every non-MSVC compiler has LP64.
|
||||
# Windows GNU also has 32-bit long. Correct that public header in a private
|
||||
# mirror; the installed SDK remains untouched and the GPU target is unchanged.
|
||||
$hipInclude = Join-Path $WorkRoot 'hip-include'
|
||||
New-Item -ItemType Directory -Force $hipInclude | Out-Null
|
||||
Copy-Item -LiteralPath (Join-Path $HipRoot 'include/hip') -Destination $hipInclude -Recurse -Force
|
||||
$warpHeader = Join-Path $hipInclude 'hip/amd_detail/amd_warp_functions.h'
|
||||
$warpSource = [IO.File]::ReadAllText($warpHeader)
|
||||
$warpSource = $warpSource.Replace('#ifndef _MSC_VER', '#if __SIZEOF_LONG__ == 8')
|
||||
[IO.File]::WriteAllText($warpHeader,$warpSource,[Text.UTF8Encoding]::new($false))
|
||||
$variants = @(
|
||||
@{Suffix=''; Flags=@()},
|
||||
@{Suffix='_constrained'; Flags=@('-ffp-model=strict','-ffp-exception-behavior=ignore')},
|
||||
@{Suffix='_w64'; Flags=@('-mwavefrontsize64','-DD4R_WAVE64')},
|
||||
@{Suffix='_constrained_w64'; Flags=@('-ffp-model=strict','-ffp-exception-behavior=ignore','-mwavefrontsize64','-DD4R_WAVE64')}
|
||||
)
|
||||
foreach ($variant in $variants) {
|
||||
$name = 'zluda_ptx_impl' + $variant.Suffix
|
||||
$raw = Join-Path $WorkRoot "$name.raw.bc"
|
||||
$ir = Join-Path $WorkRoot "$name.ll"
|
||||
$output = Join-Path $SourceRoot "ptx/lib/$name.bc"
|
||||
$arguments = @('-x','hip','-std=c++20','--target=x86_64-w64-windows-gnu','-stdlib=libc++',
|
||||
'--offload-arch=gfx1030','--offload-device-only','-emit-llvm','-c','-nogpulib','-O3',
|
||||
'-mno-wavefrontsize64','-DHIP_ENABLE_WARP_SYNC_BUILTINS',
|
||||
'-Xclang','-fdenormal-fp-math=dynamic',
|
||||
'-isystem',(Join-Path $SourceRoot 'ext/llvm-project/libcxx/include'),
|
||||
'-isystem',$configInclude,
|
||||
'-isystem',(Join-Path $toolchain 'include'),
|
||||
'-I',$hipInclude,
|
||||
'-Xclang','-mlink-bitcode-file','-Xclang',$ocml)
|
||||
$arguments += $variant.Flags
|
||||
$arguments += @('-o',$raw,(Join-Path $SourceRoot 'ptx/lib/zluda_ptx_impl.cpp'))
|
||||
$ErrorActionPreference = 'Continue'
|
||||
& $compiler @arguments 2>&1 | Tee-Object (Join-Path $WorkRoot "$name.compile.log")
|
||||
$ErrorActionPreference = 'Stop'
|
||||
if ($LASTEXITCODE) { throw "PTX helper compile failed: $name ($LASTEXITCODE)" }
|
||||
& $dis $raw -o $ir
|
||||
if ($LASTEXITCODE) { throw "Disassemble failed: $raw" }
|
||||
$clean = [Collections.Generic.List[string]]::new()
|
||||
foreach ($line in [IO.File]::ReadLines($ir)) {
|
||||
if ($line -match '@llvm.used|wchar_size|llvm.module.flags|__hip_cuid') { continue }
|
||||
$line = $line -replace 'optnone','' -replace 'define hidden','define linkonce_odr'
|
||||
$line = $line -replace '"target-cpu"="gfx1030"','' -replace '"target-features"="[^"]+"',''
|
||||
$clean.Add($line)
|
||||
}
|
||||
[IO.File]::WriteAllLines($ir,$clean,[Text.UTF8Encoding]::new($false))
|
||||
& $assembler $ir -o $output
|
||||
if ($LASTEXITCODE) { throw "Assemble failed: $ir" }
|
||||
Write-Host "Built $output ($((Get-Item $output).Length) bytes)"
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
[CmdletBinding()]
|
||||
param([string]$SourceRoot, [string]$BuildRoot, [int]$Jobs = 4)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repo = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
|
||||
if (!$SourceRoot) { $SourceRoot = Join-Path $repo 'external/ZLUDA' }
|
||||
if (!$BuildRoot) { $BuildRoot = Join-Path $repo 'build/zluda-llvm-windows-gnu' }
|
||||
$toolchain = Join-Path $repo '.tools/llvm-mingw-20260922-ucrt-x86_64'
|
||||
$cmake = Join-Path $repo '.tools/python/cmake/data/bin/cmake.exe'
|
||||
$ninja = Join-Path $repo '.tools/python/bin/ninja.exe'
|
||||
if (!(Test-Path $ninja)) { $ninja = Join-Path $repo '.tools/python/ninja/data/bin/ninja.exe' }
|
||||
if (!(Test-Path (Join-Path $SourceRoot 'ext/llvm-project/llvm/CMakeLists.txt'))) {
|
||||
throw 'Run git submodule update --init --depth 1 ext/llvm-project in the ZLUDA checkout.'
|
||||
}
|
||||
New-Item -ItemType Directory -Force $BuildRoot | Out-Null
|
||||
$oldPath = $env:PATH
|
||||
try {
|
||||
$env:PATH = "$(Join-Path $toolchain 'bin');$oldPath"
|
||||
$arguments = @('-S', (Join-Path $SourceRoot 'ext/llvm-project/llvm'), '-B', $BuildRoot,
|
||||
'-G', 'Ninja', "-DCMAKE_MAKE_PROGRAM=$ninja", '-DCMAKE_BUILD_TYPE=Release',
|
||||
"-DCMAKE_C_COMPILER=$(Join-Path $toolchain 'bin/x86_64-w64-mingw32-clang.exe')",
|
||||
"-DCMAKE_CXX_COMPILER=$(Join-Path $toolchain 'bin/x86_64-w64-mingw32-clang++.exe')",
|
||||
'-DLLVM_TARGETS_TO_BUILD=AMDGPU', '-DLLVM_ENABLE_PROJECTS=llvm;lld',
|
||||
'-DLLVM_ENABLE_WARNINGS=OFF', '-DLLVM_ENABLE_TERMINFO=OFF', '-DLLVM_ENABLE_LIBXML2=OFF',
|
||||
'-DLLVM_ENABLE_LIBEDIT=OFF', '-DLLVM_ENABLE_LIBPFM=OFF', '-DLLVM_ENABLE_ZLIB=OFF',
|
||||
'-DLLVM_ENABLE_ZSTD=OFF', '-DLLVM_INCLUDE_BENCHMARKS=OFF', '-DLLVM_INCLUDE_EXAMPLES=OFF',
|
||||
'-DLLVM_INCLUDE_TESTS=OFF', '-DLLVM_BUILD_TESTS=OFF', '-DLLVM_BUILD_TOOLS=OFF',
|
||||
'-DLLVM_OPTIMIZED_TABLEGEN=ON', '-DLLVM_PARALLEL_LINK_JOBS=1')
|
||||
$ErrorActionPreference = 'Continue'
|
||||
& $cmake @arguments 2>&1 | Tee-Object (Join-Path $BuildRoot 'configure.log')
|
||||
$ErrorActionPreference = 'Stop'
|
||||
if ($LASTEXITCODE) { throw "LLVM configure failed ($LASTEXITCODE)" }
|
||||
$ErrorActionPreference = 'Continue'
|
||||
& $cmake --build $BuildRoot --parallel $Jobs --target llvm-config llvm-libraries lldCommon lldELF `
|
||||
2>&1 | Tee-Object (Join-Path $BuildRoot 'build.log')
|
||||
$ErrorActionPreference = 'Stop'
|
||||
if ($LASTEXITCODE) { throw "LLVM build failed ($LASTEXITCODE)" }
|
||||
& (Join-Path $BuildRoot 'bin/llvm-config.exe') --version
|
||||
if ($LASTEXITCODE) { throw 'llvm-config verification failed' }
|
||||
Write-Host "Native Windows LLVM: $BuildRoot"
|
||||
} finally { $env:PATH = $oldPath }
|
||||
@@ -1,85 +0,0 @@
|
||||
[CmdletBinding()]
|
||||
param([string]$SourceRoot, [string]$LlvmBuildRoot, [string]$InstallRoot, [switch]$FetchOnly, [switch]$CacheTestsOnly, [int]$Jobs = 4)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repo = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
|
||||
if (!$SourceRoot) { $SourceRoot = Join-Path $repo 'external/ZLUDA' }
|
||||
if (!$LlvmBuildRoot) { $LlvmBuildRoot = Join-Path $repo 'build/zluda-llvm-windows-gnu' }
|
||||
if (!$InstallRoot) { $InstallRoot = Join-Path $repo 'dist/zluda-windows-final' }
|
||||
$rust = Join-Path $repo '.tools/rust-1.98.1-gnu'
|
||||
$toolchain = Join-Path $repo '.tools/llvm-mingw-20260922-ucrt-x86_64'
|
||||
$target = Join-Path $repo 'build/zluda-rust-windows-gnu'
|
||||
New-Item -ItemType Directory -Force $target,$InstallRoot | Out-Null
|
||||
$settings = @{
|
||||
PATH="$(Join-Path $rust 'bin');$(Join-Path $toolchain 'bin');$(Join-Path $repo '.tools/python/cmake/data/bin');$(Join-Path $repo '.tools/python/bin');C:\Program Files\Git\usr\bin;$env:PATH"
|
||||
CARGO_HOME=(Join-Path $repo '.tools/cargo-home')
|
||||
CARGO_TARGET_DIR=$target
|
||||
CARGO_BUILD_JOBS="$Jobs"
|
||||
RUSTC=(Join-Path $rust 'bin/rustc.exe')
|
||||
CC=(Join-Path $toolchain 'bin/x86_64-w64-mingw32-clang.exe')
|
||||
CXX=(Join-Path $toolchain 'bin/x86_64-w64-mingw32-clang++.exe')
|
||||
AR=(Join-Path $toolchain 'bin/llvm-ar.exe')
|
||||
CXXSTDLIB='c++'
|
||||
LIBRARY_PATH=(Join-Path $rust 'lib/rustlib/x86_64-pc-windows-gnu/lib/self-contained')
|
||||
CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER=(Join-Path $toolchain 'bin/x86_64-w64-mingw32-clang.exe')
|
||||
CARGO_TARGET_X86_64_PC_WINDOWS_GNU_RUSTFLAGS='-C link-arg=-static'
|
||||
ZLUDA_LLVM_BUILD_DIR=$LlvmBuildRoot
|
||||
}
|
||||
$previous = @{}
|
||||
foreach ($name in $settings.Keys) {
|
||||
$previous[$name] = [Environment]::GetEnvironmentVariable($name,'Process')
|
||||
[Environment]::SetEnvironmentVariable($name,$settings[$name],'Process')
|
||||
}
|
||||
Push-Location $SourceRoot
|
||||
try {
|
||||
$cargo = Join-Path $rust 'bin/cargo.exe'
|
||||
if (!(Test-Path $cargo)) { throw 'Run setup-rust-toolchain.ps1 first.' }
|
||||
if ($CacheTestsOnly) {
|
||||
$ErrorActionPreference = 'Continue'
|
||||
& $cargo test --locked --release --target x86_64-pc-windows-gnu -p zluda_cache `
|
||||
2>&1 | Tee-Object (Join-Path $target 'cache-tests.log')
|
||||
$ErrorActionPreference = 'Stop'
|
||||
if ($LASTEXITCODE) { throw "ZLUDA cache regression tests failed ($LASTEXITCODE)" }
|
||||
return
|
||||
}
|
||||
if ($FetchOnly) {
|
||||
$ErrorActionPreference = 'Continue'
|
||||
& $cargo fetch --locked --target x86_64-pc-windows-gnu 2>&1 | Tee-Object (Join-Path $target 'fetch.log')
|
||||
$ErrorActionPreference = 'Stop'
|
||||
if ($LASTEXITCODE) { throw "Cargo fetch failed ($LASTEXITCODE)" }
|
||||
return
|
||||
}
|
||||
if (!(Test-Path (Join-Path $LlvmBuildRoot 'bin/llvm-config.exe'))) { throw 'Run build-zluda-llvm.ps1 first.' }
|
||||
foreach ($variant in @('','_constrained','_w64','_constrained_w64')) {
|
||||
if (!(Test-Path "ptx/lib/zluda_ptx_impl$variant.bc")) { throw 'Run build-zluda-helpers.ps1 first.' }
|
||||
}
|
||||
$ErrorActionPreference = 'Continue'
|
||||
& $cargo build --locked --release --target x86_64-pc-windows-gnu -p zluda -p nvapi -p zluda_trace `
|
||||
2>&1 | Tee-Object (Join-Path $target 'build.log')
|
||||
$ErrorActionPreference = 'Stop'
|
||||
if ($LASTEXITCODE) { throw "ZLUDA build failed ($LASTEXITCODE)" }
|
||||
$ErrorActionPreference = 'Continue'
|
||||
& $cargo build --locked --release --target x86_64-pc-windows-gnu -p ptx --example d4r_emit `
|
||||
2>&1 | Tee-Object (Join-Path $target 'emit-build.log')
|
||||
$ErrorActionPreference = 'Stop'
|
||||
if ($LASTEXITCODE) { throw "d4r_emit build failed ($LASTEXITCODE)" }
|
||||
$release = Join-Path $target 'x86_64-pc-windows-gnu/release'
|
||||
Copy-Item -LiteralPath (Join-Path $release 'nvcuda.dll') -Destination $InstallRoot -Force
|
||||
Copy-Item -LiteralPath (Join-Path $release 'nvapi64.dll') -Destination $InstallRoot -Force
|
||||
# The C++ dependencies select libc++ dynamically even when Rust requests
|
||||
# static GNU support libraries. Keep these portable runtime DLLs local.
|
||||
foreach ($runtime in @('libc++.dll', 'libunwind.dll')) {
|
||||
Copy-Item -LiteralPath (Join-Path $toolchain "bin/$runtime") -Destination $InstallRoot -Force
|
||||
}
|
||||
New-Item -ItemType Directory -Force (Join-Path $InstallRoot 'trace') | Out-Null
|
||||
Copy-Item -LiteralPath (Join-Path $release 'zluda_trace.dll') -Destination (Join-Path $InstallRoot 'trace/nvcuda.dll') -Force
|
||||
Copy-Item -LiteralPath (Join-Path $release 'examples/d4r_emit.exe') -Destination $InstallRoot -Force
|
||||
@{base='ee2f25a180099fa42f36b2346732e1f2470a03ad'; target='x86_64-pc-windows-gnu';
|
||||
sourceCommit=(& git rev-parse HEAD); workingTreeDirty=[bool](& git status --porcelain);
|
||||
rust='1.98.1'; llvm=(& (Join-Path $LlvmBuildRoot 'bin/llvm-config.exe') --version);
|
||||
files=@(Get-ChildItem -LiteralPath $InstallRoot -Filter '*.dll' | Get-FileHash -Algorithm SHA256)} |
|
||||
ConvertTo-Json -Depth 5 | Set-Content -LiteralPath (Join-Path $InstallRoot 'build-info.json') -Encoding UTF8
|
||||
Write-Host "Native Windows d4r ZLUDA runtime: $InstallRoot"
|
||||
} finally {
|
||||
Pop-Location
|
||||
foreach ($name in $previous.Keys) { [Environment]::SetEnvironmentVariable($name,$previous[$name],'Process') }
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
[CmdletBinding()]
|
||||
param([Parameter(Mandatory=$true)][int]$GameProcessId, [Parameter(Mandatory=$true)][string]$OutputPath)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Add-Type -AssemblyName System.Drawing
|
||||
if (!('D4RWindowCapture' -as [type])) {
|
||||
Add-Type @'
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
public static class D4RWindowCapture {
|
||||
public delegate bool Callback(IntPtr window, IntPtr data);
|
||||
[StructLayout(LayoutKind.Sequential)] public struct Rect { public int Left, Top, Right, Bottom; }
|
||||
[DllImport("user32.dll")] public static extern bool EnumWindows(Callback callback, IntPtr data);
|
||||
[DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr window, out uint process);
|
||||
[DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr window, out Rect rect);
|
||||
[DllImport("user32.dll",CharSet=CharSet.Unicode)] public static extern int GetWindowText(IntPtr window, StringBuilder text, int count);
|
||||
[DllImport("user32.dll")] public static extern bool IsWindowVisible(IntPtr window);
|
||||
[DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
|
||||
[DllImport("user32.dll")] public static extern IntPtr SetThreadDpiAwarenessContext(IntPtr context);
|
||||
}
|
||||
'@
|
||||
}
|
||||
$windows = [Collections.Generic.List[object]]::new()
|
||||
$previousDpi=[D4RWindowCapture]::SetThreadDpiAwarenessContext([IntPtr](-4))
|
||||
try {
|
||||
$callback = [D4RWindowCapture+Callback]{ param($window,$data)
|
||||
[uint32]$owner = 0
|
||||
[void][D4RWindowCapture]::GetWindowThreadProcessId($window,[ref]$owner)
|
||||
if ($owner -eq $GameProcessId) {
|
||||
$rect = [D4RWindowCapture+Rect]::new()
|
||||
[void][D4RWindowCapture]::GetWindowRect($window,[ref]$rect)
|
||||
$title = [Text.StringBuilder]::new(512)
|
||||
[void][D4RWindowCapture]::GetWindowText($window,$title,512)
|
||||
$windows.Add([pscustomobject]@{handle=$window; title=$title.ToString(); visible=[D4RWindowCapture]::IsWindowVisible($window); rect=$rect;
|
||||
area=($rect.Right-$rect.Left)*($rect.Bottom-$rect.Top)})
|
||||
}
|
||||
return $true
|
||||
}
|
||||
[void][D4RWindowCapture]::EnumWindows($callback,[IntPtr]::Zero)
|
||||
$windows | Select-Object handle,title,visible,area | Format-Table
|
||||
$chosen = $windows | Where-Object { $_.visible -and $_.area -gt 4096 } | Sort-Object area -Descending | Select-Object -First 1
|
||||
if (!$chosen) { throw "No visible game window for PID $GameProcessId" }
|
||||
if ([D4RWindowCapture]::GetForegroundWindow() -ne $chosen.handle) {
|
||||
throw 'Game window is not foreground; capture refused to avoid including unrelated overlapping windows.'
|
||||
}
|
||||
$rect = $chosen.rect
|
||||
$bitmap = [Drawing.Bitmap]::new($rect.Right-$rect.Left,$rect.Bottom-$rect.Top)
|
||||
$graphics = [Drawing.Graphics]::FromImage($bitmap)
|
||||
try {
|
||||
$graphics.CopyFromScreen($rect.Left,$rect.Top,0,0,$bitmap.Size)
|
||||
$bitmap.Save([IO.Path]::GetFullPath($OutputPath),[Drawing.Imaging.ImageFormat]::Png)
|
||||
} finally {
|
||||
$graphics.Dispose(); $bitmap.Dispose()
|
||||
}
|
||||
Write-Host "Captured game window: $OutputPath"
|
||||
} finally {
|
||||
if ($previousDpi -ne [IntPtr]::Zero) { [void][D4RWindowCapture]::SetThreadDpiAwarenessContext($previousDpi) }
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[int]$TargetProcessId,
|
||||
[string]$ProcessName = 'SHProto-Win64-Shipping',
|
||||
[ValidateRange(5,300)][int]$Seconds = 30,
|
||||
[ValidateRange(0,300)][int]$DelaySeconds = 0,
|
||||
[string]$PresentMonPath,
|
||||
[string]$OutputDirectory
|
||||
)
|
||||
$ErrorActionPreference='Stop'
|
||||
$repo=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
|
||||
if (!$PresentMonPath) {
|
||||
$PresentMonPath=Join-Path $repo '.tools/presentmon-2.6.0/PresentMon.exe'
|
||||
if (!(Test-Path -LiteralPath $PresentMonPath)) {
|
||||
New-Item -ItemType Directory -Force (Split-Path $PresentMonPath) | Out-Null
|
||||
Invoke-WebRequest -UseBasicParsing -Uri 'https://github.com/GameTechDev/PresentMon/releases/download/v2.6.0/PresentMon-2.6.0-x64.exe' -OutFile $PresentMonPath
|
||||
}
|
||||
}
|
||||
if (!$OutputDirectory) { $OutputDirectory=Join-Path $repo ('test-results/presentmon-' + (Get-Date -Format 'yyyyMMdd-HHmmss-fff')) }
|
||||
$PresentMonPath=(Get-Item -LiteralPath $PresentMonPath -ErrorAction Stop).FullName
|
||||
if ((Get-FileHash -LiteralPath $PresentMonPath -Algorithm SHA256).Hash.ToLowerInvariant() -ne
|
||||
'b2a706bc6ad475749e3b7e3409263aa1e6906d45bdcf993f6dbc0f660188f1af') {
|
||||
throw 'Expected the official PresentMon 2.6.0 x64 CLI. See docs/windows-performance.md.'
|
||||
}
|
||||
if ($TargetProcessId) { $target=Get-Process -Id $TargetProcessId -ErrorAction Stop }
|
||||
else {
|
||||
$targets=@(Get-Process -Name $ProcessName -ErrorAction Stop)
|
||||
if ($targets.Count -ne 1) { throw 'Select one running game process using -TargetProcessId.' }
|
||||
$target=$targets[0]
|
||||
}
|
||||
$OutputDirectory=[IO.Path]::GetFullPath($OutputDirectory)
|
||||
New-Item -ItemType Directory -Force $OutputDirectory | Out-Null
|
||||
$csv=Join-Path $OutputDirectory 'presentmon.csv'
|
||||
if (Test-Path -LiteralPath $csv) { throw 'Preserve the existing capture; choose another OutputDirectory.' }
|
||||
@{version='2.6.0'; executable=$PresentMonPath; sha256=(Get-FileHash -LiteralPath $PresentMonPath -Algorithm SHA256).Hash;
|
||||
targetProcessId=$target.Id; processName=$target.ProcessName; seconds=$Seconds; delaySeconds=$DelaySeconds;
|
||||
hwsRegistry=$(Get-ItemPropertyValue -LiteralPath 'HKLM:/SYSTEM/CurrentControlSet/Control/GraphicsDrivers' -Name HwSchMode -ErrorAction SilentlyContinue);
|
||||
driver=@(Get-CimInstance Win32_VideoController | Select-Object Name,DriverVersion)} |
|
||||
ConvertTo-Json -Depth 4 | Set-Content -LiteralPath (Join-Path $OutputDirectory 'presentmon-environment.json') -Encoding UTF8
|
||||
# Unique ETW session: never stop or replace an existing user's trace. The CLI
|
||||
# terminates itself at the bound/process exit, without a service installation.
|
||||
$session='D4R_' + [Guid]::NewGuid().ToString('N')
|
||||
& $PresentMonPath --process_id $target.Id --timed $Seconds --delay $DelaySeconds --terminate_after_timed --terminate_on_proc_exit --session_name $session --no_console_stats --no_track_input --qpc_time_ms --output_file $csv 2>&1 |
|
||||
Tee-Object -FilePath (Join-Path $OutputDirectory 'presentmon-console.log')
|
||||
if ($LASTEXITCODE) { throw "PresentMon failed with exit code $LASTEXITCODE; see presentmon-console.log." }
|
||||
if (!(Test-Path -LiteralPath $csv) -or (Get-Item -LiteralPath $csv).Length -eq 0) { throw 'PresentMon captured no frames.' }
|
||||
Write-Host "PresentMon capture: $csv"
|
||||
@@ -1,25 +0,0 @@
|
||||
[CmdletBinding()]
|
||||
param([string]$SourceRoot)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repo = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
|
||||
if (!$SourceRoot) { $SourceRoot = Join-Path $repo 'external/ZLUDA' }
|
||||
$base = 'ee2f25a180099fa42f36b2346732e1f2470a03ad'
|
||||
$relative = 'build/zluda-patch-check-' + [Guid]::NewGuid().ToString('N')
|
||||
$snapshot = Join-Path $repo $relative
|
||||
New-Item -ItemType Directory -Force $snapshot | Out-Null
|
||||
$archive = "$snapshot.tar"
|
||||
& git -C $SourceRoot archive --format=tar "--output=$archive" $base
|
||||
if ($LASTEXITCODE) { throw 'Could not archive the pinned ZLUDA base.' }
|
||||
& tar.exe -xf $archive -C $snapshot
|
||||
if ($LASTEXITCODE) { throw 'Could not extract the ZLUDA patch-check snapshot.' }
|
||||
Push-Location $repo
|
||||
try {
|
||||
foreach ($patch in (Get-ChildItem -LiteralPath (Join-Path $repo 'patches/zluda') -Filter '*.patch' | Sort-Object Name)) {
|
||||
& git apply "--directory=$relative" --check $patch.FullName
|
||||
if ($LASTEXITCODE) { throw "Fresh-source patch check failed: $($patch.Name)" }
|
||||
& git apply "--directory=$relative" --whitespace=nowarn $patch.FullName
|
||||
if ($LASTEXITCODE) { throw "Fresh-source patch apply failed: $($patch.Name)" }
|
||||
Write-Host "PASS patch $($patch.Name)"
|
||||
}
|
||||
Write-Host "Pinned-source patch snapshot: $snapshot"
|
||||
} finally { Pop-Location }
|
||||
@@ -1,105 +0,0 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$PackageRoot, [string]$DiagnosticRoot, [string]$OptiScalerRoot,
|
||||
[string]$ZludaRoot, [string]$HipRoot, [string]$KernelKRoot, [string]$KernelMRoot,
|
||||
[string]$ArchivePath
|
||||
)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repo = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
|
||||
$defaults = @{PackageRoot='dist/windows-rdna4-game'; DiagnosticRoot='dist/windows-rdna4-command-list';
|
||||
OptiScalerRoot='dist/optiscaler-windows-d4r'; ZludaRoot='dist/zluda-windows-final';
|
||||
HipRoot='.tools/therock-10.2.0a20260929/_rocm_sdk_core';
|
||||
KernelKRoot='build/native-k-gfx1201'; KernelMRoot='build/native-m-gfx1201'}
|
||||
foreach ($name in $defaults.Keys) {
|
||||
if (!(Get-Variable -Name $name -ValueOnly)) { Set-Variable -Name $name -Value (Join-Path $repo $defaults[$name]) }
|
||||
}
|
||||
$PackageRoot = [IO.Path]::GetFullPath($PackageRoot)
|
||||
$sourceCommit=(& git -C $repo rev-parse HEAD).Trim()
|
||||
$sourceDirty=[bool](& git -C $repo status --porcelain)
|
||||
if ($ArchivePath -and $sourceDirty) { throw 'Commit source changes before creating the distributable archive.' }
|
||||
New-Item -ItemType Directory -Force $PackageRoot | Out-Null
|
||||
$stagedFiles = [Collections.Generic.List[string]]::new()
|
||||
function Stage([string]$source, [string]$relative) {
|
||||
if (!(Test-Path -LiteralPath $source -PathType Leaf)) { throw "Required file missing: $source" }
|
||||
$destination = Join-Path $PackageRoot $relative
|
||||
New-Item -ItemType Directory -Force (Split-Path $destination) | Out-Null
|
||||
Copy-Item -LiteralPath $source -Destination $destination -Force
|
||||
$stagedFiles.Add($relative)
|
||||
}
|
||||
Stage (Join-Path $OptiScalerRoot 'OptiScaler.dll') 'OptiScaler.dll'
|
||||
Stage (Join-Path $DiagnosticRoot 'bin/d4r_nvngx.dll') 'd4r/_nvngx.dll'
|
||||
Stage (Join-Path $DiagnosticRoot 'bin/pixel_convert_gfx1201.hsaco') 'd4r/pixel_convert_gfx1201.hsaco'
|
||||
Stage (Join-Path $DiagnosticRoot 'nvapi-compat/nvapi64.dll') 'd4r/nvapi/nvapi64.dll'
|
||||
Stage (Join-Path $DiagnosticRoot 'bin/d4r_debug_launcher.exe') 'd4r/d4r_debug_launcher.exe'
|
||||
foreach ($file in @('nvcuda.dll','nvapi64.dll','libc++.dll','libunwind.dll')) { Stage (Join-Path $ZludaRoot $file) "d4r/zluda/$file" }
|
||||
foreach ($file in @('amdhip64_7.dll','amd_comgr.dll','hiprtc0717.dll','hiprtc-builtins0717.dll','rocm_kpack.dll','AMD.ROCM.Comgr.MANIFEST','.hipVersion')) {
|
||||
Stage (Join-Path $HipRoot "bin/$file") "d4r/hip/bin/$file"
|
||||
}
|
||||
$entries = @{}
|
||||
foreach ($directory in @($KernelKRoot, $KernelMRoot)) {
|
||||
foreach ($line in Get-Content -LiteralPath (Join-Path $directory 'd4r-kernels.txt')) {
|
||||
if (!$line.Trim() -or $line.StartsWith('#')) { continue }
|
||||
if ($line -notmatch '^([A-Za-z0-9_]+) ([0-9a-f]{16})$') { throw "Invalid native manifest entry: $line" }
|
||||
if ($entries.ContainsKey($Matches[1])) { throw "Duplicate native kernel: $($Matches[1])" }
|
||||
$entries[$Matches[1]] = $line
|
||||
Stage (Join-Path $directory ($Matches[1] + '.hsaco')) ('d4r/native/' + $Matches[1] + '.hsaco')
|
||||
}
|
||||
}
|
||||
if ($entries.Count -ne 16) { throw "Expected 11 K and 5 M native objects; found $($entries.Count)" }
|
||||
@('# Audited DLSS 310.9.1: K + M gfx1201 native kernels', '# Generated by package-windows-game.ps1') + @($entries.Values | Sort-Object) |
|
||||
Set-Content -LiteralPath (Join-Path $PackageRoot 'd4r/native/d4r-kernels.txt') -Encoding ASCII
|
||||
$stagedFiles.Add('d4r/native/d4r-kernels.txt')
|
||||
Stage (Join-Path $repo 'scripts/windows/windows-game.ps1') 'windows-game.ps1'
|
||||
Stage (Join-Path $repo 'scripts/windows/capture-game-window.ps1') 'capture-game-window.ps1'
|
||||
Stage (Join-Path $repo 'scripts/windows/send-game-key.ps1') 'send-game-key.ps1'
|
||||
Stage (Join-Path $repo 'docs/windows-game.md') 'README.md'
|
||||
Stage (Join-Path $repo 'docs/windows-performance.md') 'windows-performance.md'
|
||||
Stage (Join-Path $repo 'LICENSE') 'licenses/d4r.txt'
|
||||
Stage (Join-Path $OptiScalerRoot 'GPL-3.0.txt') 'licenses/OptiScaler.txt'
|
||||
Stage (Join-Path $repo 'external/ZLUDA/LICENSE-MIT') 'licenses/ZLUDA-MIT.txt'
|
||||
Stage (Join-Path $repo 'external/ZLUDA/LICENSE-APACHE') 'licenses/ZLUDA-APACHE.txt'
|
||||
Stage (Join-Path $repo 'external/ZLUDA/ext/llvm-project/LICENSE.TXT') 'licenses/LLVM.txt'
|
||||
Stage (Join-Path $repo 'external/MinHook/LICENSE.txt') 'licenses/MinHook.txt'
|
||||
Stage (Join-Path $HipRoot 'share/doc/amd_comgr/LICENSE.txt') 'licenses/amd_comgr.txt'
|
||||
Stage (Join-Path $repo 'external/clr/LICENSE.md') 'licenses/HIP-CLR.txt'
|
||||
foreach ($patch in Get-ChildItem -LiteralPath (Join-Path $repo 'patches/optiscaler') -Filter '*.patch') { Stage $patch.FullName ('source-patches/optiscaler/' + $patch.Name) }
|
||||
foreach ($patch in Get-ChildItem -LiteralPath (Join-Path $repo 'patches/zluda') -Filter '*.patch') { Stage $patch.FullName ('source-patches/zluda/' + $patch.Name) }
|
||||
$sourceArchive=Join-Path $repo 'build/d4r-windows-rdna4-source.zip'
|
||||
New-Item -ItemType Directory -Force (Split-Path $sourceArchive) | Out-Null
|
||||
& git -C $repo archive --format=zip --output $sourceArchive $sourceCommit
|
||||
if ($LASTEXITCODE) { throw 'Cannot archive the committed d4r source.' }
|
||||
Stage $sourceArchive 'source/d4r-windows-rdna4-source.zip'
|
||||
$metadata = [ordered]@{
|
||||
architecture='gfx1201'; nativeKernels=16; dlssSha256='3975567b8943c53acce397f2b72380092f84f162d00b0d2c7d08a1025c563983';
|
||||
d4rCommit=$sourceCommit;
|
||||
d4rWorkingTreeDirty=$sourceDirty;
|
||||
optiScaler=(Get-Content -LiteralPath (Join-Path $OptiScalerRoot 'build-info.json') -Raw | ConvertFrom-Json);
|
||||
zludaBuild=(Get-Content -LiteralPath (Join-Path $ZludaRoot 'build-info.json') -Raw | ConvertFrom-Json);
|
||||
hipRuntime='TheRock 10.2.0a20260929 / HIP 7.17.26386';
|
||||
files=@($stagedFiles | ForEach-Object { Get-Item -LiteralPath (Join-Path $PackageRoot $_) } | ForEach-Object {
|
||||
@{path=$_.FullName.Substring($PackageRoot.Length+1); sha256=(Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256).Hash; version=$_.VersionInfo.FileVersion}
|
||||
})
|
||||
}
|
||||
$metadata | ConvertTo-Json -Depth 10 | Set-Content -LiteralPath (Join-Path $PackageRoot 'package.json') -Encoding UTF8
|
||||
if (Get-ChildItem -LiteralPath $PackageRoot -Recurse -File | Where-Object { $_.Name -eq 'nvngx_dlss.dll' -or ($_.Name -eq '_nvngx.dll' -and $_.DirectoryName -ne (Join-Path $PackageRoot 'd4r')) }) {
|
||||
throw 'Proprietary NVIDIA binaries must not be in the package.'
|
||||
}
|
||||
Write-Host "Windows game development package: $PackageRoot (NVIDIA DLLs excluded)"
|
||||
if ($ArchivePath) {
|
||||
$ArchivePath=[IO.Path]::GetFullPath($ArchivePath)
|
||||
if ($ArchivePath.StartsWith($PackageRoot+'\',[StringComparison]::OrdinalIgnoreCase)) { throw 'Place the ZIP outside the package directory.' }
|
||||
Add-Type -AssemblyName System.IO.Compression,System.IO.Compression.FileSystem
|
||||
New-Item -ItemType Directory -Force (Split-Path $ArchivePath) | Out-Null
|
||||
$stream=[IO.File]::Open($ArchivePath,[IO.FileMode]::Create,[IO.FileAccess]::ReadWrite,[IO.FileShare]::None)
|
||||
$archive=$null
|
||||
try {
|
||||
$archive=[IO.Compression.ZipArchive]::new($stream,[IO.Compression.ZipArchiveMode]::Create)
|
||||
# Never archive caches, results, private texture outputs or a local DLL
|
||||
# merely because it happens to be somewhere under PackageRoot.
|
||||
foreach ($relative in @($stagedFiles)+@('package.json')) {
|
||||
[void][IO.Compression.ZipFileExtensions]::CreateEntryFromFile($archive,
|
||||
(Join-Path $PackageRoot $relative),$relative.Replace('\','/'),[IO.Compression.CompressionLevel]::Optimal)
|
||||
}
|
||||
} finally { if ($archive) { $archive.Dispose() }; $stream.Dispose() }
|
||||
Write-Host "Public package archive: $ArchivePath"
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
[CmdletBinding()]
|
||||
param([string]$SourceRoot)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repo = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
|
||||
if (!$SourceRoot) { $SourceRoot = Join-Path $repo 'external/ZLUDA' }
|
||||
$SourceRoot = [IO.Path]::GetFullPath($SourceRoot)
|
||||
$base = 'ee2f25a180099fa42f36b2346732e1f2470a03ad'
|
||||
if (!(Test-Path (Join-Path $SourceRoot 'Cargo.toml'))) { throw 'Clone upstream ZLUDA into external/ZLUDA first.' }
|
||||
Push-Location $SourceRoot
|
||||
try {
|
||||
$revision = (& git rev-parse HEAD).Trim()
|
||||
if ($revision -ne $base) { throw "Expected ZLUDA $base, found $revision; review patches against the new revision." }
|
||||
$dirty = @(& git status --porcelain --untracked-files=no --ignore-submodules=all)
|
||||
if ($dirty.Count) { throw 'ZLUDA source has tracked modifications. Keep this prepared checkout; do not reapply patches.' }
|
||||
& git switch -c windows-rdna4
|
||||
if ($LASTEXITCODE) { throw 'Could not create the local ZLUDA branch.' }
|
||||
foreach ($patch in (Get-ChildItem -LiteralPath (Join-Path $repo 'patches/zluda') -Filter '*.patch' | Sort-Object Name)) {
|
||||
& git apply --check $patch.FullName
|
||||
if ($LASTEXITCODE) { throw "Patch check failed: $($patch.Name)" }
|
||||
& git apply --whitespace=nowarn $patch.FullName
|
||||
if ($LASTEXITCODE) { throw "Patch failed: $($patch.Name)" }
|
||||
Write-Host "Applied $($patch.Name)"
|
||||
}
|
||||
Write-Host "Prepared Windows ZLUDA source at $SourceRoot (base $base)"
|
||||
} finally { Pop-Location }
|
||||
@@ -1,30 +0,0 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[ValidateSet(11,13)][int]$Preset = 11,
|
||||
[string]$ZludaRoot, [string]$PackageRoot, [string]$OutputDirectory,
|
||||
[int]$Iterations = 4,
|
||||
[switch]$Translated
|
||||
)
|
||||
$ErrorActionPreference='Stop'
|
||||
$repo=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
|
||||
if (!$ZludaRoot) { $ZludaRoot=Join-Path $repo 'dist/zluda-windows-final' }
|
||||
if (!$PackageRoot) { $PackageRoot=Join-Path $repo 'dist/windows-rdna4-command-list' }
|
||||
if (!$OutputDirectory) { $OutputDirectory=Join-Path $repo ('test-results/profile-' + $Preset + '-' + (Get-Date -Format 'yyyyMMdd-HHmmss-fff')) }
|
||||
$settings=@{D4R_ZLUDA_PROFILE='1'; D4R_PROFILE_STAGES='1'; D4R_VALIDATE_OUTPUT='1';
|
||||
D4R_ZLUDA_NATIVE_DIR=$(if ($Translated) { $null } elseif ($Preset -eq 11) { Join-Path $repo 'build/native-k-gfx1201' } else { Join-Path $repo 'build/native-m-gfx1201' })}
|
||||
$old=@{}
|
||||
try {
|
||||
foreach ($key in $settings.Keys) { $old[$key]=[Environment]::GetEnvironmentVariable($key,'Process'); [Environment]::SetEnvironmentVariable($key,$settings[$key],'Process') }
|
||||
$arguments=@{RuntimeProfile='therock'; ZludaRoot=$ZludaRoot; PackageRoot=$PackageRoot; OutputDirectory=$OutputDirectory;
|
||||
NgxCore=(Join-Path $repo '_nvngx.dll'); DlssDll=(Join-Path $repo 'nvngx_dlss.dll'); NgxMode='d3d12';
|
||||
NgxAbi='project-legacy'; Preset=$Preset; NgxOnly=$true; RequireNativeNetwork=(!$Translated); CommandListBackend=$true;
|
||||
CaptureExceptions=$true; EarlyIndirectProbe=$true; PixelProfile='depth-stencil'; BarrierMode='inherited-legacy';
|
||||
Iterations=$Iterations; TimeoutSeconds=300; OptiScalerDll=(Join-Path $repo 'dist/optiscaler-windows-d4r/OptiScaler.dll')}
|
||||
& (Join-Path $PSScriptRoot 'test-windows-rdna4.ps1') @arguments
|
||||
if ($LASTEXITCODE) { throw 'Profiling workload failed; see diagnostic bundle.' }
|
||||
$python=(Get-Command python.exe -ErrorAction Stop).Source
|
||||
& $python (Join-Path $repo 'tools/windows/profile_report.py') $OutputDirectory --output (Join-Path $OutputDirectory 'profile.json')
|
||||
if ($LASTEXITCODE) { throw 'No usable profile records.' }
|
||||
} finally {
|
||||
foreach ($key in $old.Keys) { [Environment]::SetEnvironmentVariable($key,$old[$key],'Process') }
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
[CmdletBinding()]
|
||||
param([Parameter(Mandatory=$true)][int]$GameProcessId,
|
||||
[ValidateSet('Enter','Escape','Up','Down','Tab')][string]$Key='Enter')
|
||||
$ErrorActionPreference='Stop'
|
||||
$process=Get-Process -Id $GameProcessId -ErrorAction Stop
|
||||
if ($process.ProcessName -ne 'SHProto-Win64-Shipping') { throw 'This helper is scoped to the authorized Silent Hill 2 diagnostic.' }
|
||||
if (!('D4RGameKeys' -as [type])) {
|
||||
Add-Type @'
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
public static class D4RGameKeys {
|
||||
[DllImport("user32.dll")] public static extern bool PostMessage(IntPtr window, uint message, IntPtr key, IntPtr flags);
|
||||
[DllImport("user32.dll")] public static extern uint MapVirtualKey(uint code, uint kind);
|
||||
}
|
||||
'@
|
||||
}
|
||||
$window=$process.MainWindowHandle
|
||||
if ($window -eq [IntPtr]::Zero) { throw 'Game window missing on this desktop.' }
|
||||
$codes=@{Enter=13; Escape=27; Up=38; Down=40; Tab=9}
|
||||
$code=$codes[$Key]; $scan=[D4RGameKeys]::MapVirtualKey($code,0)
|
||||
$flags=[long](1 -bor ($scan -shl 16))
|
||||
if ($Key -in @('Up','Down')) { $flags=$flags -bor 0x01000000 }
|
||||
if (![D4RGameKeys]::PostMessage($window,0x100,[IntPtr]$code,[IntPtr]$flags)) { throw 'Game key-down post failed.' }
|
||||
if (![D4RGameKeys]::PostMessage($window,0x101,[IntPtr]$code,[IntPtr]($flags -bor 0xc0000000L))) { throw 'Game key-up post failed.' }
|
||||
Write-Host "Sent $Key only to diagnostic game PID $GameProcessId."
|
||||
@@ -1,48 +0,0 @@
|
||||
[CmdletBinding()]
|
||||
param([string]$ToolRoot, [string]$DownloadRoot)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repo = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
|
||||
if (!$ToolRoot) { $ToolRoot = Join-Path $repo '.tools/rust-1.98.1-gnu' }
|
||||
if (!$DownloadRoot) { $DownloadRoot = Join-Path $repo '.tools/downloads' }
|
||||
$ToolRoot = [IO.Path]::GetFullPath($ToolRoot)
|
||||
$DownloadRoot = [IO.Path]::GetFullPath($DownloadRoot)
|
||||
New-Item -ItemType Directory -Force $ToolRoot,$DownloadRoot | Out-Null
|
||||
# Pinned official Rust 2026-09-03 manifest. The toolchain is local to this
|
||||
# workspace; rustup, PATH changes and machine-wide installation are unnecessary.
|
||||
$components = @(
|
||||
@{Name='rustc'; Hash='f0e8e33973771acc4d2f87891b19d4f3f2d3827e2e7848c091d7c070bd63479c'},
|
||||
@{Name='cargo'; Hash='4bd77f16bd2a26db6eacf9320414d3a792d9998cb5e9ac122280ba56126f2a44'},
|
||||
@{Name='rust-std'; Hash='5bb599a541fcb9c0edc00e512570f60d2262623f1e2a19a44cce3a7a97208788'},
|
||||
@{Name='rust-mingw'; Hash='75d898804789c12ca969365f0a86d85c3bca1fdb070f0be615ca513a986b2674'}
|
||||
)
|
||||
foreach ($component in $components) {
|
||||
$package = "$($component.Name)-1.98.1-x86_64-pc-windows-gnu"
|
||||
$archive = Join-Path $DownloadRoot "$package.tar.xz"
|
||||
if (!(Test-Path $archive) -or (Get-FileHash -LiteralPath $archive -Algorithm SHA256).Hash -ne $component.Hash) {
|
||||
Write-Host "Downloading official Rust component $($component.Name)"
|
||||
& curl.exe --fail --location --silent --show-error --connect-timeout 15 --max-time 300 --retry 2 `
|
||||
"https://static.rust-lang.org/dist/2026-09-03/$package.tar.xz" --output $archive
|
||||
if ($LASTEXITCODE) { throw "Download failed for $package (exit=$LASTEXITCODE)" }
|
||||
}
|
||||
if ((Get-FileHash -LiteralPath $archive -Algorithm SHA256).Hash -ne $component.Hash) {
|
||||
throw "Rust component SHA256 mismatch: $archive"
|
||||
}
|
||||
$extract = Join-Path $DownloadRoot $package
|
||||
if (!(Test-Path (Join-Path $extract 'components'))) {
|
||||
& tar.exe -xf $archive -C $DownloadRoot
|
||||
if ($LASTEXITCODE) { throw "Extract failed: $archive" }
|
||||
}
|
||||
foreach ($entry in (Get-Content -LiteralPath (Join-Path $extract 'components'))) {
|
||||
$payload = Join-Path $extract $entry
|
||||
foreach ($directory in @('bin','lib','share','etc')) {
|
||||
$source = Join-Path $payload $directory
|
||||
if (Test-Path $source) { Copy-Item -LiteralPath $source -Destination $ToolRoot -Recurse -Force }
|
||||
}
|
||||
}
|
||||
Write-Host "Verified and installed $package"
|
||||
}
|
||||
& (Join-Path $ToolRoot 'bin/rustc.exe') --version
|
||||
if ($LASTEXITCODE) { throw 'Rust compiler failed to start' }
|
||||
& (Join-Path $ToolRoot 'bin/cargo.exe') --version
|
||||
if ($LASTEXITCODE) { throw 'Cargo failed to start' }
|
||||
Write-Host "Local Rust toolchain: $ToolRoot"
|
||||
@@ -1,46 +0,0 @@
|
||||
[CmdletBinding()]
|
||||
param([ValidateSet('stable', 'therock')][string]$RuntimeProfile = 'stable')
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repo = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
|
||||
$localTools = Join-Path $repo '.tools'
|
||||
$downloadDirectory = Join-Path $localTools 'downloads'
|
||||
New-Item -ItemType Directory -Force $downloadDirectory | Out-Null
|
||||
function Get-VerifiedArchive([string]$Url, [string]$Name, [string]$Hash, [string]$Destination, [string]$Marker) {
|
||||
if (Test-Path -LiteralPath $Marker) { Write-Host "Already present: $Marker"; return }
|
||||
$archive = Join-Path $downloadDirectory $Name
|
||||
if (!(Test-Path -LiteralPath $archive)) {
|
||||
Invoke-WebRequest -UseBasicParsing -Uri $Url -OutFile $archive
|
||||
}
|
||||
if ((Get-FileHash -LiteralPath $archive -Algorithm SHA256).Hash -ne $Hash) {
|
||||
throw "SHA256 mismatch: $archive (remove this download and retry)"
|
||||
}
|
||||
if ([IO.Path]::GetExtension($archive) -eq '.whl') {
|
||||
python -c 'import sys,zipfile; zipfile.ZipFile(sys.argv[1]).extractall(sys.argv[2])' $archive $Destination
|
||||
if ($LASTEXITCODE) { throw "Wheel extraction exit=$LASTEXITCODE" }
|
||||
} else { Expand-Archive -LiteralPath $archive -DestinationPath $Destination -Force }
|
||||
if (!(Test-Path -LiteralPath $Marker)) { throw "Archive layout changed: $Marker" }
|
||||
}
|
||||
Get-VerifiedArchive 'https://github.com/mstorsjo/llvm-mingw/releases/download/20260922/llvm-mingw-20260922-ucrt-x86_64.zip' `
|
||||
'llvm-mingw.zip' 'e3ad77d117a4bea19a7a3b333341824d79a5a371004a10e25b8504e7b3047666' $localTools `
|
||||
(Join-Path $localTools 'llvm-mingw-20260922-ucrt-x86_64/bin/x86_64-w64-mingw32-clang++.exe')
|
||||
Get-VerifiedArchive 'https://github.com/vosen/ZLUDA/releases/download/v7-preview.11/zluda-windows-ee2f25a.zip' `
|
||||
'zluda.zip' '7788c1ed43385e62f0cc5f4d1aead24c92b671c02f1d685f63182540dea16d74' (Join-Path $localTools 'zluda') `
|
||||
(Join-Path $localTools 'zluda/zluda/nvcuda.dll')
|
||||
if ($RuntimeProfile -eq 'therock') {
|
||||
Get-VerifiedArchive 'https://nightly.repo.amd.com/rocm/core/whl-next/rocm-sdk-core/rocm_sdk_core-10.2.0a20260929-py3-none-win_amd64.whl' `
|
||||
'rocm_sdk_core-10.2.0a20260929-py3-none-win_amd64.whl' `
|
||||
'9623b97ca511eaa176905075a504393eca6fde594222af10b55e4fb1edfe5a25' `
|
||||
(Join-Path $localTools 'therock-10.2.0a20260929') `
|
||||
(Join-Path $localTools 'therock-10.2.0a20260929/_rocm_sdk_core/bin/amdhip64_7.dll')
|
||||
}
|
||||
if (!(Test-Path (Join-Path $localTools 'python/cmake/data/bin/cmake.exe')) -or
|
||||
!(Test-Path (Join-Path $localTools 'python/bin/ninja.exe'))) {
|
||||
python -m pip install --target (Join-Path $localTools 'python') cmake==3.31.6 ninja==1.11.1.4
|
||||
if ($LASTEXITCODE) { throw "pip install exit=$LASTEXITCODE" }
|
||||
}
|
||||
if (!(Test-Path (Join-Path $localTools 'python/vendor/numpy/__init__.py'))) {
|
||||
python -m pip install --disable-pip-version-check --only-binary=:all: `
|
||||
--target (Join-Path $localTools 'python/vendor') numpy==2.4.6
|
||||
if ($LASTEXITCODE) { throw "NumPy reference install exit=$LASTEXITCODE" }
|
||||
}
|
||||
Write-Host 'Tools ready. Run scripts/windows/build-windows-rdna4.ps1.'
|
||||
@@ -1,24 +0,0 @@
|
||||
[CmdletBinding()]
|
||||
param([Parameter(Mandatory=$true)][string]$DlssDll, [string]$PackageRoot, [string]$OutputDirectory)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repo = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
|
||||
if (!$PackageRoot) { $PackageRoot = Join-Path $repo 'dist/windows-rdna4-therock' }
|
||||
if (!$OutputDirectory) { $OutputDirectory = Join-Path $repo 'build/native-k-gfx1201' }
|
||||
if (![IO.Path]::IsPathRooted($DlssDll) -or !(Test-Path -LiteralPath $DlssDll)) {
|
||||
throw 'Supply the absolute path to your local nvngx_dlss.dll.'
|
||||
}
|
||||
New-Item -ItemType Directory -Force $OutputDirectory | Out-Null
|
||||
$layers = @('enc0','enc1','enc2','enc3','enc4','dec5','dec4','dec3','dec2','dec1','dec0')
|
||||
foreach ($layer in $layers) {
|
||||
$name = "dltss_pwin_${layer}_layer"
|
||||
$source = Join-Path $PackageRoot "experimental/k/${name}_gfx1201.hsaco"
|
||||
if (!(Test-Path -LiteralPath $source)) { throw "Native K module missing: $source" }
|
||||
Copy-Item -LiteralPath $source -Destination (Join-Path $OutputDirectory "$name.hsaco") -Force
|
||||
}
|
||||
& python (Join-Path $repo 'kernels/tools/kernel_manifest.py') $OutputDirectory $DlssDll
|
||||
if ($LASTEXITCODE) { throw "Native manifest generation failed ($LASTEXITCODE)" }
|
||||
@{architecture='gfx1201'; family='K'; dlssSha256=(Get-FileHash -LiteralPath $DlssDll -Algorithm SHA256).Hash;
|
||||
validation='synthetic layers passed; actual DLSS capture/reference validation required';
|
||||
modules=@(Get-ChildItem -LiteralPath $OutputDirectory -Filter '*.hsaco' | Get-FileHash -Algorithm SHA256)} |
|
||||
ConvertTo-Json -Depth 5 | Set-Content -LiteralPath (Join-Path $OutputDirectory 'build-info.json') -Encoding UTF8
|
||||
Write-Host "Experimental native K directory: $OutputDirectory"
|
||||
@@ -1,24 +0,0 @@
|
||||
[CmdletBinding()]
|
||||
param([Parameter(Mandatory=$true)][string]$DlssDll, [string]$PackageRoot, [string]$OutputDirectory)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repo = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
|
||||
if (!$PackageRoot) { $PackageRoot = Join-Path $repo 'dist/windows-rdna4-therock' }
|
||||
if (!$OutputDirectory) { $OutputDirectory = Join-Path $repo 'build/native-m-gfx1201' }
|
||||
if (![IO.Path]::IsPathRooted($DlssDll) -or !(Test-Path -LiteralPath $DlssDll)) {
|
||||
throw 'Supply the absolute path to your local nvngx_dlss.dll.'
|
||||
}
|
||||
New-Item -ItemType Directory -Force $OutputDirectory | Out-Null
|
||||
foreach ($layer in @('enc1','enc2','enc3_tube','dec2','dec1')) {
|
||||
$name = "rrlite_${layer}_4x4"
|
||||
$source = Join-Path $PackageRoot "experimental/m/${name}_gfx1201.hsaco"
|
||||
if (!(Test-Path -LiteralPath $source)) { throw "Native M module missing: $source" }
|
||||
Copy-Item -LiteralPath $source -Destination (Join-Path $OutputDirectory "$name.hsaco") -Force
|
||||
}
|
||||
& python (Join-Path $repo 'kernels/tools/kernel_manifest.py') $OutputDirectory $DlssDll
|
||||
if ($LASTEXITCODE) { throw "Native manifest generation failed ($LASTEXITCODE)" }
|
||||
@{architecture='gfx1201'; family='M'; numerics='SWIN_EXACT; SWIN_EXACT_PV; FP16 widening; native FP8 disabled';
|
||||
dlssSha256=(Get-FileHash -LiteralPath $DlssDll -Algorithm SHA256).Hash;
|
||||
validation='synthetic layers passed; actual DLSS capture/reference validation required';
|
||||
modules=@(Get-ChildItem -LiteralPath $OutputDirectory -Filter '*.hsaco' | Get-FileHash -Algorithm SHA256)} |
|
||||
ConvertTo-Json -Depth 5 | Set-Content -LiteralPath (Join-Path $OutputDirectory 'build-info.json') -Encoding UTF8
|
||||
Write-Host "Experimental native M directory: $OutputDirectory"
|
||||
@@ -1,61 +0,0 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$NgxCore, [string]$DlssDll, [string]$NativeRoot,
|
||||
[string]$ZludaRoot, [string]$PackageRoot,
|
||||
[string]$Resolution = '3840x2160',
|
||||
[switch]$ProfileGpuBoundary,
|
||||
[string]$OutputDirectory
|
||||
)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repo = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
|
||||
if (!$NgxCore) { $NgxCore = Join-Path $repo '_nvngx.dll' }
|
||||
if (!$DlssDll) { $DlssDll = Join-Path $repo 'nvngx_dlss.dll' }
|
||||
if (!$NativeRoot) { $NativeRoot = Join-Path $repo 'build/native-k-gfx1201' }
|
||||
if (!$ZludaRoot) { $ZludaRoot = Join-Path $repo 'dist/zluda-windows-api-profile' }
|
||||
if (!$PackageRoot) { $PackageRoot = Join-Path $repo 'dist/windows-rdna4-command-list' }
|
||||
if (!$OutputDirectory) { $OutputDirectory = Join-Path $repo ('test-results/async-k-' + (Get-Date -Format 'yyyyMMdd-HHmmss-fff')) }
|
||||
if ($Resolution -notmatch '^([0-9]+)x([0-9]+)$') { throw 'Resolution must be WIDTHxHEIGHT' }
|
||||
$width=$Matches[1]; $height=$Matches[2]
|
||||
New-Item -ItemType Directory -Force $OutputDirectory | Out-Null
|
||||
$settings = @{D4R_DIAG_BURST='1'; D4R_DIAG_RECREATE=$null; D4R_ASYNC_INTEROP=$null;
|
||||
D4R_ZLUDA_NATIVE_DIR=$NativeRoot; D4R_QUIET_API='1'; D4R_VALIDATE_OUTPUT='1';
|
||||
D4R_PROFILE_STAGES='1'; D4R_ZLUDA_PROFILE=$null; D4R_ZLUDA_PROFILE_API=$null;
|
||||
D4R_PROFILE_GPU_BOUNDARY=$(if ($ProfileGpuBoundary) { '1' } else { $null });
|
||||
ZLUDA_CACHE_DIR=(Join-Path $repo 'build/zluda-cache-windows');
|
||||
PYTHONPATH=(Join-Path $repo '.tools/python/vendor')}
|
||||
$old=@{}
|
||||
try {
|
||||
foreach ($key in $settings.Keys) {
|
||||
$old[$key]=[Environment]::GetEnvironmentVariable($key,'Process')
|
||||
[Environment]::SetEnvironmentVariable($key,$settings[$key],'Process')
|
||||
}
|
||||
foreach ($mode in @('burst','recreate')) {
|
||||
[Environment]::SetEnvironmentVariable('D4R_DIAG_RECREATE', $(if ($mode -eq 'recreate') { '1' } else { $null }), 'Process')
|
||||
foreach ($variant in @('sync','async')) {
|
||||
[Environment]::SetEnvironmentVariable('D4R_ASYNC_INTEROP', $(if ($variant -eq 'async') { '1' } else { $null }), 'Process')
|
||||
$output=Join-Path $OutputDirectory "$variant-$mode"
|
||||
$arguments=@{RuntimeProfile='therock'; ZludaRoot=$ZludaRoot; PackageRoot=$PackageRoot;
|
||||
NgxCore=$NgxCore; DlssDll=$DlssDll; NgxOnly=$true; NgxMode='d3d12'; NgxAbi='project-legacy';
|
||||
Preset=11; NgxCreateFlags=11; NgxOutputResolution=$Resolution; RequireNativeNetwork=$true;
|
||||
CommandListBackend=$true; EarlyIndirectProbe=$true; PixelProfile='depth-stencil';
|
||||
BarrierMode='inherited-legacy'; Iterations=3; CaptureExceptions=$true; TimeoutSeconds=600;
|
||||
OptiScalerDll=(Join-Path $repo 'dist/optiscaler-windows-d4r/OptiScaler.dll'); OutputDirectory=$output}
|
||||
& (Join-Path $PSScriptRoot 'test-windows-rdna4.ps1') @arguments
|
||||
if ($LASTEXITCODE) { throw "Failed $variant-$mode; logs: $output" }
|
||||
if ($ProfileGpuBoundary -and $variant -eq 'async') {
|
||||
$stdout=Get-Content -LiteralPath (Join-Path $output 'd3d12-evaluate-preset-11.stdout.log') -Raw
|
||||
$timings=[regex]::Matches($stdout, '(?m)^D4R_GPU_BOUNDARY [^\r\n]*diagnostics_cpu_bytes=32 serializing=0\r?$')
|
||||
if ($timings.Count -ne 3) { throw "Expected three completed GPU timing records for $variant-$mode, found $($timings.Count)." }
|
||||
}
|
||||
}
|
||||
& python.exe (Join-Path $repo 'tools/windows/frame_compare.py') --reference (Join-Path $OutputDirectory "sync-$mode") --actual (Join-Path $OutputDirectory "async-$mode") --width $width --height $height --exact |
|
||||
Tee-Object -FilePath (Join-Path $OutputDirectory "$mode-comparison.log")
|
||||
if ($LASTEXITCODE) { throw "Async $mode output mismatch" }
|
||||
}
|
||||
@{passed=$true; architecture='gfx1201'; preset=11; resolution=$Resolution;
|
||||
modes=@('burst','recreate'); framesPerMode=3; exactRgb=$true; gpuBoundaryTiming=[bool]$ProfileGpuBoundary} |
|
||||
ConvertTo-Json | Set-Content -LiteralPath (Join-Path $OutputDirectory 'validation.json') -Encoding UTF8
|
||||
Write-Host "PASS asynchronous K burst/recreate: $OutputDirectory"
|
||||
} finally {
|
||||
foreach ($key in $old.Keys) { [Environment]::SetEnvironmentVariable($key,$old[$key],'Process') }
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
param(
|
||||
[int]$Iterations = 32,
|
||||
[switch]$NoHooks,
|
||||
[switch]$SmallSeed,
|
||||
[switch]$DebugLayer,
|
||||
[switch]$Warp,
|
||||
[switch]$NoHip,
|
||||
[ValidateSet('basic','indirect','indirect-root-reset','initial-pso')][string]$Mode = 'indirect',
|
||||
[string]$OutputDirectory = 'test-results/d3d12-command-development'
|
||||
)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repo = (Resolve-Path "$PSScriptRoot/../..").Path
|
||||
Push-Location $repo
|
||||
$names = @('D4R_COMMAND_PROBE_NO_HOOKS','D4R_COMMAND_PROBE_SMALL_SEED','D4R_COMMAND_DEBUG_SDK','D4R_DIAG_DIR','D4R_COMMAND_PROBE_WARP','D4R_COMMAND_PROBE_NO_HIP')
|
||||
$saved = @{}
|
||||
foreach ($name in $names) { $saved[$name] = [Environment]::GetEnvironmentVariable($name,'Process') }
|
||||
try {
|
||||
if ($Iterations -lt 1 -or $Iterations -gt 1024) { throw 'Iterations must be 1..1024' }
|
||||
$cmake = "$repo/.tools/python/cmake/data/bin/cmake.exe"
|
||||
$build = "$repo/build/windows-rdna4-therock"
|
||||
$hip = "$repo/.tools/therock-10.2.0a20260929/_rocm_sdk_core"
|
||||
$output = [IO.Path]::GetFullPath((Join-Path $repo $OutputDirectory))
|
||||
New-Item -ItemType Directory -Force -Path $output | Out-Null
|
||||
$target = 'd4r_d3d12_command_probe'
|
||||
if ($DebugLayer) {
|
||||
$sdk = "$repo/.tools/agility-1.619.5"
|
||||
if (!(Test-Path "$sdk/build/native/bin/x64/d3d12SDKLayers.dll")) { throw 'Unpack Microsoft.Direct3D.D3D12 1.619.5 into .tools/agility-1.619.5 for this optional debug probe' }
|
||||
& $cmake -S . -B $build "-DD4R_AGILITY_ROOT=$sdk" > "$output/configure.log"
|
||||
if ($LASTEXITCODE) { throw 'Debug probe configuration failed' }
|
||||
$target = 'd4r_d3d12_command_debug_probe'
|
||||
}
|
||||
& $cmake --build $build --target d4r_nvngx_windows $target > "$output/build.log"
|
||||
if ($LASTEXITCODE) { throw "Build failed: $output/build.log" }
|
||||
[Environment]::SetEnvironmentVariable($names[0], $(if ($NoHooks) { '1' } else { $null }), 'Process')
|
||||
[Environment]::SetEnvironmentVariable($names[1], $(if ($SmallSeed) { '1' } else { $null }), 'Process')
|
||||
[Environment]::SetEnvironmentVariable($names[2], $(if ($DebugLayer) { '1' } else { $null }), 'Process')
|
||||
[Environment]::SetEnvironmentVariable($names[3], $output, 'Process')
|
||||
[Environment]::SetEnvironmentVariable($names[4], $(if ($Warp) { '1' } else { $null }), 'Process')
|
||||
[Environment]::SetEnvironmentVariable($names[5], $(if ($NoHip -or $Warp) { '1' } else { $null }), 'Process')
|
||||
# PowerShell 5 treats native stderr as ErrorRecord: retain it without
|
||||
# converting it into a terminating exception, then check the process code.
|
||||
$ErrorActionPreference = 'Continue'
|
||||
& "$build/$target.exe" --hip-root $hip --module "$build/d4r_nvngx.dll" --interop-mode $Mode --iterations $Iterations > "$output/stdout.log" 2> "$output/stderr.log"
|
||||
$code = $LASTEXITCODE
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Get-Content "$output/stdout.log", "$output/stderr.log" | Select-String 'D3D12_VALIDATION|DRED|DEVICE_REMOVED|PROBE_GPU|DIFFERENT|FAIL|PASS|installed'
|
||||
Write-Host "D3D12 command diagnostic logs: $output"
|
||||
if ($code) { throw "D3D12 probe exit=$code" }
|
||||
} finally {
|
||||
foreach ($name in $names) { [Environment]::SetEnvironmentVariable($name,$saved[$name],'Process') }
|
||||
Pop-Location
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
[CmdletBinding()]
|
||||
param([Parameter(Mandatory=$true)][string]$CaptureDirectory, [string]$HipRoot,
|
||||
[string]$PackageRoot, [string]$OutputDirectory, [int]$Windows = 12,
|
||||
[Parameter(Mandatory=$true)][string]$DlssDll, [int]$PtxWindows = 2)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repo = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
|
||||
if (!$HipRoot) { $HipRoot = Join-Path $repo '.tools/therock-10.2.0a20260929/_rocm_sdk_core' }
|
||||
if (!$PackageRoot) { $PackageRoot = Join-Path $repo 'dist/windows-rdna4-therock' }
|
||||
if (!$OutputDirectory) { $OutputDirectory = Join-Path $repo ('test-results/k-real-reference-' + (Get-Date -Format 'yyyyMMdd-HHmmss')) }
|
||||
New-Item -ItemType Directory -Force $OutputDirectory | Out-Null
|
||||
$previousPython = $env:PYTHONPATH
|
||||
$previousDiag = $env:D4R_DIAG_DIR
|
||||
$env:PYTHONPATH = "$(Join-Path $repo '.tools/python/vendor');$previousPython"
|
||||
$summary = [ordered]@{utc=[DateTime]::UtcNow.ToString('o'); captureDirectory=$CaptureDirectory; layers=@(); passed=$false}
|
||||
try {
|
||||
foreach ($layer in @('enc0','enc1','enc2','enc3','enc4','dec5','dec4','dec3','dec2','dec1','dec0')) {
|
||||
$name = "dltss_pwin_${layer}_layer"
|
||||
$captures = @(Get-ChildItem -LiteralPath $CaptureDirectory -Directory | Where-Object Name -Match ("^replay-[0-9]+-" + $name + '$'))
|
||||
if (!$captures.Count) { throw "Missing actual K capture: $name" }
|
||||
$capture = $captures | Sort-Object Name | Select-Object -First 1
|
||||
$folder = Join-Path $OutputDirectory $layer
|
||||
$output = Join-Path $folder 'output'
|
||||
New-Item -ItemType Directory -Force $folder | Out-Null
|
||||
$env:D4R_DIAG_DIR = $folder
|
||||
& (Join-Path $PackageRoot 'bin/d4r_native_replay_probe.exe') --hip-root $HipRoot `
|
||||
--module (Join-Path $PackageRoot "experimental/k/${name}_gfx1201.hsaco") `
|
||||
--fixture-dir $capture.FullName --output-dir $output --iterations 1 `
|
||||
> (Join-Path $folder 'gpu.stdout.log') 2> (Join-Path $folder 'gpu.stderr.log')
|
||||
if ($LASTEXITCODE) { throw "K $layer actual GPU replay failed: $folder" }
|
||||
& python (Join-Path $PackageRoot 'k_replay_validate.py') validate --layer $layer `
|
||||
--fixture-dir $capture.FullName --output-dir $output --windows $Windows --real-capture `
|
||||
> (Join-Path $folder 'reference.stdout.log') 2> (Join-Path $folder 'reference.stderr.log')
|
||||
if ($LASTEXITCODE) { throw "K $layer mismatch; stopping before the next layer: $folder" }
|
||||
Get-Content -LiteralPath (Join-Path $folder 'reference.stdout.log') | Write-Host
|
||||
$ptxValidator = Join-Path $PackageRoot 'k_ptx_validate.py'
|
||||
if (!(Test-Path -LiteralPath $ptxValidator)) { $ptxValidator = Join-Path $repo 'tools/windows/k_ptx_validate.py' }
|
||||
& python -u $ptxValidator --layer $layer `
|
||||
--capture-dir $capture.FullName --native-dir $output --dlss-dll $DlssDll `
|
||||
--output-dir (Join-Path $folder 'private-ptx') --windows $PtxWindows `
|
||||
> (Join-Path $folder 'ptx-reference.stdout.log') 2> (Join-Path $folder 'ptx-reference.stderr.log')
|
||||
if ($LASTEXITCODE) { throw "K $layer independent PTX mismatch; stopping: $folder" }
|
||||
Get-Content -LiteralPath (Join-Path $folder 'ptx-reference.stdout.log') | Write-Host
|
||||
$summary.layers += @{layer=$layer; passed=$true}
|
||||
}
|
||||
$summary.passed = $true
|
||||
} catch { $summary.error=$_.Exception.Message; Write-Warning $summary.error }
|
||||
finally {
|
||||
$env:PYTHONPATH = $previousPython; $env:D4R_DIAG_DIR = $previousDiag
|
||||
$summary | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath (Join-Path $OutputDirectory 'summary.json') -Encoding UTF8
|
||||
Write-Host "Private real-weight replay results: $OutputDirectory (do not redistribute captures/allocations)"
|
||||
}
|
||||
if (!$summary.passed) { exit 1 }
|
||||
@@ -1,53 +0,0 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$HipRoot,
|
||||
[string]$PackageRoot,
|
||||
[string]$OutputDirectory,
|
||||
[string[]]$Layers = @('enc0','enc1','enc2','enc3','enc4','dec5','dec4','dec3','dec2','dec1','dec0'),
|
||||
[int]$Iterations = 32
|
||||
)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repo = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
|
||||
if (!$HipRoot) { $HipRoot = Join-Path $repo '.tools/therock-10.2.0a20260929/_rocm_sdk_core' }
|
||||
if (!$PackageRoot) { $PackageRoot = Join-Path $repo 'dist/windows-rdna4-therock' }
|
||||
if (!$OutputDirectory) { $OutputDirectory = Join-Path $repo ('test-results/k-replay-' + (Get-Date -Format 'yyyyMMdd-HHmmss')) }
|
||||
New-Item -ItemType Directory -Force $OutputDirectory | Out-Null
|
||||
$previousPython = $env:PYTHONPATH
|
||||
$previousDiag = $env:D4R_DIAG_DIR
|
||||
$env:PYTHONPATH = "$(Join-Path $repo '.tools/python/vendor');$previousPython"
|
||||
$summary = [ordered]@{utc=[DateTime]::UtcNow.ToString('o'); hipRoot=$HipRoot; layers=@(); passed=$false}
|
||||
try {
|
||||
if ($Iterations -lt 1 -or $Iterations -gt 10000) { throw 'Iterations must be 1..10000' }
|
||||
foreach ($layer in $Layers) {
|
||||
if ($layer -notmatch '^(enc[0-4]|dec[0-5])$') { throw "Invalid K layer $layer" }
|
||||
$folder = Join-Path $OutputDirectory $layer
|
||||
$fixture = Join-Path $folder 'fixture'
|
||||
$output = Join-Path $folder 'output'
|
||||
New-Item -ItemType Directory -Force $folder | Out-Null
|
||||
$env:D4R_DIAG_DIR = $folder
|
||||
& python (Join-Path $PackageRoot 'k_replay_validate.py') generate --layer $layer --fixture-dir $fixture `
|
||||
> (Join-Path $folder 'fixture.stdout.log') 2> (Join-Path $folder 'fixture.stderr.log')
|
||||
if ($LASTEXITCODE) { throw "K $layer fixture generation failed; see $folder" }
|
||||
& (Join-Path $PackageRoot 'bin/d4r_native_replay_probe.exe') --hip-root $HipRoot `
|
||||
--module (Join-Path $PackageRoot "experimental/k/dltss_pwin_${layer}_layer_gfx1201.hsaco") `
|
||||
--fixture-dir $fixture --output-dir $output --iterations $Iterations `
|
||||
> (Join-Path $folder 'gpu.stdout.log') 2> (Join-Path $folder 'gpu.stderr.log')
|
||||
if ($LASTEXITCODE) { throw "K $layer GPU replay failed; see $folder" }
|
||||
& python (Join-Path $PackageRoot 'k_replay_validate.py') validate --layer $layer --fixture-dir $fixture --output-dir $output `
|
||||
> (Join-Path $folder 'reference.stdout.log') 2> (Join-Path $folder 'reference.stderr.log')
|
||||
if ($LASTEXITCODE) { throw "K $layer reference mismatch; stopping before the next layer. See $folder" }
|
||||
Get-Content -LiteralPath (Join-Path $folder 'reference.stdout.log') | Write-Host
|
||||
$summary.layers += @{layer=$layer; passed=$true; iterations=$Iterations}
|
||||
}
|
||||
$summary.passed = $true
|
||||
} catch {
|
||||
$summary.error = $_.Exception.Message
|
||||
Write-Warning $summary.error
|
||||
} finally {
|
||||
$env:PYTHONPATH = $previousPython
|
||||
$env:D4R_DIAG_DIR = $previousDiag
|
||||
$summary | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath (Join-Path $OutputDirectory 'summary.json') -Encoding UTF8
|
||||
Compress-Archive -LiteralPath $OutputDirectory -DestinationPath "$OutputDirectory.zip" -Force
|
||||
Write-Host "K replay bundle: $OutputDirectory.zip"
|
||||
}
|
||||
if (!$summary.passed) { exit 1 }
|
||||
@@ -1,49 +0,0 @@
|
||||
[CmdletBinding()]
|
||||
param([Parameter(Mandatory=$true)][string]$CaptureDirectory, [string]$HipRoot,
|
||||
[string]$PackageRoot, [string]$OutputDirectory, [int]$Blocks = 12,
|
||||
[Parameter(Mandatory=$true)][string]$DlssDll, [int]$PtxBlocks = 2)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repo = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
|
||||
if (!$HipRoot) { $HipRoot = Join-Path $repo '.tools/therock-10.2.0a20260929/_rocm_sdk_core' }
|
||||
if (!$PackageRoot) { $PackageRoot = Join-Path $repo 'dist/windows-rdna4-therock' }
|
||||
if (!$OutputDirectory) { $OutputDirectory = Join-Path $repo ('test-results/m-real-reference-' + (Get-Date -Format 'yyyyMMdd-HHmmss')) }
|
||||
New-Item -ItemType Directory -Force $OutputDirectory | Out-Null
|
||||
$previousPython=$env:PYTHONPATH; $previousDiag=$env:D4R_DIAG_DIR
|
||||
$env:PYTHONPATH = "$(Join-Path $repo '.tools/python/vendor');$previousPython"
|
||||
$summary = [ordered]@{utc=[DateTime]::UtcNow.ToString('o'); captureDirectory=$CaptureDirectory; layers=@(); passed=$false}
|
||||
try {
|
||||
foreach ($layer in @('enc1','enc2','enc3_tube','dec2','dec1')) {
|
||||
$name = "rrlite_${layer}_4x4"
|
||||
$captures = @(Get-ChildItem -LiteralPath $CaptureDirectory -Directory | Where-Object Name -Match ("^replay-[0-9]+-" + $name + '$'))
|
||||
if (!$captures.Count) { throw "Missing actual M capture: $name" }
|
||||
$capture = $captures | Sort-Object Name | Select-Object -First 1
|
||||
$folder = Join-Path $OutputDirectory $layer
|
||||
$output = Join-Path $folder 'output'
|
||||
New-Item -ItemType Directory -Force $folder | Out-Null
|
||||
$env:D4R_DIAG_DIR = $folder
|
||||
& (Join-Path $PackageRoot 'bin/d4r_native_replay_probe.exe') --hip-root $HipRoot `
|
||||
--module (Join-Path $PackageRoot "experimental/m/${name}_gfx1201.hsaco") `
|
||||
--fixture-dir $capture.FullName --output-dir $output --iterations 1 `
|
||||
> (Join-Path $folder 'gpu.stdout.log') 2> (Join-Path $folder 'gpu.stderr.log')
|
||||
if ($LASTEXITCODE) { throw "M $layer actual GPU replay failed: $folder" }
|
||||
& python (Join-Path $PackageRoot 'm_replay_validate.py') validate --layer $layer `
|
||||
--fixture-dir $capture.FullName --output-dir $output --blocks $Blocks --real-capture `
|
||||
> (Join-Path $folder 'reference.stdout.log') 2> (Join-Path $folder 'reference.stderr.log')
|
||||
if ($LASTEXITCODE) { throw "M $layer mismatch; stopping before the next layer: $folder" }
|
||||
Get-Content -LiteralPath (Join-Path $folder 'reference.stdout.log') | Write-Host
|
||||
& python -u (Join-Path $PackageRoot 'm_ptx_validate.py') --layer $layer `
|
||||
--capture-dir $capture.FullName --native-dir $output --dlss-dll $DlssDll `
|
||||
--output-dir (Join-Path $folder 'private-ptx') --blocks $PtxBlocks `
|
||||
> (Join-Path $folder 'ptx-reference.stdout.log') 2> (Join-Path $folder 'ptx-reference.stderr.log')
|
||||
if ($LASTEXITCODE) { throw "M $layer independent PTX mismatch; stopping: $folder" }
|
||||
Get-Content -LiteralPath (Join-Path $folder 'ptx-reference.stdout.log') | Write-Host
|
||||
$summary.layers += @{layer=$layer; passed=$true}
|
||||
}
|
||||
$summary.passed=$true
|
||||
} catch { $summary.error=$_.Exception.Message; Write-Warning $summary.error }
|
||||
finally {
|
||||
$env:PYTHONPATH=$previousPython; $env:D4R_DIAG_DIR=$previousDiag
|
||||
$summary | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath (Join-Path $OutputDirectory 'summary.json') -Encoding UTF8
|
||||
Write-Host "Private real-weight replay results: $OutputDirectory (do not redistribute captures/allocations)"
|
||||
}
|
||||
if (!$summary.passed) { exit 1 }
|
||||
@@ -1,45 +0,0 @@
|
||||
[CmdletBinding()]
|
||||
param([string]$HipRoot, [string]$PackageRoot, [string]$OutputDirectory,
|
||||
[string[]]$Layers = @('enc1','enc2','enc3_tube','dec2','dec1'),
|
||||
[ValidateRange(1,10000)][int]$Iterations = 32)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repo = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
|
||||
if (!$HipRoot) { $HipRoot = Join-Path $repo '.tools/therock-10.2.0a20260929/_rocm_sdk_core' }
|
||||
if (!$PackageRoot) { $PackageRoot = Join-Path $repo 'dist/windows-rdna4-therock' }
|
||||
if (!$OutputDirectory) { $OutputDirectory = Join-Path $repo ('test-results/m-replay-' + (Get-Date -Format 'yyyyMMdd-HHmmss')) }
|
||||
New-Item -ItemType Directory -Force $OutputDirectory | Out-Null
|
||||
$previousPython = $env:PYTHONPATH
|
||||
$previousDiag = $env:D4R_DIAG_DIR
|
||||
$env:PYTHONPATH = "$(Join-Path $repo '.tools/python/vendor');$previousPython"
|
||||
$summary = [ordered]@{utc=[DateTime]::UtcNow.ToString('o'); hipRoot=$HipRoot; layers=@(); passed=$false}
|
||||
try {
|
||||
foreach ($layer in $Layers) {
|
||||
if ($layer -notmatch '^(enc[12]|enc3_tube|dec[12])$') { throw "Invalid M layer $layer" }
|
||||
$folder = Join-Path $OutputDirectory $layer
|
||||
$fixture = Join-Path $folder 'fixture'
|
||||
$output = Join-Path $folder 'output'
|
||||
New-Item -ItemType Directory -Force $folder | Out-Null
|
||||
$env:D4R_DIAG_DIR = $folder
|
||||
& python (Join-Path $PackageRoot 'm_replay_validate.py') generate --layer $layer --fixture-dir $fixture `
|
||||
> (Join-Path $folder 'fixture.stdout.log') 2> (Join-Path $folder 'fixture.stderr.log')
|
||||
if ($LASTEXITCODE) { throw "M $layer fixture generation failed: $folder" }
|
||||
& (Join-Path $PackageRoot 'bin/d4r_native_replay_probe.exe') --hip-root $HipRoot `
|
||||
--module (Join-Path $PackageRoot "experimental/m/rrlite_${layer}_4x4_gfx1201.hsaco") `
|
||||
--fixture-dir $fixture --output-dir $output --iterations $Iterations `
|
||||
> (Join-Path $folder 'gpu.stdout.log') 2> (Join-Path $folder 'gpu.stderr.log')
|
||||
if ($LASTEXITCODE) { throw "M $layer GPU replay failed: $folder" }
|
||||
& python (Join-Path $PackageRoot 'm_replay_validate.py') validate --layer $layer --fixture-dir $fixture --output-dir $output `
|
||||
> (Join-Path $folder 'reference.stdout.log') 2> (Join-Path $folder 'reference.stderr.log')
|
||||
if ($LASTEXITCODE) { throw "M $layer reference mismatch; stopping before the next layer: $folder" }
|
||||
Get-Content -LiteralPath (Join-Path $folder 'reference.stdout.log') | Write-Host
|
||||
$summary.layers += @{layer=$layer; passed=$true; iterations=$Iterations}
|
||||
}
|
||||
$summary.passed = $true
|
||||
} catch { $summary.error=$_.Exception.Message; Write-Warning $summary.error }
|
||||
finally {
|
||||
$env:PYTHONPATH=$previousPython; $env:D4R_DIAG_DIR=$previousDiag
|
||||
$summary | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath (Join-Path $OutputDirectory 'summary.json') -Encoding UTF8
|
||||
Compress-Archive -LiteralPath $OutputDirectory -DestinationPath "$OutputDirectory.zip" -Force
|
||||
Write-Host "M replay bundle: $OutputDirectory.zip"
|
||||
}
|
||||
if (!$summary.passed) { exit 1 }
|
||||
@@ -1,28 +0,0 @@
|
||||
[CmdletBinding()]
|
||||
param([Parameter(Mandatory=$true)][string]$CaptureDirectory,
|
||||
[Parameter(Mandatory=$true)][string]$BaselineValidation,
|
||||
[string]$ModuleDirectory, [string]$HipRoot, [string]$OutputDirectory, [switch]$Benchmark)
|
||||
$ErrorActionPreference='Stop'
|
||||
$repo=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
|
||||
if (!$ModuleDirectory) { $ModuleDirectory=Join-Path $repo 'build/native-m-fp8-experimental' }
|
||||
if (!$HipRoot) { $HipRoot=Join-Path $repo '.tools/therock-10.2.0a20260929/_rocm_sdk_core' }
|
||||
if (!$OutputDirectory) { $OutputDirectory=Join-Path $repo 'test-results/m-native-fp8-replay' }
|
||||
$previousPython=$env:PYTHONPATH
|
||||
try {
|
||||
$env:PYTHONPATH="$(Join-Path $repo '.tools/python/vendor');$previousPython"
|
||||
$metadata=Get-Content -LiteralPath (Join-Path $ModuleDirectory 'build-info.json') -Raw | ConvertFrom-Json
|
||||
if ($metadata.hardwarePacking) {
|
||||
& python -u (Join-Path $repo 'tools/windows/fp8_pack_validate.py') `
|
||||
--probe (Join-Path $repo 'dist/windows-rdna4-command-list/bin/d4r_native_replay_probe.exe') `
|
||||
--module (Join-Path $ModuleDirectory 'packing-probe/fp8_pack_probe.hsaco') `
|
||||
--hip-root $HipRoot --output-dir (Join-Path $OutputDirectory 'packing-check')
|
||||
if ($LASTEXITCODE) { throw 'Hardware packing does not preserve every finite FP8 encoding; stopping before network replay.' }
|
||||
}
|
||||
$benchmarkArguments=@()
|
||||
if ($Benchmark) { $benchmarkArguments=@('--benchmark-control-dir',(Join-Path $repo 'build/native-m-gfx1201')) }
|
||||
& python -u (Join-Path $repo 'tools/windows/m_native_replay_compare.py') `
|
||||
--capture-dir $CaptureDirectory --module-dir $ModuleDirectory --hip-root $HipRoot `
|
||||
--probe (Join-Path $repo 'dist/windows-rdna4-command-list/bin/d4r_native_replay_probe.exe') `
|
||||
--baseline-validation $BaselineValidation --output-dir $OutputDirectory @benchmarkArguments
|
||||
if ($LASTEXITCODE) { throw "Experimental FP8 validation failed; baseline unchanged. Logs: $OutputDirectory" }
|
||||
} finally { $env:PYTHONPATH=$previousPython }
|
||||
@@ -1,50 +0,0 @@
|
||||
[CmdletBinding()]
|
||||
param([string]$HipRoot, [string]$PackageRoot, [string]$ZludaRoot, [string]$OutputDirectory)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repo = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
|
||||
if (!$HipRoot) { $HipRoot = Join-Path $repo '.tools/therock-10.2.0a20260929/_rocm_sdk_core' }
|
||||
if (!$PackageRoot) { $PackageRoot = Join-Path $repo 'dist/windows-rdna4-therock' }
|
||||
if (!$ZludaRoot) { $ZludaRoot = Join-Path $repo 'dist/zluda-windows-final' }
|
||||
if (!$OutputDirectory) { $OutputDirectory = Join-Path $repo ('test-results/native-identity-' + (Get-Date -Format 'yyyyMMdd-HHmmss')) }
|
||||
New-Item -ItemType Directory -Force $OutputDirectory | Out-Null
|
||||
$previous = @{D4R_ZLUDA_NATIVE_DIR=$env:D4R_ZLUDA_NATIVE_DIR; D4R_ZLUDA_VERBOSE=$env:D4R_ZLUDA_VERBOSE; D4R_DIAG_DIR=$env:D4R_DIAG_DIR}
|
||||
function Invoke-NativeProbe([string]$Name) {
|
||||
$info = New-Object Diagnostics.ProcessStartInfo
|
||||
$info.FileName = Join-Path $PackageRoot 'bin/d4r_cuda_driver_probe.exe'
|
||||
$info.Arguments = '--hip-root "' + $HipRoot + '" --cuda-dll "' + (Join-Path $ZludaRoot 'nvcuda.dll') + '" --iterations 32'
|
||||
$info.UseShellExecute=$false; $info.CreateNoWindow=$true; $info.RedirectStandardOutput=$true; $info.RedirectStandardError=$true
|
||||
$process = New-Object Diagnostics.Process
|
||||
$process.StartInfo=$info
|
||||
if (!$process.Start()) { throw 'Cannot start native override probe' }
|
||||
$stdout=$process.StandardOutput.ReadToEndAsync(); $stderr=$process.StandardError.ReadToEndAsync()
|
||||
if (!$process.WaitForExit(120000)) { $process.Kill(); $process.WaitForExit(); throw 'Native override test timed out' }
|
||||
[IO.File]::WriteAllText((Join-Path $OutputDirectory "$Name.stdout.log"),$stdout.GetAwaiter().GetResult())
|
||||
[IO.File]::WriteAllText((Join-Path $OutputDirectory "$Name.stderr.log"),$stderr.GetAwaiter().GetResult())
|
||||
$code=$process.ExitCode; $process.Dispose()
|
||||
return $code
|
||||
}
|
||||
try {
|
||||
$env:D4R_ZLUDA_VERBOSE = '1'; $env:D4R_DIAG_DIR = $OutputDirectory
|
||||
$bad = Join-Path $OutputDirectory 'wrong-identity'
|
||||
$good = Join-Path $OutputDirectory 'matching-identity'
|
||||
New-Item -ItemType Directory -Force $bad,$good | Out-Null
|
||||
foreach ($directory in @($bad,$good)) {
|
||||
Copy-Item -LiteralPath (Join-Path $PackageRoot 'bin/probe_gfx1201.hsaco') -Destination (Join-Path $directory 'd4r_ptx_pattern.hsaco') -Force
|
||||
}
|
||||
'd4r_ptx_pattern 0000000000000000' | Set-Content -LiteralPath (Join-Path $bad 'd4r-kernels.txt') -Encoding ASCII
|
||||
$env:D4R_ZLUDA_NATIVE_DIR = $bad
|
||||
if (Invoke-NativeProbe 'wrong') { throw 'Wrong identity must use the correct translated fallback' }
|
||||
if (!(Select-String -LiteralPath (Join-Path $OutputDirectory 'wrong.stderr.log') -Pattern 'reason=ptx_identity.*fallback=translated' -Quiet)) {
|
||||
throw 'Wrong identity was not explicitly rejected'
|
||||
}
|
||||
$identity = Select-String -LiteralPath (Join-Path $OutputDirectory 'wrong.stdout.log') -Pattern 'fnv1a64=([0-9a-f]{16})'
|
||||
if (!$identity) { throw 'PTX probe did not report its identity' }
|
||||
"d4r_ptx_pattern $($identity.Matches[0].Groups[1].Value)" | Set-Content -LiteralPath (Join-Path $good 'd4r-kernels.txt') -Encoding ASCII
|
||||
$env:D4R_ZLUDA_NATIVE_DIR = $good
|
||||
if (Invoke-NativeProbe 'matching') { throw 'Matching native override produced incorrect output' }
|
||||
$hits = @(Select-String -LiteralPath (Join-Path $OutputDirectory 'matching.stderr.log') -Pattern '\[d4r-launch\].*backend=native')
|
||||
if ($hits.Count -ne 32) { throw "Expected 32 native launches, got $($hits.Count)" }
|
||||
Write-Host 'PASS NATIVE_IDENTITY wrong_hash=fallback matching_hash=native launches=32'
|
||||
} finally {
|
||||
foreach ($name in $previous.Keys) { [Environment]::SetEnvironmentVariable($name,$previous[$name],'Process') }
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
[CmdletBinding()]
|
||||
param([string]$TextureRoot, [string]$OutputDirectory, [int]$Iterations=8, [string]$OutputResolution='512x288')
|
||||
$ErrorActionPreference='Stop'
|
||||
$repo=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
|
||||
if (!$TextureRoot) { $TextureRoot=Join-Path $repo 'build/private-textures-gfx1201' }
|
||||
if (!$OutputDirectory) { $OutputDirectory=Join-Path $repo 'test-results/k-native-texture-reference' }
|
||||
if ($OutputResolution -notmatch '^([0-9]+)x([0-9]+)$') { throw 'OutputResolution must be WIDTHxHEIGHT.' }
|
||||
$outputWidth=[int]$Matches[1]; $outputHeight=[int]$Matches[2]
|
||||
$combined=Join-Path $OutputDirectory 'private-native'
|
||||
New-Item -ItemType Directory -Force $combined | Out-Null
|
||||
foreach ($directory in @((Join-Path $repo 'build/native-k-gfx1201'),$TextureRoot)) {
|
||||
Get-ChildItem -LiteralPath $directory -Filter '*.hsaco' | Copy-Item -Destination $combined -Force
|
||||
}
|
||||
& python (Join-Path $repo 'kernels/tools/kernel_manifest.py') $combined (Join-Path $repo 'nvngx_dlss.dll')
|
||||
if ($LASTEXITCODE) { throw 'Combined private manifest failed.' }
|
||||
$settings=@{D4R_VALIDATE_OUTPUT='1'; D4R_PROFILE_STAGES='1'; D4R_ZLUDA_PROFILE='1'; D4R_QUIET_API='1';
|
||||
ZLUDA_CACHE_DIR=(Join-Path $repo 'build/zluda-cache-windows'); PYTHONPATH=(Join-Path $repo '.tools/python/vendor');
|
||||
D4R_ZLUDA_NATIVE_DIR=$null}
|
||||
$old=@{}
|
||||
try {
|
||||
foreach ($key in $settings.Keys) { $old[$key]=[Environment]::GetEnvironmentVariable($key,'Process'); [Environment]::SetEnvironmentVariable($key,$settings[$key],'Process') }
|
||||
foreach ($flags in @(0,11)) {
|
||||
foreach ($mode in @('control','candidate')) {
|
||||
$env:D4R_ZLUDA_NATIVE_DIR=if ($mode -eq 'control') { Join-Path $repo 'build/native-k-gfx1201' } else { $combined }
|
||||
$arguments=@{RuntimeProfile='therock'; ZludaRoot=(Join-Path $repo 'dist/zluda-windows-final');
|
||||
PackageRoot=(Join-Path $repo 'dist/windows-rdna4-command-list'); NgxCore=(Join-Path $repo '_nvngx.dll');
|
||||
DlssDll=(Join-Path $repo 'nvngx_dlss.dll'); NgxMode='d3d12'; NgxAbi='project-legacy'; Preset=11;
|
||||
NgxCreateFlags=$flags; NgxOutputResolution=$OutputResolution; NgxOnly=$true; RequireNativeNetwork=$true; CommandListBackend=$true;
|
||||
PixelProfile='depth-stencil'; BarrierMode='inherited-legacy'; CaptureExceptions=$true; EarlyIndirectProbe=$true;
|
||||
Iterations=$Iterations; TimeoutSeconds=300; OptiScalerDll=(Join-Path $repo 'dist/optiscaler-windows-d4r/OptiScaler.dll');
|
||||
OutputDirectory=(Join-Path $OutputDirectory "$mode-$flags")}
|
||||
& (Join-Path $PSScriptRoot 'test-windows-rdna4.ps1') @arguments
|
||||
if ($LASTEXITCODE) { throw "Texture $mode flags=$flags workload failed." }
|
||||
& python (Join-Path $repo 'tools/windows/profile_report.py') $arguments.OutputDirectory --output (Join-Path $arguments.OutputDirectory 'profile.json')
|
||||
if ($LASTEXITCODE) { throw 'Texture profile report failed.' }
|
||||
}
|
||||
$control=Join-Path $OutputDirectory "control-$flags"; $candidate=Join-Path $OutputDirectory "candidate-$flags"
|
||||
$kernel=if ($flags -eq 11) { 'hiluma_engine_output_depthinv_mvlo_hdr_max_v2_rel' } else { 'hiluma_engine_output_depthreg_mvhi_ldr_max_v2_rel' }
|
||||
$stderr=Get-Content -LiteralPath (Join-Path $candidate 'd3d12-evaluate-preset-11.stderr.log') -Raw
|
||||
$hits=[regex]::Matches($stderr, ('\[d4r-launch\] kernel="' + $kernel + '" backend=native')).Count
|
||||
if ($hits -ne $Iterations) { throw "Texture override was not executed on every frame: $kernel hits=$hits" }
|
||||
& python (Join-Path $repo 'tools/windows/frame_compare.py') --reference $control --actual $candidate --width $outputWidth --height $outputHeight --exact
|
||||
if ($LASTEXITCODE) { throw "Texture flags=$flags full-frame RGB mismatch; preserve baseline." }
|
||||
}
|
||||
@{passed=$true; architecture='gfx1201'; framesPerVariant=$Iterations; outputResolution=$OutputResolution; createFlags=@(0,11); strictRgb=$true;
|
||||
manifestSha256=(Get-FileHash -LiteralPath (Join-Path $TextureRoot 'd4r-kernels.txt') -Algorithm SHA256).Hash.ToLowerInvariant();
|
||||
privateNvidiaDerivedCode=$true; source=(Get-Content -LiteralPath (Join-Path $TextureRoot 'build-info.json') -Raw | ConvertFrom-Json)} |
|
||||
ConvertTo-Json -Depth 12 | Set-Content -LiteralPath (Join-Path $TextureRoot 'validation.json') -Encoding UTF8
|
||||
Write-Host "PASS: both private K output variants executed and matched every RGB component. $OutputDirectory"
|
||||
} finally {
|
||||
foreach ($key in $old.Keys) { [Environment]::SetEnvironmentVariable($key,$old[$key],'Process') }
|
||||
}
|
||||
@@ -1,397 +0,0 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$HipRoot = $env:HIP_PATH,
|
||||
[ValidateSet('stable', 'therock')][string]$RuntimeProfile = 'stable',
|
||||
[string]$ZludaRoot,
|
||||
[string]$PackageRoot,
|
||||
[string]$OutputDirectory,
|
||||
[string]$NgxCore = $env:D4R_NGX_CORE,
|
||||
[string]$DlssDll = $env:D4R_DLSS_DLL,
|
||||
[ValidateSet('init', 'evaluate', 'd3d12')][string]$NgxMode = 'init',
|
||||
[ValidateSet('driver','sdk','project','project-legacy')][string]$NgxAbi = 'driver',
|
||||
[string]$OptiScalerDll,
|
||||
[ValidateSet(5, 11, 13)][int]$Preset = 11,
|
||||
[ValidateSet(0,11)][int]$NgxCreateFlags = 0,
|
||||
[string]$NgxOutputResolution = '512x288',
|
||||
[switch]$NgxOnly,
|
||||
[switch]$Trace,
|
||||
[switch]$RequireNativeNetwork,
|
||||
[switch]$CommandListBackend,
|
||||
[switch]$CaptureExceptions,
|
||||
[switch]$EarlyIndirectProbe,
|
||||
[ValidateSet('baseline','packed','unorm','depth-stencil')][string]$PixelProfile = 'baseline',
|
||||
[ValidateSet('legacy','enhanced','inherited-legacy','inherited-enhanced')][string]$BarrierMode = 'legacy',
|
||||
[int]$Iterations = 32,
|
||||
[int]$TimeoutSeconds = 180
|
||||
)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repoRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
|
||||
if ($RuntimeProfile -eq 'therock') {
|
||||
# Works both from scripts/windows and the installed dist/<profile> directory.
|
||||
if (!$PSBoundParameters.ContainsKey('HipRoot')) { $HipRoot = Join-Path $repoRoot '.tools/therock-10.2.0a20260929/_rocm_sdk_core' }
|
||||
if (!$PackageRoot) {
|
||||
if (Test-Path (Join-Path $PSScriptRoot 'bin/d4r_hip_gfx1201_probe.exe')) { $PackageRoot = $PSScriptRoot }
|
||||
else { $PackageRoot = Join-Path $repoRoot 'dist/windows-rdna4-therock' }
|
||||
}
|
||||
}
|
||||
if (!$PackageRoot) {
|
||||
if (Test-Path (Join-Path $PSScriptRoot 'bin/d4r_hip_gfx1201_probe.exe')) { $PackageRoot = $PSScriptRoot }
|
||||
else { $PackageRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../../dist/windows-rdna4-diagnostics')) }
|
||||
}
|
||||
$PackageRoot = [IO.Path]::GetFullPath($PackageRoot)
|
||||
if (!$HipRoot) { $HipRoot = 'C:\Program Files\AMD\ROCm\7.2' }
|
||||
$HipRoot = [IO.Path]::GetFullPath($HipRoot)
|
||||
if (!$ZludaRoot) {
|
||||
$builtZluda = Join-Path $repoRoot 'dist/zluda-windows-final'
|
||||
$localZluda = [IO.Path]::GetFullPath((Join-Path $PackageRoot '../../.tools/zluda/zluda'))
|
||||
if (Test-Path (Join-Path $builtZluda 'build-info.json')) { $ZludaRoot = $builtZluda }
|
||||
elseif (Test-Path (Join-Path $localZluda 'nvcuda.dll')) { $ZludaRoot = $localZluda }
|
||||
else { throw 'Pass -ZludaRoot with the directory containing the Windows ZLUDA nvcuda.dll.' }
|
||||
}
|
||||
$ZludaRoot = [IO.Path]::GetFullPath($ZludaRoot)
|
||||
if (!$OutputDirectory) { $OutputDirectory = Join-Path $PackageRoot ('results/' + (Get-Date -Format 'yyyyMMdd-HHmmss-fff')) }
|
||||
$OutputDirectory = [IO.Path]::GetFullPath($OutputDirectory)
|
||||
New-Item -ItemType Directory -Force $OutputDirectory | Out-Null
|
||||
$originalPath = $env:PATH
|
||||
$originalHip = $env:HIP_PATH
|
||||
$originalDiag = $env:D4R_DIAG_DIR
|
||||
$originalLog = $env:ZLUDA_LOG_DIR
|
||||
$originalCuda = $env:ZLUDA_CUDA_LIB
|
||||
$originalNvapi = $env:D4R_NVAPI_BACKEND
|
||||
$originalPythonPath = $env:PYTHONPATH
|
||||
$originalVerbose = $env:D4R_ZLUDA_VERBOSE
|
||||
$originalCache = $env:ZLUDA_CACHE_DIR
|
||||
$originalCodegen = @{}
|
||||
foreach ($setting in @('D4R_ZLUDA_WMMA','D4R_ZLUDA_WMMA_FP8','D4R_ZLUDA_WMMA_FP8_NATIVE','D4R_ZLUDA_WMMA_F16_REFERENCE')) {
|
||||
$originalCodegen[$setting] = [Environment]::GetEnvironmentVariable($setting,'Process')
|
||||
}
|
||||
$exitStatus = 1
|
||||
$ngxRuntimeDirectory = $null
|
||||
$summary = [ordered]@{utc=[DateTime]::UtcNow.ToString('o'); profile=$RuntimeProfile; hipRoot=$HipRoot; zludaRoot=$ZludaRoot; tests=@()}
|
||||
function Quote-Argument([string]$Value) {
|
||||
# All generated paths are absolute file/directory paths, with no trailing backslash.
|
||||
if ($Value.Contains('"')) { throw 'Double quotes are not allowed in arguments' }
|
||||
return '"' + $Value.TrimEnd('\') + '"'
|
||||
}
|
||||
function Invoke-Probe([string]$Name, [string]$Exe, [string[]]$Arguments) {
|
||||
if (!(Test-Path -LiteralPath $Exe)) { throw "Executable missing: $Exe" }
|
||||
if ($CaptureExceptions) {
|
||||
$Arguments = @('--output-directory', $OutputDirectory, '--', $Exe) + $Arguments
|
||||
$Exe = Join-Path $PackageRoot 'bin/d4r_debug_launcher.exe'
|
||||
if (!(Test-Path -LiteralPath $Exe)) { throw "Debugger launcher missing: $Exe" }
|
||||
}
|
||||
$stdout = Join-Path $OutputDirectory "$Name.stdout.log"
|
||||
$stderr = Join-Path $OutputDirectory "$Name.stderr.log"
|
||||
$argumentLine = ($Arguments | ForEach-Object { Quote-Argument $_ }) -join ' '
|
||||
$info = New-Object System.Diagnostics.ProcessStartInfo
|
||||
$info.FileName = $Exe
|
||||
$info.Arguments = $argumentLine
|
||||
$info.UseShellExecute = $false
|
||||
$info.CreateNoWindow = $true
|
||||
$info.RedirectStandardOutput = $true
|
||||
$info.RedirectStandardError = $true
|
||||
$process = New-Object System.Diagnostics.Process
|
||||
$process.StartInfo = $info
|
||||
if (!$process.Start()) { throw "Process start failed: $Exe" }
|
||||
# Consume both streams concurrently; a verbose JIT trace must not block its child.
|
||||
$outFile = [IO.File]::Open($stdout, [IO.FileMode]::Create, [IO.FileAccess]::Write, [IO.FileShare]::Read)
|
||||
$errFile = [IO.File]::Open($stderr, [IO.FileMode]::Create, [IO.FileAccess]::Write, [IO.FileShare]::Read)
|
||||
$outTask = $process.StandardOutput.BaseStream.CopyToAsync($outFile)
|
||||
$errTask = $process.StandardError.BaseStream.CopyToAsync($errFile)
|
||||
$finished = $process.WaitForExit($TimeoutSeconds * 1000)
|
||||
if (!$finished) { $process.Kill(); $process.WaitForExit() }
|
||||
$code = $process.ExitCode
|
||||
try {
|
||||
$null = $outTask.GetAwaiter().GetResult()
|
||||
$null = $errTask.GetAwaiter().GetResult()
|
||||
} finally { $outFile.Dispose(); $errFile.Dispose() }
|
||||
$process.Dispose()
|
||||
$codeHex = if ($null -eq $code) { 'unknown' } else {
|
||||
'0x{0:x8}' -f [BitConverter]::ToUInt32([BitConverter]::GetBytes([int]$code), 0)
|
||||
}
|
||||
$pass = $finished -and $code -eq 0 -and (Select-String -LiteralPath $stdout -Pattern '^PASS ' -Quiet)
|
||||
$summary.tests += [ordered]@{name=$Name; passed=$pass; timedOut=(!$finished); exitCode=$code; exceptionOrExitHex=$codeHex}
|
||||
Write-Host "$Name passed=$pass exit=$codeHex timeout=$(!$finished)"
|
||||
return $pass
|
||||
}
|
||||
try {
|
||||
if (!$env:ZLUDA_CACHE_DIR) { $env:ZLUDA_CACHE_DIR = Join-Path $repoRoot 'build/zluda-cache-windows' }
|
||||
$summary.zludaCacheDirectory = $env:ZLUDA_CACHE_DIR
|
||||
foreach ($setting in @('D4R_ZLUDA_WMMA','D4R_ZLUDA_WMMA_FP8','D4R_ZLUDA_WMMA_FP8_NATIVE','D4R_ZLUDA_WMMA_F16_REFERENCE')) {
|
||||
if ($null -eq $originalCodegen[$setting]) {
|
||||
[Environment]::SetEnvironmentVariable($setting, $(if ($setting -eq 'D4R_ZLUDA_WMMA_FP8_NATIVE') { '0' } else { '1' }), 'Process')
|
||||
}
|
||||
}
|
||||
$summary.codegen = @{}
|
||||
foreach ($setting in $originalCodegen.Keys) { $summary.codegen[$setting] = [Environment]::GetEnvironmentVariable($setting,'Process') }
|
||||
Write-Host "ZLUDA codegen: WMMA=$env:D4R_ZLUDA_WMMA FP8 widening=$env:D4R_ZLUDA_WMMA_FP8 native FP8=$env:D4R_ZLUDA_WMMA_FP8_NATIVE f16 reference=$env:D4R_ZLUDA_WMMA_F16_REFERENCE"
|
||||
if ($NgxOnly -and (!$NgxCore -or !$DlssDll)) { throw '-NgxOnly requires both locally supplied NVIDIA DLL paths.' }
|
||||
if ($RequireNativeNetwork) {
|
||||
if ($NgxMode -notin @('evaluate','d3d12') -or $Preset -notin @(11,13) -or !$env:D4R_ZLUDA_NATIVE_DIR) {
|
||||
throw '-RequireNativeNetwork requires K/M Evaluate and D4R_ZLUDA_NATIVE_DIR.'
|
||||
}
|
||||
$env:D4R_ZLUDA_VERBOSE = '1'
|
||||
}
|
||||
$summary.foundationDiagnosticsPerformed = !$NgxOnly
|
||||
if ($Iterations -lt 1 -or $Iterations -gt 10000) { throw 'Iterations must be 1..10000' }
|
||||
if ($TimeoutSeconds -lt 1) { throw 'TimeoutSeconds must be positive' }
|
||||
$env:HIP_PATH = $HipRoot
|
||||
$env:PATH = "$(Join-Path $HipRoot 'bin');$ZludaRoot;$originalPath"
|
||||
$env:D4R_DIAG_DIR = $OutputDirectory
|
||||
$env:ZLUDA_LOG_DIR = Join-Path $OutputDirectory 'zluda-trace'
|
||||
$env:ZLUDA_CUDA_LIB = Join-Path $ZludaRoot 'nvcuda.dll'
|
||||
$env:D4R_NVAPI_BACKEND = Join-Path $ZludaRoot 'nvapi64.dll'
|
||||
$localPythonVendor = Join-Path $repoRoot '.tools/python/vendor'
|
||||
if (Test-Path (Join-Path $localPythonVendor 'numpy/__init__.py')) {
|
||||
$env:PYTHONPATH = "$localPythonVendor;$originalPythonPath"
|
||||
}
|
||||
$comgr = Join-Path $HipRoot 'bin/amd_comgr_3.dll'
|
||||
if (!(Test-Path -LiteralPath $comgr)) { $comgr = Join-Path $HipRoot 'bin/amd_comgr.dll' }
|
||||
$files = @((Join-Path $HipRoot 'bin/amdhip64_7.dll'), $comgr,
|
||||
(Join-Path $ZludaRoot 'nvcuda.dll'), (Join-Path $PackageRoot 'bin/probe_gfx1201.hsaco'),
|
||||
(Join-Path $PackageRoot 'bin/wmma_gfx1201.hsaco'),
|
||||
(Join-Path $PackageRoot 'experimental/k/dltss_pwin_enc1_layer_gfx1201.hsaco'),
|
||||
(Join-Path $PackageRoot 'experimental/k/dltss_pwin_enc2_layer_gfx1201.hsaco'))
|
||||
if ($OptiScalerDll) {
|
||||
if ($NgxMode -ne 'd3d12' -or !$CommandListBackend) { throw '-OptiScalerDll requires D3D12 and -CommandListBackend' }
|
||||
if (![IO.Path]::IsPathRooted($OptiScalerDll)) { throw '-OptiScalerDll must be absolute' }
|
||||
$files += $OptiScalerDll
|
||||
}
|
||||
if ($NgxCore -or $DlssDll) {
|
||||
if (!$NgxCore -or !$DlssDll) { throw 'Supply both -NgxCore and -DlssDll absolute paths.' }
|
||||
if (![IO.Path]::IsPathRooted($NgxCore) -or ![IO.Path]::IsPathRooted($DlssDll)) {
|
||||
throw 'NVIDIA DLL paths must be absolute.'
|
||||
}
|
||||
$files += @($NgxCore, $DlssDll)
|
||||
}
|
||||
$summary.files = @($files | ForEach-Object {
|
||||
if (!(Test-Path -LiteralPath $_)) { throw "Required file missing: $_" }
|
||||
$file = Get-Item -LiteralPath $_
|
||||
[ordered]@{path=$file.FullName; version=$file.VersionInfo.FileVersion;
|
||||
sha256=(Get-FileHash -LiteralPath $_ -Algorithm SHA256).Hash}
|
||||
})
|
||||
try {
|
||||
$summary.displayDrivers = @(Get-CimInstance Win32_VideoController |
|
||||
Select-Object Name,DriverVersion,DriverDate,PNPDeviceID)
|
||||
$summary.os = Get-CimInstance Win32_OperatingSystem | Select-Object Caption,Version,BuildNumber
|
||||
} catch { $summary.inventoryError = $_.Exception.Message }
|
||||
$bin = Join-Path $PackageRoot 'bin'
|
||||
$hipOk = $true
|
||||
if (!$NgxOnly) { $hipOk = Invoke-Probe 'hip' (Join-Path $bin 'd4r_hip_gfx1201_probe.exe') @(
|
||||
'--hip-root', $HipRoot, '--module', (Join-Path $bin 'probe_gfx1201.hsaco'), '--iterations', "$Iterations")
|
||||
}
|
||||
if ($hipOk) {
|
||||
if (!$NgxOnly) {
|
||||
$wmmaOk = Invoke-Probe 'gfx12-wmma' (Join-Path $bin 'd4r_gfx12_wmma_probe.exe') @(
|
||||
'--hip-root', $HipRoot, '--module', (Join-Path $bin 'wmma_gfx1201.hsaco'), '--iterations', "$Iterations")
|
||||
if (!$wmmaOk) { throw 'gfx12 WMMA layout validation failed; refusing to mark K/M readiness.' }
|
||||
$pythonCommand = Get-Command python -ErrorAction SilentlyContinue
|
||||
if (!$pythonCommand) { throw 'Python 3.11+ with NumPy 2.4.6 is needed for K reference checks; run setup-windows-tools.ps1.' }
|
||||
foreach ($layer in @('enc1', 'enc2')) {
|
||||
$kFixture = Join-Path $OutputDirectory "k-$layer-fixture"
|
||||
$kModuleOk = Invoke-Probe "k-$layer-gpu" (Join-Path $bin 'd4r_k_module_probe.exe') @(
|
||||
'--hip-root', $HipRoot,
|
||||
'--module', (Join-Path $PackageRoot "experimental/k/dltss_pwin_${layer}_layer_gfx1201.hsaco"),
|
||||
'--kernel-name', $layer, '--fixture-dir', $kFixture, '--iterations', "$Iterations")
|
||||
if (!$kModuleOk) { throw "K $layer GPU execution failed; see the k-$layer-gpu logs." }
|
||||
$kReferenceOk = Invoke-Probe "k-$layer-reference" $pythonCommand.Source @(
|
||||
(Join-Path $PackageRoot 'k_layer_validate.py'), '--kernel-name', $layer, '--fixture-dir', $kFixture)
|
||||
if (!$kReferenceOk) { throw "K $layer output differs from pwin_model.py; see the k-$layer-reference logs." }
|
||||
}
|
||||
}
|
||||
$cudaOk = $true
|
||||
if (!$NgxOnly) {
|
||||
foreach ($context in @('primary', 'created')) {
|
||||
$ok = Invoke-Probe "cuda-$context" (Join-Path $bin 'd4r_cuda_driver_probe.exe') @(
|
||||
'--hip-root', $HipRoot, '--cuda-dll', (Join-Path $ZludaRoot 'nvcuda.dll'),
|
||||
'--context', $context, '--iterations', "$Iterations")
|
||||
$cudaOk = $cudaOk -and $ok
|
||||
# On failure collect the upstream trace automatically if available.
|
||||
if (!$ok -and (Test-Path (Join-Path $ZludaRoot 'trace/nvcuda.dll'))) {
|
||||
Invoke-Probe "cuda-$context-trace" (Join-Path $bin 'd4r_cuda_driver_probe.exe') @(
|
||||
'--hip-root', $HipRoot, '--cuda-dll', (Join-Path $ZludaRoot 'trace/nvcuda.dll'),
|
||||
'--context', $context, '--iterations', '1') | Out-Null
|
||||
}
|
||||
}
|
||||
if ($cudaOk -and ($NgxMode -eq 'evaluate' -or (Test-Path (Join-Path $ZludaRoot 'build-info.json')))) {
|
||||
$cudaOk = Invoke-Probe 'cuda-images' (Join-Path $bin 'd4r_cuda_image_probe.exe') @(
|
||||
'--hip-root', $HipRoot, '--cuda-dll', (Join-Path $ZludaRoot 'nvcuda.dll'), '--iterations', "$Iterations")
|
||||
if ($cudaOk) {
|
||||
$previousF16 = $env:D4R_ZLUDA_WMMA_F16_REFERENCE
|
||||
try {
|
||||
$env:D4R_ZLUDA_WMMA_F16_REFERENCE = '1'
|
||||
$cudaOk = Invoke-Probe 'cuda-f16-mma-reference' (Join-Path $bin 'd4r_cuda_mma_probe.exe') @(
|
||||
'--hip-root', $HipRoot, '--cuda-dll', (Join-Path $ZludaRoot 'nvcuda.dll'))
|
||||
} finally { $env:D4R_ZLUDA_WMMA_F16_REFERENCE = $previousF16 }
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($cudaOk) {
|
||||
$mapOk = $interopOk = $true
|
||||
if (!$NgxOnly) {
|
||||
$commandOk = Invoke-Probe 'd3d12-command-backend' (Join-Path $bin 'd4r_d3d12_command_probe.exe') @(
|
||||
'--hip-root', $HipRoot, '--module', (Join-Path $bin 'd4r_nvngx.dll'), '--interop-mode', 'indirect', '--iterations', "$Iterations")
|
||||
if (!$commandOk) { throw 'D3D12 command-list order/state/lifetime validation failed.' }
|
||||
$pixelsOk = Invoke-Probe 'd3d12-gpu-pixel-formats' (Join-Path $bin 'd4r_d3d12_pixel_probe.exe') @(
|
||||
'--hip-root', $HipRoot, '--module', (Join-Path $bin 'pixel_convert_gfx1201.hsaco'), '--iterations', '2')
|
||||
if (!$pixelsOk) { throw 'D3D12 typed SRV/UAV format conversion validation failed.' }
|
||||
$mapOk = Invoke-Probe 'interop-map-lifetime' (Join-Path $bin 'd4r_d3d12_hip_interop_probe.exe') @(
|
||||
'--hip-root', $HipRoot, '--interop-mode', 'map', '--iterations', '64')
|
||||
$interopOk = Invoke-Probe 'interop-roundtrip' (Join-Path $bin 'd4r_d3d12_hip_interop_probe.exe') @(
|
||||
'--hip-root', $HipRoot, '--module', (Join-Path $bin 'probe_gfx1201.hsaco'), '--iterations', "$Iterations")
|
||||
if (!$mapOk -or !$interopOk) {
|
||||
foreach ($mode in @('resource', 'import')) {
|
||||
Invoke-Probe "interop-$mode-lifetime" (Join-Path $bin 'd4r_d3d12_hip_interop_probe.exe') @(
|
||||
'--hip-root', $HipRoot, '--interop-mode', $mode, '--iterations', '32') | Out-Null
|
||||
}
|
||||
Invoke-Probe 'hip-stream-lifetime' (Join-Path $bin 'd4r_hip_stream_lifecycle_probe.exe') @(
|
||||
'--hip-root', $HipRoot, '--module', (Join-Path $bin 'probe_gfx1201.hsaco'), '--iterations', '32') | Out-Null
|
||||
}
|
||||
}
|
||||
if ($mapOk -and $interopOk) { $exitStatus = 0 }
|
||||
if ($exitStatus -eq 0 -and $NgxCore) {
|
||||
# The driver Init ABI searches the executable directory. Keep the
|
||||
# supplied binaries in a private temporary runtime, outside dist/ZIP.
|
||||
$ngxRuntimeDirectory = Join-Path ([IO.Path]::GetTempPath()) ('d4r-ngx-' + [Guid]::NewGuid().ToString('N'))
|
||||
New-Item -ItemType Directory -Path $ngxRuntimeDirectory | Out-Null
|
||||
$ngxExe = Join-Path $ngxRuntimeDirectory 'd4r_ngx_cuda_init_probe.exe'
|
||||
$localCore = Join-Path $ngxRuntimeDirectory '_nvngx.dll'
|
||||
$localDlss = Join-Path $ngxRuntimeDirectory 'nvngx_dlss.dll'
|
||||
$probeName = if ($NgxMode -eq 'd3d12') { 'd4r_d3d12_dlss_probe.exe' } else { 'd4r_ngx_cuda_init_probe.exe' }
|
||||
Copy-Item -LiteralPath (Join-Path $bin $probeName) -Destination $ngxExe
|
||||
Copy-Item -LiteralPath $NgxCore -Destination $localCore
|
||||
Copy-Item -LiteralPath $DlssDll -Destination $localDlss
|
||||
$selectedCuda = if ($Trace) { Join-Path $ZludaRoot 'trace/nvcuda.dll' } else { Join-Path $ZludaRoot 'nvcuda.dll' }
|
||||
if (!(Test-Path -LiteralPath $selectedCuda)) { throw "CUDA runtime missing: $selectedCuda" }
|
||||
$summary.traceRequested = [bool]$Trace
|
||||
$ngxArguments = @('--hip-root', $HipRoot, '--cuda-dll', $selectedCuda,
|
||||
'--ngx-core', $localCore, '--dlss-dll', $localDlss,
|
||||
'--nvapi-dll', (Join-Path $PackageRoot 'nvapi-compat/nvapi64.dll'),
|
||||
'--ngx-mode', $NgxMode, '--ngx-abi', $NgxAbi, '--preset', "$Preset", '--iterations', "$Iterations")
|
||||
if ($NgxMode -eq 'd3d12') {
|
||||
# The driver NGX core resolves feature DLLs relative to its
|
||||
# caller module as well as the executable. Keep the shim
|
||||
# alongside the private user-supplied DLLs for this test.
|
||||
$localShim = Join-Path $ngxRuntimeDirectory 'd4r_nvngx.dll'
|
||||
Copy-Item -LiteralPath (Join-Path $bin 'd4r_nvngx.dll') -Destination $localShim
|
||||
Copy-Item -LiteralPath (Join-Path $bin 'pixel_convert_gfx1201.hsaco') -Destination $ngxRuntimeDirectory
|
||||
if ($OptiScalerDll) {
|
||||
$bridgeDirectory = Join-Path $ngxRuntimeDirectory 'd4r'
|
||||
New-Item -ItemType Directory -Path $bridgeDirectory | Out-Null
|
||||
Copy-Item -LiteralPath $localShim -Destination (Join-Path $bridgeDirectory '_nvngx.dll')
|
||||
Copy-Item -LiteralPath (Join-Path $bin 'pixel_convert_gfx1201.hsaco') -Destination $bridgeDirectory
|
||||
$localShim = Join-Path $ngxRuntimeDirectory 'OptiScaler.dll'
|
||||
Copy-Item -LiteralPath $OptiScalerDll -Destination $localShim
|
||||
$ini = @"
|
||||
[Upscalers]
|
||||
Dx12Upscaler=dlss
|
||||
[FrameGen]
|
||||
Enabled=false
|
||||
FGInput=nofg
|
||||
FGOutput=nofg
|
||||
[DLSS]
|
||||
Enabled=true
|
||||
AllowExternalBackend=true
|
||||
RenderPresetOverride=true
|
||||
RenderPresetForAll=$Preset
|
||||
UseGenericAppIdWithDlss=false
|
||||
[Libraries]
|
||||
NvngxPath=$bridgeDirectory
|
||||
NvngxDlssPath=$localDlss
|
||||
NvngxFeaturePath=$ngxRuntimeDirectory
|
||||
[Hotfix]
|
||||
RestoreComputeSignature=false
|
||||
RestoreGraphicSignature=false
|
||||
ExtendedStateRestore=false
|
||||
[Log]
|
||||
LogToFile=true
|
||||
LogLevel=0
|
||||
[Menu]
|
||||
DisableSplash=true
|
||||
"@
|
||||
$ini | Set-Content -LiteralPath (Join-Path $ngxRuntimeDirectory 'OptiScaler.ini') -Encoding UTF8
|
||||
$summary.optiScaler = @{path=$OptiScalerDll; mode='standalone'; preset=$Preset}
|
||||
$ngxArguments += @('--ngx-frontend', 'optiscaler')
|
||||
}
|
||||
$ngxArguments += @('--module', $localShim, '--pixel-profile', $PixelProfile, '--barrier-mode', $BarrierMode,
|
||||
'--ngx-create-flags', "$NgxCreateFlags", '--ngx-output-resolution', $NgxOutputResolution)
|
||||
if ($EarlyIndirectProbe) {
|
||||
if (!$CommandListBackend) { throw '-EarlyIndirectProbe requires -CommandListBackend' }
|
||||
$ngxArguments += @('--early-indirect', '1')
|
||||
}
|
||||
if ($CommandListBackend) { $ngxArguments += @('--interop-mode', 'command-list') }
|
||||
}
|
||||
$ngxName = if ($NgxMode -eq 'evaluate') { "ngx-evaluate-preset-$Preset" } elseif ($NgxMode -eq 'd3d12') { "d3d12-evaluate-preset-$Preset" } else { 'ngx-init' }
|
||||
$ngxOk = Invoke-Probe $ngxName $ngxExe $ngxArguments
|
||||
if ($OptiScalerDll) {
|
||||
Get-ChildItem -LiteralPath $ngxRuntimeDirectory -Filter '*.log' -File |
|
||||
ForEach-Object { Copy-Item -LiteralPath $_.FullName -Destination (Join-Path $OutputDirectory ('optiscaler-' + $_.Name)) }
|
||||
Copy-Item -LiteralPath (Join-Path $ngxRuntimeDirectory 'OptiScaler.ini') -Destination (Join-Path $OutputDirectory 'OptiScaler.ini')
|
||||
}
|
||||
if ($ngxOk -and $RequireNativeNetwork) {
|
||||
$hits = @()
|
||||
$nativeLayers = if ($Preset -eq 11) { @('enc0','enc1','enc2','enc3','enc4','dec5','dec4','dec3','dec2','dec1','dec0') }
|
||||
else { @('enc1','enc2','enc3_tube','dec2','dec1') }
|
||||
foreach ($layer in $nativeLayers) {
|
||||
$name = if ($Preset -eq 11) { "dltss_pwin_${layer}_layer" } else { "rrlite_${layer}_4x4" }
|
||||
$pattern = '\[d4r-launch\] kernel="' + $name + '" backend=native'
|
||||
$count = @(Select-String -LiteralPath (Join-Path $OutputDirectory "$ngxName.stderr.log") -Pattern $pattern).Count
|
||||
$hits += @{layer=$layer; nativeLaunches=$count; expectedFrames=$Iterations}
|
||||
if ($count -lt $Iterations) { $ngxOk=$false }
|
||||
}
|
||||
$summary.nativeTransformer = $hits
|
||||
$summary.nativeTransformerPassed = $ngxOk
|
||||
Write-Host "Native preset $Preset transformer launches validated=$ngxOk"
|
||||
}
|
||||
if (!$ngxOk) {
|
||||
$exitStatus = 1
|
||||
if (!$Trace -and (Test-Path (Join-Path $ZludaRoot 'trace/nvcuda.dll'))) {
|
||||
$traceArguments = @(
|
||||
'--hip-root', $HipRoot, '--cuda-dll', (Join-Path $ZludaRoot 'trace/nvcuda.dll'),
|
||||
'--ngx-core', $localCore, '--dlss-dll', $localDlss,
|
||||
'--nvapi-dll', (Join-Path $PackageRoot 'nvapi-compat/nvapi64.dll'),
|
||||
'--ngx-mode', $NgxMode, '--preset', "$Preset", '--iterations', '1')
|
||||
if ($NgxMode -eq 'd3d12') { $traceArguments += @('--module', $localShim) }
|
||||
Invoke-Probe "$ngxName-trace" $ngxExe $traceArguments | Out-Null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
$summary.error = $_.Exception.Message
|
||||
Write-Warning $summary.error
|
||||
} finally {
|
||||
if ($ngxRuntimeDirectory -and (Test-Path -LiteralPath $ngxRuntimeDirectory)) {
|
||||
$resolvedRuntime = [IO.Path]::GetFullPath($ngxRuntimeDirectory)
|
||||
$tempRoot = [IO.Path]::GetFullPath([IO.Path]::GetTempPath()).TrimEnd('\') + '\'
|
||||
if (!$resolvedRuntime.StartsWith($tempRoot, [StringComparison]::OrdinalIgnoreCase) -or
|
||||
[IO.Path]::GetFileName($resolvedRuntime) -notmatch '^d4r-ngx-[0-9a-f]{32}$') {
|
||||
throw 'Refusing to remove a path outside the private NGX temporary runtime.'
|
||||
}
|
||||
# A scanner can briefly hold the just-unloaded DLL open. Cleanup must
|
||||
# not skip environment restoration or discard the completed GPU logs.
|
||||
$cleanupError=$null
|
||||
foreach ($attempt in 1..10) {
|
||||
try { Remove-Item -LiteralPath $resolvedRuntime -Recurse -Force -ErrorAction Stop; $cleanupError=$null; break }
|
||||
catch { $cleanupError=$_.Exception.Message; if ($attempt -lt 10) { Start-Sleep -Milliseconds 300 } }
|
||||
}
|
||||
if ($cleanupError) { $summary.cleanupError=$cleanupError; $exitStatus=1; Write-Warning "Private runtime cleanup failed: $cleanupError" }
|
||||
}
|
||||
$summary.passed = $exitStatus -eq 0
|
||||
$summary | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath (Join-Path $OutputDirectory 'summary.json') -Encoding UTF8
|
||||
$env:PATH = $originalPath
|
||||
$env:HIP_PATH = $originalHip
|
||||
$env:D4R_DIAG_DIR = $originalDiag
|
||||
$env:ZLUDA_LOG_DIR = $originalLog
|
||||
$env:ZLUDA_CUDA_LIB = $originalCuda
|
||||
$env:D4R_NVAPI_BACKEND = $originalNvapi
|
||||
$env:PYTHONPATH = $originalPythonPath
|
||||
$env:D4R_ZLUDA_VERBOSE = $originalVerbose
|
||||
$env:ZLUDA_CACHE_DIR = $originalCache
|
||||
foreach ($setting in $originalCodegen.Keys) { [Environment]::SetEnvironmentVariable($setting, $originalCodegen[$setting], 'Process') }
|
||||
$archive = "$OutputDirectory.zip"
|
||||
Compress-Archive -LiteralPath $OutputDirectory -DestinationPath $archive -Force
|
||||
Write-Host "Diagnostic bundle: $archive"
|
||||
}
|
||||
exit $exitStatus
|
||||
@@ -1,297 +0,0 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[ValidateSet('install','restore','run')][string]$Action = 'run',
|
||||
[Parameter(Mandatory=$true)][string]$GameExe,
|
||||
[string]$NgxCore, [string]$DlssDll,
|
||||
[ValidateSet(11,13)][int]$Preset = 11,
|
||||
[int]$RunSeconds = 0,
|
||||
[string]$DiagnosticResolution,
|
||||
[string]$CacheDirectory = $env:ZLUDA_CACHE_DIR,
|
||||
[string]$LocalTextureKernels,
|
||||
[switch]$ValidateOutput,
|
||||
[switch]$ProfileStages,
|
||||
[switch]$ProfileKernels,
|
||||
[switch]$ProfileCommandHooks,
|
||||
[switch]$ProfileCudaApi,
|
||||
[switch]$ProfileGpuBoundary,
|
||||
[switch]$UncachedInteropLists,
|
||||
[switch]$AsyncInterop,
|
||||
[switch]$VerboseRuntime,
|
||||
[switch]$CaptureExceptions,
|
||||
[string[]]$GameArguments = @('-dx12'),
|
||||
[string]$OutputDirectory
|
||||
)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
if ($ProfileGpuBoundary -and !$AsyncInterop) { throw '-ProfileGpuBoundary requires -AsyncInterop.' }
|
||||
$package = [IO.Path]::GetFullPath($PSScriptRoot)
|
||||
$GameExe = (Get-Item -LiteralPath $GameExe -ErrorAction Stop).FullName
|
||||
$game = Split-Path $GameExe
|
||||
$backup = Join-Path $game '.d4r-backup'
|
||||
$manifestPath = Join-Path $backup 'manifest.json'
|
||||
function GamePath([string]$relative) {
|
||||
$path = [IO.Path]::GetFullPath((Join-Path $game $relative))
|
||||
if (!$path.StartsWith($game + '\', [StringComparison]::OrdinalIgnoreCase)) { throw "Path escapes game directory: $relative" }
|
||||
return $path
|
||||
}
|
||||
if (Get-Process -Name ([IO.Path]::GetFileNameWithoutExtension($GameExe)) -ErrorAction SilentlyContinue) { throw 'Close the game before installing, restoring or running this diagnostic.' }
|
||||
if ($Action -eq 'restore') {
|
||||
if (!(Test-Path -LiteralPath $manifestPath)) { throw 'No d4r installation manifest exists.' }
|
||||
$manifest = Get-Content -LiteralPath $manifestPath -Raw | ConvertFrom-Json
|
||||
foreach ($entry in $manifest.files) {
|
||||
$path = GamePath $entry.path
|
||||
if ((Test-Path -LiteralPath $path) -and (Get-FileHash -LiteralPath $path -Algorithm SHA256).Hash -ne $entry.installedHash) { throw "File changed since installation; preserved: $path" }
|
||||
}
|
||||
foreach ($entry in $manifest.files) {
|
||||
$path = GamePath $entry.path
|
||||
if ($entry.existed) { Copy-Item -LiteralPath (Join-Path $backup ('original/' + $entry.path)) -Destination $path -Force }
|
||||
elseif (Test-Path -LiteralPath $path) { Remove-Item -LiteralPath $path -Force }
|
||||
}
|
||||
# Remove only empty directories; preserve user files and diagnostic logs.
|
||||
if (Test-Path -LiteralPath (GamePath 'd4r')) {
|
||||
Get-ChildItem -LiteralPath (GamePath 'd4r') -Directory -Recurse | Sort-Object { $_.FullName.Length } -Descending | ForEach-Object {
|
||||
if (!(Get-ChildItem -LiteralPath $_.FullName -Force)) { Remove-Item -LiteralPath $_.FullName }
|
||||
}
|
||||
if (!(Get-ChildItem -LiteralPath (GamePath 'd4r') -Force)) { Remove-Item -LiteralPath (GamePath 'd4r') }
|
||||
}
|
||||
Move-Item -LiteralPath $manifestPath -Destination (Join-Path $backup ('restored-' + (Get-Date -Format 'yyyyMMdd-HHmmss-fff') + '.json'))
|
||||
Write-Host "Original game files restored. Backups retained: $backup"
|
||||
return
|
||||
}
|
||||
if (!$NgxCore -or !$DlssDll) { throw 'Pass both local NVIDIA DLL paths with -NgxCore and -DlssDll.' }
|
||||
$NgxCore = (Get-Item -LiteralPath $NgxCore).FullName
|
||||
$DlssDll = (Get-Item -LiteralPath $DlssDll).FullName
|
||||
$metadata = Get-Content -LiteralPath (Join-Path $package 'package.json') -Raw | ConvertFrom-Json
|
||||
foreach ($file in $metadata.files) {
|
||||
if ((Get-FileHash -LiteralPath (Join-Path $package $file.path) -Algorithm SHA256).Hash -ne $file.sha256) { throw "Package file changed; rebuild package: $($file.path)" }
|
||||
}
|
||||
if ((Get-FileHash -LiteralPath $DlssDll -Algorithm SHA256).Hash -ne $metadata.dlssSha256) { throw 'DLSS DLL does not match the validated 310.9.1 native manifest. Rebuild and validate native manifests for this DLL first.' }
|
||||
$textureFiles=@(); $textureManifest=@()
|
||||
if ($LocalTextureKernels) {
|
||||
$LocalTextureKernels=(Get-Item -LiteralPath $LocalTextureKernels -ErrorAction Stop).FullName
|
||||
$validation=Get-Content -LiteralPath (Join-Path $LocalTextureKernels 'validation.json') -Raw | ConvertFrom-Json
|
||||
if (!$validation.passed -or !$validation.strictRgb -or $validation.architecture -ne 'gfx1201' -or
|
||||
$validation.source.dlssSha256 -ne $metadata.dlssSha256 -or !$validation.source.accuracy) {
|
||||
throw 'Local texture kernels must pass test-native-texture.ps1 with the exact packaged DLSS identity.'
|
||||
}
|
||||
foreach ($object in $validation.source.objects.PSObject.Properties) {
|
||||
if ($object.Name -notin @('hiluma_engine_output_depthinv_mvlo_hdr_max_v2_rel.hsaco','hiluma_engine_output_depthreg_mvhi_ldr_max_v2_rel.hsaco')) {
|
||||
throw "Unvalidated local texture variant: $($object.Name)"
|
||||
}
|
||||
$path=Join-Path $LocalTextureKernels $object.Name
|
||||
if ((Get-FileHash -LiteralPath $path -Algorithm SHA256).Hash.ToLowerInvariant() -ne $object.Value) { throw 'Local texture object changed after validation.' }
|
||||
$textureFiles+=Get-Item -LiteralPath $path
|
||||
}
|
||||
if ($textureFiles.Count -ne 2) { throw 'Expected both validated local K output variants.' }
|
||||
# Use the manifest covered by validation, rather than a mutable extra file.
|
||||
foreach ($line in Get-Content -LiteralPath (Join-Path $LocalTextureKernels 'd4r-kernels.txt')) {
|
||||
if (!$line.Trim() -or $line.StartsWith('#')) { continue }
|
||||
if ($line -notmatch '^([A-Za-z0-9_]+) ([0-9a-f]{16})$' -or ($Matches[1]+'.hsaco') -notin @($textureFiles.Name)) { throw 'Invalid local texture manifest.' }
|
||||
$textureManifest+=$line
|
||||
}
|
||||
if ($textureManifest.Count -ne 2) { throw 'Expected two local texture manifest identities.' }
|
||||
if ((Get-FileHash -LiteralPath (Join-Path $LocalTextureKernels 'd4r-kernels.txt') -Algorithm SHA256).Hash.ToLowerInvariant() -ne $validation.manifestSha256) { throw 'Local texture manifest changed after validation.' }
|
||||
}
|
||||
$manifest = if (Test-Path -LiteralPath $manifestPath) { Get-Content -LiteralPath $manifestPath -Raw | ConvertFrom-Json } else { [pscustomobject]@{game=$GameExe; files=@()} }
|
||||
if ($manifest.game -ne $GameExe) { throw 'Backup manifest belongs to another executable.' }
|
||||
if (!(Test-Path -LiteralPath $manifestPath) -and (Test-Path -LiteralPath (GamePath 'd4r'))) { throw 'An existing d4r directory has no installation manifest; preserved.' }
|
||||
New-Item -ItemType Directory -Force $backup | Out-Null
|
||||
function InstallFile([string]$source, [string]$relative) {
|
||||
$relative=$relative.Replace('/','\')
|
||||
$path = GamePath $relative
|
||||
$entry = @($manifest.files | Where-Object { $_.path.Replace('/','\') -eq $relative }) | Select-Object -First 1
|
||||
if ($entry) {
|
||||
if ((Test-Path -LiteralPath $path) -and (Get-FileHash -LiteralPath $path -Algorithm SHA256).Hash -ne $entry.installedHash) { throw "Installed file changed; preserved: $path" }
|
||||
} else {
|
||||
$existed = Test-Path -LiteralPath $path
|
||||
if ($existed) {
|
||||
$original = Join-Path $backup ('original/' + $relative)
|
||||
New-Item -ItemType Directory -Force (Split-Path $original) | Out-Null
|
||||
Copy-Item -LiteralPath $path -Destination $original
|
||||
}
|
||||
$entry = [pscustomobject]@{path=$relative; existed=[bool]$existed; installedHash=''}
|
||||
$manifest.files = @($manifest.files) + @($entry)
|
||||
}
|
||||
New-Item -ItemType Directory -Force (Split-Path $path) | Out-Null
|
||||
Copy-Item -LiteralPath $source -Destination $path -Force
|
||||
$entry.installedHash = (Get-FileHash -LiteralPath $path -Algorithm SHA256).Hash
|
||||
$manifest | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath $manifestPath -Encoding UTF8
|
||||
}
|
||||
InstallFile (Join-Path $package 'OptiScaler.dll') 'dxgi.dll'
|
||||
foreach ($file in Get-ChildItem -LiteralPath (Join-Path $package 'd4r') -Recurse -File) { InstallFile $file.FullName $file.FullName.Substring($package.Length+1) }
|
||||
if ($LocalTextureKernels) {
|
||||
foreach ($file in $textureFiles) { InstallFile $file.FullName ('d4r/native/' + $file.Name) }
|
||||
$combinedManifest=Join-Path $backup 'generated-native-manifest.txt'
|
||||
@(Get-Content -LiteralPath (Join-Path $package 'd4r/native/d4r-kernels.txt')) + $textureManifest |
|
||||
Set-Content -LiteralPath $combinedManifest -Encoding ASCII
|
||||
InstallFile $combinedManifest 'd4r/native/d4r-kernels.txt'
|
||||
}
|
||||
# Private local test copies only; these are never added to the public package.
|
||||
InstallFile $NgxCore 'd4r/vendor/_nvngx.dll'
|
||||
InstallFile $DlssDll 'd4r/vendor/nvngx_dlss.dll'
|
||||
$bridge = GamePath 'd4r'
|
||||
$vendor = GamePath 'd4r/vendor'
|
||||
$ini = @"
|
||||
[Upscalers]
|
||||
Dx12Upscaler=dlss
|
||||
[FrameGen]
|
||||
Enabled=false
|
||||
FGInput=nofg
|
||||
FGOutput=nofg
|
||||
[DLSS]
|
||||
Enabled=true
|
||||
AllowExternalBackend=true
|
||||
RenderPresetOverride=true
|
||||
RenderPresetForAll=$Preset
|
||||
UseGenericAppIdWithDlss=false
|
||||
[Libraries]
|
||||
NvngxPath=$bridge
|
||||
NvngxDlssPath=$vendor\nvngx_dlss.dll
|
||||
NvngxFeaturePath=$vendor
|
||||
[Hotfix]
|
||||
RestoreComputeSignature=false
|
||||
RestoreGraphicSignature=false
|
||||
ExtendedStateRestore=false
|
||||
[Log]
|
||||
LogToFile=true
|
||||
LogLevel=$(if ($VerboseRuntime) { 0 } else { 2 })
|
||||
[Menu]
|
||||
DisableSplash=true
|
||||
"@
|
||||
$tempIni = Join-Path $backup 'generated-OptiScaler.ini'
|
||||
$ini | Set-Content -LiteralPath $tempIni -Encoding UTF8
|
||||
InstallFile $tempIni 'OptiScaler.ini'
|
||||
if ($Action -eq 'install') { Write-Host "Installed development backend for preset $Preset. Use -Action restore to revert."; return }
|
||||
if (!$OutputDirectory) { $OutputDirectory = Join-Path $package ('results/game-' + (Get-Date -Format 'yyyyMMdd-HHmmss-fff')) }
|
||||
$OutputDirectory = [IO.Path]::GetFullPath($OutputDirectory)
|
||||
New-Item -ItemType Directory -Force $OutputDirectory | Out-Null
|
||||
if (!$CacheDirectory) { $CacheDirectory = Join-Path $package 'cache' }
|
||||
$CacheDirectory = [IO.Path]::GetFullPath($CacheDirectory)
|
||||
$settings = @{
|
||||
D4R_HIP_ROOT=(GamePath 'd4r/hip'); HIP_PATH=(GamePath 'd4r/hip');
|
||||
D4R_NVCUDA_DLL=(GamePath 'd4r/zluda/nvcuda.dll'); ZLUDA_CUDA_LIB=(GamePath 'd4r/zluda/nvcuda.dll');
|
||||
D4R_NVAPI_DLL=(GamePath 'd4r/nvapi/nvapi64.dll'); D4R_NVAPI_BACKEND=(GamePath 'd4r/zluda/nvapi64.dll');
|
||||
D4R_NGX_CORE=(GamePath 'd4r/vendor/_nvngx.dll'); D4R_DLSS_DLL=(GamePath 'd4r/vendor/nvngx_dlss.dll');
|
||||
D4R_FORMAT_MODULE=(GamePath 'd4r/pixel_convert_gfx1201.hsaco');
|
||||
D4R_ZLUDA_NATIVE_DIR=(GamePath 'd4r/native'); D4R_D3D12_COMMAND_BACKEND='1'; D4R_ZLUDA_VERBOSE='1';
|
||||
D4R_ZLUDA_WMMA='1'; D4R_ZLUDA_WMMA_FP8='1'; D4R_ZLUDA_WMMA_FP8_NATIVE='0'; D4R_ZLUDA_WMMA_F16_REFERENCE='1';
|
||||
# Do not inherit relaxation flags from unrelated experiments. Unset and
|
||||
# false have the same numerical effect; unset reuses the validated cache.
|
||||
D4R_ZLUDA_IGNORE_DENORMAL=$null; D4R_ZLUDA_FAST_MATH=$null;
|
||||
D4R_ZLUDA_WMMA_F32ACC=$null; D4R_ZLUDA_WAVE64=$null;
|
||||
D4R_QUIET_API=$(if ($VerboseRuntime) { $null } else { '1' });
|
||||
D4R_VALIDATE_OUTPUT=$(if ($ValidateOutput) { '1' } else { $null });
|
||||
D4R_PROFILE_STAGES=$(if ($ProfileStages) { '1' } else { $null });
|
||||
D4R_ZLUDA_PROFILE=$(if ($ProfileKernels) { '1' } else { $null });
|
||||
D4R_PROFILE_COMMAND_HOOKS=$(if ($ProfileCommandHooks) { '1' } else { $null });
|
||||
D4R_ZLUDA_PROFILE_API=$(if ($ProfileCudaApi) { '1' } else { $null });
|
||||
D4R_PROFILE_GPU_BOUNDARY=$(if ($ProfileGpuBoundary) { '1' } else { $null });
|
||||
D4R_DISABLE_INTEROP_LIST_CACHE=$(if ($UncachedInteropLists) { '1' } else { $null });
|
||||
D4R_ASYNC_INTEROP=$(if ($AsyncInterop) { '1' } else { $null });
|
||||
D4R_DIAG_DIR=$OutputDirectory; ZLUDA_LOG_DIR=(Join-Path $OutputDirectory 'zluda-trace');
|
||||
ZLUDA_CACHE_DIR=$CacheDirectory; PATH=((GamePath 'd4r/hip/bin') + ';' + (GamePath 'd4r/zluda') + ';' + $env:PATH)
|
||||
}
|
||||
$old = @{}; $process = $null; $originalSettings = @()
|
||||
$outFile = $null; $errFile = $null; $outTask = $null; $errTask = $null
|
||||
try {
|
||||
if ($DiagnosticResolution) {
|
||||
if ([IO.Path]::GetFileName($GameExe) -ne 'SHProto-Win64-Shipping.exe' -or $DiagnosticResolution -notmatch '^([0-9]{3,4})x([0-9]{3,4})$') { throw 'DiagnosticResolution currently supports Silent Hill 2 only; use e.g. 1280x720.' }
|
||||
$renderWidth=[int]$Matches[1]; $renderHeight=[int]$Matches[2]
|
||||
if ($renderWidth -gt 4096 -or $renderHeight -gt 2160) { throw 'Diagnostic resolution is too large.' }
|
||||
$userRoot=Join-Path $env:LOCALAPPDATA 'SilentHill2/Saved'
|
||||
foreach ($relative in @('Config/Windows/GameUserSettings.ini','SaveGames/GFXSettings.SHProto-Win64-Shipping.xml')) {
|
||||
$path=Join-Path $userRoot $relative
|
||||
$saved=Join-Path $OutputDirectory ('original-settings/' + $relative)
|
||||
New-Item -ItemType Directory -Force (Split-Path $saved) | Out-Null
|
||||
Copy-Item -LiteralPath $path -Destination $saved
|
||||
$originalSettings += @{path=$path; saved=$saved}
|
||||
if ($relative.EndsWith('.ini')) {
|
||||
$text=[IO.File]::ReadAllText($path)
|
||||
$text=$text -replace 'Resolution=\(X=[0-9]+,Y=[0-9]+\)', "Resolution=(X=$renderWidth,Y=$renderHeight)"
|
||||
$text=$text -replace 'ScreenMode=Borderless', 'ScreenMode=Windowed'
|
||||
$text=$text -replace '(?m)^ResolutionSizeX=[0-9]+', "ResolutionSizeX=$renderWidth"
|
||||
$text=$text -replace '(?m)^ResolutionSizeY=[0-9]+', "ResolutionSizeY=$renderHeight"
|
||||
$text=$text -replace '(?m)^FullscreenMode=[0-9]+', 'FullscreenMode=2'
|
||||
[IO.File]::WriteAllText($path,$text,[Text.UTF8Encoding]::new($false))
|
||||
} else {
|
||||
$xml=[xml](Get-Content -LiteralPath $path -Raw -Encoding Unicode)
|
||||
$xml.GSA_SDK.GAMESETTINGS.RESOLUTION.Width="$renderWidth"
|
||||
$xml.GSA_SDK.GAMESETTINGS.RESOLUTION.Height="$renderHeight"
|
||||
$xml.Save($path)
|
||||
}
|
||||
}
|
||||
Write-Host "Temporary diagnostic resolution: $DiagnosticResolution; original settings restore at exit."
|
||||
}
|
||||
foreach ($key in $settings.Keys) { $old[$key] = [Environment]::GetEnvironmentVariable($key,'Process'); [Environment]::SetEnvironmentVariable($key,$settings[$key],'Process') }
|
||||
@{preset=$Preset; game=$GameExe; package=$metadata; attachedDebugger=[bool]$CaptureExceptions; driver=@(Get-CimInstance Win32_VideoController | Select-Object Name,DriverVersion,DriverDate); arguments=$GameArguments} |
|
||||
ConvertTo-Json -Depth 12 | Set-Content -LiteralPath (Join-Path $OutputDirectory 'environment.json') -Encoding UTF8
|
||||
function Quote([string]$value) { if ($value.Contains('"')) { throw 'A command argument contains an unsupported quote.' }; return '"' + $value + '"' }
|
||||
$arguments = @('--output-directory', (Quote $OutputDirectory))
|
||||
if (!$CaptureExceptions) { $arguments += '--no-debug' }
|
||||
$arguments += @('--', (Quote $GameExe)) + @($GameArguments | ForEach-Object { Quote $_ })
|
||||
# Drain raw bytes concurrently. PowerShell's line-oriented redirection can
|
||||
# throttle a verbose game and distort the very timings we are collecting.
|
||||
$info = [Diagnostics.ProcessStartInfo]::new()
|
||||
$info.FileName = GamePath 'd4r/d4r_debug_launcher.exe'
|
||||
$info.Arguments = $arguments -join ' '
|
||||
$info.WorkingDirectory = $game
|
||||
$info.UseShellExecute = $false; $info.CreateNoWindow = $true
|
||||
$info.RedirectStandardOutput = $true; $info.RedirectStandardError = $true
|
||||
$process = [Diagnostics.Process]::new(); $process.StartInfo = $info
|
||||
if (!$process.Start()) { throw 'Game diagnostic launch failed.' }
|
||||
$outFile = [IO.File]::Open((Join-Path $OutputDirectory 'd4r.stdout.log'), [IO.FileMode]::Create, [IO.FileAccess]::Write, [IO.FileShare]::Read)
|
||||
$errFile = [IO.File]::Open((Join-Path $OutputDirectory 'd4r.stderr.log'), [IO.FileMode]::Create, [IO.FileAccess]::Write, [IO.FileShare]::Read)
|
||||
$outTask = $process.StandardOutput.BaseStream.CopyToAsync($outFile)
|
||||
$errTask = $process.StandardError.BaseStream.CopyToAsync($errFile)
|
||||
Write-Host "Game diagnostic started (preset $Preset). Logs: $OutputDirectory"
|
||||
$timer = [Diagnostics.Stopwatch]::StartNew()
|
||||
while (!$process.WaitForExit(1000)) {
|
||||
if ($RunSeconds -gt 0 -and $timer.Elapsed.TotalSeconds -ge $RunSeconds) { $process.Kill(); $process.WaitForExit(); break }
|
||||
}
|
||||
$process.WaitForExit()
|
||||
[void]$outTask.GetAwaiter().GetResult(); [void]$errTask.GetAwaiter().GetResult()
|
||||
$outFile.Dispose(); $outFile = $null; $errFile.Dispose(); $errFile = $null
|
||||
foreach ($file in Get-ChildItem -LiteralPath $game -Filter 'OptiScaler*.log' -File) { Copy-Item -LiteralPath $file.FullName -Destination $OutputDirectory -Force }
|
||||
$stderr = Get-Content -LiteralPath (Join-Path $OutputDirectory 'd4r.stderr.log') -Raw
|
||||
$stdout = Get-Content -LiteralPath (Join-Path $OutputDirectory 'd4r.stdout.log') -Raw
|
||||
$debugger = Get-Content -LiteralPath (Join-Path $OutputDirectory 'debugger.log') -Raw
|
||||
$gameExit = [regex]::Match($debugger, 'EXIT code=0x([0-9a-f]+)')
|
||||
$gameCrash = $null
|
||||
if ([IO.Path]::GetFileName($GameExe) -eq 'SHProto-Win64-Shipping.exe' -and $gameExit.Success -and $gameExit.Groups[1].Value -ne '0') {
|
||||
$crashRoot = Join-Path $env:LOCALAPPDATA 'SilentHill2/Saved/Crashes'
|
||||
$latest = Get-ChildItem -LiteralPath $crashRoot -Directory -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.LastWriteTime -ge (Get-Date).AddSeconds(-$timer.Elapsed.TotalSeconds-5) } |
|
||||
Sort-Object LastWriteTime -Descending | Select-Object -First 1
|
||||
if ($latest) {
|
||||
Copy-Item -LiteralPath $latest.FullName -Destination (Join-Path $OutputDirectory 'ue-crash') -Recurse
|
||||
[xml]$context = Get-Content -LiteralPath (Join-Path $latest.FullName 'CrashContext.runtime-xml') -Raw
|
||||
$gameCrash = @{type=[string]$context.FGenericCrashContext.RuntimeProperties.CrashType;
|
||||
reason=[string]$context.FGenericCrashContext.RuntimeProperties.ErrorMessage}
|
||||
}
|
||||
}
|
||||
# A bounded stop can interrupt the last printf; count only complete records.
|
||||
$frames = [regex]::Matches($stdout, '(?m)^D4R_FRAME cpu_frame_copies=([0-9]+) frame_age=([0-9]+) interop_ngx_ms=([0-9.]+)\r?$')
|
||||
$checks = [regex]::Matches($stdout, '(?m)^D4R_OUTPUT_VALIDATION elements=([0-9]+) nan=([0-9]+) inf=([0-9]+) diagnostics_cpu_bytes=8\r?$')
|
||||
$nonfinite = @($checks | Where-Object { $_.Groups[2].Value -ne '0' -or $_.Groups[3].Value -ne '0' }).Count
|
||||
$launches = [regex]::Matches($stderr, '\[d4r-launch\] kernel="([^"]+)" backend=(native|translated)')
|
||||
$kernels = @($launches | ForEach-Object { $_.Groups[1].Value + ' ' + $_.Groups[2].Value } | Group-Object | ForEach-Object {
|
||||
$parts=$_.Name.Split(' '); @{kernel=$parts[0]; backend=$parts[1]; launches=$_.Count}
|
||||
})
|
||||
@{exitCode=$(if ($gameExit.Success) { '0x' + $gameExit.Groups[1].Value } else { 'diagnostic_timeout' }); runSeconds=$timer.Elapsed.TotalSeconds; preset=$Preset; gameCrash=$gameCrash;
|
||||
nativeLaunches=([regex]::Matches($stderr, '\[d4r-launch\].*backend=native')).Count;
|
||||
completedFrames=$frames.Count; outputValidationRequested=[bool]$ValidateOutput;
|
||||
outputGpuChecks=$checks.Count; nonfiniteOutputs=$nonfinite; kernels=$kernels;
|
||||
previousFrameOutputs=@($frames | Where-Object { $_.Groups[2].Value -ne '0' }).Count;
|
||||
cpuImageCopyFrames=@($frames | Where-Object { $_.Groups[1].Value -ne '0' }).Count;
|
||||
failures=([regex]::Matches($stderr, 'D4R_[A-Z0-9_]*FAILURE\b')).Count} |
|
||||
ConvertTo-Json -Depth 6 | Set-Content -LiteralPath (Join-Path $OutputDirectory 'summary.json') -Encoding UTF8
|
||||
Compress-Archive -LiteralPath $OutputDirectory -DestinationPath ($OutputDirectory + '.zip') -Force
|
||||
Write-Host "Game diagnostic bundle: $OutputDirectory.zip"
|
||||
} finally {
|
||||
if ($process -and !$process.HasExited) { $process.Kill(); $process.WaitForExit() }
|
||||
if ($outTask) { try { [void]$outTask.GetAwaiter().GetResult() } catch {} }
|
||||
if ($errTask) { try { [void]$errTask.GetAwaiter().GetResult() } catch {} }
|
||||
if ($outFile) { $outFile.Dispose() }; if ($errFile) { $errFile.Dispose() }
|
||||
if ($process) { $process.Dispose() }
|
||||
foreach ($key in $old.Keys) { [Environment]::SetEnvironmentVariable($key,$old[$key],'Process') }
|
||||
foreach ($entry in $originalSettings) { Copy-Item -LiteralPath $entry.saved -Destination $entry.path -Force }
|
||||
}
|
||||
@@ -31,9 +31,6 @@ int main(int argc, char** argv) {
|
||||
puts("");
|
||||
return 0;
|
||||
}
|
||||
if (argc == 3 && strcmp(argv[1], "kernel") == 0) {
|
||||
printf("%d\n", d4r_native_kernel_allowed(argv[2])); return 0;
|
||||
}
|
||||
if (argc != 5) return 2;
|
||||
char path[1024] = {0};
|
||||
int kind = d4r_select_native_source(argv[1], argv[2], atoi(argv[3]), atoi(argv[4]), path, sizeof(path));
|
||||
@@ -143,37 +140,6 @@ int main(int argc, char** argv) {
|
||||
result = self.config("[Kernels]\nPreferAccuracy=perhaps\n", expected=2)
|
||||
self.assertIn("PreferAccuracy must be true or false", result.stderr)
|
||||
|
||||
def test_swin_encoder_fallback_keeps_other_layers(self):
|
||||
encoders = ("rrlite_enc1_4x4", "rrlite_enc2_4x4")
|
||||
others = ("rrlite_enc3_tube_4x4", "rrlite_dec1_4x4", "rrlite_dec2_4x4",
|
||||
"rrlite_enc0_4x4_mvhi_hdr", "rrlite_post_3_1_mvhi_hdr", "dltss_pwin_enc1_layer")
|
||||
for value in ("0", "1", None):
|
||||
env = {k: v for k, v in os.environ.items() if k != "D4R_NATIVE_SWIN_ENCODERS"}
|
||||
if value is not None:
|
||||
env["D4R_NATIVE_SWIN_ENCODERS"] = value
|
||||
for name in encoders + others:
|
||||
got = subprocess.check_output([str(self.runner), "kernel", name], env=env, text=True).strip()
|
||||
self.assertEqual(got, "0" if value == "0" and name in encoders else "1")
|
||||
|
||||
def test_swin_encoder_config_and_environment_override(self):
|
||||
for value, expected in (("false", "0"), ("true", "1"), ("auto", "1")):
|
||||
self.assertIn(f"export D4R_NATIVE_SWIN_ENCODERS={expected}",
|
||||
self.config(f"[Kernels]\nNativeSwinEncoders = {value}\n").stdout)
|
||||
result = self.config("[Kernels]\nNativeSwinEncoders = false\n",
|
||||
{"D4R_NATIVE_SWIN_ENCODERS": "1"})
|
||||
self.assertNotIn("export D4R_NATIVE_SWIN_ENCODERS", result.stdout)
|
||||
self.config("[Kernels]\nNativeSwinEncoders = maybe\n", expected=2)
|
||||
|
||||
def test_motion_dilation_config_bounds_and_environment_precedence(self):
|
||||
for value, expected in (("0", "0"), ("1", "1"), ("2", "2"), ("auto", "0")):
|
||||
self.assertIn(f"export D4R_MOTION_DILATION={expected}",
|
||||
self.config(f"[Interop]\nMotionVectorDilation = {value}\n").stdout)
|
||||
for value in ("-1", "3", "true", "1.5"):
|
||||
self.config(f"[Interop]\nMotionVectorDilation = {value}\n", expected=2)
|
||||
self.assertNotIn("export D4R_MOTION_DILATION",
|
||||
self.config("[Interop]\nMotionVectorDilation = 2\n",
|
||||
{"D4R_MOTION_DILATION": "0"}).stdout)
|
||||
|
||||
def test_shipped_configs_default_off(self):
|
||||
for file in ("config/d4r.ini.default", "packaging/d4r.ini"):
|
||||
self.assertRegex((ROOT / file).read_text(), r"(?m)^PreferAccuracy\s*=\s*false$")
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
"""Completion/error contracts for the sleeping CUDA event wait (no GPU needed)."""
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
class EventWaitTests(unittest.TestCase):
|
||||
def test_completion_pending_and_error(self):
|
||||
source = r'''
|
||||
#include "d4r_event_wait.h"
|
||||
#include <cassert>
|
||||
int main() {
|
||||
int sleeps = 0, queries = 0;
|
||||
assert(d4r_wait_event([] { return 0; }, [&] { ++sleeps; }) == 0);
|
||||
assert(sleeps == 0);
|
||||
assert(d4r_wait_event([&] { return ++queries <= 7 ? 600 : 0; },
|
||||
[&] { ++sleeps; }) == 0);
|
||||
assert(queries == 8 && sleeps == 7);
|
||||
sleeps = 0;
|
||||
assert(d4r_wait_event([] { return 719; }, [&] { ++sleeps; }) == 719);
|
||||
assert(sleeps == 0);
|
||||
queries = 0;
|
||||
assert(d4r_wait_event([&] { return ++queries == 1 ? 600 : 700; },
|
||||
[&] { ++sleeps; }) == 700);
|
||||
assert(sleeps == 1);
|
||||
}
|
||||
'''
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
runner = Path(tmp) / "wait"
|
||||
subprocess.run(["c++", "-x", "c++", "-std=c++17", "-Wall", "-Wextra", "-Werror",
|
||||
"-I", str(ROOT / "tools"), "-o", str(runner), "-"],
|
||||
input=source, text=True, check=True, capture_output=True)
|
||||
subprocess.run([str(runner)], check=True, timeout=5)
|
||||
@@ -1,48 +0,0 @@
|
||||
"""L must retain its unfolded neural math when native surface stores are substituted."""
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
class ModelLTests(unittest.TestCase):
|
||||
def test_model_l_config_and_environment_precedence(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
ini = Path(directory) / 'd4r.ini'
|
||||
ini.write_text('[DLSS]\nModel = L\n')
|
||||
env = dict(os.environ)
|
||||
env.pop('D4R_DLSS_PRESET', None)
|
||||
cmd = ['python3', str(ROOT / 'scripts/d4r_config.py'), '--config', str(ini)]
|
||||
self.assertIn('export D4R_DLSS_PRESET=12', subprocess.check_output(cmd, env=env, text=True))
|
||||
env['D4R_DLSS_PRESET'] = '11'
|
||||
self.assertNotIn('export D4R_DLSS_PRESET=', subprocess.check_output(cmd, env=env, text=True))
|
||||
|
||||
def test_unfolded_variants_preserve_neural_arithmetic(self):
|
||||
names = ['rrlite_dec0_4x4']
|
||||
for mv in ('mvhi', 'mvlo'):
|
||||
for dynamic_range in ('hdr', 'ldr'):
|
||||
names.append(f'rrlite_enc0_4x4_{mv}_{dynamic_range}')
|
||||
names.extend(f'rrlite_post_{v}_{mv}_{dynamic_range}' for v in ('3_1', '3_2'))
|
||||
math = 'mma.sync.aligned.m16n8k32.row.col.f16.e4m3.e4m3.f16 {%r1, %r2}, {%r3, %r4, %r5, %r6}, {%r7, %r8}, {%r9, %r10};'
|
||||
store = 'sust.b.2d.v2.b16.zero [%rd1, {%r1,%r2}], {%rs1,%rs2};'
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
ptx = Path(directory)
|
||||
env = dict(os.environ, D4R_DLSS_PTX_DIR=directory, D4R_SUST_KINDS='b16,p,b32')
|
||||
for name in names:
|
||||
with self.subTest(kernel=name):
|
||||
(ptx / 'input.ptx').write_text(f'.visible .entry {name}(\n)\n{{\n{math}\n{store}\nret;\n}}\n')
|
||||
subprocess.run(['python3', str(ROOT / 'kernels/tex/make_ptx.py'), name, str(ptx / 'out.ptx')],
|
||||
env=env, check=True, capture_output=True)
|
||||
output = (ptx / 'out.ptx').read_text()
|
||||
self.assertIn(math, output)
|
||||
self.assertNotIn(store, output)
|
||||
self.assertIn('call d4r_sust_v2b16', output)
|
||||
self.assertNotIn('d4r_enc0_tail', output)
|
||||
self.assertNotIn('d4r_dec0_head', output)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -1,45 +0,0 @@
|
||||
"""Motion-grid selection and padded low-resolution allocation regression."""
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
class MotionGeometryTests(unittest.TestCase):
|
||||
def test_low_res_padding_flags_and_display_res_mapping(self):
|
||||
source = r'''
|
||||
#include "d4r_motion_dilation.h"
|
||||
#include <cassert>
|
||||
int main() {
|
||||
D4rMotionDilationParams p = {};
|
||||
p.render_width = 854; p.render_height = 480;
|
||||
p.motion_width = 856; p.motion_height = 480;
|
||||
p.motion_pitch = 4096; p.jitter_x = 0.375f; p.jitter_y = -0.25f;
|
||||
auto low = p;
|
||||
assert(d4r_motion_dilation_geometry(&low, 0xb, 2560, 1440));
|
||||
assert(low.motion_width == 854 && low.motion_height == 480);
|
||||
assert(low.motion_pitch == 4096 && low.jitter_x == 0 && low.jitter_y == 0);
|
||||
low = p;
|
||||
assert(d4r_motion_dilation_geometry(&low, 0x4b, 2560, 1440));
|
||||
low = p; low.motion_width = 853;
|
||||
assert(!d4r_motion_dilation_geometry(&low, 0xb, 2560, 1440));
|
||||
low = p; low.motion_height = 479;
|
||||
assert(!d4r_motion_dilation_geometry(&low, 0xb, 2560, 1440));
|
||||
low = p;
|
||||
assert(!d4r_motion_dilation_geometry(&low, 0xf, 2560, 1440));
|
||||
assert(!d4r_motion_dilation_geometry(&p, 0x49, 2560, 1440));
|
||||
p.motion_width = 2560; p.motion_height = 1440;
|
||||
assert(d4r_motion_dilation_geometry(&p, 0x49, 2560, 1440));
|
||||
assert(p.jitter_x == 0.375f && p.jitter_y == -0.25f);
|
||||
p.render_width = 0;
|
||||
assert(!d4r_motion_dilation_geometry(&p, 0x49, 2560, 1440));
|
||||
}
|
||||
'''
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
runner = Path(tmp) / "geometry"
|
||||
subprocess.run(["c++", "-x", "c++", "-std=c++17", "-Wall", "-Wextra", "-Werror",
|
||||
"-I", str(ROOT / "tools"), "-o", str(runner), "-"],
|
||||
input=source, text=True, check=True, capture_output=True)
|
||||
subprocess.run([str(runner)], check=True, timeout=5)
|
||||
@@ -1,123 +0,0 @@
|
||||
"""Optional GPU regression: independently specified thin-foreground velocity coverage.
|
||||
|
||||
D4R_RUN_GPU_TESTS=1 D4R_DUMP_RUNNER=/path/to/dump_runner python3 -m unittest discover -s tests -v
|
||||
Build the embedded kernels first with scripts/build_d4r_motion_kernels.sh.
|
||||
"""
|
||||
import os
|
||||
from pathlib import Path
|
||||
import struct
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
@unittest.skipUnless(os.environ.get('D4R_RUN_GPU_TESTS') == '1', 'opt-in GPU test')
|
||||
class MotionKernelGpuTests(unittest.TestCase):
|
||||
def test_low_resolution_opaque_edges_and_padded_rows(self):
|
||||
runner = os.environ['D4R_DUMP_RUNNER']
|
||||
kernel = ROOT / 'build/wine-nvcuda/generated' / (os.environ.get('D4R_GPU_ARCH', 'gfx1101') + '.hsaco')
|
||||
width, height, stride = 5, 3, 8
|
||||
sentinel = 0xA5A5A5A5
|
||||
for reversed_depth in (False, True):
|
||||
for radius in (1, 2):
|
||||
with self.subTest(reversed_depth=reversed_depth, radius=radius), tempfile.TemporaryDirectory() as t:
|
||||
directory = Path(t); output = directory / 'out'; output.mkdir()
|
||||
depth = [0.1 if reversed_depth else 0.9] * (stride*height)
|
||||
depth[stride+2] = 0.9 if reversed_depth else 0.1
|
||||
motion = list(range(stride*height))
|
||||
arrays = [struct.pack('<24f', *depth), struct.pack('<24I', *motion), struct.pack('<24I', *([sentinel]*24))]
|
||||
addresses = (0x100000000, 0x200000000, 0x300000000)
|
||||
args = struct.pack('<5Q4I2f2I', *addresses, stride*4, stride*4,
|
||||
width, height, width, height, 0.0, 0.0, radius, reversed_depth)
|
||||
(directory/'args.bin').write_bytes(args)
|
||||
manifest = ['kernel d4r_motion_dilate', 'launch 1 1 1 128 1 1 0', 'args 72']
|
||||
for i, data in enumerate(arrays):
|
||||
(directory/f'alloc-{i}.bin').write_bytes(data)
|
||||
manifest += [f'alloc {i} {hex(addresses[i])} {len(data)}', f'pointer {i*8} {i} 0']
|
||||
(directory/'manifest.txt').write_text('\n'.join(manifest)+'\n')
|
||||
subprocess.run([runner, str(kernel), 'd4r_motion_dilate', str(directory), str(output), '1'], check=True, capture_output=True)
|
||||
self.assertEqual((output/'alloc-0.bin').read_bytes(), arrays[0])
|
||||
self.assertEqual((output/'alloc-1.bin').read_bytes(), arrays[1])
|
||||
expected = [sentinel] * 24
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
expected[y*stride+x] = motion[stride+2] if abs(x-2) <= radius and abs(y-1) <= radius else motion[y*stride+x]
|
||||
self.assertEqual((output/'alloc-2.bin').read_bytes(), struct.pack('<24I', *expected))
|
||||
|
||||
def test_foreground_coverage_padding_and_input_immutability(self):
|
||||
runner = os.environ['D4R_DUMP_RUNNER']
|
||||
kernel = ROOT / 'build/wine-nvcuda/generated' / (os.environ.get('D4R_GPU_ARCH', 'gfx1101') + '.hsaco')
|
||||
sentinel = 0xA5A5A5A5
|
||||
for reversed_depth in (False, True):
|
||||
for radius in (1, 2):
|
||||
for jitter in ((0.0, 0.0), (0.25, -0.25)):
|
||||
with self.subTest(reversed_depth=reversed_depth, radius=radius, jitter=jitter), tempfile.TemporaryDirectory() as t:
|
||||
directory = Path(t); output = directory / 'out'; output.mkdir()
|
||||
depth = [1000.0] * 16
|
||||
for y in range(2):
|
||||
for x in range(3):
|
||||
depth[y*8+x] = 0.1 if reversed_depth else 0.9
|
||||
depth[1] = 0.9 if reversed_depth else 0.1
|
||||
motion = list(range(96))
|
||||
arrays = [struct.pack('<16f', *depth), struct.pack('<96I', *motion), struct.pack('<96I', *([sentinel]*96))]
|
||||
addresses = (0x100000000, 0x200000000, 0x300000000)
|
||||
args = struct.pack('<5Q4I2f2I', *addresses, 32, 64, 3, 2, 9, 6, *jitter, radius, reversed_depth)
|
||||
(directory/'args.bin').write_bytes(args)
|
||||
manifest = ['kernel d4r_motion_dilate', 'launch 1 1 1 128 1 1 0', 'args 72']
|
||||
for i, data in enumerate(arrays):
|
||||
(directory/f'alloc-{i}.bin').write_bytes(data)
|
||||
manifest += [f'alloc {i} {hex(addresses[i])} {len(data)}', f'pointer {i*8} {i} 0']
|
||||
(directory/'manifest.txt').write_text('\n'.join(manifest)+'\n')
|
||||
subprocess.run([runner, str(kernel), 'd4r_motion_dilate', str(directory), str(output), '1'], check=True, capture_output=True)
|
||||
self.assertEqual((output/'alloc-0.bin').read_bytes(), arrays[0])
|
||||
self.assertEqual((output/'alloc-1.bin').read_bytes(), arrays[1])
|
||||
expected = [sentinel] * 96
|
||||
# Known single foreground cell: all neighbouring background follows its velocity.
|
||||
# Foreground itself preserves its original per-pixel velocity; row padding is untouched.
|
||||
for y in range(6):
|
||||
for x in range(9):
|
||||
inside = (3 <= x <= 5 and y <= 2) if jitter == (0.0, 0.0) else (2 <= x <= 4 and y <= 3)
|
||||
expected[y*16+x] = motion[y*16+x] if inside else motion[1*16+4] if jitter == (0.0, 0.0) else motion[2*16+3]
|
||||
self.assertEqual((output/'alloc-2.bin').read_bytes(), struct.pack('<96I', *expected))
|
||||
|
||||
def test_automatic_sky_fill_circle_nearest_ties_and_empty_sky(self):
|
||||
runner = os.environ['D4R_DUMP_RUNNER']
|
||||
kernel = ROOT / 'build/wine-nvcuda/generated' / (os.environ.get('D4R_GPU_ARCH', 'gfx1101') + '.hsaco')
|
||||
width, height, stride = 23, 23, 32
|
||||
sentinel = 0xA5A5A5A5
|
||||
for reversed_depth in (False, True):
|
||||
for radius in (1, 2):
|
||||
for empty, near_far in ((False, False), (True, False), (False, True)):
|
||||
with self.subTest(reversed_depth=reversed_depth, radius=radius, empty=empty, near_far=near_far), tempfile.TemporaryDirectory() as t:
|
||||
directory = Path(t); output = directory / 'out'; output.mkdir()
|
||||
depth = [1000.0] * (stride*height)
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
depth[y*stride+x] = (1.e-7 if near_far else 0.0) if reversed_depth else 1.0
|
||||
sources = [] if empty else [(8, 11), (14, 11)]
|
||||
for x, y in sources:
|
||||
depth[y*stride+x] = (1.5e-7 if near_far else 0.9) if reversed_depth else (0.99 if near_far else 0.1)
|
||||
motion = list(range(stride*height))
|
||||
arrays = [struct.pack(f'<{len(depth)}f', *depth), struct.pack(f'<{len(motion)}I', *motion), struct.pack(f'<{len(motion)}I', *([sentinel]*len(motion)))]
|
||||
addresses = (0x100000000, 0x200000000, 0x300000000)
|
||||
args = struct.pack('<5Q4I2f2I', *addresses, stride*4, stride*4, width, height, width, height, 0.0, 0.0, radius, reversed_depth)
|
||||
(directory/'args.bin').write_bytes(args)
|
||||
manifest = ['kernel d4r_motion_dilate', 'launch 5 1 1 128 1 1 0', 'args 72']
|
||||
for i, data in enumerate(arrays):
|
||||
(directory/f'alloc-{i}.bin').write_bytes(data)
|
||||
manifest += [f'alloc {i} {hex(addresses[i])} {len(data)}', f'pointer {i*8} {i} 0']
|
||||
(directory/'manifest.txt').write_text('\n'.join(manifest)+'\n')
|
||||
subprocess.run([runner, str(kernel), 'd4r_motion_dilate', str(directory), str(output), '1'], check=True, capture_output=True)
|
||||
self.assertEqual((output/'alloc-0.bin').read_bytes(), arrays[0])
|
||||
self.assertEqual((output/'alloc-1.bin').read_bytes(), arrays[1])
|
||||
expected = [sentinel] * len(motion)
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
candidates = sorted(((x-sx)**2+(y-sy)**2, sx, sy) for sx, sy in sources)
|
||||
expected[y*stride+x] = motion[y*stride+x]
|
||||
if candidates and candidates[0][0] <= 64:
|
||||
_, sx, sy = candidates[0]
|
||||
expected[y*stride+x] = motion[sy*stride+sx]
|
||||
self.assertEqual((output/'alloc-2.bin').read_bytes(), struct.pack(f'<{len(expected)}I', *expected))
|
||||
@@ -20,9 +20,6 @@
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include "d4r_vkd3d_interop.h"
|
||||
|
||||
using NgxResult = unsigned int;
|
||||
constexpr NgxResult NGX_SUCCESS = 1;
|
||||
@@ -106,90 +103,6 @@ static void submit_and_wait()
|
||||
g_list->Reset(g_allocator, nullptr);
|
||||
}
|
||||
|
||||
class LifetimeProbe final : public IUnknown
|
||||
{
|
||||
public:
|
||||
explicit LifetimeProbe(std::atomic<bool>& destroyed) : destroyed_(destroyed) {}
|
||||
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void** out) override
|
||||
{
|
||||
if (!out) return E_POINTER;
|
||||
*out = nullptr;
|
||||
if (iid != __uuidof(IUnknown)) return E_NOINTERFACE;
|
||||
*out = this; AddRef(); return S_OK;
|
||||
}
|
||||
ULONG STDMETHODCALLTYPE AddRef() override { return ++refs_; }
|
||||
ULONG STDMETHODCALLTYPE Release() override
|
||||
{
|
||||
ULONG refs = --refs_;
|
||||
if (!refs) { destroyed_ = true; delete this; }
|
||||
return refs;
|
||||
}
|
||||
private:
|
||||
std::atomic<ULONG> refs_{1};
|
||||
std::atomic<bool>& destroyed_;
|
||||
};
|
||||
|
||||
static void lifecycle_report(const char* message)
|
||||
{
|
||||
std::printf("%s\n", message);
|
||||
std::fflush(stdout);
|
||||
if (const char* path = std::getenv("D4R_HARNESS_TEST_REPORT"))
|
||||
if (FILE* file = std::fopen(path, "a"))
|
||||
{
|
||||
std::fprintf(file, "%s\n", message);
|
||||
std::fclose(file);
|
||||
}
|
||||
}
|
||||
|
||||
static bool test_external_lifetime()
|
||||
{
|
||||
ID3D12DXVKInteropDeviceD4R2* interop = nullptr;
|
||||
if (!check(g_device->QueryInterface(__uuidof(ID3D12DXVKInteropDeviceD4R2),
|
||||
reinterpret_cast<void**>(&interop)), "lifetime interface"))
|
||||
return false;
|
||||
for (bool submitted : {false, true})
|
||||
{
|
||||
std::atomic<bool> destroyed{false};
|
||||
auto* owner = new LifetimeProbe(destroyed);
|
||||
const HRESULT retained = interop->RetainExternalResources(g_list, owner);
|
||||
const HRESULT repeated = interop->RetainExternalResources(g_list, owner);
|
||||
owner->Release();
|
||||
if (!check(retained, "retain resources") || !check(repeated, "deduplicate resources") || destroyed)
|
||||
return false;
|
||||
g_list->Close();
|
||||
ID3D12Fence* gate = nullptr;
|
||||
if (submitted)
|
||||
{
|
||||
if (!check(g_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, __uuidof(ID3D12Fence),
|
||||
reinterpret_cast<void**>(&gate)), "lifetime gate")) return false;
|
||||
g_queue->Wait(gate, 1);
|
||||
ID3D12CommandList* lists[] = {g_list};
|
||||
g_queue->ExecuteCommandLists(1, lists);
|
||||
Sleep(20);
|
||||
g_allocator->Reset(); // Pending submissions must keep their external owners.
|
||||
if (destroyed) { gate->Signal(1); return false; }
|
||||
gate->Signal(1);
|
||||
g_queue->Signal(g_fence, ++g_fenceValue);
|
||||
if (g_fence->GetCompletedValue() < g_fenceValue)
|
||||
{
|
||||
g_fence->SetEventOnCompletion(g_fenceValue, g_event);
|
||||
if (WaitForSingleObject(g_event, 5000) != WAIT_OBJECT_0) return false;
|
||||
}
|
||||
gate->Release();
|
||||
}
|
||||
// The fence callback may take a moment to drop the submission's allocator ref.
|
||||
const ULONGLONG start = GetTickCount64();
|
||||
do { g_allocator->Reset(); if (!destroyed) Sleep(1); }
|
||||
while (!destroyed && GetTickCount64() - start < 5000);
|
||||
if (!destroyed) return false;
|
||||
if (!check(g_list->Reset(g_allocator, nullptr), "lifetime list reset")) return false;
|
||||
lifecycle_report(submitted ? "LIFETIME submitted: retained until safe allocator reset"
|
||||
: "LIFETIME discarded: retained until safe allocator reset");
|
||||
}
|
||||
interop->Release();
|
||||
return true;
|
||||
}
|
||||
|
||||
static ID3D12Resource* create_buffer(D3D12_HEAP_TYPE type, UINT64 size, D3D12_RESOURCE_STATES state)
|
||||
{
|
||||
D3D12_HEAP_PROPERTIES heap = {};
|
||||
@@ -693,8 +606,6 @@ int main(int argc, char** argv)
|
||||
d4r_ngx_set_int(parameters, "DLSS.Feature.Create.Flags",
|
||||
createFlags != nullptr ? static_cast<int>(std::strtol(createFlags, nullptr, 0)) : 0);
|
||||
NgxHandle* feature = nullptr;
|
||||
if (std::getenv("D4R_HARNESS_LIFETIME_TEST") != nullptr && !test_external_lifetime())
|
||||
return 1;
|
||||
result = createFeature(g_list, 1, parameters, &feature);
|
||||
std::printf("CreateFeature -> 0x%08x handle=%p\n", result, static_cast<void*>(feature));
|
||||
if (result != NGX_SUCCESS)
|
||||
@@ -874,8 +785,6 @@ int main(int argc, char** argv)
|
||||
d4r_ngx_set_int(parameters, "Reset", forceReset || frame == 1 ? 1 : 0);
|
||||
const DWORD start = GetTickCount();
|
||||
result = evaluate(g_list, feature, parameters, nullptr);
|
||||
if (const char* delay = std::getenv("D4R_HARNESS_DELAY_SUBMIT_MS"))
|
||||
Sleep(static_cast<DWORD>(std::clamp(std::atoi(delay), 0, 4000)));
|
||||
submit_and_wait();
|
||||
std::printf("frame %d: EvaluateFeature -> 0x%08x (%lu ms incl. submit)\n", frame, result, GetTickCount() - start);
|
||||
Sleep(frameWaitMs); // let the CUDA worker finish before the next presentation
|
||||
@@ -976,25 +885,6 @@ int main(int argc, char** argv)
|
||||
nonzero += value != 0;
|
||||
std::printf("output read back: %zu of %zu bytes nonzero, written to %s\n", nonzero, output.size(), argv[2]);
|
||||
|
||||
if (std::getenv("D4R_HARNESS_DISCARD_EVALUATION") != nullptr)
|
||||
{
|
||||
const NgxResult queued = evaluate(g_list, feature, parameters, nullptr);
|
||||
const ULONGLONG start = GetTickCount64();
|
||||
const NgxResult retired = release(feature);
|
||||
feature = nullptr;
|
||||
if (queued != NGX_SUCCESS || retired != NGX_SUCCESS || GetTickCount64() - start > 2000)
|
||||
{
|
||||
std::fprintf(stderr, "DISCARD evaluation release failed or blocked\n");
|
||||
return 1;
|
||||
}
|
||||
g_list->Close();
|
||||
g_allocator->Reset();
|
||||
g_list->Reset(g_allocator, nullptr);
|
||||
result = createFeature(g_list, 1, parameters, &feature);
|
||||
if (result != NGX_SUCCESS) return 1;
|
||||
lifecycle_report("DISCARD evaluation: release completed without submitting inputs");
|
||||
}
|
||||
|
||||
// D4R_HARNESS_RECREATE=N: N more release/create cycles at alternating render sizes (as when a game's DLSS
|
||||
// quality setting changes), a few evaluations each, logging this process's VRAM to find leaks per cycle.
|
||||
const int recreateCycles = std::getenv("D4R_HARNESS_RECREATE") != nullptr ? std::atoi(std::getenv("D4R_HARNESS_RECREATE")) : 0;
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
// Query completion rather than trusting HIP event flags to put the CPU to
|
||||
// sleep. Never treat NOT_READY as completion, including during retirement.
|
||||
template <typename Query, typename Sleep>
|
||||
int d4r_wait_event(Query query, Sleep sleep)
|
||||
{
|
||||
constexpr int cudaNotReady = 600;
|
||||
for (;;)
|
||||
{
|
||||
const int result = query();
|
||||
if (result != cudaNotReady)
|
||||
return result;
|
||||
sleep();
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
/* Shared Wine/HIP ABI. Pitches are bytes; motion pixels are packed RG16F. */
|
||||
typedef struct D4rMotionDilationParams
|
||||
{
|
||||
uint64_t depth, motion, output;
|
||||
uint64_t depth_pitch, motion_pitch;
|
||||
uint32_t render_width, render_height, motion_width, motion_height;
|
||||
float jitter_x, jitter_y;
|
||||
uint32_t radius, reversed_depth;
|
||||
} D4rMotionDilationParams;
|
||||
|
||||
/* Low-resolution velocity and depth share the render raster grid. Allocation
|
||||
padding is not part of that grid; retain the original NGX flags and MV scale.
|
||||
Display-resolution velocity still needs the jittered depth-grid mapping. */
|
||||
static inline int d4r_motion_dilation_geometry(D4rMotionDilationParams* p, uint32_t flags,
|
||||
uint32_t output_width, uint32_t output_height)
|
||||
{
|
||||
if ((flags & 4u) != 0 || p->render_width == 0 || p->render_height == 0)
|
||||
return 0; /* Jittered motion values remain unsupported. */
|
||||
if ((flags & 2u) != 0)
|
||||
{
|
||||
if (p->motion_width < p->render_width || p->motion_height < p->render_height)
|
||||
return 0;
|
||||
p->motion_width = p->render_width;
|
||||
p->motion_height = p->render_height;
|
||||
p->jitter_x = p->jitter_y = 0;
|
||||
return 1;
|
||||
}
|
||||
return p->motion_width == output_width && p->motion_height == output_height;
|
||||
}
|
||||
#ifdef __cplusplus
|
||||
static_assert(sizeof(D4rMotionDilationParams) == 72, "motion dilation ABI");
|
||||
#else
|
||||
_Static_assert(sizeof(D4rMotionDilationParams) == 72, "motion dilation ABI");
|
||||
#endif
|
||||
@@ -5,18 +5,6 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static int d4r_native_swin_encoders_enabled(void)
|
||||
{
|
||||
const char* value = getenv("D4R_NATIVE_SWIN_ENCODERS");
|
||||
return value == NULL || strcmp(value, "0") != 0;
|
||||
}
|
||||
|
||||
static int d4r_native_kernel_allowed(const char* name)
|
||||
{
|
||||
return d4r_native_swin_encoders_enabled() ||
|
||||
(strcmp(name, "rrlite_enc1_4x4") != 0 && strcmp(name, "rrlite_enc2_4x4") != 0);
|
||||
}
|
||||
|
||||
static int d4r_apply_accuracy_policy(void)
|
||||
{
|
||||
const char* prefer = getenv("D4R_PREFER_ACCURACY");
|
||||
|
||||
+183
-332
@@ -38,7 +38,6 @@
|
||||
#include <windows.h>
|
||||
#include <d3d12.h>
|
||||
#include <vulkan/vulkan_core.h>
|
||||
#include "d4r_motion_dilation.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
@@ -58,9 +57,6 @@
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include "d4r_event_wait.h"
|
||||
#include "d4r_win32_wait.h"
|
||||
#include "d4r_vkd3d_interop.h"
|
||||
|
||||
// --- NGX types (declared locally: the core's exports differ from the SDK) --
|
||||
|
||||
@@ -225,7 +221,6 @@ struct CudaApi
|
||||
int(WINAPI* ctxSynchronize)() = nullptr;
|
||||
int(WINAPI* eventCreate)(CudaEvent*, unsigned int) = nullptr;
|
||||
int(WINAPI* eventRecord)(CudaEvent, void*) = nullptr;
|
||||
int(WINAPI* eventQuery)(CudaEvent) = nullptr;
|
||||
int(WINAPI* eventSynchronize)(CudaEvent) = nullptr;
|
||||
int(WINAPI* eventElapsedTime)(float*, CudaEvent, CudaEvent) = nullptr;
|
||||
int(WINAPI* eventDestroy)(CudaEvent) = nullptr;
|
||||
@@ -246,7 +241,6 @@ struct CudaApi
|
||||
int(WINAPI* outputKernelNative)() = nullptr;
|
||||
// Optional (d4r nvcuda bridge): report a pitch-linear texture to NGX as an array of that size/format.
|
||||
int(WINAPI* registerLinearTexture)(CudaObject, size_t, size_t, uint32_t, uint32_t) = nullptr;
|
||||
int(WINAPI* dilateMotion)(const D4rMotionDilationParams*) = nullptr;
|
||||
};
|
||||
|
||||
// --- official NGX core CUDA API --------------------------------------------
|
||||
@@ -467,7 +461,7 @@ static std::string preset_number(const std::string& model)
|
||||
for (const auto& [letter, number] : letters)
|
||||
if (name == letter)
|
||||
return std::to_string(number);
|
||||
g_portable.notes.push_back("d4r.ini: [DLSS] Model '" + model + "' is not a preset (use K, E, L, M or a number); "
|
||||
g_portable.notes.push_back("d4r.ini: [DLSS] Model '" + model + "' is not a preset (use K, E, M or a number); "
|
||||
"leaving the preset to the game");
|
||||
return std::string();
|
||||
}
|
||||
@@ -537,7 +531,6 @@ static void load_portable_config()
|
||||
const std::string native = ascii_lower(ini_value(ini, "kernels", "NativeKernels"));
|
||||
// The bridge selects accuracy variants and enforces their compiler/sync policy before cuInit.
|
||||
portable_set_unix("D4R_PREFER_ACCURACY", ini_flag(ini, "kernels", "PreferAccuracy", 0) ? "1" : "0");
|
||||
portable_set_unix("D4R_NATIVE_SWIN_ENCODERS", ini_flag(ini, "kernels", "NativeSwinEncoders", 1) ? "1" : "0");
|
||||
const bool nativeOn = native.empty() || native == "on" || native == "true" || native == "1" || native == "fast";
|
||||
if (!nativeOn && native != "off" && native != "false" && native != "0")
|
||||
g_portable.notes.push_back("d4r.ini: [Kernels] NativeKernels must be on or off, not '" + native + "'");
|
||||
@@ -557,7 +550,7 @@ static void load_portable_config()
|
||||
const char* variable;
|
||||
int fallback;
|
||||
} interop[] = {{"VramInterop", "D4R_SHIM_VRAM_INTEROP", 1}, {"InputSync", "D4R_SHIM_INPUT_SYNC", 1},
|
||||
{"LinearInputs", "D4R_SHIM_LINEAR_INPUTS", 1},
|
||||
{"GpuWait", "D4R_SHIM_GPU_WAIT", 1}, {"LinearInputs", "D4R_SHIM_LINEAR_INPUTS", 1},
|
||||
{"EvalSync", "D4R_SHIM_EVAL_SYNC", 0}};
|
||||
for (const auto& setting : interop)
|
||||
portable_set(setting.variable, ini_flag(ini, "interop", setting.key, setting.fallback) ? "1" : "0");
|
||||
@@ -569,11 +562,6 @@ static void load_portable_config()
|
||||
portable_set("D4R_SHIM_OUTPUT_DIRECT", ini_flag(ini, "interop", "DirectOutput", nativeOn) ? "1" : "0");
|
||||
|
||||
portable_set("D4R_SHIM_WATERMARK", ini_flag(ini, "dlss", "ShowWatermark", 0) ? "1" : "0");
|
||||
const std::string dilation = ini_value(ini, "interop", "MotionVectorDilation");
|
||||
if (dilation.empty() || dilation == "0" || dilation == "1" || dilation == "2")
|
||||
portable_set("D4R_MOTION_DILATION", dilation.empty() ? "0" : dilation);
|
||||
else
|
||||
g_portable.notes.push_back("MotionVectorDilation must be 0, 1 or 2; using original motion vectors");
|
||||
if (ini_flag(ini, "debug", "Profile", 0))
|
||||
portable_set("D4R_PROFILE", "1");
|
||||
if (const std::string level = ini_value(ini, "debug", "NgxLogLevel"); !level.empty())
|
||||
@@ -1133,9 +1121,6 @@ struct Global
|
||||
Worker worker; // every NGX-core and CUDA call
|
||||
std::thread::id workerThread;
|
||||
Worker prep, finish; // CPU-only pipeline stages around it
|
||||
Worker cleanup; // deferred GPU resource destruction, never under allocator locks
|
||||
std::atomic<unsigned int> resourceOwners{0};
|
||||
bool shutdownPending = false; // under mutex
|
||||
HMODULE core = nullptr;
|
||||
CoreApi ngx;
|
||||
HMODULE cuda = nullptr;
|
||||
@@ -1150,7 +1135,6 @@ struct Global
|
||||
};
|
||||
|
||||
static Global g;
|
||||
static std::mutex g_featureApiMutex;
|
||||
|
||||
template <typename Function> static bool load_export(HMODULE module, const char* name, Function& function)
|
||||
{
|
||||
@@ -1321,20 +1305,17 @@ static bool load_libraries()
|
||||
g.cu.outputKernelNative = nullptr;
|
||||
if (!load_export(g.cuda, "d4rRegisterLinearTexture", g.cu.registerLinearTexture))
|
||||
g.cu.registerLinearTexture = nullptr;
|
||||
g.cu.dilateMotion = reinterpret_cast<decltype(g.cu.dilateMotion)>(
|
||||
reinterpret_cast<void*>(GetProcAddress(g.cuda, "d4rDilateMotionVectors")));
|
||||
if (profile_enabled() || env_uint("D4R_SHIM_BLOCKING_SYNC", 1) != 0)
|
||||
if (profile_enabled() || env_uint("D4R_SHIM_BLOCKING_SYNC", 0) != 0)
|
||||
{
|
||||
const bool create = load_export(g.cuda, "cuEventCreate", g.cu.eventCreate);
|
||||
const bool record = load_export(g.cuda, "cuEventRecord", g.cu.eventRecord);
|
||||
const bool wait = env_uint("D4R_SHIM_BLOCKING_SYNC", 1) != 0
|
||||
const bool wait = env_uint("D4R_SHIM_BLOCKING_SYNC", 0) != 0
|
||||
? load_export(g.cuda, "d4rEventSynchronize", g.cu.eventSynchronize) : true;
|
||||
const bool query = load_export(g.cuda, "cuEventQuery", g.cu.eventQuery);
|
||||
const bool destroy = load_export(g.cuda, "cuEventDestroy", g.cu.eventDestroy);
|
||||
const bool elapsed = profile_enabled() ? load_export(g.cuda, "cuEventElapsedTime", g.cu.eventElapsedTime) : true;
|
||||
if (profile_enabled() && !(create && record && destroy && elapsed))
|
||||
logf("D4R_PROFILE: CUDA event timing unavailable; CPU stage timings remain enabled");
|
||||
if (env_uint("D4R_SHIM_BLOCKING_SYNC", 1) != 0 && !(create && record && wait && query && destroy))
|
||||
if (env_uint("D4R_SHIM_BLOCKING_SYNC", 0) != 0 && !(create && record && wait && destroy))
|
||||
logf("blocking output wait unavailable; using context sync");
|
||||
}
|
||||
logf("loaded NGX core %ls and nvcuda.dll: %s", corePath, ok ? "all exports present" : "exports missing");
|
||||
@@ -1355,22 +1336,14 @@ static NgxResult initialize(unsigned long long applicationId, const wchar_t* dat
|
||||
const ProjectIdentity* project = nullptr)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g.mutex);
|
||||
g.shutdownPending = false;
|
||||
if (g.ngxInitialized)
|
||||
return NGX_SUCCESS;
|
||||
if (!g.started)
|
||||
{
|
||||
// Allocators can retain COM callbacks and detached workers past NGX
|
||||
// shutdown. Keep their code loaded until process exit.
|
||||
HMODULE pinned = nullptr;
|
||||
if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_PIN,
|
||||
reinterpret_cast<LPCWSTR>(g_selfModule), &pinned))
|
||||
return NGX_FAIL_PLATFORM_ERROR;
|
||||
g.worker.start();
|
||||
g.workerThread = g.worker.call([] { return std::this_thread::get_id(); });
|
||||
g.prep.start();
|
||||
g.finish.start();
|
||||
g.cleanup.start();
|
||||
g.started = true;
|
||||
}
|
||||
ensure_portable_config();
|
||||
@@ -1490,24 +1463,10 @@ struct Staging
|
||||
UINT width = 0;
|
||||
UINT height = 0;
|
||||
|
||||
std::vector<ID3D12Resource*> retired;
|
||||
|
||||
void retire()
|
||||
{
|
||||
if (buffer != nullptr)
|
||||
retired.push_back(buffer);
|
||||
buffer = nullptr;
|
||||
mapped = nullptr;
|
||||
width = height = 0;
|
||||
format = DXGI_FORMAT_UNKNOWN;
|
||||
}
|
||||
|
||||
void release()
|
||||
{
|
||||
if (buffer != nullptr)
|
||||
buffer->Release();
|
||||
for (ID3D12Resource* old : retired)
|
||||
old->Release();
|
||||
*this = Staging{};
|
||||
}
|
||||
};
|
||||
@@ -1531,7 +1490,6 @@ struct FrameParams
|
||||
bool hasExposure = false;
|
||||
bool vram = false; // inputs and output stay in VRAM (VRAM interop)
|
||||
bool split = false; // this frame presents its own result (split frames)
|
||||
unsigned motionDilation = 0; // actually applied, worker only
|
||||
};
|
||||
|
||||
struct FrameTiming
|
||||
@@ -1543,7 +1501,6 @@ struct FrameTiming
|
||||
double download = 0, outputConvert = 0;
|
||||
double prep = 0, worker = 0, workerWait = 0, finishWait = 0, markerWait = 0;
|
||||
double ngxHost = 0, ctxSync = 0, gpuEval = -1;
|
||||
double motionDilationWall = 0;
|
||||
bool gpuEventsRecorded = false;
|
||||
double outputSyncWall = 0, outputSyncCpu = -1;
|
||||
bool outputSyncBlocking = false;
|
||||
@@ -1576,11 +1533,6 @@ struct InputSlot
|
||||
CudaDevicePtr linearPointer[4] = {};
|
||||
UINT linearWidth[4] = {}, linearHeight[4] = {};
|
||||
size_t linearPitch[4] = {};
|
||||
CudaDevicePtr dilatedMotion = 0;
|
||||
size_t dilatedBytes = 0;
|
||||
CudaObject dilatedTexture = 0;
|
||||
UINT dilatedWidth = 0, dilatedHeight = 0;
|
||||
size_t dilatedPitch = 0;
|
||||
std::atomic<bool> busy{false}; // readback staging owned by the pipeline
|
||||
HostPlane host[4];
|
||||
std::atomic<bool> hostBusy{false}; // host planes not yet uploaded by the worker
|
||||
@@ -1597,8 +1549,6 @@ struct OutputSlot
|
||||
struct Feature
|
||||
{
|
||||
NgxHandle handle = {}; // returned to the caller
|
||||
std::atomic<bool> retiring{false};
|
||||
IUnknown* resources = nullptr; // main reference plus command-allocator references
|
||||
NgxHandle* cudaHandle = nullptr;
|
||||
void* cudaParams = nullptr;
|
||||
CudaDevicePtr scratch = 0;
|
||||
@@ -1636,20 +1586,26 @@ struct Feature
|
||||
VramImage outputConversion;
|
||||
// Split frames: the rest of frame N's command list is submitted only once
|
||||
// splitSemaphore reaches N (signalled when N retires, or by the watchdog).
|
||||
std::atomic<bool> split{false};
|
||||
bool split = false;
|
||||
VkSemaphore splitSemaphore = VK_NULL_HANDLE;
|
||||
std::mutex splitMutex;
|
||||
uint64_t splitSignalled = 0; // under splitMutex
|
||||
// Inputs become ready on the CPU before CUDA work is queued. GPU-side waits
|
||||
// for recorded-but-discarded command lists can otherwise wedge the device.
|
||||
// GPU-side start (D4R_SHIM_GPU_WAIT, split frames): the input copies end with a vkCmdFillBuffer of the
|
||||
// frame number into gpuMarker, and the null stream waits for it on the GPU instead of the CPU polling
|
||||
// the frame marker before any DLSS work is queued.
|
||||
bool gpuWait = false;
|
||||
VramBuffer gpuMarker;
|
||||
// Frame the null stream currently waits for (0: none) and since when; the watchdog releases a wait for a
|
||||
// frame whose command list never reaches the GPU (e.g. recorded but not executed), so DLSS cannot stall.
|
||||
std::atomic<uint32_t> gpuWaitFrame{0};
|
||||
std::atomic<int64_t> gpuWaitSince{0};
|
||||
// D4R_SHIM_OUTPUT_DIRECT: this frame's result is stored by the native output kernel straight into its
|
||||
// destination buffer (worker only), so the array -> buffer copy is skipped.
|
||||
bool outputRedirected = false;
|
||||
bool outputDirectAllowed = false; // the forced preset's output kernel honours the redirect
|
||||
// D4R_SHIM_LINEAR_INPUTS: NGX samples the interop buffers directly through pitch-linear
|
||||
// D4R_SHIM_LINEAR_INPUTS (with gpuWait): NGX samples the interop buffers directly through pitch-linear
|
||||
// texture objects (rows padded to 256 bytes), so the buffer -> array copies disappear.
|
||||
bool linearInputs = false;
|
||||
bool dilationReported = false;
|
||||
std::vector<VramBuffer> retiredBuffers; // may still be read by queued command lists
|
||||
std::vector<VramImage> retiredImages;
|
||||
ID3D12Resource* marker = nullptr;
|
||||
@@ -1710,7 +1666,7 @@ static bool ensure_staging(Staging& staging, ID3D12Resource* resource, D3D12_HEA
|
||||
if (staging.buffer != nullptr && staging.format == desc.Format && staging.width == desc.Width &&
|
||||
staging.height == desc.Height)
|
||||
return true;
|
||||
staging.retire();
|
||||
staging.release();
|
||||
UINT64 rowSize = 0;
|
||||
g.device->GetCopyableFootprints(&desc, 0, 1, 0, &staging.layout, &staging.rows, &rowSize, &staging.total);
|
||||
if (!create_buffer(heapType, staging.total, &staging.buffer, &staging.mapped))
|
||||
@@ -2046,12 +2002,59 @@ static uint32_t plane_channels(Plane plane)
|
||||
// worker then only moves data between them and the CUDA arrays on the GPU.
|
||||
// Frames whose formats do not qualify use the readback/upload path.
|
||||
|
||||
MIDL_INTERFACE("39da4e09-bd1c-4198-9fae-86bbe3be41fd")
|
||||
ID3D12DXVKInteropDevice : public IUnknown
|
||||
{
|
||||
virtual HRESULT STDMETHODCALLTYPE GetDXGIAdapter(REFIID iid, void** object) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetInstanceExtensions(UINT* count, const char** extensions) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetDeviceExtensions(UINT* count, const char** extensions) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetDeviceFeatures(const VkPhysicalDeviceFeatures2** features) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetVulkanHandles(VkInstance* instance, VkPhysicalDevice* physical,
|
||||
VkDevice* device) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetVulkanQueueInfo(ID3D12CommandQueue* queue, VkQueue* vkQueue,
|
||||
UINT32* family) = 0;
|
||||
virtual void STDMETHODCALLTYPE GetVulkanImageLayout(ID3D12Resource* resource, D3D12_RESOURCE_STATES state,
|
||||
VkImageLayout* layout) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetVulkanResourceInfo(ID3D12Resource* resource, UINT64* handle,
|
||||
UINT64* offset) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE LockCommandQueue(ID3D12CommandQueue* queue) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE UnlockCommandQueue(ID3D12CommandQueue* queue) = 0;
|
||||
};
|
||||
|
||||
MIDL_INTERFACE("902d8115-59eb-4406-9518-fe00f991ee65")
|
||||
ID3D12DXVKInteropDevice1 : public ID3D12DXVKInteropDevice
|
||||
{
|
||||
virtual HRESULT STDMETHODCALLTYPE GetVulkanResourceInfo1(ID3D12Resource* resource, UINT64* handle, UINT64* offset,
|
||||
VkFormat* format) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE CreateInteropCommandQueue(const D3D12_COMMAND_QUEUE_DESC* desc, UINT32 family,
|
||||
ID3D12CommandQueue** queue) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE CreateInteropCommandAllocator(D3D12_COMMAND_LIST_TYPE type, UINT32 family,
|
||||
ID3D12CommandAllocator** allocator) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE BeginVkCommandBufferInterop(ID3D12CommandList* list, VkCommandBuffer* buffer) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE EndVkCommandBufferInterop(ID3D12CommandList* list) = 0;
|
||||
};
|
||||
__CRT_UUID_DECL(ID3D12DXVKInteropDevice1, 0x902d8115, 0x59eb, 0x4406, 0x95, 0x18, 0xfe, 0x00, 0xf9, 0x91, 0xee, 0x65)
|
||||
|
||||
// Added by the d4r vkd3d-proton patch (patches/vkd3d-proton/): ends the list's
|
||||
// current Vulkan command buffer; what is recorded afterwards is submitted
|
||||
// separately, with a queue-level wait for the timeline semaphore value.
|
||||
MIDL_INTERFACE("5a7c8b3e-2f61-4d0e-9c1a-7e3b52d4a901")
|
||||
ID3D12DXVKInteropDeviceD4R : public ID3D12DXVKInteropDevice1
|
||||
{
|
||||
virtual HRESULT STDMETHODCALLTYPE LockVulkanQueue(ID3D12CommandQueue* queue) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE UnlockVulkanQueue(ID3D12CommandQueue* queue) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetVulkanHeapInfo(ID3D12Heap* heap, UINT64* memory, UINT64* offset,
|
||||
UINT32* type) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE SplitCommandListForExternalWait(ID3D12CommandList* list, UINT64 semaphore,
|
||||
UINT64 value) = 0;
|
||||
};
|
||||
__CRT_UUID_DECL(ID3D12DXVKInteropDeviceD4R, 0x5a7c8b3e, 0x2f61, 0x4d0e, 0x9c, 0x1a, 0x7e, 0x3b, 0x52, 0xd4, 0xa9, 0x01)
|
||||
|
||||
struct VulkanInterop
|
||||
{
|
||||
std::once_flag once;
|
||||
bool ready = false;
|
||||
ID3D12DXVKInteropDevice1* interop = nullptr;
|
||||
ID3D12DXVKInteropDeviceD4R2* lifetime = nullptr;
|
||||
ID3D12DXVKInteropDeviceD4R* split = nullptr; // only with the d4r vkd3d-proton patch
|
||||
VkDevice device = VK_NULL_HANDLE;
|
||||
VkPhysicalDeviceMemoryProperties memory = {};
|
||||
@@ -2156,8 +2159,6 @@ static void init_vram_interop()
|
||||
load(g_vk.signalSemaphore, "vkSignalSemaphore");
|
||||
if (FAILED(g.device->QueryInterface(__uuidof(ID3D12DXVKInteropDeviceD4R), reinterpret_cast<void**>(&g_vk.split))))
|
||||
g_vk.split = nullptr;
|
||||
if (FAILED(g.device->QueryInterface(__uuidof(ID3D12DXVKInteropDeviceD4R2), reinterpret_cast<void**>(&g_vk.lifetime))))
|
||||
g_vk.lifetime = nullptr;
|
||||
g_vk.ready = ok;
|
||||
logf("VRAM interop: %s (VkDevice %p), split frames %s", ok ? "ready" : "missing Vulkan entry points",
|
||||
static_cast<void*>(g_vk.device), g_vk.split != nullptr ? "available" : "unavailable (stock vkd3d-proton)");
|
||||
@@ -2491,7 +2492,7 @@ static VkBufferImageCopy full_region(const VramCopy& copy)
|
||||
// Records the input copies into the slot's buffers. The resources are in
|
||||
// COPY_SOURCE state already.
|
||||
static bool record_vram_inputs(Feature& feature, ID3D12GraphicsCommandList* list, InputSlot& slot,
|
||||
const VramCopy* copies, int count)
|
||||
const VramCopy* copies, int count, uint32_t frame)
|
||||
{
|
||||
VkCommandBuffer cmd = VK_NULL_HANDLE;
|
||||
if (FAILED(g_vk.interop->BeginVkCommandBufferInterop(list, &cmd)))
|
||||
@@ -2547,6 +2548,9 @@ static bool record_vram_inputs(Feature& feature, ID3D12GraphicsCommandList* list
|
||||
}
|
||||
g_vk.barrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &kAfterTransfer, 0,
|
||||
nullptr, 0, nullptr);
|
||||
// After the barrier: the copies are complete and their writes available when the value lands.
|
||||
if (feature.gpuWait)
|
||||
g_vk.fill(cmd, feature.gpuMarker.buffer, 0, 4, frame);
|
||||
return SUCCEEDED(g_vk.interop->EndVkCommandBufferInterop(list));
|
||||
}
|
||||
|
||||
@@ -2619,8 +2623,7 @@ static void signal_split(Feature* feature, uint64_t value, const char* reason)
|
||||
info.semaphore = feature->splitSemaphore;
|
||||
info.value = value;
|
||||
const VkResult result = g_vk.signalSemaphore(g_vk.device, &info);
|
||||
if (result == VK_SUCCESS)
|
||||
feature->splitSignalled = value;
|
||||
feature->splitSignalled = value;
|
||||
if (result != VK_SUCCESS || reason != nullptr)
|
||||
logf("split frame %llu released%s%s (VkResult %d)", static_cast<unsigned long long>(value),
|
||||
reason != nullptr ? " by " : "", reason != nullptr ? reason : "", result);
|
||||
@@ -2658,17 +2661,22 @@ static void split_watchdog()
|
||||
std::lock_guard<std::mutex> splitLock(feature->splitMutex);
|
||||
signalled = feature->splitSignalled;
|
||||
}
|
||||
// GPU-side input wait for a frame the game's queue has not reached for over a second
|
||||
const uint32_t waiting = feature->gpuWaitFrame.load();
|
||||
if (feature->gpuWait && waiting != 0 && static_cast<int32_t>(marker - waiting) < 0 &&
|
||||
now.time_since_epoch().count() - feature->gpuWaitSince.load() >
|
||||
std::chrono::duration_cast<std::chrono::steady_clock::duration>(std::chrono::seconds(1)).count() &&
|
||||
[&] { uint32_t expected = waiting; return feature->gpuWaitFrame.compare_exchange_strong(expected, 0u); }())
|
||||
{
|
||||
logf("frame %u: its inputs never reached the GPU (marker %u); releasing the GPU-side wait", waiting, marker);
|
||||
g.cu.writeValue32(feature->gpuMarker.device, waiting);
|
||||
}
|
||||
if (marker <= signalled)
|
||||
continue; // nothing on the GPU is waiting on us
|
||||
if (it->second.marker != marker)
|
||||
it->second = Seen{marker, now};
|
||||
else if (now - it->second.at > timeout)
|
||||
{
|
||||
// A slow producer is not a completed producer. Releasing this
|
||||
// wait would race the game's output copy with a late CUDA write.
|
||||
logf("split frame %u still waiting for DLSS; preserving output synchronization", marker);
|
||||
it->second.at = now;
|
||||
}
|
||||
signal_split(feature, marker, "the watchdog");
|
||||
}
|
||||
// Forget released features.
|
||||
seen.erase(std::remove_if(seen.begin(), seen.end(),
|
||||
@@ -2727,6 +2735,7 @@ static void release_vram(Feature& feature)
|
||||
recycle_vram_buffer(buffer);
|
||||
for (OutputSlot& slot : feature.outputs)
|
||||
recycle_vram_buffer(slot.vram);
|
||||
recycle_vram_buffer(feature.gpuMarker);
|
||||
for (VramBuffer& buffer : feature.retiredBuffers)
|
||||
recycle_vram_buffer(buffer);
|
||||
feature.retiredBuffers.clear();
|
||||
@@ -3297,7 +3306,7 @@ static bool wait_for_release(const std::atomic<bool>& flag)
|
||||
{
|
||||
if (std::chrono::steady_clock::now() - start > std::chrono::seconds(2))
|
||||
return false;
|
||||
d4r_sleep_us(100);
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(100));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -3507,91 +3516,6 @@ static bool ensure_linear_inputs(InputSlot& slot, bool hasExposure)
|
||||
return true;
|
||||
}
|
||||
|
||||
// VRAM interop, worker thread: moves the planes from the imported buffers
|
||||
// Opt-in thin-feature motion coverage. Keep the original imported velocity buffer
|
||||
// immutable; the new texture is owned by this input slot until its GPU work retires.
|
||||
static CudaObject prepare_dilated_motion(Feature& feature, InputSlot& slot, const FrameParams& p)
|
||||
{
|
||||
const unsigned radius = env_uint("D4R_MOTION_DILATION", 0);
|
||||
if (radius == 0)
|
||||
return 0;
|
||||
const auto& depth = slot.host[1];
|
||||
const auto& motion = slot.host[2];
|
||||
D4rMotionDilationParams args = {slot.vram[1].device, slot.vram[2].device, slot.dilatedMotion,
|
||||
depth.rowBytes, motion.rowBytes, p.renderWidth, p.renderHeight, motion.width, motion.height,
|
||||
p.jitterX, p.jitterY, radius, (feature.flags & 8u) != 0};
|
||||
const bool motionGeometry = d4r_motion_dilation_geometry(&args, feature.flags,
|
||||
feature.outWidth, feature.outHeight);
|
||||
const bool supported = radius <= 2 && p.vram && feature.linearInputs && g.cu.dilateMotion != nullptr &&
|
||||
motionGeometry && p.colorBaseX == 0 && p.colorBaseY == 0 && p.depthBaseX == 0 &&
|
||||
p.depthBaseY == 0 && p.mvBaseX == 0 && p.mvBaseY == 0 && p.outputBaseX == 0 && p.outputBaseY == 0 &&
|
||||
p.renderWidth > 0 && p.renderHeight > 0 && std::isfinite(p.jitterX) && std::isfinite(p.jitterY) &&
|
||||
p.renderWidth <= depth.width && p.renderHeight <= depth.height;
|
||||
if (!supported)
|
||||
{
|
||||
if (!feature.dilationReported)
|
||||
logf("motion-vector dilation unavailable for this feature; original motion vectors retained");
|
||||
feature.dilationReported = true;
|
||||
return 0;
|
||||
}
|
||||
const size_t bytes = motion.size();
|
||||
const bool recreate = slot.dilatedBytes < bytes || slot.dilatedWidth != motion.width ||
|
||||
slot.dilatedHeight != motion.height || slot.dilatedPitch != motion.rowBytes;
|
||||
if (recreate)
|
||||
{
|
||||
if (slot.dilatedTexture != 0)
|
||||
g.cu.texObjectDestroy(slot.dilatedTexture), slot.dilatedTexture = 0;
|
||||
if (slot.dilatedMotion != 0)
|
||||
g.cu.memFree(slot.dilatedMotion), slot.dilatedMotion = 0;
|
||||
slot.dilatedBytes = 0;
|
||||
if (g.cu.memAlloc(&slot.dilatedMotion, bytes) != 0)
|
||||
return 0;
|
||||
slot.dilatedBytes = bytes;
|
||||
struct Pitch2D { CudaDevicePtr pointer; uint32_t format, channels; size_t width, height, pitch; };
|
||||
const Pitch2D pitch = {slot.dilatedMotion, plane_format(Plane::Motion), 2, motion.width, motion.height, motion.rowBytes};
|
||||
CudaResourceDesc resource = {};
|
||||
resource.resType = 3;
|
||||
static_assert(sizeof(pitch) == 40);
|
||||
std::memcpy(&resource.res, &pitch, sizeof(pitch));
|
||||
const CudaTextureDesc sampler = input_sampler(motion.width, 2, false);
|
||||
int result = g.cu.texObjectCreate(&slot.dilatedTexture, &resource, &sampler, nullptr);
|
||||
if (result == 0)
|
||||
result = g.cu.registerLinearTexture(slot.dilatedTexture, motion.width, motion.height, pitch.format, 2);
|
||||
if (result != 0)
|
||||
{
|
||||
if (slot.dilatedTexture != 0)
|
||||
g.cu.texObjectDestroy(slot.dilatedTexture), slot.dilatedTexture = 0;
|
||||
return 0;
|
||||
}
|
||||
slot.dilatedWidth = motion.width, slot.dilatedHeight = motion.height, slot.dilatedPitch = motion.rowBytes;
|
||||
}
|
||||
if (slot.dilatedTexture == 0)
|
||||
return 0;
|
||||
args.output = slot.dilatedMotion;
|
||||
// Preserve allocation padding, which DLSS may sample at the active edge.
|
||||
// The dilation kernel only writes the active render grid for low-res MVs.
|
||||
if (args.motion_width != motion.width || args.motion_height != motion.height)
|
||||
{
|
||||
CudaMemcpy2D copy = {};
|
||||
copy.srcMemoryType = copy.dstMemoryType = CUDA_MEMORY_DEVICE;
|
||||
copy.srcDevice = args.motion;
|
||||
copy.dstDevice = args.output;
|
||||
copy.srcPitch = copy.dstPitch = motion.rowBytes;
|
||||
copy.WidthInBytes = motion.rowBytes;
|
||||
copy.Height = motion.height;
|
||||
if (copy_2d(copy) != 0)
|
||||
return 0;
|
||||
}
|
||||
const int result = g.cu.dilateMotion(&args);
|
||||
if (!feature.dilationReported)
|
||||
logf("motion-vector dilation %s: radius=%u render pixels, GPU status=%d, %s active=%ux%u allocation=%ux%u",
|
||||
result == 0 ? "enabled" : "unavailable", radius, result,
|
||||
(feature.flags & 2u) != 0 ? "low-resolution" : "display-resolution",
|
||||
args.motion_width, args.motion_height, motion.width, motion.height);
|
||||
feature.dilationReported = true;
|
||||
return result == 0 ? slot.dilatedTexture : 0;
|
||||
}
|
||||
|
||||
// VRAM interop, worker thread: moves the planes from the imported buffers
|
||||
// into the CUDA arrays on the GPU. With D4R_SHIM_VRAM_VERIFY also compares
|
||||
// them with the host-staged planes of the same frame.
|
||||
@@ -3684,32 +3608,6 @@ static bool upload_inputs_vram(Feature& feature, InputSlot& slot, bool hasExposu
|
||||
return result == 0;
|
||||
}
|
||||
|
||||
// Only called on the CUDA worker. A completion event covers queued work on
|
||||
// the default stream; do not rely on HIP's blocking flag to release the CPU.
|
||||
static int synchronize_default_stream(Feature& feature, bool* querySleep = nullptr)
|
||||
{
|
||||
if (feature.outputReadyEvent != nullptr)
|
||||
{
|
||||
const int recorded = g.cu.eventRecord(feature.outputReadyEvent, nullptr);
|
||||
if (recorded == 0)
|
||||
{
|
||||
const int result = d4r_wait_event(
|
||||
[&] { return g.cu.eventQuery(feature.outputReadyEvent); },
|
||||
[] { d4r_sleep_us(200); });
|
||||
if (result == 0)
|
||||
{
|
||||
if (querySleep != nullptr) *querySleep = true;
|
||||
// The event is already complete; this also flushes bridge kernel profiles.
|
||||
return g.cu.eventSynchronize(feature.outputReadyEvent);
|
||||
}
|
||||
logf("output event query failed (%d); using context synchronization", result);
|
||||
}
|
||||
else
|
||||
logf("output event record failed (%d); using context synchronization", recorded);
|
||||
}
|
||||
return g.cu.ctxSynchronize();
|
||||
}
|
||||
|
||||
// VRAM interop, worker thread: copies the finished result into a free output
|
||||
// slot's imported buffer and publishes it; evaluate() copies it on to the game.
|
||||
static void publish_vram(Feature* feature, uint32_t frame, const FrameParams& params, FrameTiming& timing,
|
||||
@@ -3740,7 +3638,23 @@ static void publish_vram(Feature* feature, uint32_t frame, const FrameParams& pa
|
||||
{
|
||||
const auto syncStart = timing.enabled ? ProfileClock::now() : ProfileClock::time_point{};
|
||||
const double cpuStart = timing.enabled ? profile_thread_cpu_ms() : -1.0;
|
||||
result = synchronize_default_stream(*feature, &timing.outputSyncBlocking);
|
||||
if (feature->outputReadyEvent != nullptr)
|
||||
{
|
||||
const int recordResult = g.cu.eventRecord(feature->outputReadyEvent, nullptr);
|
||||
const int waitResult = recordResult == 0 ? g.cu.eventSynchronize(feature->outputReadyEvent) : recordResult;
|
||||
if (waitResult == 0)
|
||||
timing.outputSyncBlocking = true;
|
||||
else
|
||||
{
|
||||
logf("frame %u: blocking output wait failed (record %d, wait %d); using context sync", frame,
|
||||
recordResult, waitResult);
|
||||
result = g.cu.ctxSynchronize();
|
||||
g.cu.eventDestroy(feature->outputReadyEvent);
|
||||
feature->outputReadyEvent = nullptr;
|
||||
}
|
||||
}
|
||||
else
|
||||
result = g.cu.ctxSynchronize();
|
||||
if (timing.enabled)
|
||||
{
|
||||
timing.outputSyncWall = profile_ms(syncStart, ProfileClock::now());
|
||||
@@ -3832,9 +3746,22 @@ static void run_evaluation(Feature* feature, int slotIndex, uint32_t frame, Fram
|
||||
timing.workerWait = profile_ms(timing.workerQueued, start);
|
||||
FrameTiming* stages = timing.enabled ? &timing : nullptr;
|
||||
const bool overlapped = async_copies() && copy_streams() != nullptr;
|
||||
if (params.vram && env_uint("D4R_SHIM_VRAM_VERIFY", 0) == 0)
|
||||
const bool gpuWait = params.vram && feature->gpuWait;
|
||||
if (params.vram && !gpuWait && env_uint("D4R_SHIM_VRAM_VERIFY", 0) == 0)
|
||||
dump_vram_inputs(slot, frame, params, params.hasExposure ? 4 : 3);
|
||||
const bool linearInputs = params.vram && feature->linearInputs;
|
||||
if (gpuWait)
|
||||
{
|
||||
feature->gpuWaitSince = std::chrono::steady_clock::now().time_since_epoch().count();
|
||||
feature->gpuWaitFrame = frame;
|
||||
}
|
||||
if (gpuWait && g.cu.streamWaitValue32(feature->gpuMarker.device, frame) != 0)
|
||||
{
|
||||
logf("frame %u: queueing the GPU-side input wait failed", frame);
|
||||
slot.busy = false;
|
||||
frame_retired(feature, frame);
|
||||
return;
|
||||
}
|
||||
const bool linearInputs = gpuWait && feature->linearInputs;
|
||||
bool ok = linearInputs ? ensure_linear_inputs(slot, params.hasExposure)
|
||||
: params.vram ? upload_inputs_vram(*feature, slot, params.hasExposure, frame, stages)
|
||||
: overlapped
|
||||
@@ -3846,30 +3773,22 @@ static void run_evaluation(Feature* feature, int slotIndex, uint32_t frame, Fram
|
||||
upload_plane(Plane::Exposure, slot.host[3], feature->exposure, frame, stages, 3));
|
||||
ok = ok && ensure_cuda_image(feature->output, feature->outputWidth, feature->outputHeight, CUDA_FORMAT_HALF, 4,
|
||||
true, false);
|
||||
const auto dilationStart = timing.enabled ? ProfileClock::now() : ProfileClock::time_point{};
|
||||
const CudaObject dilatedMotion = ok ? prepare_dilated_motion(*feature, slot, params) : 0;
|
||||
// Device-to-array copies (VRAM interop, staged uploads) return before they
|
||||
// finish, and NGX does not evaluate on the stream they were issued on, so
|
||||
// without this DLSS can sample a mix of this frame's and the previous
|
||||
// frame's colour/depth/motion: invisible when the camera is still, beaded
|
||||
// thin geometry in motion. D4R_SHIM_INPUT_SYNC=0 restores the old behaviour.
|
||||
static const bool inputSync = env_uint("D4R_SHIM_INPUT_SYNC", 1) != 0;
|
||||
// Linear inputs are already ready; other copies need completion before NGX samples them.
|
||||
if (ok && (dilatedMotion != 0 || (inputSync && !linearInputs)))
|
||||
// With gpuWait everything up to the output copy is queued in order on the null stream.
|
||||
if (ok && inputSync && !gpuWait)
|
||||
{
|
||||
const int syncResult = synchronize_default_stream(*feature);
|
||||
const int syncResult = g.cu.ctxSynchronize();
|
||||
if (syncResult != 0)
|
||||
{
|
||||
logf("frame %u: input copy synchronize failed: %d", frame, syncResult);
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
if (dilatedMotion != 0 && ok)
|
||||
{
|
||||
params.motionDilation = env_uint("D4R_MOTION_DILATION", 0);
|
||||
if (timing.enabled)
|
||||
timing.motionDilationWall = profile_ms(dilationStart, ProfileClock::now());
|
||||
}
|
||||
slot.hostBusy = false; // the host planes are no longer needed
|
||||
// The previous frame's download ran alongside these uploads.
|
||||
complete_pending_download();
|
||||
@@ -3890,7 +3809,7 @@ static void run_evaluation(Feature* feature, int slotIndex, uint32_t frame, Fram
|
||||
// the parameters point at these variables
|
||||
feature->colorHandle = slot.linearTexture[0];
|
||||
feature->depthHandle = slot.linearTexture[1];
|
||||
feature->motionHandle = dilatedMotion != 0 ? dilatedMotion : slot.linearTexture[2];
|
||||
feature->motionHandle = slot.linearTexture[2];
|
||||
feature->exposureHandle = params.hasExposure ? slot.linearTexture[3] : feature->exposureHandle;
|
||||
}
|
||||
static const bool outputDirect = env_uint("D4R_SHIM_OUTPUT_DIRECT", 0) != 0;
|
||||
@@ -3936,18 +3855,24 @@ static void run_evaluation(Feature* feature, int slotIndex, uint32_t frame, Fram
|
||||
// D4R_SHIM_EVAL_SYNC=0 (VRAM interop): no wait between NGX's kernels and the output copy queued
|
||||
// behind them on the same stream; publish_vram synchronises before the result is released.
|
||||
static const bool evalSync = env_uint("D4R_SHIM_EVAL_SYNC", 1) != 0;
|
||||
const int syncResult = params.vram && !evalSync ? 0 : synchronize_default_stream(*feature);
|
||||
const int syncResult = params.vram && !evalSync ? 0 : g.cu.ctxSynchronize();
|
||||
const auto evaluated = ProfileClock::now();
|
||||
const bool gpuEventsRecorded = eventStartResult == 0 && eventEndResult == 0;
|
||||
float gpuEvalMs = -1.0f;
|
||||
if (gpuEventsRecorded && syncResult == 0 && (!params.vram || evalSync) &&
|
||||
g.cu.eventElapsedTime(&gpuEvalMs, feature->profileStart, feature->profileEnd) != 0)
|
||||
gpuEvalMs = -1.0f;
|
||||
// Keep the input slot owned until output completion, including linear reads.
|
||||
if (params.vram && !gpuWait)
|
||||
slot.busy = false; // the input copies ran before the evaluation
|
||||
if (result != NGX_SUCCESS || syncResult != 0)
|
||||
{
|
||||
logf("frame %u: CUDA_EvaluateFeature -> 0x%08x, sync %d", frame, result, syncResult);
|
||||
slot.busy = false;
|
||||
if (gpuWait)
|
||||
{
|
||||
g.cu.ctxSynchronize();
|
||||
feature->gpuWaitFrame = 0;
|
||||
slot.busy = false;
|
||||
}
|
||||
frame_retired(feature, frame);
|
||||
return;
|
||||
}
|
||||
@@ -3961,7 +3886,11 @@ static void run_evaluation(Feature* feature, int slotIndex, uint32_t frame, Fram
|
||||
if (params.vram)
|
||||
{
|
||||
publish_vram(feature, frame, params, timing, start);
|
||||
slot.busy = false; // output synchronization completed all reads of this input slot
|
||||
if (gpuWait)
|
||||
{
|
||||
feature->gpuWaitFrame = 0;
|
||||
slot.busy = false; // publish_vram waited for the whole frame, input copies included
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4003,14 +3932,13 @@ static void prepare_inputs(Feature* feature, int slotIndex, uint32_t frame, Fram
|
||||
{
|
||||
InputSlot& slot = feature->inputs[slotIndex];
|
||||
const auto start = ProfileClock::now();
|
||||
// Wait for actual input readiness without queueing an unbounded GPU-side wait.
|
||||
while (static_cast<int32_t>(*feature->markerValue - frame) < 0)
|
||||
// Wait until the game's command list with this frame's input copies ran (with gpuWait the GPU waits).
|
||||
while (!(params.vram && feature->gpuWait) && static_cast<int32_t>(*feature->markerValue - frame) < 0)
|
||||
{
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
if (feature->retiring.load() || std::chrono::steady_clock::now() - start > std::chrono::seconds(5))
|
||||
if (std::chrono::steady_clock::now() - start > std::chrono::seconds(5))
|
||||
{
|
||||
logf("frame %u: %s waiting for GPU marker (at %u); dropping", frame,
|
||||
feature->retiring.load() ? "feature retired while" : "timed out", *feature->markerValue);
|
||||
logf("frame %u: timed out waiting for GPU marker (at %u); dropping", frame, *feature->markerValue);
|
||||
slot.busy = false;
|
||||
frame_retired(feature, frame);
|
||||
return;
|
||||
@@ -4020,7 +3948,7 @@ static void prepare_inputs(Feature* feature, int slotIndex, uint32_t frame, Fram
|
||||
if (markerPollUs == 0)
|
||||
std::this_thread::yield();
|
||||
else
|
||||
d4r_sleep_us(markerPollUs);
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(markerPollUs));
|
||||
}
|
||||
const auto ready = ProfileClock::now();
|
||||
// With VRAM interop the inputs are already on the GPU; host staging runs
|
||||
@@ -4085,7 +4013,7 @@ static int claim_output_slot(Feature* feature)
|
||||
}
|
||||
if (std::chrono::steady_clock::now() - start > std::chrono::milliseconds(50))
|
||||
return -1;
|
||||
d4r_sleep_us(200);
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(200));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4118,10 +4046,10 @@ static void finish_publish(Feature* feature, int target, uint32_t frame, const F
|
||||
"color_convert=%.3f depth_convert=%.3f motion_convert=%.3f exposure_convert=%.3f "
|
||||
"color_h2d=%.3f depth_h2d=%.3f motion_h2d=%.3f exposure_h2d=%.3f "
|
||||
"ngx_host=%.3f ctx_sync=%.3f gpu_eval=%.3f output_sync_wall=%.3f "
|
||||
"output_sync_cpu=%.3f output_sync_blocking=%d output_sync_method=%s d2h=%.3f output_convert=%.3f "
|
||||
"output_sync_cpu=%.3f output_sync_blocking=%d d2h=%.3f output_convert=%.3f "
|
||||
"h2d_total=%.3f d2h_issue=%.3f d2h_wait=%.3f "
|
||||
"prep=%.3f worker_wait=%.3f worker=%.3f finish_wait=%.3f finish=%.3f pipeline=%.3f "
|
||||
"throughput_interval=%.3f presented=%u age=%d motion_dilation=%u motion_dilation_wall=%.3f",
|
||||
"throughput_interval=%.3f presented=%u age=%d",
|
||||
frame, params.renderWidth, params.renderHeight, feature->outWidth, feature->outHeight,
|
||||
slot.host[0].width, slot.host[0].height, slot.host[1].width, slot.host[1].height,
|
||||
slot.host[2].width, slot.host[2].height, params.hasExposure, feature->quality, feature->flags,
|
||||
@@ -4132,13 +4060,11 @@ static void finish_publish(Feature* feature, int target, uint32_t frame, const F
|
||||
timing.convert[0], timing.convert[1], timing.convert[2], timing.convert[3],
|
||||
timing.upload[0], timing.upload[1], timing.upload[2], timing.upload[3],
|
||||
timing.ngxHost, timing.ctxSync, timing.gpuEval, timing.outputSyncWall, timing.outputSyncCpu,
|
||||
timing.outputSyncBlocking, timing.outputSyncBlocking ? "event_query_sleep" : "context",
|
||||
timing.download, timing.outputConvert,
|
||||
timing.outputSyncBlocking, timing.download, timing.outputConvert,
|
||||
timing.h2dTotal, timing.d2hIssue, timing.d2hWait,
|
||||
timing.prep, timing.workerWait, timing.worker, timing.finishWait, profile_ms(start, finished),
|
||||
profile_ms(timing.prepStart, finished), interval,
|
||||
timing.presentedFrame, timing.presentedFrame != 0 ? static_cast<int>(frame - timing.presentedFrame) : -1,
|
||||
params.motionDilation, timing.motionDilationWall);
|
||||
timing.presentedFrame, timing.presentedFrame != 0 ? static_cast<int>(frame - timing.presentedFrame) : -1);
|
||||
}
|
||||
if (completed <= 5 || completed % 120 == 0)
|
||||
logf("frame %u done (slot %d)", frame, target);
|
||||
@@ -4173,40 +4099,6 @@ static void drain_pipeline()
|
||||
g.finish.call([] { return 0; });
|
||||
}
|
||||
|
||||
static void destroy_recorded_resources(Feature* feature);
|
||||
static void finish_shutdown();
|
||||
|
||||
class DeferredResources final : public IUnknown
|
||||
{
|
||||
public:
|
||||
explicit DeferredResources(Feature* feature) : feature_(feature) { ++g.resourceOwners; }
|
||||
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void** out) override
|
||||
{
|
||||
if (out == nullptr) return E_POINTER;
|
||||
*out = nullptr;
|
||||
if (iid != __uuidof(IUnknown)) return E_NOINTERFACE;
|
||||
*out = static_cast<IUnknown*>(this);
|
||||
AddRef();
|
||||
return S_OK;
|
||||
}
|
||||
ULONG STDMETHODCALLTYPE AddRef() override { return ++refs_; }
|
||||
ULONG STDMETHODCALLTYPE Release() override
|
||||
{
|
||||
const ULONG remaining = --refs_;
|
||||
if (remaining == 0)
|
||||
g.cleanup.post([this] {
|
||||
destroy_recorded_resources(feature_);
|
||||
delete this;
|
||||
--g.resourceOwners;
|
||||
finish_shutdown();
|
||||
});
|
||||
return remaining;
|
||||
}
|
||||
private:
|
||||
std::atomic<ULONG> refs_{1};
|
||||
Feature* feature_;
|
||||
};
|
||||
|
||||
// --- exported NGX D3D12 API ------------------------------------------------------
|
||||
|
||||
#define D4R_EXPORT extern "C" __declspec(dllexport)
|
||||
@@ -4392,16 +4284,8 @@ static float get_float_or(void* parameters, const char* name, float fallback)
|
||||
D4R_EXPORT NgxResult NVSDK_NGX_D3D12_CreateFeature(ID3D12GraphicsCommandList*, unsigned int featureId, void* parameters,
|
||||
NgxHandle** handle)
|
||||
{
|
||||
std::lock_guard<std::mutex> apiLock(g_featureApiMutex);
|
||||
std::lock_guard<std::mutex> stateLock(g.mutex);
|
||||
if (!g.ngxInitialized)
|
||||
return NGX_FAIL_NOT_INITIALIZED;
|
||||
std::call_once(g_vk.once, init_vram_interop);
|
||||
if (g_vk.lifetime == nullptr)
|
||||
{
|
||||
logf("DLSS requires lifetime-aware d4r d3d12.dll/d3d12core.dll; update both files with the shim");
|
||||
return NGX_FAIL_PLATFORM_ERROR;
|
||||
}
|
||||
if (featureId != NGX_FEATURE_SUPER_SAMPLING || parameters == nullptr || handle == nullptr)
|
||||
{
|
||||
logf("NVSDK_NGX_D3D12_CreateFeature(feature=%u) unsupported", featureId);
|
||||
@@ -4471,12 +4355,12 @@ D4R_EXPORT NgxResult NVSDK_NGX_D3D12_CreateFeature(ID3D12GraphicsCommandList*, u
|
||||
else
|
||||
feature->scratch = 0;
|
||||
const NgxResult created = g.ngx.createFeature(NGX_FEATURE_SUPER_SAMPLING, p, &feature->cudaHandle);
|
||||
if (created == NGX_SUCCESS && env_uint("D4R_SHIM_BLOCKING_SYNC", 1) != 0 &&
|
||||
if (created == NGX_SUCCESS && env_uint("D4R_SHIM_BLOCKING_SYNC", 0) != 0 &&
|
||||
g.cu.eventCreate != nullptr && g.cu.eventRecord != nullptr &&
|
||||
g.cu.eventSynchronize != nullptr && g.cu.eventQuery != nullptr && g.cu.eventDestroy != nullptr)
|
||||
g.cu.eventSynchronize != nullptr && g.cu.eventDestroy != nullptr)
|
||||
{
|
||||
// Explicit query/sleep waits avoid HIP's spinning event-sync path.
|
||||
// Timing is unnecessary for this completion event.
|
||||
// All VRAM-path input waits, NGX kernels and output copies use the null stream.
|
||||
// A blocking event yields the CPU while the GPU finishes that stream.
|
||||
constexpr unsigned int kBlockingSyncNoTiming = 0x1u | 0x2u;
|
||||
const int eventResult = g.cu.eventCreate(&feature->outputReadyEvent, kBlockingSyncNoTiming);
|
||||
if (eventResult != 0)
|
||||
@@ -4485,7 +4369,7 @@ D4R_EXPORT NgxResult NVSDK_NGX_D3D12_CreateFeature(ID3D12GraphicsCommandList*, u
|
||||
feature->outputReadyEvent = nullptr;
|
||||
}
|
||||
else
|
||||
logf("output event query/sleep wait enabled for this feature");
|
||||
logf("blocking output wait enabled for this feature");
|
||||
}
|
||||
if (created == NGX_SUCCESS && profile_enabled() && g.cu.eventCreate != nullptr &&
|
||||
g.cu.eventRecord != nullptr && g.cu.eventElapsedTime != nullptr && g.cu.eventDestroy != nullptr)
|
||||
@@ -4518,7 +4402,6 @@ D4R_EXPORT NgxResult NVSDK_NGX_D3D12_CreateFeature(ID3D12GraphicsCommandList*, u
|
||||
return NGX_FAIL_PLATFORM_ERROR;
|
||||
}
|
||||
*feature->markerValue = 0;
|
||||
feature->resources = new DeferredResources(feature);
|
||||
feature->handle.Id = g.nextHandleId++;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_featuresMutex);
|
||||
@@ -4532,7 +4415,7 @@ static Feature* find_feature(const NgxHandle* handle)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_featuresMutex);
|
||||
for (Feature* feature : g_features)
|
||||
if (&feature->handle == handle && !feature->retiring.load())
|
||||
if (&feature->handle == handle)
|
||||
return feature;
|
||||
return nullptr;
|
||||
}
|
||||
@@ -4550,7 +4433,6 @@ static ID3D12Resource* get_resource(void* parameters, const char* name)
|
||||
|
||||
static NgxResult evaluate(ID3D12GraphicsCommandList* list, const NgxHandle* handle, void* parameters)
|
||||
{
|
||||
std::lock_guard<std::mutex> apiLock(g_featureApiMutex);
|
||||
FrameTiming timing;
|
||||
timing.enabled = profile_enabled();
|
||||
if (timing.enabled)
|
||||
@@ -4661,18 +4543,11 @@ static NgxResult evaluate(ID3D12GraphicsCommandList* list, const NgxHandle* hand
|
||||
logf("frame %u: input slot %d busy or %d frames in flight; skipping evaluation", frame, slotIndex, feature->inFlight.load());
|
||||
return NGX_SUCCESS;
|
||||
}
|
||||
d4r_sleep_us(200);
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(200));
|
||||
}
|
||||
if (timing.enabled)
|
||||
timing.slotWait = profile_ms(waitStart, ProfileClock::now());
|
||||
|
||||
if (g_vk.lifetime == nullptr ||
|
||||
FAILED(g_vk.lifetime->RetainExternalResources(list, feature->resources)))
|
||||
{
|
||||
logf("frame %u: unable to retain command-list resources; evaluation not recorded", frame);
|
||||
return NGX_FAIL_PLATFORM_ERROR;
|
||||
}
|
||||
|
||||
// VRAM interop when every resource qualifies (decided once per feature).
|
||||
ID3D12Resource* const inputs[4] = {color, depth, motion, exposure};
|
||||
const Plane planes[4] = {Plane::Color, Plane::Depth, Plane::Motion, Plane::Exposure};
|
||||
@@ -4690,13 +4565,17 @@ static NgxResult evaluate(ID3D12GraphicsCommandList* list, const NgxHandle* hand
|
||||
feature->vram = p.vram;
|
||||
feature->split = p.vram && env_uint("D4R_SHIM_SPLIT_FRAME", 0) != 0 && g_vk.split != nullptr &&
|
||||
create_split_semaphore(*feature);
|
||||
feature->linearInputs = feature->split && env_uint("D4R_SHIM_LINEAR_INPUTS", 0) != 0 &&
|
||||
if (feature->split && env_uint("D4R_SHIM_GPU_WAIT", 0) != 0 && g.cu.streamWaitValue32 != nullptr &&
|
||||
g.cu.writeValue32 != nullptr && g_vk.fill != nullptr)
|
||||
feature->gpuWait = ensure_vram_buffer(*feature, feature->gpuMarker, 256) &&
|
||||
g.worker.call([feature] { return g.cu.writeValue32(feature->gpuMarker.device, 0); }) == 0;
|
||||
feature->linearInputs = feature->gpuWait && env_uint("D4R_SHIM_LINEAR_INPUTS", 0) != 0 &&
|
||||
g.cu.registerLinearTexture != nullptr;
|
||||
if (feature->linearInputs)
|
||||
logf("linear inputs: NGX samples the interop buffers directly");
|
||||
logf("VRAM interop %s for this feature%s%s", p.vram ? "on" : "off",
|
||||
feature->split ? ", presenting each frame's own result (split frames)" : "",
|
||||
p.vram ? ", waiting for input readiness with CPU sleeps" : "");
|
||||
feature->gpuWait ? ", DLSS queued behind a GPU-side wait for the inputs" : "");
|
||||
}
|
||||
else if (feature->vram && !p.vram)
|
||||
{
|
||||
@@ -4707,6 +4586,7 @@ static NgxResult evaluate(ID3D12GraphicsCommandList* list, const NgxHandle* hand
|
||||
feature->split = false;
|
||||
}
|
||||
feature->vram = false;
|
||||
feature->gpuWait = false;
|
||||
feature->linearInputs = false;
|
||||
feature->latestOutput = -1;
|
||||
feature->outputFormat = DXGI_FORMAT_UNKNOWN; // recreates the output staging below
|
||||
@@ -4760,7 +4640,7 @@ static NgxResult evaluate(ID3D12GraphicsCommandList* list, const NgxHandle* hand
|
||||
feature->outputHeight = outputDesc.Height;
|
||||
for (OutputSlot& outputSlot : feature->outputs)
|
||||
{
|
||||
outputSlot.staging.retire();
|
||||
outputSlot.staging.release();
|
||||
if (p.vram ? !ensure_vram_buffer(*feature, outputSlot.vram, static_cast<size_t>(outputDesc.Width) * 8 *
|
||||
outputDesc.Height)
|
||||
: !ensure_staging(outputSlot.staging, output, D3D12_HEAP_TYPE_UPLOAD))
|
||||
@@ -4777,7 +4657,7 @@ static NgxResult evaluate(ID3D12GraphicsCommandList* list, const NgxHandle* hand
|
||||
const D3D12_RESOURCE_STATES states[4] = {inputState, depthState, inputState, inputState};
|
||||
for (int index = 0; index < inputCount; ++index)
|
||||
transition(list, inputs[index], states[index], D3D12_RESOURCE_STATE_COPY_SOURCE);
|
||||
const bool recorded = record_vram_inputs(*feature, list, slot, vramInputs, inputCount);
|
||||
const bool recorded = record_vram_inputs(*feature, list, slot, vramInputs, inputCount, frame);
|
||||
for (int index = 0; index < inputCount; ++index)
|
||||
transition(list, inputs[index], D3D12_RESOURCE_STATE_COPY_SOURCE, states[index]);
|
||||
if (!recorded)
|
||||
@@ -4899,15 +4779,9 @@ static void release_feature(Feature* feature)
|
||||
if (feature->cudaParams != nullptr)
|
||||
g.ngx.destroyParameters(feature->cudaParams);
|
||||
for (InputSlot& slot : feature->inputs)
|
||||
{
|
||||
for (CudaObject& texture : slot.linearTexture)
|
||||
if (texture != 0)
|
||||
g.cu.texObjectDestroy(texture), texture = 0;
|
||||
if (slot.dilatedTexture != 0)
|
||||
g.cu.texObjectDestroy(slot.dilatedTexture), slot.dilatedTexture = 0;
|
||||
if (slot.dilatedMotion != 0)
|
||||
g.cu.memFree(slot.dilatedMotion), slot.dilatedMotion = 0;
|
||||
}
|
||||
for (CudaImage* image : {&feature->color, &feature->depth, &feature->motion, &feature->exposure})
|
||||
{
|
||||
if (image->object != 0)
|
||||
@@ -4939,40 +4813,38 @@ static void release_feature(Feature* feature)
|
||||
release_host(host);
|
||||
return 0;
|
||||
});
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_featuresMutex);
|
||||
g_features.erase(std::remove(g_features.begin(), g_features.end(), feature), g_features.end());
|
||||
}
|
||||
// Recorded lists retain this owner through their allocator. Reset/discard
|
||||
// or GPU completion followed by allocator destruction releases those refs.
|
||||
feature->resources->Release();
|
||||
}
|
||||
|
||||
static void destroy_recorded_resources(Feature* feature)
|
||||
{
|
||||
for (InputSlot& slot : feature->inputs)
|
||||
for (Staging* staging : {&slot.color, &slot.depth, &slot.motion, &slot.exposure})
|
||||
staging->release();
|
||||
for (OutputSlot& slot : feature->outputs)
|
||||
slot.staging.release();
|
||||
if (g_vk.ready && feature->vramDecided)
|
||||
{
|
||||
// Queued command lists may still copy from or wait on these; the last
|
||||
// marker lands just before the last output copy.
|
||||
const auto start = std::chrono::steady_clock::now();
|
||||
while (feature->markerValue != nullptr &&
|
||||
static_cast<int32_t>(*feature->markerValue - feature->frame) < 0 &&
|
||||
std::chrono::steady_clock::now() - start < std::chrono::seconds(2))
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
release_vram(*feature);
|
||||
}
|
||||
if (feature->marker != nullptr)
|
||||
feature->marker->Release();
|
||||
logf("feature %u recorded resources retired after allocator completion", feature->handle.Id);
|
||||
delete feature;
|
||||
}
|
||||
|
||||
D4R_EXPORT NgxResult NVSDK_NGX_D3D12_ReleaseFeature(NgxHandle* handle)
|
||||
{
|
||||
std::lock_guard<std::mutex> apiLock(g_featureApiMutex);
|
||||
Feature* feature = nullptr;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_featuresMutex);
|
||||
for (Feature* candidate : g_features)
|
||||
if (&candidate->handle == handle && !candidate->retiring.exchange(true))
|
||||
for (auto it = g_features.begin(); it != g_features.end(); ++it)
|
||||
if (&(*it)->handle == handle)
|
||||
{
|
||||
feature = candidate;
|
||||
feature = *it;
|
||||
g_features.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -4984,51 +4856,30 @@ D4R_EXPORT NgxResult NVSDK_NGX_D3D12_ReleaseFeature(NgxHandle* handle)
|
||||
return NGX_SUCCESS;
|
||||
}
|
||||
|
||||
static void finish_shutdown()
|
||||
D4R_EXPORT NgxResult NVSDK_NGX_D3D12_Shutdown()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g.mutex);
|
||||
if (!g.shutdownPending || g.resourceOwners.load() != 0)
|
||||
return;
|
||||
logf("NVSDK_NGX_D3D12_Shutdown");
|
||||
std::vector<Feature*> features;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_featuresMutex);
|
||||
features.swap(g_features);
|
||||
}
|
||||
for (Feature* feature : features)
|
||||
release_feature(feature);
|
||||
if (g_vk.ready)
|
||||
free_vram_pool();
|
||||
std::lock_guard<std::mutex> lock(g.mutex);
|
||||
if (g.ngxInitialized)
|
||||
{
|
||||
drain_pipeline();
|
||||
g.worker.call([] { return g.ngx.shutdown(); });
|
||||
}
|
||||
g.ngxInitialized = false;
|
||||
g.shutdownPending = false;
|
||||
if (g.device != nullptr)
|
||||
{
|
||||
g.device->Release();
|
||||
g.device = nullptr;
|
||||
}
|
||||
logf("NGX shutdown completed after recorded resources retired");
|
||||
}
|
||||
|
||||
D4R_EXPORT NgxResult NVSDK_NGX_D3D12_Shutdown()
|
||||
{
|
||||
std::lock_guard<std::mutex> apiLock(g_featureApiMutex);
|
||||
logf("NVSDK_NGX_D3D12_Shutdown");
|
||||
std::vector<Feature*> features;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_featuresMutex);
|
||||
features = g_features;
|
||||
for (Feature* feature : features)
|
||||
feature->retiring = true;
|
||||
}
|
||||
for (Feature* feature : features)
|
||||
release_feature(feature);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g.mutex);
|
||||
if (!g.started)
|
||||
return NGX_SUCCESS;
|
||||
g.shutdownPending = true;
|
||||
}
|
||||
// Drain already queued cleanup before returning. If command allocators
|
||||
// still own resources, finish_shutdown leaves NGX alive until their last
|
||||
// reference retires; otherwise shutdown completes synchronously.
|
||||
g.cleanup.call([] { finish_shutdown(); return 0; });
|
||||
return NGX_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
#pragma once
|
||||
#include <d3d12.h>
|
||||
#include <vulkan/vulkan_core.h>
|
||||
|
||||
MIDL_INTERFACE("39da4e09-bd1c-4198-9fae-86bbe3be41fd")
|
||||
ID3D12DXVKInteropDevice : public IUnknown
|
||||
{
|
||||
virtual HRESULT STDMETHODCALLTYPE GetDXGIAdapter(REFIID iid, void** object) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetInstanceExtensions(UINT* count, const char** extensions) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetDeviceExtensions(UINT* count, const char** extensions) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetDeviceFeatures(const VkPhysicalDeviceFeatures2** features) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetVulkanHandles(VkInstance* instance, VkPhysicalDevice* physical,
|
||||
VkDevice* device) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetVulkanQueueInfo(ID3D12CommandQueue* queue, VkQueue* vkQueue,
|
||||
UINT32* family) = 0;
|
||||
virtual void STDMETHODCALLTYPE GetVulkanImageLayout(ID3D12Resource* resource, D3D12_RESOURCE_STATES state,
|
||||
VkImageLayout* layout) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetVulkanResourceInfo(ID3D12Resource* resource, UINT64* handle,
|
||||
UINT64* offset) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE LockCommandQueue(ID3D12CommandQueue* queue) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE UnlockCommandQueue(ID3D12CommandQueue* queue) = 0;
|
||||
};
|
||||
|
||||
MIDL_INTERFACE("902d8115-59eb-4406-9518-fe00f991ee65")
|
||||
ID3D12DXVKInteropDevice1 : public ID3D12DXVKInteropDevice
|
||||
{
|
||||
virtual HRESULT STDMETHODCALLTYPE GetVulkanResourceInfo1(ID3D12Resource* resource, UINT64* handle, UINT64* offset,
|
||||
VkFormat* format) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE CreateInteropCommandQueue(const D3D12_COMMAND_QUEUE_DESC* desc, UINT32 family,
|
||||
ID3D12CommandQueue** queue) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE CreateInteropCommandAllocator(D3D12_COMMAND_LIST_TYPE type, UINT32 family,
|
||||
ID3D12CommandAllocator** allocator) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE BeginVkCommandBufferInterop(ID3D12CommandList* list, VkCommandBuffer* buffer) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE EndVkCommandBufferInterop(ID3D12CommandList* list) = 0;
|
||||
};
|
||||
__CRT_UUID_DECL(ID3D12DXVKInteropDevice1, 0x902d8115, 0x59eb, 0x4406, 0x95, 0x18, 0xfe, 0x00, 0xf9, 0x91, 0xee, 0x65)
|
||||
|
||||
// Added by the d4r vkd3d-proton patch (patches/vkd3d-proton/): ends the list's
|
||||
// current Vulkan command buffer; what is recorded afterwards is submitted
|
||||
// separately, with a queue-level wait for the timeline semaphore value.
|
||||
MIDL_INTERFACE("5a7c8b3e-2f61-4d0e-9c1a-7e3b52d4a901")
|
||||
ID3D12DXVKInteropDeviceD4R : public ID3D12DXVKInteropDevice1
|
||||
{
|
||||
virtual HRESULT STDMETHODCALLTYPE LockVulkanQueue(ID3D12CommandQueue* queue) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE UnlockVulkanQueue(ID3D12CommandQueue* queue) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetVulkanHeapInfo(ID3D12Heap* heap, UINT64* memory, UINT64* offset,
|
||||
UINT32* type) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE SplitCommandListForExternalWait(ID3D12CommandList* list, UINT64 semaphore,
|
||||
UINT64 value) = 0;
|
||||
};
|
||||
__CRT_UUID_DECL(ID3D12DXVKInteropDeviceD4R, 0x5a7c8b3e, 0x2f61, 0x4d0e, 0x9c, 0x1a, 0x7e, 0x3b, 0x52, 0xd4, 0xa9, 0x01)
|
||||
|
||||
MIDL_INTERFACE("4f8c9462-8197-4dc1-a584-cf44d9706db2")
|
||||
ID3D12DXVKInteropDeviceD4R2 : public ID3D12DXVKInteropDeviceD4R
|
||||
{
|
||||
virtual HRESULT STDMETHODCALLTYPE RetainExternalResources(ID3D12CommandList* list, IUnknown* resources) = 0;
|
||||
};
|
||||
__CRT_UUID_DECL(ID3D12DXVKInteropDeviceD4R2, 0x4f8c9462, 0x8197, 0x4dc1, 0xa5, 0x84, 0xcf, 0x44, 0xd9, 0x70, 0x6d, 0xb2)
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
#pragma once
|
||||
#include <windows.h>
|
||||
#include <cstdint>
|
||||
|
||||
// MinGW's Win32 std::sleep_for can truncate submillisecond durations to zero.
|
||||
// NtDelayExecution retains 100 ns precision and actually sleeps under Wine.
|
||||
inline void d4r_sleep_us(unsigned int microseconds)
|
||||
{
|
||||
using Delay = LONG(WINAPI*)(BOOLEAN, LARGE_INTEGER*);
|
||||
static const auto delay = reinterpret_cast<Delay>(reinterpret_cast<void*>(
|
||||
GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtDelayExecution")));
|
||||
if (microseconds == 0)
|
||||
{
|
||||
SwitchToThread();
|
||||
return;
|
||||
}
|
||||
LARGE_INTEGER interval;
|
||||
interval.QuadPart = -static_cast<LONGLONG>(microseconds) * 10;
|
||||
if (delay != nullptr && delay(FALSE, &interval) >= 0)
|
||||
return;
|
||||
// A rounded-up millisecond wait still releases the CPU if ntdll is absent.
|
||||
Sleep(static_cast<DWORD>((static_cast<uint64_t>(microseconds) + 999) / 1000));
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
"""Build a private gfx1201 texture override from the user's local DLSS DLL.
|
||||
|
||||
The resulting PTX/code object contains NVIDIA code and must remain local.
|
||||
No Wine, shell script, GPU, CUDA SDK or system installation is required.
|
||||
"""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--dlss-dll', type=pathlib.Path, required=True)
|
||||
parser.add_argument('--hip-root', type=pathlib.Path, required=True)
|
||||
parser.add_argument('--zluda-root', type=pathlib.Path, required=True)
|
||||
parser.add_argument('--kernel', required=True)
|
||||
parser.add_argument('--output-directory', type=pathlib.Path, required=True)
|
||||
args = parser.parse_args()
|
||||
if not re.fullmatch(r'hiluma_engine_output_depth(?:inv|reg)_mv(?:hi|lo)_(?:hdr|ldr)(?:_max)?_v2_rel', args.kernel):
|
||||
parser.error('this initial validated build recipe supports K v2 output variants only')
|
||||
repo = pathlib.Path(__file__).resolve().parents[2]
|
||||
output = args.output_directory.resolve()
|
||||
output.mkdir(parents=True, exist_ok=True)
|
||||
work = output / ('work-' + args.kernel)
|
||||
work.mkdir(exist_ok=True)
|
||||
hip = args.hip_root.resolve()
|
||||
zluda = args.zluda_root.resolve()
|
||||
tools = {name: hip / 'bin' / (name + '.exe') for name in ('clang++', 'llvm-dis', 'llvm-as')}
|
||||
emitter = zluda / 'd4r_emit.exe'
|
||||
for path in [args.dlss_dll, emitter, *tools.values()]:
|
||||
if not path.is_file():
|
||||
parser.error(f'missing dependency: {path}')
|
||||
env = os.environ.copy()
|
||||
env.update(D4R_PREFER_ACCURACY='1', D4R_ZLUDA_WMMA='1', D4R_ZLUDA_WMMA_FP8='1',
|
||||
D4R_ZLUDA_WMMA_FP8_NATIVE='0', D4R_ZLUDA_WMMA_F16_REFERENCE='1',
|
||||
D4R_ZLUDA_IMPLICIT_MAX_BLOCK='256')
|
||||
for name in ('D4R_ZLUDA_IGNORE_DENORMAL', 'D4R_ZLUDA_FAST_MATH', 'D4R_ZLUDA_WMMA_F32ACC',
|
||||
'D4R_ZLUDA_WAVE64', 'D4R_ZLUDA_PROFILE'):
|
||||
env.pop(name, None)
|
||||
env['PATH'] = str(zluda) + os.pathsep + str(hip / 'bin') + os.pathsep + env['PATH']
|
||||
|
||||
def run(label, command):
|
||||
with (work / (label + '.log')).open('wb') as log:
|
||||
result = subprocess.run([str(arg) for arg in command], env=env, stdout=log, stderr=subprocess.STDOUT)
|
||||
if result.returncode:
|
||||
print((work / (label + '.log')).read_text(errors='replace')[-8000:], file=sys.stderr)
|
||||
raise RuntimeError(f'{label} failed ({result.returncode}); see {work}')
|
||||
|
||||
ptx = work / 'ptx'
|
||||
run('extract', [sys.executable, repo / 'kernels/tools/extract_dlss_ptx.py', args.dlss_dll, ptx])
|
||||
env['D4R_DLSS_PTX_DIR'] = str(ptx)
|
||||
edited = work / (args.kernel + '.ptx')
|
||||
run('make-ptx', [sys.executable, repo / 'kernels/tex/make_ptx.py', args.kernel, edited])
|
||||
raw, ir, extra = work / 'raw.bc', work / 'raw.ll', work / 'extra.bc'
|
||||
run('compile-helper', [tools['clang++'], '-x', 'hip', '-std=c++20', '-nogpuinc', '-nogpulib', '-O3',
|
||||
'-mno-wavefrontsize64', '--offload-device-only', '--offload-arch=gfx1201',
|
||||
'-fgpu-rdc', '-emit-llvm', '-c', '-Xclang', '-fdenormal-fp-math=dynamic',
|
||||
'-DD4R_ACCURACY', '-DD4R_KERNEL_NAME=' + args.kernel,
|
||||
'-o', raw, repo / 'kernels/tex/sust_only.hip'])
|
||||
run('disassemble', [tools['llvm-dis'], raw, '-o', ir])
|
||||
lines = []
|
||||
for line in ir.read_text().splitlines():
|
||||
if re.search(r'@llvm.used|wchar_size|llvm.module.flags|__hip_cuid', line):
|
||||
continue
|
||||
line = line.replace('optnone', '').replace('"target-cpu"="gfx1201"', '')
|
||||
lines.append(re.sub(r'"target-features"="[^"]+"', '', line))
|
||||
ir.write_text('\n'.join(lines) + '\n')
|
||||
run('assemble', [tools['llvm-as'], ir, '-o', extra])
|
||||
env['D4R_ZLUDA_EXTRA_BC'] = str(extra)
|
||||
run('emit', [emitter, edited, work / 'emitted', 'gfx1201'])
|
||||
shutil.copyfile(work / 'emitted/module.hsaco', output / (args.kernel + '.hsaco'))
|
||||
run('manifest', [sys.executable, repo / 'kernels/tools/kernel_manifest.py', output, args.dlss_dll])
|
||||
metadata = dict(architecture='gfx1201', accuracy=True, nativeFP8=False,
|
||||
privateNvidiaDerivedCode=True, validation='not yet validated; do not install before comparison',
|
||||
dlssSha256=hashlib.sha256(args.dlss_dll.read_bytes()).hexdigest(),
|
||||
zludaBuild=json.loads((zluda / 'build-info.json').read_text(encoding='utf-8-sig')),
|
||||
compiler=subprocess.check_output([str(tools['clang++']), '--version'], env=env).decode(errors='replace'),
|
||||
objects={p.name: hashlib.sha256(p.read_bytes()).hexdigest() for p in output.glob('*.hsaco')})
|
||||
(output / 'build-info.json').write_text(json.dumps(metadata, indent=2) + '\n')
|
||||
print(f'Built PRIVATE gfx1201 output override: {output}; validation required before use')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,48 +0,0 @@
|
||||
#pragma once
|
||||
#include "diagnostic.h"
|
||||
// CUDA Driver API ABI subset. No CUDA SDK, CUDA runtime or NVIDIA binary is needed.
|
||||
using CUdevice = int;
|
||||
using CUdeviceptr = uint64_t;
|
||||
using CUcontext = void*;
|
||||
using CUmodule = void*;
|
||||
using CUfunction = void*;
|
||||
struct CudaApi {
|
||||
bool verbose = true;
|
||||
d4r::diag::SearchDirectory search;
|
||||
d4r::diag::Library library;
|
||||
#define D4R_CU(name, ret, ...) using name##_fn = ret(WINAPI*)(__VA_ARGS__); name##_fn name = library.symbol<name##_fn>(#name)
|
||||
D4R_CU(cuInit, int, unsigned);
|
||||
D4R_CU(cuDriverGetVersion, int, int*);
|
||||
D4R_CU(cuDeviceGetCount, int, int*);
|
||||
D4R_CU(cuDeviceGet, int, CUdevice*, int);
|
||||
D4R_CU(cuDeviceGetName, int, char*, int, CUdevice);
|
||||
D4R_CU(cuDeviceGetAttribute, int, int*, int, CUdevice);
|
||||
D4R_CU(cuDevicePrimaryCtxRetain, int, CUcontext*, CUdevice);
|
||||
D4R_CU(cuDevicePrimaryCtxRelease_v2, int, CUdevice);
|
||||
D4R_CU(cuCtxSetCurrent, int, CUcontext);
|
||||
D4R_CU(cuCtxCreate_v2, int, CUcontext*, unsigned, CUdevice);
|
||||
D4R_CU(cuCtxDestroy_v2, int, CUcontext);
|
||||
D4R_CU(cuCtxSynchronize, int);
|
||||
D4R_CU(cuMemAlloc_v2, int, CUdeviceptr*, size_t);
|
||||
D4R_CU(cuMemFree_v2, int, CUdeviceptr);
|
||||
D4R_CU(cuMemcpyHtoD_v2, int, CUdeviceptr, const void*, size_t);
|
||||
D4R_CU(cuMemcpyDtoH_v2, int, void*, CUdeviceptr, size_t);
|
||||
D4R_CU(cuModuleLoadData, int, CUmodule*, const void*);
|
||||
D4R_CU(cuModuleUnload, int, CUmodule);
|
||||
D4R_CU(cuModuleGetFunction, int, CUfunction*, CUmodule, const char*);
|
||||
D4R_CU(cuLaunchKernel, int, CUfunction, unsigned, unsigned, unsigned, unsigned, unsigned, unsigned,
|
||||
unsigned, void*, void**, void**);
|
||||
D4R_CU(cuGetErrorName, int, int, const char**);
|
||||
D4R_CU(cuGetErrorString, int, int, const char**);
|
||||
#undef D4R_CU
|
||||
explicit CudaApi(const std::string& path) :
|
||||
search(std::filesystem::path(d4r::diag::wide(path)).parent_path()),
|
||||
library(d4r::diag::wide(path)) {}
|
||||
void check(int result, const char* call) {
|
||||
const char* name = "unknown", *message = "unknown";
|
||||
if (result) { cuGetErrorName(result, &name); cuGetErrorString(result, &message); }
|
||||
else name = "CUDA_SUCCESS";
|
||||
if (verbose || result) std::printf("CUDA %s -> %d (%s)\n", call, result, name ? name : "unknown");
|
||||
if (result) throw std::runtime_error(std::string(call) + ": " + (message ? message : "unknown"));
|
||||
}
|
||||
};
|
||||
@@ -1,121 +0,0 @@
|
||||
#include "hip_api.h"
|
||||
#include <algorithm>
|
||||
|
||||
#include "cuda_api.h"
|
||||
|
||||
static constexpr char ptx[] = R"PTX(
|
||||
.version 7.0
|
||||
.target sm_75
|
||||
.address_size 64
|
||||
.visible .entry d4r_ptx_pattern(
|
||||
.param .u64 output, .param .u32 count, .param .u32 seed)
|
||||
{
|
||||
.reg .pred %p;
|
||||
.reg .b32 %r<8>;
|
||||
.reg .b64 %rd<4>;
|
||||
ld.param.u64 %rd0, [output];
|
||||
ld.param.u32 %r0, [count];
|
||||
ld.param.u32 %r1, [seed];
|
||||
mov.u32 %r2, %ctaid.x;
|
||||
mov.u32 %r3, %ntid.x;
|
||||
mov.u32 %r4, %tid.x;
|
||||
mad.lo.u32 %r5, %r2, %r3, %r4;
|
||||
setp.ge.u32 %p, %r5, %r0;
|
||||
@%p bra done;
|
||||
mad.lo.u32 %r6, %r5, 1664525, %r1;
|
||||
xor.b32 %r7, %r6, 0xa5a55a5a;
|
||||
mul.wide.u32 %rd1, %r5, 4;
|
||||
add.u64 %rd2, %rd0, %rd1;
|
||||
st.global.u32 [%rd2], %r7;
|
||||
done:
|
||||
ret;
|
||||
}
|
||||
)PTX";
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
using namespace d4r::diag;
|
||||
start();
|
||||
try {
|
||||
Args args(argc, argv);
|
||||
uint64_t identity = 0xcbf29ce484222325ull;
|
||||
for (const char* c = ptx; *c; ++c) identity = (identity ^ uint8_t(*c)) * 0x100000001b3ull;
|
||||
std::printf("PTX_IDENTITY kernel=d4r_ptx_pattern fnv1a64=%016llx\n", static_cast<unsigned long long>(identity));
|
||||
if (args.cuda_dll.empty()) throw std::runtime_error("--cuda-dll is required (absolute ZLUDA nvcuda.dll path)");
|
||||
if (args.context != "primary" && args.context != "created") throw std::runtime_error("context must be primary or created");
|
||||
// Require the real HIP architecture, then match the CUDA device by PCI identity.
|
||||
HipApi hip(args.hip_root);
|
||||
hipDeviceProp_t props{};
|
||||
hip.select_gfx1201(args.device, props);
|
||||
CudaApi api(args.cuda_dll);
|
||||
loaded_modules();
|
||||
std::printf("STAGE cuInit\n");
|
||||
api.check(api.cuInit(0), "cuInit");
|
||||
int driver = 0, count = 0;
|
||||
api.check(api.cuDriverGetVersion(&driver), "cuDriverGetVersion");
|
||||
api.check(api.cuDeviceGetCount(&count), "cuDeviceGetCount");
|
||||
std::printf("CUDA driver_api=%d devices=%d\n", driver, count);
|
||||
CUdevice device = -1;
|
||||
for (int ordinal = 0; ordinal < count; ++ordinal) {
|
||||
CUdevice candidate;
|
||||
api.check(api.cuDeviceGet(&candidate, ordinal), "cuDeviceGet");
|
||||
char name[256]{};
|
||||
int bus = -1, pci_device = -1, domain = -1;
|
||||
api.check(api.cuDeviceGetName(name, sizeof(name), candidate), "cuDeviceGetName");
|
||||
api.check(api.cuDeviceGetAttribute(&bus, 33, candidate), "cuDeviceGetAttribute(PCI_BUS_ID)");
|
||||
api.check(api.cuDeviceGetAttribute(&pci_device, 34, candidate), "cuDeviceGetAttribute(PCI_DEVICE_ID)");
|
||||
api.check(api.cuDeviceGetAttribute(&domain, 50, candidate), "cuDeviceGetAttribute(PCI_DOMAIN_ID)");
|
||||
std::printf("CUDA GPU ordinal=%d name=%s pci=%04x:%02x:%02x\n", ordinal, name, domain, bus, pci_device);
|
||||
if (bus == props.pciBusID && pci_device == props.pciDeviceID && domain == props.pciDomainID) device = candidate;
|
||||
}
|
||||
if (device < 0) throw std::runtime_error("CUDA/HIP PCI identity mismatch");
|
||||
CUcontext context = nullptr;
|
||||
const bool primary = args.context == "primary";
|
||||
if (primary) {
|
||||
api.check(api.cuDevicePrimaryCtxRetain(&context, device), "cuDevicePrimaryCtxRetain");
|
||||
} else api.check(api.cuCtxCreate_v2(&context, 0, device), "cuCtxCreate_v2");
|
||||
struct ContextCleanup {
|
||||
CudaApi& api; CUcontext context; CUdevice device; bool primary;
|
||||
~ContextCleanup() { if (context) { api.cuCtxSetCurrent(nullptr);
|
||||
if (primary) api.cuDevicePrimaryCtxRelease_v2(device); else api.cuCtxDestroy_v2(context); } }
|
||||
} cleanup{api, context, device, primary};
|
||||
api.check(api.cuCtxSetCurrent(context), "cuCtxSetCurrent");
|
||||
CUmodule module = nullptr;
|
||||
std::printf("STAGE cuModuleLoadData (PTX JIT)\n");
|
||||
api.check(api.cuModuleLoadData(&module, ptx), "cuModuleLoadData");
|
||||
struct ModuleCleanup { CudaApi& api; CUmodule m; ~ModuleCleanup() { if (m) api.cuModuleUnload(m); } } mc{api, module};
|
||||
loaded_modules();
|
||||
CUfunction function = nullptr;
|
||||
api.check(api.cuModuleGetFunction(&function, module, "d4r_ptx_pattern"), "cuModuleGetFunction");
|
||||
constexpr uint32_t nmax = 4099;
|
||||
std::vector<uint32_t> result(nmax + 61, 0xdeadbeefu);
|
||||
const size_t bytes = result.size() * sizeof(uint32_t);
|
||||
CUdeviceptr pointer = 0;
|
||||
api.check(api.cuMemAlloc_v2(&pointer, bytes), "cuMemAlloc_v2");
|
||||
struct MemCleanup { CudaApi& api; CUdeviceptr p; ~MemCleanup() { if (p) api.cuMemFree_v2(p); } } ac{api, pointer};
|
||||
for (unsigned i = 0; i < args.iterations; ++i) {
|
||||
uint32_t n = nmax - i % 129u, seed = 0x12345678u + i * 31u;
|
||||
std::fill(result.begin(), result.end(), 0xdeadbeefu);
|
||||
api.check(api.cuMemcpyHtoD_v2(pointer, result.data(), bytes), "cuMemcpyHtoD_v2(sentinel)");
|
||||
void* parameters[] = {&pointer, &n, &seed};
|
||||
api.check(api.cuLaunchKernel(function, (n + 127u) / 128u, 1, 1, 128, 1, 1,
|
||||
0, nullptr, parameters, nullptr), "cuLaunchKernel");
|
||||
api.check(api.cuCtxSynchronize(), "cuCtxSynchronize");
|
||||
api.check(api.cuMemcpyDtoH_v2(result.data(), pointer, bytes), "cuMemcpyDtoH_v2");
|
||||
if (!verify(result, n, seed)) return 5;
|
||||
}
|
||||
api.check(api.cuMemFree_v2(pointer), "cuMemFree_v2"); ac.p = 0;
|
||||
api.check(api.cuModuleUnload(module), "cuModuleUnload"); mc.m = nullptr;
|
||||
api.check(api.cuCtxSetCurrent(nullptr), "cuCtxSetCurrent(NULL)");
|
||||
if (primary) api.check(api.cuDevicePrimaryCtxRelease_v2(device), "cuDevicePrimaryCtxRelease_v2");
|
||||
else api.check(api.cuCtxDestroy_v2(context), "cuCtxDestroy_v2");
|
||||
cleanup.context = nullptr;
|
||||
std::printf("PASS CUDA architecture=gfx1201 context=%s iterations=%u guard_verified=1\n",
|
||||
args.context.c_str(), args.iterations);
|
||||
return 0;
|
||||
} catch (const std::exception& e) {
|
||||
std::fprintf(stderr, "FAIL CUDA %s\n", e.what());
|
||||
loaded_modules();
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
#pragma once
|
||||
#include "cuda_api.h"
|
||||
|
||||
namespace d4r::cuda {
|
||||
// Public CUDA Driver API layouts, kept independent of NVIDIA SDK headers.
|
||||
using Array = void*;
|
||||
using Texture = uint64_t;
|
||||
struct ArrayDescriptor { size_t width, height; uint32_t format, channels; };
|
||||
struct Array3DDescriptor {
|
||||
size_t width, height, depth;
|
||||
uint32_t format, channels, flags;
|
||||
};
|
||||
struct Copy2D {
|
||||
size_t srcX, srcY;
|
||||
uint32_t srcType, srcAlignment;
|
||||
const void* srcHost;
|
||||
CUdeviceptr srcDevice;
|
||||
Array srcArray;
|
||||
size_t srcPitch, dstX, dstY;
|
||||
uint32_t dstType, dstAlignment;
|
||||
void* dstHost;
|
||||
CUdeviceptr dstDevice;
|
||||
Array dstArray;
|
||||
size_t dstPitch, widthBytes, height;
|
||||
};
|
||||
struct ResourceDescriptor {
|
||||
uint32_t type, alignment;
|
||||
union { Array array; int reserved[32]; } resource;
|
||||
uint32_t flags, reserved;
|
||||
};
|
||||
struct TextureDescriptor {
|
||||
uint32_t addressMode[3], filterMode, flags, maxAnisotropy, mipmapFilterMode;
|
||||
float mipmapBias, minMipmapClamp, maxMipmapClamp, borderColor[4];
|
||||
int reserved[12];
|
||||
};
|
||||
static_assert(sizeof(Array3DDescriptor) == 40);
|
||||
static_assert(sizeof(ArrayDescriptor) == 24);
|
||||
static_assert(sizeof(Copy2D) == 128);
|
||||
static_assert(sizeof(ResourceDescriptor) == 144);
|
||||
static_assert(sizeof(TextureDescriptor) == 104);
|
||||
|
||||
struct ImageApi {
|
||||
CudaApi& cuda;
|
||||
#define D4R_IMAGE(name, ...) using name##_fn = int(WINAPI*)(__VA_ARGS__); name##_fn name = cuda.library.symbol<name##_fn>(#name)
|
||||
D4R_IMAGE(cuArray3DCreate_v2, Array*, const Array3DDescriptor*);
|
||||
D4R_IMAGE(cuArrayGetDescriptor_v2, ArrayDescriptor*, Array);
|
||||
D4R_IMAGE(cuArray3DGetDescriptor_v2, Array3DDescriptor*, Array);
|
||||
D4R_IMAGE(cuArrayDestroy, Array);
|
||||
D4R_IMAGE(cuMemcpy2D_v2, const Copy2D*);
|
||||
D4R_IMAGE(cuTexObjectCreate, Texture*, const ResourceDescriptor*, const TextureDescriptor*, const void*);
|
||||
D4R_IMAGE(cuTexObjectDestroy, Texture);
|
||||
D4R_IMAGE(cuTexObjectGetResourceDesc, ResourceDescriptor*, Texture);
|
||||
D4R_IMAGE(cuSurfObjectCreate, Texture*, const ResourceDescriptor*);
|
||||
D4R_IMAGE(cuSurfObjectDestroy, Texture);
|
||||
D4R_IMAGE(cuSurfObjectGetResourceDesc, ResourceDescriptor*, Texture);
|
||||
#undef D4R_IMAGE
|
||||
explicit ImageApi(CudaApi& api) : cuda(api) {}
|
||||
};
|
||||
|
||||
class Image {
|
||||
ImageApi& api_;
|
||||
void release() noexcept {
|
||||
if (object) {
|
||||
const int result = surface_ ? api_.cuSurfObjectDestroy(object) : api_.cuTexObjectDestroy(object);
|
||||
if (result) std::fprintf(stderr, "IMAGE_DESTROY object result=%d\n", result);
|
||||
object = 0;
|
||||
}
|
||||
if (array_) {
|
||||
const int result = api_.cuArrayDestroy(array_);
|
||||
if (result) std::fprintf(stderr, "IMAGE_DESTROY array result=%d\n", result);
|
||||
array_ = nullptr;
|
||||
}
|
||||
}
|
||||
Array array_ = nullptr;
|
||||
bool surface_;
|
||||
size_t rowBytes_, height_;
|
||||
public:
|
||||
Texture object = 0;
|
||||
Image(ImageApi& api, unsigned width, unsigned height, uint32_t format,
|
||||
unsigned channels, bool surface, unsigned filter = 0)
|
||||
: api_(api), surface_(surface), rowBytes_(size_t(width) * channels * (format == 16 ? 2 : 4)), height_(height) {
|
||||
try {
|
||||
const Array3DDescriptor descriptor{width, height, 0, format, channels, surface ? 2u : 0u};
|
||||
api_.cuda.check(api_.cuArray3DCreate_v2(&array_, &descriptor), "cuArray3DCreate_v2");
|
||||
ResourceDescriptor resource{};
|
||||
resource.resource.array = array_;
|
||||
if (surface) api_.cuda.check(api_.cuSurfObjectCreate(&object, &resource), "cuSurfObjectCreate");
|
||||
else {
|
||||
TextureDescriptor texture{};
|
||||
texture.addressMode[0] = texture.addressMode[1] = 1; // clamp
|
||||
texture.filterMode = filter;
|
||||
texture.flags = 2; // normalized coordinates
|
||||
api_.cuda.check(api_.cuTexObjectCreate(&object, &resource, &texture, nullptr), "cuTexObjectCreate");
|
||||
}
|
||||
ResourceDescriptor roundtrip{};
|
||||
api_.cuda.check(surface ? api_.cuSurfObjectGetResourceDesc(&roundtrip, object) :
|
||||
api_.cuTexObjectGetResourceDesc(&roundtrip, object), "CUDA object resource descriptor roundtrip");
|
||||
if (roundtrip.type != 0 || roundtrip.resource.array != array_ || roundtrip.flags != 0)
|
||||
throw std::runtime_error("CUDA object descriptor does not match its array");
|
||||
ArrayDescriptor twoD{};
|
||||
Array3DDescriptor threeD{};
|
||||
api_.cuda.check(api_.cuArrayGetDescriptor_v2(&twoD, array_), "cuArrayGetDescriptor_v2");
|
||||
api_.cuda.check(api_.cuArray3DGetDescriptor_v2(&threeD, array_), "cuArray3DGetDescriptor_v2");
|
||||
if (twoD.width != width || twoD.height != height || twoD.format != format || twoD.channels != channels ||
|
||||
threeD.width != width || threeD.height != height || threeD.depth != 0 || threeD.format != format ||
|
||||
threeD.channels != channels || threeD.flags != descriptor.flags)
|
||||
throw std::runtime_error("CUDA array descriptor geometry or format mismatch");
|
||||
} catch (...) { release(); throw; }
|
||||
}
|
||||
Image(const Image&) = delete;
|
||||
Image& operator=(const Image&) = delete;
|
||||
~Image() { release(); }
|
||||
void verify_wrong_object_kind() {
|
||||
ResourceDescriptor wrong{};
|
||||
const int result = surface_ ? api_.cuTexObjectGetResourceDesc(&wrong, object) :
|
||||
api_.cuSurfObjectGetResourceDesc(&wrong, object);
|
||||
if (!result) throw std::runtime_error("Wrong CUDA object kind was accepted as a valid resource");
|
||||
std::printf("CUDA wrong-kind descriptor safely rejected result=%d\n", result);
|
||||
}
|
||||
void upload(const void* source) {
|
||||
Copy2D copy{};
|
||||
copy.srcType = 1; copy.srcHost = source; copy.srcPitch = rowBytes_;
|
||||
copy.dstType = 3; copy.dstArray = array_;
|
||||
copy.widthBytes = rowBytes_; copy.height = height_;
|
||||
api_.cuda.check(api_.cuMemcpy2D_v2(©), "cuMemcpy2D_v2(host to diagnostic array)");
|
||||
}
|
||||
void download(void* destination) {
|
||||
Copy2D copy{};
|
||||
copy.srcType = 3; copy.srcArray = array_;
|
||||
copy.dstType = 1; copy.dstHost = destination; copy.dstPitch = rowBytes_;
|
||||
copy.widthBytes = rowBytes_; copy.height = height_;
|
||||
api_.cuda.check(api_.cuMemcpy2D_v2(©), "cuMemcpy2D_v2(diagnostic array to host)");
|
||||
}
|
||||
void upload_device(CUdeviceptr source, size_t pitch) {
|
||||
if (pitch < rowBytes_) throw std::runtime_error("Device input pitch is smaller than its row");
|
||||
Copy2D copy{};
|
||||
copy.srcType = 2; copy.srcDevice = source; copy.srcPitch = pitch;
|
||||
copy.dstType = 3; copy.dstArray = array_;
|
||||
copy.widthBytes = rowBytes_; copy.height = height_;
|
||||
api_.cuda.check(api_.cuMemcpy2D_v2(©), "cuMemcpy2D_v2(external VRAM to array)");
|
||||
}
|
||||
void download_device(CUdeviceptr destination, size_t pitch) {
|
||||
if (pitch < rowBytes_) throw std::runtime_error("Device output pitch is smaller than its row");
|
||||
Copy2D copy{};
|
||||
copy.srcType = 3; copy.srcArray = array_;
|
||||
copy.dstType = 2; copy.dstDevice = destination; copy.dstPitch = pitch;
|
||||
copy.widthBytes = rowBytes_; copy.height = height_;
|
||||
api_.cuda.check(api_.cuMemcpy2D_v2(©), "cuMemcpy2D_v2(array to external VRAM)");
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
#include "hip_api.h"
|
||||
#include "cuda_image_api.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
using namespace d4r::diag;
|
||||
start();
|
||||
try {
|
||||
Args args(argc, argv);
|
||||
HipApi hip(args.hip_root);
|
||||
hipDeviceProp_t props{};
|
||||
hip.select_gfx1201(args.device, props);
|
||||
SearchDirectory search(std::filesystem::path(wide(args.cuda_dll)).parent_path());
|
||||
CudaApi cuda(args.cuda_dll);
|
||||
cuda.check(cuda.cuInit(0), "cuInit");
|
||||
CUdevice device = -1;
|
||||
int count = 0;
|
||||
cuda.check(cuda.cuDeviceGetCount(&count), "cuDeviceGetCount");
|
||||
for (int ordinal = 0; ordinal < count; ++ordinal) {
|
||||
CUdevice candidate = -1;
|
||||
int bus = -1, pciDevice = -1, domain = -1;
|
||||
cuda.check(cuda.cuDeviceGet(&candidate, ordinal), "cuDeviceGet");
|
||||
cuda.check(cuda.cuDeviceGetAttribute(&bus, 33, candidate), "cuDeviceGetAttribute(PCI_BUS_ID)");
|
||||
cuda.check(cuda.cuDeviceGetAttribute(&pciDevice, 34, candidate), "cuDeviceGetAttribute(PCI_DEVICE_ID)");
|
||||
cuda.check(cuda.cuDeviceGetAttribute(&domain, 50, candidate), "cuDeviceGetAttribute(PCI_DOMAIN_ID)");
|
||||
if (bus == props.pciBusID && pciDevice == props.pciDeviceID && domain == props.pciDomainID) device = candidate;
|
||||
}
|
||||
if (device < 0) throw std::runtime_error("CUDA image probe HIP/CUDA PCI identity mismatch");
|
||||
CUcontext context = nullptr;
|
||||
cuda.check(cuda.cuCtxCreate_v2(&context, 0, device), "cuCtxCreate_v2");
|
||||
struct Cleanup { CudaApi& api; CUcontext context; ~Cleanup() { (void)api.cuCtxDestroy_v2(context); } } cleanup{cuda, context};
|
||||
d4r::cuda::ImageApi images(cuda);
|
||||
for (unsigned iteration = 0; iteration < args.iterations; ++iteration) {
|
||||
for (unsigned channels : {1u, 2u, 4u}) for (bool surface : {false, true}) {
|
||||
d4r::cuda::Image image(images, 17, 9, 16, channels, surface);
|
||||
image.verify_wrong_object_kind();
|
||||
std::vector<uint16_t> input(17 * 9 * channels), output(input.size());
|
||||
for (size_t i = 0; i < input.size(); ++i) input[i] = uint16_t((i + iteration) % 8 * 0x100);
|
||||
image.upload(input.data());
|
||||
image.download(output.data());
|
||||
if (input != output) throw std::runtime_error("CUDA array storage roundtrip mismatch");
|
||||
const size_t rowBytes = 17 * channels * sizeof(uint16_t), pitch = (rowBytes + 255) & ~size_t(255);
|
||||
CUdeviceptr devicePixels = 0;
|
||||
cuda.check(cuda.cuMemAlloc_v2(&devicePixels, pitch * 9), "cuMemAlloc_v2(pitched image regression)");
|
||||
struct PixelsCleanup { CudaApi& cuda; CUdeviceptr pointer; ~PixelsCleanup() { (void)cuda.cuMemFree_v2(pointer); } } pixelsCleanup{cuda, devicePixels};
|
||||
std::vector<uint8_t> padded(pitch * 9, 0xcd);
|
||||
for (unsigned y = 0; y < 9; ++y) std::memcpy(padded.data() + y * pitch, input.data() + y * 17 * channels, rowBytes);
|
||||
cuda.check(cuda.cuMemcpyHtoD_v2(devicePixels, padded.data(), padded.size()), "cuMemcpyHtoD_v2(pitched pattern)");
|
||||
image.upload_device(devicePixels, pitch);
|
||||
image.download(output.data());
|
||||
if (input != output) throw std::runtime_error("CUDA device-to-array pitched copy mismatch");
|
||||
std::fill(padded.begin(), padded.end(), 0xee);
|
||||
cuda.check(cuda.cuMemcpyHtoD_v2(devicePixels, padded.data(), padded.size()), "cuMemcpyHtoD_v2(output sentinel)");
|
||||
image.download_device(devicePixels, pitch);
|
||||
cuda.check(cuda.cuMemcpyDtoH_v2(padded.data(), devicePixels, padded.size()), "cuMemcpyDtoH_v2(device array output)");
|
||||
for (unsigned y = 0; y < 9; ++y) {
|
||||
if (std::memcmp(padded.data() + y * pitch, input.data() + y * 17 * channels, rowBytes))
|
||||
throw std::runtime_error("CUDA array-to-device pitched copy mismatch");
|
||||
if (!std::all_of(padded.begin() + y * pitch + rowBytes, padded.begin() + (y + 1) * pitch, [](uint8_t value) { return value == 0xee; }))
|
||||
throw std::runtime_error("CUDA array-to-device copy overwrote pitch padding");
|
||||
}
|
||||
}
|
||||
}
|
||||
cuda.check(cuda.cuCtxSynchronize(), "cuCtxSynchronize");
|
||||
std::printf("PASS CUDA_IMAGES architecture=gfx1201 iterations=%u descriptor_and_storage=1 pitched_device_copies=1\n", args.iterations);
|
||||
return 0;
|
||||
} catch (const std::exception& e) {
|
||||
std::fprintf(stderr, "FAIL CUDA_IMAGES %s\n", e.what());
|
||||
loaded_modules();
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
#include "hip_api.h"
|
||||
#include "cuda_api.h"
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
// Public synthetic matrix fragments, independent of NVIDIA DLLs and weights.
|
||||
static constexpr char source[] = R"PTX(
|
||||
.version 7.0
|
||||
.target sm_75
|
||||
.address_size 64
|
||||
.visible .entry d4r_f16_mma_reference(
|
||||
.param .u64 A, .param .u64 B, .param .u64 C, .param .u64 D)
|
||||
{
|
||||
.reg .b32 %r<24>;
|
||||
.reg .b64 %rd<12>;
|
||||
.reg .b16 %h<4>;
|
||||
ld.param.u64 %rd0, [A];
|
||||
ld.param.u64 %rd1, [B];
|
||||
ld.param.u64 %rd2, [C];
|
||||
ld.param.u64 %rd3, [D];
|
||||
mov.u32 %r0, %ctaid.x;
|
||||
mov.u32 %r1, %tid.x;
|
||||
shr.u32 %r2, %r1, 2;
|
||||
and.b32 %r3, %r1, 3;
|
||||
shl.b32 %r3, %r3, 1;
|
||||
mul.wide.u32 %rd4, %r0, 512;
|
||||
add.u64 %rd0, %rd0, %rd4;
|
||||
mul.wide.u32 %rd4, %r0, 256;
|
||||
add.u64 %rd1, %rd1, %rd4;
|
||||
add.u64 %rd2, %rd2, %rd4;
|
||||
add.u64 %rd3, %rd3, %rd4;
|
||||
mad.lo.u32 %r4, %r2, 16, %r3;
|
||||
mul.wide.u32 %rd4, %r4, 2;
|
||||
add.u64 %rd5, %rd0, %rd4;
|
||||
ld.global.u32 %r8, [%rd5];
|
||||
ld.global.u32 %r9, [%rd5+256];
|
||||
ld.global.u32 %r10, [%rd5+16];
|
||||
ld.global.u32 %r11, [%rd5+272];
|
||||
mad.lo.u32 %r4, %r3, 8, %r2;
|
||||
mul.wide.u32 %rd4, %r4, 2;
|
||||
add.u64 %rd5, %rd1, %rd4;
|
||||
ld.global.b16 %h0, [%rd5];
|
||||
ld.global.b16 %h1, [%rd5+16];
|
||||
mov.b32 %r12, {%h0,%h1};
|
||||
ld.global.b16 %h2, [%rd5+128];
|
||||
ld.global.b16 %h3, [%rd5+144];
|
||||
mov.b32 %r13, {%h2,%h3};
|
||||
mad.lo.u32 %r4, %r2, 8, %r3;
|
||||
mul.wide.u32 %rd4, %r4, 2;
|
||||
add.u64 %rd5, %rd2, %rd4;
|
||||
ld.global.u32 %r14, [%rd5];
|
||||
ld.global.u32 %r15, [%rd5+128];
|
||||
mma.sync.aligned.m16n8k16.row.col.f16.f16.f16.f16
|
||||
{%r16,%r17}, {%r8,%r9,%r10,%r11}, {%r12,%r13}, {%r14,%r15};
|
||||
add.u64 %rd5, %rd3, %rd4;
|
||||
st.global.u32 [%rd5], %r16;
|
||||
st.global.u32 [%rd5+128], %r17;
|
||||
ret;
|
||||
}
|
||||
)PTX";
|
||||
|
||||
static uint16_t half_bits(double value) {
|
||||
_Float16 half = static_cast<_Float16>(value);
|
||||
uint16_t bits;
|
||||
std::memcpy(&bits, &half, sizeof(bits));
|
||||
return bits;
|
||||
}
|
||||
static double half_value(uint16_t bits) {
|
||||
_Float16 half;
|
||||
std::memcpy(&half, &bits, sizeof(bits));
|
||||
return static_cast<double>(half);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
using namespace d4r::diag;
|
||||
start();
|
||||
try {
|
||||
Args args(argc, argv);
|
||||
HipApi hip(args.hip_root);
|
||||
hipDeviceProp_t props{};
|
||||
hip.select_gfx1201(args.device, props);
|
||||
CudaApi cuda(args.cuda_dll);
|
||||
cuda.check(cuda.cuInit(0), "cuInit");
|
||||
CUdevice device = -1;
|
||||
int count = 0;
|
||||
cuda.check(cuda.cuDeviceGetCount(&count), "cuDeviceGetCount");
|
||||
for (int i = 0; i < count; ++i) {
|
||||
CUdevice candidate;
|
||||
int bus, pci, domain;
|
||||
cuda.check(cuda.cuDeviceGet(&candidate, i), "cuDeviceGet");
|
||||
cuda.check(cuda.cuDeviceGetAttribute(&bus, 33, candidate), "PCI bus");
|
||||
cuda.check(cuda.cuDeviceGetAttribute(&pci, 34, candidate), "PCI device");
|
||||
cuda.check(cuda.cuDeviceGetAttribute(&domain, 50, candidate), "PCI domain");
|
||||
if (bus == props.pciBusID && pci == props.pciDeviceID && domain == props.pciDomainID) device = candidate;
|
||||
}
|
||||
if (device < 0) throw std::runtime_error("CUDA/HIP PCI identity mismatch");
|
||||
CUcontext context = nullptr;
|
||||
cuda.check(cuda.cuDevicePrimaryCtxRetain(&context, device), "primary context");
|
||||
struct Context { CudaApi& api; CUdevice device; ~Context() { api.cuCtxSetCurrent(nullptr); api.cuDevicePrimaryCtxRelease_v2(device); } } context_owner{cuda, device};
|
||||
cuda.check(cuda.cuCtxSetCurrent(context), "cuCtxSetCurrent");
|
||||
CUmodule module = nullptr;
|
||||
cuda.check(cuda.cuModuleLoadData(&module, source), "MMA PTX JIT");
|
||||
struct Module { CudaApi& api; CUmodule module; ~Module() { api.cuModuleUnload(module); } } module_owner{cuda, module};
|
||||
CUfunction kernel = nullptr;
|
||||
cuda.check(cuda.cuModuleGetFunction(&kernel, module, "d4r_f16_mma_reference"), "MMA kernel");
|
||||
constexpr size_t cases = 512;
|
||||
std::vector<uint16_t> a(cases * 256), b(cases * 128), c(cases * 128), d(cases * 128);
|
||||
uint32_t random = 0x192a321u;
|
||||
auto next = [&]() { random = random * 1664525u + 1013904223u; return random; };
|
||||
for (size_t n = 0; n < cases; ++n) {
|
||||
for (size_t i = 0; i < 256; ++i) a[n * 256 + i] = uint16_t(0x2800u + (next() >> 20));
|
||||
for (size_t i = 0; i < 128; ++i) {
|
||||
b[n * 128 + i] = uint16_t((next() & 0x8000u) | (0x3000u + (next() >> 20)));
|
||||
c[n * 128 + i] = half_bits(n % 3 ? 0. : 0.015625);
|
||||
}
|
||||
// Sweep large cancellation terms and a tiny surviving product
|
||||
// across K positions; this exposes loss hidden by integer fixtures.
|
||||
if (n < 256) {
|
||||
for (size_t i = 0; i < 256; ++i) a[n * 256 + i] = half_bits(1.);
|
||||
std::fill(b.begin() + n * 128, b.begin() + (n + 1) * 128, half_bits(0.));
|
||||
for (size_t col = 0; col < 8; ++col) {
|
||||
b[n * 128 + (n % 16) * 8 + col] = half_bits(32768.);
|
||||
b[n * 128 + ((n % 16 + 1) % 16) * 8 + col] = half_bits(-32768.);
|
||||
b[n * 128 + ((n % 16 + 2 + n / 16 % 14) % 16) * 8 + col] = half_bits(0.0009765625);
|
||||
}
|
||||
}
|
||||
}
|
||||
struct Allocation {
|
||||
CudaApi& api; CUdeviceptr pointer = 0;
|
||||
Allocation(CudaApi& api, size_t bytes) : api(api) { api.check(api.cuMemAlloc_v2(&pointer, bytes), "MMA allocate"); }
|
||||
~Allocation() { if (pointer) api.cuMemFree_v2(pointer); }
|
||||
} da(cuda, a.size() * 2), db(cuda, b.size() * 2), dc(cuda, c.size() * 2), dd(cuda, d.size() * 2);
|
||||
cuda.check(cuda.cuMemcpyHtoD_v2(da.pointer, a.data(), a.size() * 2), "MMA A upload");
|
||||
cuda.check(cuda.cuMemcpyHtoD_v2(db.pointer, b.data(), b.size() * 2), "MMA B upload");
|
||||
cuda.check(cuda.cuMemcpyHtoD_v2(dc.pointer, c.data(), c.size() * 2), "MMA C upload");
|
||||
std::fill(d.begin(), d.end(), uint16_t(0x7e00));
|
||||
cuda.check(cuda.cuMemcpyHtoD_v2(dd.pointer, d.data(), d.size() * 2), "MMA NaN sentinel");
|
||||
void* parameters[] = {&da.pointer, &db.pointer, &dc.pointer, &dd.pointer};
|
||||
cuda.check(cuda.cuLaunchKernel(kernel, cases, 1, 1, 32, 1, 1, 0, nullptr, parameters, nullptr), "MMA launch");
|
||||
cuda.check(cuda.cuCtxSynchronize(), "MMA synchronize");
|
||||
cuda.check(cuda.cuMemcpyDtoH_v2(d.data(), dd.pointer, d.size() * 2), "MMA download");
|
||||
size_t mismatch = 0;
|
||||
for (size_t n = 0; n < cases; ++n)
|
||||
for (size_t row = 0; row < 16; ++row)
|
||||
for (size_t col = 0; col < 8; ++col) {
|
||||
double sum = half_value(c[n * 128 + row * 8 + col]);
|
||||
for (size_t k = 0; k < 16; ++k)
|
||||
sum += half_value(a[n * 256 + row * 16 + k]) * half_value(b[n * 128 + k * 8 + col]);
|
||||
const uint16_t expected = half_bits(sum);
|
||||
const uint16_t actual = d[n * 128 + row * 8 + col];
|
||||
if (expected != actual) {
|
||||
if (mismatch++ < 8) std::fprintf(stderr, "MMA_DIFFERENT case=%zu row=%zu column=%zu expected=%04x actual=%04x\n", n, row, col, expected, actual);
|
||||
}
|
||||
}
|
||||
std::printf("MMA_REFERENCE cases=%zu elements=%zu differing=%zu\n", cases, d.size(), mismatch);
|
||||
if (mismatch) return 5;
|
||||
std::printf("PASS CUDA_F16_MMA architecture=gfx1201 cases=%zu exact_f16=1\n", cases);
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::fprintf(stderr, "FAIL CUDA_F16_MMA %s\n", error.what());
|
||||
loaded_modules();
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
#pragma once
|
||||
#include "d3d12_external.h"
|
||||
#include <cstring>
|
||||
|
||||
namespace d4r::win {
|
||||
// Diagnostic timestamps, read only after the copy slot's existing completion
|
||||
// fence. No extra queue/CPU wait, and no image readback. The interval across
|
||||
// the external fence includes HIP execution and scheduling, not just kernels.
|
||||
class BoundaryTiming {
|
||||
ComPtr<ID3D12QueryHeap> queries_;
|
||||
ComPtr<ID3D12Resource> readback_;
|
||||
uint64_t ticket_ = 0;
|
||||
public:
|
||||
explicit BoundaryTiming(ID3D12Device* device) {
|
||||
D3D12_QUERY_HEAP_DESC query{};
|
||||
query.Type = D3D12_QUERY_HEAP_TYPE_TIMESTAMP; query.Count = 4;
|
||||
dx(device->CreateQueryHeap(&query, IID_PPV_ARGS(queries_.GetAddressOf())), "Boundary timestamp heap");
|
||||
D3D12_HEAP_PROPERTIES heap{}; heap.Type = D3D12_HEAP_TYPE_READBACK;
|
||||
D3D12_RESOURCE_DESC desc{};
|
||||
desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; desc.Width = 4 * sizeof(uint64_t);
|
||||
desc.Height = 1; desc.DepthOrArraySize = 1; desc.MipLevels = 1;
|
||||
desc.SampleDesc.Count = 1; desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
|
||||
dx(device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc,
|
||||
D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(readback_.GetAddressOf())), "Boundary timestamp readback");
|
||||
}
|
||||
void input_begin(ID3D12GraphicsCommandList* list) { list->EndQuery(queries_.Get(), D3D12_QUERY_TYPE_TIMESTAMP, 0); }
|
||||
void input_end(ID3D12GraphicsCommandList* list) { list->EndQuery(queries_.Get(), D3D12_QUERY_TYPE_TIMESTAMP, 1); }
|
||||
void output_begin(ID3D12GraphicsCommandList* list) { list->EndQuery(queries_.Get(), D3D12_QUERY_TYPE_TIMESTAMP, 2); }
|
||||
void output_end(ID3D12GraphicsCommandList* list) {
|
||||
list->EndQuery(queries_.Get(), D3D12_QUERY_TYPE_TIMESTAMP, 3);
|
||||
list->ResolveQueryData(queries_.Get(), D3D12_QUERY_TYPE_TIMESTAMP, 0, 4, readback_.Get(), 0);
|
||||
}
|
||||
void submitted(uint64_t ticket) { ticket_ = ticket; }
|
||||
// Caller has observed the slot's completion fence, including ResolveQueryData.
|
||||
void report(ID3D12CommandQueue* queue, const void* feature) {
|
||||
if (!ticket_) return;
|
||||
uint64_t ticks[4]{}, frequency = 0;
|
||||
D3D12_RANGE read{0, sizeof(ticks)}, written{0, 0};
|
||||
void* mapped = nullptr;
|
||||
dx(readback_->Map(0, &read, &mapped), "Map completed boundary timestamps");
|
||||
std::memcpy(ticks, mapped, sizeof(ticks)); readback_->Unmap(0, &written);
|
||||
dx(queue->GetTimestampFrequency(&frequency), "Boundary timestamp frequency");
|
||||
if (!frequency || !ticks[0] || ticks[0] > ticks[1] || ticks[1] > ticks[2] || ticks[2] > ticks[3])
|
||||
throw std::runtime_error("Invalid completed boundary timestamps");
|
||||
const double ms = 1000. / double(frequency);
|
||||
std::printf("D4R_GPU_BOUNDARY feature=%p ticket=%llu input_copy_ms=%.6f external_span_ms=%.6f output_copy_ms=%.6f total_ms=%.6f input_start_ticks=%llu input_end_ticks=%llu output_start_ticks=%llu output_end_ticks=%llu frequency=%llu diagnostics_cpu_bytes=32 serializing=0\n",
|
||||
feature, (unsigned long long)ticket_, double(ticks[1] - ticks[0]) * ms,
|
||||
double(ticks[2] - ticks[1]) * ms, double(ticks[3] - ticks[2]) * ms,
|
||||
double(ticks[3] - ticks[0]) * ms, (unsigned long long)ticks[0],
|
||||
(unsigned long long)ticks[1], (unsigned long long)ticks[2],
|
||||
(unsigned long long)ticks[3], (unsigned long long)frequency);
|
||||
ticket_ = 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,654 +0,0 @@
|
||||
#include "d3d12_command_hooks.h"
|
||||
#include "d3d12_newer_commands.h"
|
||||
#include "d3d12_retirement.h"
|
||||
#include <MinHook.h>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
|
||||
namespace d4r::win::commands {
|
||||
namespace {
|
||||
thread_local bool internal = false;
|
||||
std::mutex map_mutex, installation_mutex, error_mutex;
|
||||
std::string last_error;
|
||||
bool installed = false;
|
||||
using Clock = std::chrono::steady_clock;
|
||||
bool profile_commands() { static const bool enabled = std::getenv("D4R_PROFILE_STAGES") != nullptr; return enabled; }
|
||||
double milliseconds(Clock::time_point start) { return std::chrono::duration<double, std::milli>(Clock::now() - start).count(); }
|
||||
struct Event { UINT metadata; std::vector<uint8_t> data; };
|
||||
using State = std::function<void(ID3D12GraphicsCommandList*)>;
|
||||
struct TrackedResource {
|
||||
// D3D12 command lists do not retain resource references. The caller keeps
|
||||
// them alive through submission/completion. An extra tracking reference
|
||||
// would keep swap-chain buffers alive and make ResizeBuffers fail.
|
||||
ResourceAccess state;
|
||||
};
|
||||
using ResourceStates = std::unordered_map<ID3D12Resource*, TrackedResource>;
|
||||
static constexpr GUID submitted_access_tag = {0x741b92df,0x9b13,0x4fc9,{0xb0,0x02,0x2c,0xe3,0x5a,0x68,0x92,0x41}};
|
||||
void publish_states(const ResourceStates& states) {
|
||||
for (const auto& entry : states) {
|
||||
// Buffers/simultaneous-access textures have automatic legacy state
|
||||
// decay at ExecuteCommandLists completion. Only ordinary textures
|
||||
// retain these explicit transitions across submission boundaries.
|
||||
const auto desc = resource_desc(entry.first);
|
||||
if (desc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER || (desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS)) continue;
|
||||
dx(entry.first->SetPrivateData(submitted_access_tag, sizeof(ResourceAccess), &entry.second.state), "Publish submitted texture access");
|
||||
}
|
||||
}
|
||||
struct Segment {
|
||||
// The application's original list is a weak pointer; holding it in its
|
||||
// own routing map would prevent final Release and leak the entire feature.
|
||||
ID3D12GraphicsCommandList* list = nullptr;
|
||||
ComPtr<ID3D12GraphicsCommandList> owner;
|
||||
ComPtr<ID3D12CommandAllocator> allocator;
|
||||
Boundary boundary;
|
||||
ResourceStates resource_states;
|
||||
};
|
||||
struct Recording {
|
||||
std::recursive_mutex mutex;
|
||||
ID3D12GraphicsCommandList* original;
|
||||
ComPtr<ID3D12GraphicsCommandList> current;
|
||||
ComPtr<ID3D12CommandAllocator> current_allocator;
|
||||
std::vector<Segment> segments;
|
||||
std::vector<State> state;
|
||||
std::vector<Event> events;
|
||||
ResourceStates resource_states;
|
||||
unsigned active_queries = 0;
|
||||
bool render_pass = false, predication = false, indirect_state_unknown = false, closed = false;
|
||||
explicit Recording(ID3D12GraphicsCommandList* list) : original(list) {}
|
||||
};
|
||||
std::unordered_map<ID3D12GraphicsCommandList*, std::shared_ptr<Recording>> recordings;
|
||||
std::atomic<uint64_t> recording_generation{0};
|
||||
struct RecordingCache {
|
||||
ID3D12GraphicsCommandList* list = nullptr;
|
||||
uint64_t generation = 0;
|
||||
// A thread's last lookup must not retain suffix lists, allocators or game
|
||||
// resources after Reset/final Release. Address reuse also needs a new
|
||||
// generation, even if the old Recording is alive in a submit callback.
|
||||
std::weak_ptr<Recording> value;
|
||||
};
|
||||
thread_local RecordingCache recording_cache;
|
||||
std::shared_ptr<Recording> get_recording(ID3D12GraphicsCommandList* self, bool create = true) {
|
||||
if (internal) return {};
|
||||
const auto generation = recording_generation.load(std::memory_order_acquire);
|
||||
if (recording_cache.list == self && recording_cache.generation == generation) {
|
||||
if (auto value = recording_cache.value.lock()) return value;
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(map_mutex);
|
||||
auto found = recordings.find(self);
|
||||
std::shared_ptr<Recording> value;
|
||||
if (found != recordings.end()) value = found->second;
|
||||
else {
|
||||
if (!create) return {};
|
||||
value = std::make_shared<Recording>(self);
|
||||
recordings.emplace(self, value);
|
||||
recording_generation.fetch_add(1, std::memory_order_release);
|
||||
}
|
||||
recording_cache.list = self;
|
||||
recording_cache.value = value;
|
||||
recording_cache.generation = recording_generation.load(std::memory_order_relaxed);
|
||||
return value;
|
||||
}
|
||||
void forget(ID3D12GraphicsCommandList* self) {
|
||||
std::shared_ptr<Recording> released;
|
||||
{ std::lock_guard<std::mutex> lock(map_mutex);
|
||||
auto it = recordings.find(self);
|
||||
if (it != recordings.end()) {
|
||||
released = std::move(it->second); recordings.erase(it);
|
||||
recording_generation.fetch_add(1, std::memory_order_release);
|
||||
} }
|
||||
// Release retained COM objects without holding the map mutex.
|
||||
}
|
||||
struct HookSample {
|
||||
uint32_t random = GetCurrentThreadId() | 1u;
|
||||
uint64_t calls = 0, samples = 0, marked = 0;
|
||||
double access_ms = 0, capture_ms = 0, driver_ms = 0;
|
||||
Clock::time_point since = Clock::now();
|
||||
bool choose() {
|
||||
++calls; random ^= random << 13; random ^= random >> 17; random ^= random << 5;
|
||||
return !(random & 63u);
|
||||
}
|
||||
void flush() {
|
||||
const double elapsed = milliseconds(since);
|
||||
if (elapsed < 2000) return;
|
||||
std::printf("D4R_COMMAND_HOOK_SAMPLE thread=%lu period_ms=%.3f calls=%llu samples=%llu marked=%llu access_sum_ms=%.6f capture_sum_ms=%.6f driver_sum_ms=%.6f sampling=64\n",
|
||||
GetCurrentThreadId(), elapsed, (unsigned long long)calls, (unsigned long long)samples,
|
||||
(unsigned long long)marked, access_ms, capture_ms, driver_ms);
|
||||
calls = samples = marked = 0; access_ms = capture_ms = driver_ms = 0; since = Clock::now();
|
||||
}
|
||||
};
|
||||
thread_local HookSample hook_sample;
|
||||
bool sample_hooks() { static const bool enabled = std::getenv("D4R_PROFILE_COMMAND_HOOKS") != nullptr; return enabled; }
|
||||
struct Access {
|
||||
std::shared_ptr<Recording> recording;
|
||||
std::unique_lock<std::recursive_mutex> lock;
|
||||
ID3D12GraphicsCommandList* self;
|
||||
bool sampled = false;
|
||||
Clock::time_point begin{}, acquired{}, driver{};
|
||||
explicit Access(ID3D12GraphicsCommandList* list) : self(list) {
|
||||
sampled = !internal && sample_hooks() && hook_sample.choose();
|
||||
if (sampled) begin = Clock::now();
|
||||
recording = get_recording(list);
|
||||
if (recording) lock = std::unique_lock<std::recursive_mutex>(recording->mutex);
|
||||
if (sampled) acquired = Clock::now();
|
||||
}
|
||||
void driver_call() {
|
||||
if (sampled) driver = Clock::now();
|
||||
}
|
||||
~Access() {
|
||||
if (!sampled) return;
|
||||
++hook_sample.samples;
|
||||
hook_sample.access_ms += std::chrono::duration<double, std::milli>(acquired - begin).count();
|
||||
if (driver != Clock::time_point{}) {
|
||||
++hook_sample.marked;
|
||||
hook_sample.capture_ms += std::chrono::duration<double, std::milli>(driver - acquired).count();
|
||||
hook_sample.driver_ms += milliseconds(driver);
|
||||
}
|
||||
hook_sample.flush();
|
||||
}
|
||||
ID3D12GraphicsCommandList* target() const {
|
||||
return recording && recording->current ? recording->current.Get() : self;
|
||||
}
|
||||
};
|
||||
template<class T> ComPtr<T> retain(T* pointer) { return ComPtr<T>(pointer); }
|
||||
template<class T> std::vector<T> copy_values(const T* pointer, size_t count) {
|
||||
return pointer ? std::vector<T>(pointer, pointer + count) : std::vector<T>();
|
||||
}
|
||||
template<class T> T* values_pointer(std::vector<T>& values) { return values.empty() ? nullptr : values.data(); }
|
||||
template<class T> struct RetainedArray { std::vector<ComPtr<T>> owners; std::vector<T*> raw; };
|
||||
template<class T> RetainedArray<T> retain_values(T* const* pointer, size_t count) {
|
||||
RetainedArray<T> result;
|
||||
for (size_t i = 0; pointer && i < count; ++i) { result.owners.emplace_back(pointer[i]); result.raw.push_back(pointer[i]); }
|
||||
return result;
|
||||
}
|
||||
struct Hook { void* target; void* replacement; };
|
||||
std::vector<Hook> attached;
|
||||
void attach_method(void* target, void* replacement, void** original, const char* name) {
|
||||
for (const auto& hook : attached) if (hook.target == target)
|
||||
throw std::runtime_error(std::string("Shared D3D12 implementation needs an explicit alias adapter: ") + name);
|
||||
const auto result = MH_CreateHook(target, replacement, original);
|
||||
if (result != MH_OK) throw std::runtime_error(std::string("Cannot intercept D3D12 ") + name + ": " + MH_StatusToString(result));
|
||||
attached.push_back({target, replacement});
|
||||
std::printf("D4R_D3D12_HOOK method=%s\n", name);
|
||||
}
|
||||
|
||||
#include "d3d12_command_hooks.generated.h"
|
||||
|
||||
static constexpr GUID signature_description_tag = {0xbdda6148,0x46f0,0x4d82,{0xa1,0x90,0x08,0x47,0x7e,0xda,0x32,0x12}};
|
||||
using SignatureFn = HRESULT(STDMETHODCALLTYPE*)(ID3D12Device*, const D3D12_COMMAND_SIGNATURE_DESC*, ID3D12RootSignature*, REFIID, void**);
|
||||
SignatureFn original_signature = nullptr;
|
||||
using CreateListFn = HRESULT(STDMETHODCALLTYPE*)(ID3D12Device*, UINT, D3D12_COMMAND_LIST_TYPE,
|
||||
ID3D12CommandAllocator*, ID3D12PipelineState*, REFIID, void**);
|
||||
CreateListFn original_create_list = nullptr;
|
||||
HRESULT STDMETHODCALLTYPE hook_create_list(ID3D12Device* device, UINT node, D3D12_COMMAND_LIST_TYPE type,
|
||||
ID3D12CommandAllocator* allocator, ID3D12PipelineState* initial, REFIID iid, void** out) {
|
||||
const auto result = original_create_list(device, node, type, allocator, initial, iid, out);
|
||||
if (!internal && SUCCEEDED(result) && out && *out) try {
|
||||
ComPtr<ID3D12GraphicsCommandList> list;
|
||||
if (SUCCEEDED(static_cast<IUnknown*>(*out)->QueryInterface(IID_PPV_ARGS(list.GetAddressOf())))) {
|
||||
auto recording = get_recording(list.Get());
|
||||
std::lock_guard<std::recursive_mutex> lock(recording->mutex);
|
||||
auto pipeline = retain(initial);
|
||||
if (pipeline) recording->state.emplace_back([pipeline](ID3D12GraphicsCommandList* target) {
|
||||
original_SetPipelineState(target, pipeline.Get());
|
||||
});
|
||||
}
|
||||
} catch (const std::exception& failure) {
|
||||
// A public Create must retain its result even if tracking runs out of
|
||||
// memory. A later split must fail explicitly instead of replaying an
|
||||
// incomplete initial state.
|
||||
{ std::lock_guard<std::mutex> lock(error_mutex); last_error = failure.what(); }
|
||||
std::fprintf(stderr, "D4R_CREATE_LIST_TRACK_FAILURE %s\n", failure.what());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE hook_signature(ID3D12Device* device, const D3D12_COMMAND_SIGNATURE_DESC* desc, ID3D12RootSignature* root, REFIID iid, void** out) {
|
||||
const auto result = original_signature(device, desc, root, iid, out);
|
||||
if (SUCCEEDED(result) && out && *out && desc && desc->NumArgumentDescs <= 4096) try {
|
||||
ComPtr<ID3D12CommandSignature> signature;
|
||||
if (SUCCEEDED(static_cast<IUnknown*>(*out)->QueryInterface(IID_PPV_ARGS(signature.GetAddressOf())))) {
|
||||
std::vector<uint8_t> value(sizeof(UINT) + desc->NumArgumentDescs * sizeof(D3D12_INDIRECT_ARGUMENT_DESC));
|
||||
std::memcpy(value.data(), &desc->NumArgumentDescs, sizeof(UINT));
|
||||
if (desc->NumArgumentDescs)
|
||||
std::memcpy(value.data() + sizeof(UINT), desc->pArgumentDescs, value.size() - sizeof(UINT));
|
||||
const auto tagged = signature->SetPrivateData(signature_description_tag, UINT(value.size()), value.data());
|
||||
if (FAILED(tagged)) std::fprintf(stderr, "D4R_SIGNATURE_TRACK_FAILURE result=0x%08x\n", unsigned(tagged));
|
||||
else std::fprintf(stderr, "D4R_COMMAND_SIGNATURE arguments=%u tracked=1\n", desc->NumArgumentDescs);
|
||||
}
|
||||
} catch (const std::exception& failure) {
|
||||
// Preserve the successful public API call. An untracked signature is
|
||||
// rejected at a later split instead of unwinding into the application.
|
||||
std::fprintf(stderr, "D4R_SIGNATURE_TRACK_FAILURE %s\n", failure.what());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
void STDMETHODCALLTYPE hook_SetProgram(ID3D12GraphicsCommandList* self, const NewProgramDescription* desc) {
|
||||
Access access(self);
|
||||
if (access.recording && desc) {
|
||||
auto saved = *desc;
|
||||
// INITIALIZE changes backing memory. Rebinding an already initialized
|
||||
// work graph in the suffix must preserve its prefix's GPU state.
|
||||
if (saved.type == 5) saved.work_graph.flags &= ~1u;
|
||||
access.recording->state.emplace_back([saved](ID3D12GraphicsCommandList* target) { original_SetProgram(target, &saved); });
|
||||
}
|
||||
original_SetProgram(access.target(), desc);
|
||||
}
|
||||
|
||||
using ReleaseFn = ULONG(STDMETHODCALLTYPE*)(IUnknown*);
|
||||
ReleaseFn original_release = nullptr;
|
||||
ULONG STDMETHODCALLTYPE hook_release(IUnknown* self) {
|
||||
const ULONG count = original_release(self);
|
||||
if (!count) forget(reinterpret_cast<ID3D12GraphicsCommandList*>(self));
|
||||
return count;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE hook_Close(ID3D12GraphicsCommandList* self) {
|
||||
Access access(self);
|
||||
const auto result = original_Close(access.target());
|
||||
if (SUCCEEDED(result) && access.recording) access.recording->closed = true;
|
||||
return result;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE hook_Reset(ID3D12GraphicsCommandList* self, ID3D12CommandAllocator* allocator, ID3D12PipelineState* initial_state) {
|
||||
if (internal) return original_Reset(self, allocator, initial_state);
|
||||
forget(self);
|
||||
const auto result = original_Reset(self, allocator, initial_state);
|
||||
if (SUCCEEDED(result)) {
|
||||
auto recording = get_recording(self);
|
||||
auto pipeline = retain(initial_state);
|
||||
recording->state.emplace_back([pipeline](ID3D12GraphicsCommandList* target) {
|
||||
if (pipeline) original_SetPipelineState(target, pipeline.Get());
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
void STDMETHODCALLTYPE hook_ClearState(ID3D12GraphicsCommandList* self, ID3D12PipelineState* pipeline_state) {
|
||||
Access access(self);
|
||||
if (access.recording) {
|
||||
access.recording->state.clear(); access.recording->indirect_state_unknown = false;
|
||||
auto pipeline = retain(pipeline_state);
|
||||
access.recording->state.emplace_back([pipeline](ID3D12GraphicsCommandList* target) { original_ClearState(target, pipeline.Get()); });
|
||||
}
|
||||
original_ClearState(access.target(), pipeline_state);
|
||||
}
|
||||
void STDMETHODCALLTYPE hook_ResourceBarrier(ID3D12GraphicsCommandList* self, UINT barrier_count, const D3D12_RESOURCE_BARRIER* barriers) {
|
||||
Access access(self);
|
||||
if (access.recording) for (UINT i = 0; i < barrier_count; ++i)
|
||||
if (barriers[i].Type == D3D12_RESOURCE_BARRIER_TYPE_TRANSITION &&
|
||||
(barriers[i].Transition.Subresource == 0 || barriers[i].Transition.Subresource == D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES)) {
|
||||
auto& tracked = access.recording->resource_states[barriers[i].Transition.pResource];
|
||||
auto& state = tracked.state;
|
||||
state = ResourceAccess(barriers[i].Transition.StateAfter);
|
||||
state.pending_split = barriers[i].Flags == D3D12_RESOURCE_BARRIER_FLAG_BEGIN_ONLY;
|
||||
}
|
||||
original_ResourceBarrier(access.target(), barrier_count, barriers);
|
||||
}
|
||||
void STDMETHODCALLTYPE hook_Barrier(ID3D12GraphicsCommandList* self, UINT32 barrier_groups_count, const D3D12_BARRIER_GROUP* barrier_groups) {
|
||||
Access access(self);
|
||||
if (access.recording) for (UINT32 g = 0; g < barrier_groups_count; ++g) if (barrier_groups[g].Type == D3D12_BARRIER_TYPE_TEXTURE)
|
||||
for (UINT32 b = 0; b < barrier_groups[g].NumBarriers; ++b) {
|
||||
const auto& barrier = barrier_groups[g].pTextureBarriers[b]; const auto& range = barrier.Subresources;
|
||||
const bool containsZero = range.NumMipLevels == 0 ? (range.IndexOrFirstMipLevel == 0 || range.IndexOrFirstMipLevel == UINT32_MAX) :
|
||||
range.IndexOrFirstMipLevel == 0 && range.FirstArraySlice == 0 && range.FirstPlane == 0;
|
||||
if (containsZero) {
|
||||
auto& tracked = access.recording->resource_states[barrier.pResource];
|
||||
auto& state = tracked.state; state.enhanced = true; state.inherited = false;
|
||||
state.layout = barrier.LayoutAfter; state.access = barrier.AccessAfter; state.sync = barrier.SyncAfter;
|
||||
state.pending_split = barrier.SyncAfter == D3D12_BARRIER_SYNC_SPLIT;
|
||||
}
|
||||
}
|
||||
original_Barrier(access.target(), barrier_groups_count, barrier_groups);
|
||||
}
|
||||
void STDMETHODCALLTYPE hook_BeginEvent(ID3D12GraphicsCommandList* self, UINT metadata, const void* data, UINT size) {
|
||||
Access access(self);
|
||||
if (access.recording) access.recording->events.push_back({metadata, copy_values(static_cast<const uint8_t*>(data), size)});
|
||||
original_BeginEvent(access.target(), metadata, data, size);
|
||||
}
|
||||
void STDMETHODCALLTYPE hook_EndEvent(ID3D12GraphicsCommandList* self) {
|
||||
Access access(self);
|
||||
if (access.recording && !access.recording->events.empty()) access.recording->events.pop_back();
|
||||
original_EndEvent(access.target());
|
||||
}
|
||||
void STDMETHODCALLTYPE hook_BeginRenderPass(ID3D12GraphicsCommandList* self, UINT render_target_count,
|
||||
const D3D12_RENDER_PASS_RENDER_TARGET_DESC* render_targets, const D3D12_RENDER_PASS_DEPTH_STENCIL_DESC* depth_stencil, D3D12_RENDER_PASS_FLAGS flags) {
|
||||
Access access(self); if (access.recording) access.recording->render_pass = true;
|
||||
original_BeginRenderPass(access.target(), render_target_count, render_targets, depth_stencil, flags);
|
||||
}
|
||||
void STDMETHODCALLTYPE hook_EndRenderPass(ID3D12GraphicsCommandList* self) {
|
||||
Access access(self); if (access.recording) access.recording->render_pass = false;
|
||||
original_EndRenderPass(access.target());
|
||||
}
|
||||
void STDMETHODCALLTYPE hook_BeginQuery(ID3D12GraphicsCommandList* self, ID3D12QueryHeap* heap, D3D12_QUERY_TYPE type, UINT index) {
|
||||
Access access(self); if (access.recording) ++access.recording->active_queries;
|
||||
original_BeginQuery(access.target(), heap, type, index);
|
||||
}
|
||||
void STDMETHODCALLTYPE hook_EndQuery(ID3D12GraphicsCommandList* self, ID3D12QueryHeap* heap, D3D12_QUERY_TYPE type, UINT index) {
|
||||
Access access(self); if (access.recording && access.recording->active_queries) --access.recording->active_queries;
|
||||
original_EndQuery(access.target(), heap, type, index);
|
||||
}
|
||||
void STDMETHODCALLTYPE hook_SetPredication(ID3D12GraphicsCommandList* self, ID3D12Resource* buffer, UINT64 aligned_buffer_offset, D3D12_PREDICATION_OP operation) {
|
||||
Access access(self);
|
||||
if (access.recording) {
|
||||
access.recording->predication = buffer != nullptr;
|
||||
auto retained = retain(buffer);
|
||||
access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) { original_SetPredication(target, retained.Get(), aligned_buffer_offset, operation); });
|
||||
}
|
||||
original_SetPredication(access.target(), buffer, aligned_buffer_offset, operation);
|
||||
}
|
||||
void STDMETHODCALLTYPE hook_ExecuteBundle(ID3D12GraphicsCommandList* self, ID3D12GraphicsCommandList* command_list) {
|
||||
Access access(self);
|
||||
auto bundle = get_recording(command_list, false);
|
||||
if (access.recording && bundle) {
|
||||
std::lock_guard<std::recursive_mutex> lock(bundle->mutex);
|
||||
access.recording->state.insert(access.recording->state.end(), bundle->state.begin(), bundle->state.end());
|
||||
access.recording->indirect_state_unknown |= bundle->indirect_state_unknown;
|
||||
} else if (access.recording) access.recording->indirect_state_unknown = true;
|
||||
original_ExecuteBundle(access.target(), command_list);
|
||||
}
|
||||
void STDMETHODCALLTYPE hook_ExecuteIndirect(ID3D12GraphicsCommandList* self, ID3D12CommandSignature* command_signature,
|
||||
UINT max_command_count, ID3D12Resource* arg_buffer, UINT64 arg_buffer_offset, ID3D12Resource* count_buffer, UINT64 count_buffer_offset) {
|
||||
Access access(self);
|
||||
if (access.recording) {
|
||||
UINT bytes = 0;
|
||||
(void)command_signature->GetPrivateData(signature_description_tag, &bytes, nullptr);
|
||||
if (bytes < sizeof(UINT) || bytes > sizeof(UINT) + 4096 * sizeof(D3D12_INDIRECT_ARGUMENT_DESC)) {
|
||||
access.recording->indirect_state_unknown = true;
|
||||
} else {
|
||||
std::vector<uint8_t> description(bytes);
|
||||
UINT count = 0;
|
||||
const auto result = command_signature->GetPrivateData(signature_description_tag, &bytes, description.data());
|
||||
std::memcpy(&count, description.data(), sizeof(count));
|
||||
if (FAILED(result) || count > 4096 || bytes != sizeof(UINT) + count * sizeof(D3D12_INDIRECT_ARGUMENT_DESC)) access.recording->indirect_state_unknown = true;
|
||||
else {
|
||||
std::vector<D3D12_INDIRECT_ARGUMENT_DESC> arguments(count);
|
||||
std::memcpy(arguments.data(), description.data() + sizeof(UINT), count * sizeof(D3D12_INDIRECT_ARGUMENT_DESC));
|
||||
bool compute = false;
|
||||
for (const auto& arg : arguments) compute |= arg.Type == D3D12_INDIRECT_ARGUMENT_TYPE_DISPATCH || arg.Type == D3D12_INDIRECT_ARGUMENT_TYPE_DISPATCH_RAYS;
|
||||
// Microsoft documents that only bindings modified by the
|
||||
// signature are reset to zero/NULL after ExecuteIndirect.
|
||||
access.recording->state.emplace_back([arguments, compute](ID3D12GraphicsCommandList* target) {
|
||||
for (const auto& arg : arguments) switch (arg.Type) {
|
||||
// Public SDK addition after the pinned MinGW header:
|
||||
// INCREMENTING_CONSTANT has the same first two UINTs.
|
||||
case static_cast<D3D12_INDIRECT_ARGUMENT_TYPE>(11):
|
||||
(compute ? original_SetComputeRoot32BitConstant : original_SetGraphicsRoot32BitConstant)
|
||||
(target, arg.Constant.RootParameterIndex, 0, arg.Constant.DestOffsetIn32BitValues); break;
|
||||
case D3D12_INDIRECT_ARGUMENT_TYPE_CONSTANT: {
|
||||
std::vector<UINT> zeros(arg.Constant.Num32BitValuesToSet);
|
||||
auto setter = compute ? original_SetComputeRoot32BitConstants : original_SetGraphicsRoot32BitConstants;
|
||||
setter(target, arg.Constant.RootParameterIndex, UINT(zeros.size()), zeros.data(), arg.Constant.DestOffsetIn32BitValues); break;
|
||||
}
|
||||
case D3D12_INDIRECT_ARGUMENT_TYPE_CONSTANT_BUFFER_VIEW:
|
||||
(compute ? original_SetComputeRootConstantBufferView : original_SetGraphicsRootConstantBufferView)(target, arg.ConstantBufferView.RootParameterIndex, 0); break;
|
||||
case D3D12_INDIRECT_ARGUMENT_TYPE_SHADER_RESOURCE_VIEW:
|
||||
(compute ? original_SetComputeRootShaderResourceView : original_SetGraphicsRootShaderResourceView)(target, arg.ShaderResourceView.RootParameterIndex, 0); break;
|
||||
case D3D12_INDIRECT_ARGUMENT_TYPE_UNORDERED_ACCESS_VIEW:
|
||||
(compute ? original_SetComputeRootUnorderedAccessView : original_SetGraphicsRootUnorderedAccessView)(target, arg.UnorderedAccessView.RootParameterIndex, 0); break;
|
||||
case D3D12_INDIRECT_ARGUMENT_TYPE_INDEX_BUFFER_VIEW: original_IASetIndexBuffer(target, nullptr); break;
|
||||
case D3D12_INDIRECT_ARGUMENT_TYPE_VERTEX_BUFFER_VIEW: {
|
||||
D3D12_VERTEX_BUFFER_VIEW empty{}; original_IASetVertexBuffers(target, arg.VertexBuffer.Slot, 1, &empty); break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
original_ExecuteIndirect(access.target(), command_signature, max_command_count, arg_buffer, arg_buffer_offset, count_buffer, count_buffer_offset);
|
||||
}
|
||||
|
||||
using ExecuteFn = void(STDMETHODCALLTYPE*)(ID3D12CommandQueue*, UINT, ID3D12CommandList* const*);
|
||||
ExecuteFn original_execute = nullptr;
|
||||
// Queue-owned synchronization metadata has no reference back to the queue.
|
||||
// It serializes a split submission against other threads using the same queue.
|
||||
static constexpr GUID submission_lock_tag = {0x6c6158ba,0x209f,0x4a09,{0x97,0x10,0xa7,0x03,0x52,0x32,0xa0,0x69}};
|
||||
std::mutex queue_metadata_mutex;
|
||||
struct SubmissionLock final : IUnknown {
|
||||
std::atomic<ULONG> references{1};
|
||||
std::recursive_mutex mutex;
|
||||
ComPtr<ID3D12Fence> completion_fence;
|
||||
Handle completion_event;
|
||||
uint64_t completion_value = 0;
|
||||
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void** out) override {
|
||||
if (!out) return E_POINTER; *out = nullptr;
|
||||
if (iid != IID_IUnknown) return E_NOINTERFACE;
|
||||
*out = static_cast<IUnknown*>(this); AddRef(); return S_OK;
|
||||
}
|
||||
ULONG STDMETHODCALLTYPE AddRef() override { return ++references; }
|
||||
ULONG STDMETHODCALLTYPE Release() override { const auto n = --references; if (!n) delete this; return n; }
|
||||
};
|
||||
ComPtr<SubmissionLock> submission_lock(ID3D12CommandQueue* queue) {
|
||||
std::lock_guard<std::mutex> lock(queue_metadata_mutex);
|
||||
ComPtr<SubmissionLock> result; UINT bytes = sizeof(IUnknown*);
|
||||
if (SUCCEEDED(queue->GetPrivateData(submission_lock_tag, &bytes, result.GetAddressOf()))) return result;
|
||||
result.Attach(new SubmissionLock);
|
||||
dx(queue->SetPrivateDataInterface(submission_lock_tag, result.Get()), "Queue submission metadata");
|
||||
return result;
|
||||
}
|
||||
using QueueFenceFn = HRESULT(STDMETHODCALLTYPE*)(ID3D12CommandQueue*, ID3D12Fence*, UINT64);
|
||||
QueueFenceFn original_queue_signal = nullptr, original_queue_wait = nullptr;
|
||||
HRESULT queue_fence(QueueFenceFn function, ID3D12CommandQueue* queue, ID3D12Fence* fence, UINT64 value) noexcept {
|
||||
if (internal) return function(queue, fence, value);
|
||||
try {
|
||||
auto queueLock = submission_lock(queue);
|
||||
std::lock_guard<std::recursive_mutex> ordered(queueLock->mutex);
|
||||
return function(queue, fence, value);
|
||||
} catch (const std::exception& failure) {
|
||||
{ std::lock_guard<std::mutex> lock(error_mutex); last_error = failure.what(); }
|
||||
std::fprintf(stderr, "D4R_QUEUE_FENCE_FAILURE %s\n", failure.what()); return E_FAIL;
|
||||
}
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE hook_queue_signal(ID3D12CommandQueue* queue, ID3D12Fence* fence, UINT64 value) {
|
||||
return queue_fence(original_queue_signal, queue, fence, value);
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE hook_queue_wait(ID3D12CommandQueue* queue, ID3D12Fence* fence, UINT64 value) {
|
||||
return queue_fence(original_queue_wait, queue, fence, value);
|
||||
}
|
||||
void complete(ID3D12CommandQueue* queue) {
|
||||
auto owner = submission_lock(queue);
|
||||
std::lock_guard<std::recursive_mutex> ordered(owner->mutex);
|
||||
if (!owner->completion_fence) {
|
||||
ComPtr<ID3D12Device> device; dx(queue->GetDevice(IID_PPV_ARGS(device.GetAddressOf())), "Queue device(submit drain)");
|
||||
dx(device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(owner->completion_fence.GetAddressOf())), "Queue fence(submit drain)");
|
||||
}
|
||||
if (!owner->completion_event.value) owner->completion_event.value = CreateEventW(nullptr, FALSE, FALSE, nullptr);
|
||||
if (!owner->completion_event.value) throw std::runtime_error("CreateEvent(submit drain)");
|
||||
const auto value = ++owner->completion_value;
|
||||
dx(queue->Signal(owner->completion_fence.Get(), value), "Queue Signal(submit drain)");
|
||||
if (owner->completion_fence->GetCompletedValue() < value) {
|
||||
dx(owner->completion_fence->SetEventOnCompletion(value, owner->completion_event.value), "Queue event(submit drain)");
|
||||
if (WaitForSingleObject(owner->completion_event.value, 30000) != WAIT_OBJECT_0) throw std::runtime_error("GPU submit boundary timed out");
|
||||
}
|
||||
}
|
||||
void retire(ID3D12CommandQueue* queue, std::vector<std::shared_ptr<Recording>> owners) {
|
||||
auto owner = submission_lock(queue);
|
||||
if (!owner->completion_fence) {
|
||||
ComPtr<ID3D12Device> device; dx(queue->GetDevice(IID_PPV_ARGS(device.GetAddressOf())), "Retirement device");
|
||||
dx(device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(owner->completion_fence.GetAddressOf())), "Retirement fence");
|
||||
}
|
||||
const auto value = ++owner->completion_value;
|
||||
dx(queue->Signal(owner->completion_fence.Get(), value), "Queue Signal(retirement)");
|
||||
GpuRetirement::instance().submit(owner->completion_fence.Get(), value,
|
||||
std::make_shared<std::vector<std::shared_ptr<Recording>>>(std::move(owners)));
|
||||
}
|
||||
void STDMETHODCALLTYPE hook_execute(ID3D12CommandQueue* queue, UINT count, ID3D12CommandList* const* lists) {
|
||||
if (internal) { original_execute(queue, count, lists); return; }
|
||||
const auto submitStart = Clock::now();
|
||||
double publishMs = 0, boundaryMs = 0, drainMs = 0;
|
||||
std::vector<std::shared_ptr<Recording>> owners;
|
||||
std::vector<ID3D12CommandList*> pending;
|
||||
std::vector<ResourceStates> pending_states;
|
||||
InternalScope scope;
|
||||
auto flush = [&] {
|
||||
if (!pending.empty()) {
|
||||
original_execute(queue, UINT(pending.size()), pending.data());
|
||||
// Publish in the actual submitted order, never while recording.
|
||||
// The callback's external fence waits for these producers on GPU.
|
||||
const auto publishStart = Clock::now();
|
||||
for (const auto& states : pending_states) publish_states(states);
|
||||
publishMs += milliseconds(publishStart);
|
||||
pending.clear(); pending_states.clear();
|
||||
}
|
||||
};
|
||||
bool boundaries = false;
|
||||
try {
|
||||
auto queueLock = submission_lock(queue);
|
||||
std::lock_guard<std::recursive_mutex> ordered(queueLock->mutex);
|
||||
for (UINT i = 0; i < count; ++i) {
|
||||
std::shared_ptr<Recording> recording;
|
||||
{ std::lock_guard<std::mutex> lock(map_mutex);
|
||||
auto found = recordings.find(static_cast<ID3D12GraphicsCommandList*>(lists[i]));
|
||||
if (found != recordings.end()) recording = found->second; }
|
||||
if (!recording) { pending.push_back(lists[i]); pending_states.emplace_back(); continue; }
|
||||
std::lock_guard<std::recursive_mutex> lock(recording->mutex);
|
||||
if (recording->segments.empty()) {
|
||||
pending.push_back(lists[i]); pending_states.push_back(recording->resource_states); continue;
|
||||
}
|
||||
if (!recording->closed) throw std::runtime_error("Logical command list submitted before Close");
|
||||
owners.push_back(recording); boundaries = true;
|
||||
for (const auto& segment : recording->segments) {
|
||||
pending.push_back(segment.list); pending_states.push_back(segment.resource_states); flush();
|
||||
const auto boundaryStart = Clock::now();
|
||||
segment.boundary(queue);
|
||||
boundaryMs += milliseconds(boundaryStart);
|
||||
}
|
||||
pending.push_back(recording->current.Get());
|
||||
pending_states.push_back(recording->resource_states);
|
||||
}
|
||||
flush();
|
||||
if (boundaries) {
|
||||
const auto drainStart = Clock::now();
|
||||
if (std::getenv("D4R_ASYNC_INTEROP")) retire(queue, std::move(owners)); else complete(queue);
|
||||
drainMs = milliseconds(drainStart);
|
||||
if (profile_commands()) std::printf("D4R_COMMAND_SUBMIT total_ms=%.3f boundary_ms=%.3f publish_ms=%.3f suffix_drain_ms=%.3f\n",
|
||||
milliseconds(submitStart), boundaryMs, publishMs, drainMs);
|
||||
}
|
||||
} catch (const std::exception& failure) {
|
||||
{ std::lock_guard<std::mutex> lock(error_mutex); last_error = failure.what(); }
|
||||
std::fprintf(stderr, "D4R_D3D12_SUBMIT_FAILURE %s\n", failure.what());
|
||||
try { complete(queue); } catch (...) {}
|
||||
// Do not submit a consumer suffix after a failed DLSS boundary.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InternalScope::InternalScope() : previous(internal) { internal = true; }
|
||||
InternalScope::~InternalScope() { internal = previous; }
|
||||
std::string error() { std::lock_guard<std::mutex> lock(error_mutex); return last_error; }
|
||||
size_t live_recordings() { std::lock_guard<std::mutex> lock(map_mutex); return recordings.size(); }
|
||||
D3D12_RESOURCE_STATES resource_state(ID3D12GraphicsCommandList* list, ID3D12Resource* resource, D3D12_RESOURCE_STATES fallback) {
|
||||
const auto state = resource_access(list, resource, ResourceAccess(fallback));
|
||||
if (state.enhanced) throw std::runtime_error("Enhanced texture layout cannot be treated as a legacy state");
|
||||
return state.legacy;
|
||||
}
|
||||
ResourceAccess resource_access(ID3D12GraphicsCommandList* list, ID3D12Resource* resource, ResourceAccess fallback) {
|
||||
auto recording = get_recording(list, false);
|
||||
fallback.inherited = true;
|
||||
if (!recording) return fallback;
|
||||
std::lock_guard<std::recursive_mutex> lock(recording->mutex);
|
||||
auto found = recording->resource_states.find(resource);
|
||||
return found == recording->resource_states.end() ? fallback : found->second.state;
|
||||
}
|
||||
ResourceAccess submitted_resource_access(ID3D12Resource* resource, ResourceAccess fallback) {
|
||||
if (!resource) return fallback;
|
||||
ResourceAccess submitted; UINT size = sizeof(submitted);
|
||||
if (SUCCEEDED(resource->GetPrivateData(submitted_access_tag, &size, &submitted)) && size == sizeof(submitted)) return submitted;
|
||||
return fallback;
|
||||
}
|
||||
void install(ID3D12Device* device) {
|
||||
std::lock_guard<std::mutex> lock(installation_mutex);
|
||||
if (installed) return;
|
||||
InternalScope scope;
|
||||
// An application may unload its NGX handle while native D3D12 objects are
|
||||
// still alive. Public method callbacks must remain callable until exit.
|
||||
HMODULE owner = nullptr;
|
||||
if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_PIN,
|
||||
reinterpret_cast<LPCWSTR>(&install), &owner)) throw std::runtime_error("Cannot retain the D3D12 callback module");
|
||||
const auto init = MH_Initialize();
|
||||
if (init != MH_OK && init != MH_ERROR_ALREADY_INITIALIZED) throw std::runtime_error(MH_StatusToString(init));
|
||||
ComPtr<ID3D12CommandAllocator> allocator; ComPtr<ID3D12GraphicsCommandList> list;
|
||||
dx(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(allocator.GetAddressOf())), "Create hook allocator");
|
||||
dx(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, allocator.Get(), nullptr, IID_PPV_ARGS(list.GetAddressOf())), "Create hook command list");
|
||||
try {
|
||||
// The public COM interface inherits one ordered method table.
|
||||
unsigned version = 0;
|
||||
ComPtr<IUnknown> newest;
|
||||
const GUID* ids[] = {&IID_ID3D12GraphicsCommandList, &IID_ID3D12GraphicsCommandList1, &IID_ID3D12GraphicsCommandList2,
|
||||
&IID_ID3D12GraphicsCommandList3, &IID_ID3D12GraphicsCommandList4, &IID_ID3D12GraphicsCommandList5,
|
||||
&IID_ID3D12GraphicsCommandList6, &IID_ID3D12GraphicsCommandList7,
|
||||
&command_list8_iid, &command_list9_iid, &command_list10_iid};
|
||||
for (unsigned n = 0; n < 11; ++n) {
|
||||
ComPtr<IUnknown> candidate;
|
||||
if (SUCCEEDED(list->QueryInterface(*ids[n], reinterpret_cast<void**>(candidate.GetAddressOf())))) { newest = std::move(candidate); version = n; }
|
||||
}
|
||||
if (newest.Get() != static_cast<IUnknown*>(list.Get())) throw std::runtime_error("D3D12 command-list versions need a separate interface identity adapter");
|
||||
auto table = *reinterpret_cast<void***>(list.Get());
|
||||
attach_recording_methods(table, version);
|
||||
attach_method(table[2], reinterpret_cast<void*>(hook_release), reinterpret_cast<void**>(&original_release), "Release");
|
||||
D3D12_COMMAND_QUEUE_DESC desc{}; desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
|
||||
ComPtr<ID3D12CommandQueue> queue; dx(device->CreateCommandQueue(&desc, IID_PPV_ARGS(queue.GetAddressOf())), "Create hook queue");
|
||||
auto queueTable = *reinterpret_cast<void***>(queue.Get());
|
||||
// Queue inherits IUnknown, Object, DeviceChild; UpdateTileMappings,
|
||||
// CopyTileMappings precede ExecuteCommandLists in the public SDK.
|
||||
attach_method(queueTable[10], reinterpret_cast<void*>(hook_execute), reinterpret_cast<void**>(&original_execute), "ExecuteCommandLists");
|
||||
attach_method(queueTable[queue_signal_slot], reinterpret_cast<void*>(hook_queue_signal), reinterpret_cast<void**>(&original_queue_signal), "QueueSignal");
|
||||
attach_method(queueTable[queue_wait_slot], reinterpret_cast<void*>(hook_queue_wait), reinterpret_cast<void**>(&original_queue_wait), "QueueWait");
|
||||
auto deviceTable = *reinterpret_cast<void***>(device);
|
||||
attach_method(deviceTable[device_command_list_slot], reinterpret_cast<void*>(hook_create_list), reinterpret_cast<void**>(&original_create_list), "CreateCommandList");
|
||||
attach_method(deviceTable[device_signature_slot], reinterpret_cast<void*>(hook_signature), reinterpret_cast<void**>(&original_signature), "CreateCommandSignature");
|
||||
for (const auto& hook : attached) {
|
||||
const auto result = MH_QueueEnableHook(hook.target);
|
||||
if (result != MH_OK) throw std::runtime_error(MH_StatusToString(result));
|
||||
}
|
||||
const auto result = MH_ApplyQueued();
|
||||
if (result != MH_OK) throw std::runtime_error(MH_StatusToString(result));
|
||||
installed = true;
|
||||
std::printf("D4R_D3D12_COMMAND_BACKEND installed=1 interface=%u hooks=%zu frame_age=0\n", version, attached.size());
|
||||
} catch (...) {
|
||||
for (const auto& hook : attached) { (void)MH_DisableHook(hook.target); (void)MH_RemoveHook(hook.target); }
|
||||
attached.clear(); throw;
|
||||
}
|
||||
}
|
||||
void record_boundary(ID3D12GraphicsCommandList* list, Boundary callback) {
|
||||
const auto recordStart = Clock::now();
|
||||
if (!installed || !list || !callback) throw std::runtime_error("D3D12 command backend is not initialized");
|
||||
if (!error().empty()) throw std::runtime_error(error());
|
||||
if (list->GetType() != D3D12_COMMAND_LIST_TYPE_DIRECT) throw std::runtime_error("DLSS requires a direct command list");
|
||||
Access access(list);
|
||||
auto& recording = *access.recording;
|
||||
if (recording.closed || recording.render_pass || recording.active_queries || recording.predication || recording.indirect_state_unknown)
|
||||
throw std::runtime_error("Cannot split a closed list or an active render/query/predication/unknown indirect state scope");
|
||||
// Construct/replay the suffix before modifying the original list so an
|
||||
// allocation failure leaves the caller's recording intact.
|
||||
InternalScope scope;
|
||||
ComPtr<ID3D12Device> device; dx(list->GetDevice(IID_PPV_ARGS(device.GetAddressOf())), "Boundary device");
|
||||
Segment segment; segment.list = access.target(); segment.owner = recording.current;
|
||||
segment.allocator = recording.current_allocator; segment.boundary = std::move(callback);
|
||||
segment.resource_states = recording.resource_states;
|
||||
ComPtr<ID3D12CommandAllocator> nextAllocator; ComPtr<ID3D12GraphicsCommandList> next;
|
||||
dx(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(nextAllocator.GetAddressOf())), "Suffix allocator");
|
||||
dx(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, nextAllocator.Get(), nullptr, IID_PPV_ARGS(next.GetAddressOf())), "Suffix command list");
|
||||
const auto replayStart = Clock::now();
|
||||
for (auto& state : recording.state) state(next.Get());
|
||||
const auto replayMs = milliseconds(replayStart);
|
||||
recording.segments.reserve(recording.segments.size() + 1);
|
||||
for (const auto& event : recording.events) original_BeginEvent(next.Get(), event.metadata, event.data.data(), UINT(event.data.size()));
|
||||
for (size_t n = 0; n < recording.events.size(); ++n) original_EndEvent(segment.list);
|
||||
dx(original_Close(segment.list), "Close boundary prefix");
|
||||
// Each suffix owns an independent allocator until GPU completion/reset.
|
||||
recording.segments.push_back(std::move(segment));
|
||||
recording.current_allocator = std::move(nextAllocator);
|
||||
recording.current = std::move(next);
|
||||
std::printf("D4R_D3D12_RECORD boundary=%zu state_calls=%zu frame_age=0\n", recording.segments.size(), recording.state.size());
|
||||
if (profile_commands()) {
|
||||
// This is the interval between NGX recording calls on this thread,
|
||||
// not a Present/FPS measurement or an extra queued frame.
|
||||
thread_local Clock::time_point previous{};
|
||||
const double interval = previous == Clock::time_point{} ? 0 : std::chrono::duration<double, std::milli>(recordStart - previous).count();
|
||||
previous = recordStart;
|
||||
std::printf("D4R_COMMAND_RECORD total_ms=%.3f replay_ms=%.3f state_calls=%zu interval_ms=%.3f\n",
|
||||
milliseconds(recordStart), replayMs, recording.state.size(), interval);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,766 +0,0 @@
|
||||
// Generated by generate_d3d12_hooks.py from the public SDK. Do not edit.
|
||||
|
||||
using Fn_Close = HRESULT(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self);
|
||||
static Fn_Close original_Close = nullptr;
|
||||
static HRESULT STDMETHODCALLTYPE hook_Close(ID3D12GraphicsCommandList* self);
|
||||
|
||||
using Fn_Reset = HRESULT(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, ID3D12CommandAllocator *allocator, ID3D12PipelineState *initial_state);
|
||||
static Fn_Reset original_Reset = nullptr;
|
||||
static HRESULT STDMETHODCALLTYPE hook_Reset(ID3D12GraphicsCommandList* self, ID3D12CommandAllocator *allocator, ID3D12PipelineState *initial_state);
|
||||
|
||||
using Fn_ClearState = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, ID3D12PipelineState *pipeline_state);
|
||||
static Fn_ClearState original_ClearState = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_ClearState(ID3D12GraphicsCommandList* self, ID3D12PipelineState *pipeline_state);
|
||||
|
||||
using Fn_DrawInstanced = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, UINT vertex_count_per_instance, UINT instance_count, UINT start_vertex_location, UINT start_instance_location);
|
||||
static Fn_DrawInstanced original_DrawInstanced = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_DrawInstanced(ID3D12GraphicsCommandList* self, UINT vertex_count_per_instance, UINT instance_count, UINT start_vertex_location, UINT start_instance_location) {
|
||||
Access access(self);
|
||||
access.driver_call();
|
||||
return original_DrawInstanced(access.target(), vertex_count_per_instance, instance_count, start_vertex_location, start_instance_location);
|
||||
}
|
||||
|
||||
using Fn_DrawIndexedInstanced = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, UINT index_count_per_instance, UINT instance_count, UINT start_vertex_location, INT base_vertex_location, UINT start_instance_location);
|
||||
static Fn_DrawIndexedInstanced original_DrawIndexedInstanced = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_DrawIndexedInstanced(ID3D12GraphicsCommandList* self, UINT index_count_per_instance, UINT instance_count, UINT start_vertex_location, INT base_vertex_location, UINT start_instance_location) {
|
||||
Access access(self);
|
||||
access.driver_call();
|
||||
return original_DrawIndexedInstanced(access.target(), index_count_per_instance, instance_count, start_vertex_location, base_vertex_location, start_instance_location);
|
||||
}
|
||||
|
||||
using Fn_Dispatch = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, UINT x, UINT u, UINT z);
|
||||
static Fn_Dispatch original_Dispatch = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_Dispatch(ID3D12GraphicsCommandList* self, UINT x, UINT u, UINT z) {
|
||||
Access access(self);
|
||||
access.driver_call();
|
||||
return original_Dispatch(access.target(), x, u, z);
|
||||
}
|
||||
|
||||
using Fn_CopyBufferRegion = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, ID3D12Resource *dst_buffer, UINT64 dst_offset, ID3D12Resource *src_buffer, UINT64 src_offset, UINT64 byte_count);
|
||||
static Fn_CopyBufferRegion original_CopyBufferRegion = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_CopyBufferRegion(ID3D12GraphicsCommandList* self, ID3D12Resource *dst_buffer, UINT64 dst_offset, ID3D12Resource *src_buffer, UINT64 src_offset, UINT64 byte_count) {
|
||||
Access access(self);
|
||||
access.driver_call();
|
||||
return original_CopyBufferRegion(access.target(), dst_buffer, dst_offset, src_buffer, src_offset, byte_count);
|
||||
}
|
||||
|
||||
using Fn_CopyTextureRegion = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, const D3D12_TEXTURE_COPY_LOCATION *dst, UINT dst_x, UINT dst_y, UINT dst_z, const D3D12_TEXTURE_COPY_LOCATION *src, const D3D12_BOX *src_box);
|
||||
static Fn_CopyTextureRegion original_CopyTextureRegion = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_CopyTextureRegion(ID3D12GraphicsCommandList* self, const D3D12_TEXTURE_COPY_LOCATION *dst, UINT dst_x, UINT dst_y, UINT dst_z, const D3D12_TEXTURE_COPY_LOCATION *src, const D3D12_BOX *src_box) {
|
||||
Access access(self);
|
||||
access.driver_call();
|
||||
return original_CopyTextureRegion(access.target(), dst, dst_x, dst_y, dst_z, src, src_box);
|
||||
}
|
||||
|
||||
using Fn_CopyResource = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, ID3D12Resource *dst_resource, ID3D12Resource *src_resource);
|
||||
static Fn_CopyResource original_CopyResource = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_CopyResource(ID3D12GraphicsCommandList* self, ID3D12Resource *dst_resource, ID3D12Resource *src_resource) {
|
||||
Access access(self);
|
||||
access.driver_call();
|
||||
return original_CopyResource(access.target(), dst_resource, src_resource);
|
||||
}
|
||||
|
||||
using Fn_CopyTiles = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, ID3D12Resource *tiled_resource, const D3D12_TILED_RESOURCE_COORDINATE *tile_region_start_coordinate, const D3D12_TILE_REGION_SIZE *tile_region_size, ID3D12Resource *buffer, UINT64 buffer_offset, D3D12_TILE_COPY_FLAGS flags);
|
||||
static Fn_CopyTiles original_CopyTiles = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_CopyTiles(ID3D12GraphicsCommandList* self, ID3D12Resource *tiled_resource, const D3D12_TILED_RESOURCE_COORDINATE *tile_region_start_coordinate, const D3D12_TILE_REGION_SIZE *tile_region_size, ID3D12Resource *buffer, UINT64 buffer_offset, D3D12_TILE_COPY_FLAGS flags) {
|
||||
Access access(self);
|
||||
access.driver_call();
|
||||
return original_CopyTiles(access.target(), tiled_resource, tile_region_start_coordinate, tile_region_size, buffer, buffer_offset, flags);
|
||||
}
|
||||
|
||||
using Fn_ResolveSubresource = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, ID3D12Resource *dst_resource, UINT dst_sub_resource, ID3D12Resource *src_resource, UINT src_sub_resource, DXGI_FORMAT format);
|
||||
static Fn_ResolveSubresource original_ResolveSubresource = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_ResolveSubresource(ID3D12GraphicsCommandList* self, ID3D12Resource *dst_resource, UINT dst_sub_resource, ID3D12Resource *src_resource, UINT src_sub_resource, DXGI_FORMAT format) {
|
||||
Access access(self);
|
||||
access.driver_call();
|
||||
return original_ResolveSubresource(access.target(), dst_resource, dst_sub_resource, src_resource, src_sub_resource, format);
|
||||
}
|
||||
|
||||
using Fn_IASetPrimitiveTopology = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, D3D12_PRIMITIVE_TOPOLOGY primitive_topology);
|
||||
static Fn_IASetPrimitiveTopology original_IASetPrimitiveTopology = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_IASetPrimitiveTopology(ID3D12GraphicsCommandList* self, D3D12_PRIMITIVE_TOPOLOGY primitive_topology) {
|
||||
Access access(self);
|
||||
if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable {
|
||||
original_IASetPrimitiveTopology(target, primitive_topology);
|
||||
});
|
||||
access.driver_call();
|
||||
return original_IASetPrimitiveTopology(access.target(), primitive_topology);
|
||||
}
|
||||
|
||||
using Fn_RSSetViewports = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, UINT viewport_count, const D3D12_VIEWPORT *viewports);
|
||||
static Fn_RSSetViewports original_RSSetViewports = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_RSSetViewports(ID3D12GraphicsCommandList* self, UINT viewport_count, const D3D12_VIEWPORT *viewports) {
|
||||
Access access(self);
|
||||
auto saved_viewports = copy_values(viewports, viewport_count);
|
||||
if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable {
|
||||
original_RSSetViewports(target, viewport_count, values_pointer(saved_viewports));
|
||||
});
|
||||
access.driver_call();
|
||||
return original_RSSetViewports(access.target(), viewport_count, viewports);
|
||||
}
|
||||
|
||||
using Fn_RSSetScissorRects = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, UINT rect_count, const D3D12_RECT *rects);
|
||||
static Fn_RSSetScissorRects original_RSSetScissorRects = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_RSSetScissorRects(ID3D12GraphicsCommandList* self, UINT rect_count, const D3D12_RECT *rects) {
|
||||
Access access(self);
|
||||
auto saved_rects = copy_values(rects, rect_count);
|
||||
if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable {
|
||||
original_RSSetScissorRects(target, rect_count, values_pointer(saved_rects));
|
||||
});
|
||||
access.driver_call();
|
||||
return original_RSSetScissorRects(access.target(), rect_count, rects);
|
||||
}
|
||||
|
||||
using Fn_OMSetBlendFactor = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, const FLOAT blend_factor[4]);
|
||||
static Fn_OMSetBlendFactor original_OMSetBlendFactor = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_OMSetBlendFactor(ID3D12GraphicsCommandList* self, const FLOAT blend_factor[4]) {
|
||||
Access access(self);
|
||||
auto saved_blend_factor = copy_values(blend_factor, 4);
|
||||
if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable {
|
||||
original_OMSetBlendFactor(target, values_pointer(saved_blend_factor));
|
||||
});
|
||||
access.driver_call();
|
||||
return original_OMSetBlendFactor(access.target(), blend_factor);
|
||||
}
|
||||
|
||||
using Fn_OMSetStencilRef = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, UINT stencil_ref);
|
||||
static Fn_OMSetStencilRef original_OMSetStencilRef = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_OMSetStencilRef(ID3D12GraphicsCommandList* self, UINT stencil_ref) {
|
||||
Access access(self);
|
||||
if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable {
|
||||
original_OMSetStencilRef(target, stencil_ref);
|
||||
});
|
||||
access.driver_call();
|
||||
return original_OMSetStencilRef(access.target(), stencil_ref);
|
||||
}
|
||||
|
||||
using Fn_SetPipelineState = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, ID3D12PipelineState *pipeline_state);
|
||||
static Fn_SetPipelineState original_SetPipelineState = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_SetPipelineState(ID3D12GraphicsCommandList* self, ID3D12PipelineState *pipeline_state) {
|
||||
Access access(self);
|
||||
auto saved_pipeline_state = retain(pipeline_state);
|
||||
if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable {
|
||||
original_SetPipelineState(target, saved_pipeline_state.Get());
|
||||
});
|
||||
access.driver_call();
|
||||
return original_SetPipelineState(access.target(), pipeline_state);
|
||||
}
|
||||
|
||||
using Fn_ResourceBarrier = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, UINT barrier_count, const D3D12_RESOURCE_BARRIER *barriers);
|
||||
static Fn_ResourceBarrier original_ResourceBarrier = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_ResourceBarrier(ID3D12GraphicsCommandList* self, UINT barrier_count, const D3D12_RESOURCE_BARRIER *barriers);
|
||||
|
||||
using Fn_ExecuteBundle = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, ID3D12GraphicsCommandList *command_list);
|
||||
static Fn_ExecuteBundle original_ExecuteBundle = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_ExecuteBundle(ID3D12GraphicsCommandList* self, ID3D12GraphicsCommandList *command_list);
|
||||
|
||||
using Fn_SetDescriptorHeaps = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, UINT heap_count, ID3D12DescriptorHeap *const *heaps);
|
||||
static Fn_SetDescriptorHeaps original_SetDescriptorHeaps = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_SetDescriptorHeaps(ID3D12GraphicsCommandList* self, UINT heap_count, ID3D12DescriptorHeap *const *heaps) {
|
||||
Access access(self);
|
||||
auto saved_heaps = retain_values(heaps, heap_count);
|
||||
if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable {
|
||||
original_SetDescriptorHeaps(target, heap_count, saved_heaps.raw.data());
|
||||
});
|
||||
access.driver_call();
|
||||
return original_SetDescriptorHeaps(access.target(), heap_count, heaps);
|
||||
}
|
||||
|
||||
using Fn_SetComputeRootSignature = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, ID3D12RootSignature *root_signature);
|
||||
static Fn_SetComputeRootSignature original_SetComputeRootSignature = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_SetComputeRootSignature(ID3D12GraphicsCommandList* self, ID3D12RootSignature *root_signature) {
|
||||
Access access(self);
|
||||
auto saved_root_signature = retain(root_signature);
|
||||
if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable {
|
||||
original_SetComputeRootSignature(target, saved_root_signature.Get());
|
||||
});
|
||||
access.driver_call();
|
||||
return original_SetComputeRootSignature(access.target(), root_signature);
|
||||
}
|
||||
|
||||
using Fn_SetGraphicsRootSignature = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, ID3D12RootSignature *root_signature);
|
||||
static Fn_SetGraphicsRootSignature original_SetGraphicsRootSignature = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_SetGraphicsRootSignature(ID3D12GraphicsCommandList* self, ID3D12RootSignature *root_signature) {
|
||||
Access access(self);
|
||||
auto saved_root_signature = retain(root_signature);
|
||||
if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable {
|
||||
original_SetGraphicsRootSignature(target, saved_root_signature.Get());
|
||||
});
|
||||
access.driver_call();
|
||||
return original_SetGraphicsRootSignature(access.target(), root_signature);
|
||||
}
|
||||
|
||||
using Fn_SetComputeRootDescriptorTable = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, UINT root_parameter_index, D3D12_GPU_DESCRIPTOR_HANDLE base_descriptor);
|
||||
static Fn_SetComputeRootDescriptorTable original_SetComputeRootDescriptorTable = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_SetComputeRootDescriptorTable(ID3D12GraphicsCommandList* self, UINT root_parameter_index, D3D12_GPU_DESCRIPTOR_HANDLE base_descriptor) {
|
||||
Access access(self);
|
||||
if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable {
|
||||
original_SetComputeRootDescriptorTable(target, root_parameter_index, base_descriptor);
|
||||
});
|
||||
access.driver_call();
|
||||
return original_SetComputeRootDescriptorTable(access.target(), root_parameter_index, base_descriptor);
|
||||
}
|
||||
|
||||
using Fn_SetGraphicsRootDescriptorTable = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, UINT root_parameter_index, D3D12_GPU_DESCRIPTOR_HANDLE base_descriptor);
|
||||
static Fn_SetGraphicsRootDescriptorTable original_SetGraphicsRootDescriptorTable = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_SetGraphicsRootDescriptorTable(ID3D12GraphicsCommandList* self, UINT root_parameter_index, D3D12_GPU_DESCRIPTOR_HANDLE base_descriptor) {
|
||||
Access access(self);
|
||||
if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable {
|
||||
original_SetGraphicsRootDescriptorTable(target, root_parameter_index, base_descriptor);
|
||||
});
|
||||
access.driver_call();
|
||||
return original_SetGraphicsRootDescriptorTable(access.target(), root_parameter_index, base_descriptor);
|
||||
}
|
||||
|
||||
using Fn_SetComputeRoot32BitConstant = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, UINT root_parameter_index, UINT data, UINT dst_offset);
|
||||
static Fn_SetComputeRoot32BitConstant original_SetComputeRoot32BitConstant = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_SetComputeRoot32BitConstant(ID3D12GraphicsCommandList* self, UINT root_parameter_index, UINT data, UINT dst_offset) {
|
||||
Access access(self);
|
||||
if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable {
|
||||
original_SetComputeRoot32BitConstant(target, root_parameter_index, data, dst_offset);
|
||||
});
|
||||
access.driver_call();
|
||||
return original_SetComputeRoot32BitConstant(access.target(), root_parameter_index, data, dst_offset);
|
||||
}
|
||||
|
||||
using Fn_SetGraphicsRoot32BitConstant = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, UINT root_parameter_index, UINT data, UINT dst_offset);
|
||||
static Fn_SetGraphicsRoot32BitConstant original_SetGraphicsRoot32BitConstant = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_SetGraphicsRoot32BitConstant(ID3D12GraphicsCommandList* self, UINT root_parameter_index, UINT data, UINT dst_offset) {
|
||||
Access access(self);
|
||||
if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable {
|
||||
original_SetGraphicsRoot32BitConstant(target, root_parameter_index, data, dst_offset);
|
||||
});
|
||||
access.driver_call();
|
||||
return original_SetGraphicsRoot32BitConstant(access.target(), root_parameter_index, data, dst_offset);
|
||||
}
|
||||
|
||||
using Fn_SetComputeRoot32BitConstants = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, UINT root_parameter_index, UINT constant_count, const void *data, UINT dst_offset);
|
||||
static Fn_SetComputeRoot32BitConstants original_SetComputeRoot32BitConstants = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_SetComputeRoot32BitConstants(ID3D12GraphicsCommandList* self, UINT root_parameter_index, UINT constant_count, const void *data, UINT dst_offset) {
|
||||
Access access(self);
|
||||
auto saved_data = copy_values(static_cast<const UINT*>(data), constant_count);
|
||||
if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable {
|
||||
original_SetComputeRoot32BitConstants(target, root_parameter_index, constant_count, values_pointer(saved_data), dst_offset);
|
||||
});
|
||||
access.driver_call();
|
||||
return original_SetComputeRoot32BitConstants(access.target(), root_parameter_index, constant_count, data, dst_offset);
|
||||
}
|
||||
|
||||
using Fn_SetGraphicsRoot32BitConstants = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, UINT root_parameter_index, UINT constant_count, const void *data, UINT dst_offset);
|
||||
static Fn_SetGraphicsRoot32BitConstants original_SetGraphicsRoot32BitConstants = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_SetGraphicsRoot32BitConstants(ID3D12GraphicsCommandList* self, UINT root_parameter_index, UINT constant_count, const void *data, UINT dst_offset) {
|
||||
Access access(self);
|
||||
auto saved_data = copy_values(static_cast<const UINT*>(data), constant_count);
|
||||
if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable {
|
||||
original_SetGraphicsRoot32BitConstants(target, root_parameter_index, constant_count, values_pointer(saved_data), dst_offset);
|
||||
});
|
||||
access.driver_call();
|
||||
return original_SetGraphicsRoot32BitConstants(access.target(), root_parameter_index, constant_count, data, dst_offset);
|
||||
}
|
||||
|
||||
using Fn_SetComputeRootConstantBufferView = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, UINT root_parameter_index, D3D12_GPU_VIRTUAL_ADDRESS address);
|
||||
static Fn_SetComputeRootConstantBufferView original_SetComputeRootConstantBufferView = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_SetComputeRootConstantBufferView(ID3D12GraphicsCommandList* self, UINT root_parameter_index, D3D12_GPU_VIRTUAL_ADDRESS address) {
|
||||
Access access(self);
|
||||
if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable {
|
||||
original_SetComputeRootConstantBufferView(target, root_parameter_index, address);
|
||||
});
|
||||
access.driver_call();
|
||||
return original_SetComputeRootConstantBufferView(access.target(), root_parameter_index, address);
|
||||
}
|
||||
|
||||
using Fn_SetGraphicsRootConstantBufferView = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, UINT root_parameter_index, D3D12_GPU_VIRTUAL_ADDRESS address);
|
||||
static Fn_SetGraphicsRootConstantBufferView original_SetGraphicsRootConstantBufferView = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_SetGraphicsRootConstantBufferView(ID3D12GraphicsCommandList* self, UINT root_parameter_index, D3D12_GPU_VIRTUAL_ADDRESS address) {
|
||||
Access access(self);
|
||||
if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable {
|
||||
original_SetGraphicsRootConstantBufferView(target, root_parameter_index, address);
|
||||
});
|
||||
access.driver_call();
|
||||
return original_SetGraphicsRootConstantBufferView(access.target(), root_parameter_index, address);
|
||||
}
|
||||
|
||||
using Fn_SetComputeRootShaderResourceView = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, UINT root_parameter_index, D3D12_GPU_VIRTUAL_ADDRESS address);
|
||||
static Fn_SetComputeRootShaderResourceView original_SetComputeRootShaderResourceView = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_SetComputeRootShaderResourceView(ID3D12GraphicsCommandList* self, UINT root_parameter_index, D3D12_GPU_VIRTUAL_ADDRESS address) {
|
||||
Access access(self);
|
||||
if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable {
|
||||
original_SetComputeRootShaderResourceView(target, root_parameter_index, address);
|
||||
});
|
||||
access.driver_call();
|
||||
return original_SetComputeRootShaderResourceView(access.target(), root_parameter_index, address);
|
||||
}
|
||||
|
||||
using Fn_SetGraphicsRootShaderResourceView = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, UINT root_parameter_index, D3D12_GPU_VIRTUAL_ADDRESS address);
|
||||
static Fn_SetGraphicsRootShaderResourceView original_SetGraphicsRootShaderResourceView = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_SetGraphicsRootShaderResourceView(ID3D12GraphicsCommandList* self, UINT root_parameter_index, D3D12_GPU_VIRTUAL_ADDRESS address) {
|
||||
Access access(self);
|
||||
if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable {
|
||||
original_SetGraphicsRootShaderResourceView(target, root_parameter_index, address);
|
||||
});
|
||||
access.driver_call();
|
||||
return original_SetGraphicsRootShaderResourceView(access.target(), root_parameter_index, address);
|
||||
}
|
||||
|
||||
using Fn_SetComputeRootUnorderedAccessView = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, UINT root_parameter_index, D3D12_GPU_VIRTUAL_ADDRESS address);
|
||||
static Fn_SetComputeRootUnorderedAccessView original_SetComputeRootUnorderedAccessView = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_SetComputeRootUnorderedAccessView(ID3D12GraphicsCommandList* self, UINT root_parameter_index, D3D12_GPU_VIRTUAL_ADDRESS address) {
|
||||
Access access(self);
|
||||
if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable {
|
||||
original_SetComputeRootUnorderedAccessView(target, root_parameter_index, address);
|
||||
});
|
||||
access.driver_call();
|
||||
return original_SetComputeRootUnorderedAccessView(access.target(), root_parameter_index, address);
|
||||
}
|
||||
|
||||
using Fn_SetGraphicsRootUnorderedAccessView = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, UINT root_parameter_index, D3D12_GPU_VIRTUAL_ADDRESS address);
|
||||
static Fn_SetGraphicsRootUnorderedAccessView original_SetGraphicsRootUnorderedAccessView = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_SetGraphicsRootUnorderedAccessView(ID3D12GraphicsCommandList* self, UINT root_parameter_index, D3D12_GPU_VIRTUAL_ADDRESS address) {
|
||||
Access access(self);
|
||||
if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable {
|
||||
original_SetGraphicsRootUnorderedAccessView(target, root_parameter_index, address);
|
||||
});
|
||||
access.driver_call();
|
||||
return original_SetGraphicsRootUnorderedAccessView(access.target(), root_parameter_index, address);
|
||||
}
|
||||
|
||||
using Fn_IASetIndexBuffer = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, const D3D12_INDEX_BUFFER_VIEW *view);
|
||||
static Fn_IASetIndexBuffer original_IASetIndexBuffer = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_IASetIndexBuffer(ID3D12GraphicsCommandList* self, const D3D12_INDEX_BUFFER_VIEW *view) {
|
||||
Access access(self);
|
||||
auto saved_view = copy_values(view, 1);
|
||||
if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable {
|
||||
original_IASetIndexBuffer(target, values_pointer(saved_view));
|
||||
});
|
||||
access.driver_call();
|
||||
return original_IASetIndexBuffer(access.target(), view);
|
||||
}
|
||||
|
||||
using Fn_IASetVertexBuffers = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, UINT start_slot, UINT view_count, const D3D12_VERTEX_BUFFER_VIEW *views);
|
||||
static Fn_IASetVertexBuffers original_IASetVertexBuffers = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_IASetVertexBuffers(ID3D12GraphicsCommandList* self, UINT start_slot, UINT view_count, const D3D12_VERTEX_BUFFER_VIEW *views) {
|
||||
Access access(self);
|
||||
auto saved_views = copy_values(views, view_count);
|
||||
if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable {
|
||||
original_IASetVertexBuffers(target, start_slot, view_count, values_pointer(saved_views));
|
||||
});
|
||||
access.driver_call();
|
||||
return original_IASetVertexBuffers(access.target(), start_slot, view_count, views);
|
||||
}
|
||||
|
||||
using Fn_SOSetTargets = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, UINT start_slot, UINT view_count, const D3D12_STREAM_OUTPUT_BUFFER_VIEW *views);
|
||||
static Fn_SOSetTargets original_SOSetTargets = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_SOSetTargets(ID3D12GraphicsCommandList* self, UINT start_slot, UINT view_count, const D3D12_STREAM_OUTPUT_BUFFER_VIEW *views) {
|
||||
Access access(self);
|
||||
auto saved_views = copy_values(views, view_count);
|
||||
if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable {
|
||||
original_SOSetTargets(target, start_slot, view_count, values_pointer(saved_views));
|
||||
});
|
||||
access.driver_call();
|
||||
return original_SOSetTargets(access.target(), start_slot, view_count, views);
|
||||
}
|
||||
|
||||
using Fn_OMSetRenderTargets = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, UINT render_target_descriptor_count, const D3D12_CPU_DESCRIPTOR_HANDLE *render_target_descriptors, WINBOOL single_descriptor_handle, const D3D12_CPU_DESCRIPTOR_HANDLE *depth_stencil_descriptor);
|
||||
static Fn_OMSetRenderTargets original_OMSetRenderTargets = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_OMSetRenderTargets(ID3D12GraphicsCommandList* self, UINT render_target_descriptor_count, const D3D12_CPU_DESCRIPTOR_HANDLE *render_target_descriptors, WINBOOL single_descriptor_handle, const D3D12_CPU_DESCRIPTOR_HANDLE *depth_stencil_descriptor) {
|
||||
Access access(self);
|
||||
auto saved_render_target_descriptors = copy_values(render_target_descriptors, (single_descriptor_handle ? 1 : render_target_descriptor_count));
|
||||
auto saved_depth_stencil_descriptor = copy_values(depth_stencil_descriptor, 1);
|
||||
if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable {
|
||||
original_OMSetRenderTargets(target, render_target_descriptor_count, values_pointer(saved_render_target_descriptors), single_descriptor_handle, values_pointer(saved_depth_stencil_descriptor));
|
||||
});
|
||||
access.driver_call();
|
||||
return original_OMSetRenderTargets(access.target(), render_target_descriptor_count, render_target_descriptors, single_descriptor_handle, depth_stencil_descriptor);
|
||||
}
|
||||
|
||||
using Fn_ClearDepthStencilView = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, D3D12_CPU_DESCRIPTOR_HANDLE dsv, D3D12_CLEAR_FLAGS flags, FLOAT depth, UINT8 stencil, UINT rect_count, const D3D12_RECT *rects);
|
||||
static Fn_ClearDepthStencilView original_ClearDepthStencilView = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_ClearDepthStencilView(ID3D12GraphicsCommandList* self, D3D12_CPU_DESCRIPTOR_HANDLE dsv, D3D12_CLEAR_FLAGS flags, FLOAT depth, UINT8 stencil, UINT rect_count, const D3D12_RECT *rects) {
|
||||
Access access(self);
|
||||
access.driver_call();
|
||||
return original_ClearDepthStencilView(access.target(), dsv, flags, depth, stencil, rect_count, rects);
|
||||
}
|
||||
|
||||
using Fn_ClearRenderTargetView = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, D3D12_CPU_DESCRIPTOR_HANDLE rtv, const FLOAT color[4], UINT rect_count, const D3D12_RECT *rects);
|
||||
static Fn_ClearRenderTargetView original_ClearRenderTargetView = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_ClearRenderTargetView(ID3D12GraphicsCommandList* self, D3D12_CPU_DESCRIPTOR_HANDLE rtv, const FLOAT color[4], UINT rect_count, const D3D12_RECT *rects) {
|
||||
Access access(self);
|
||||
access.driver_call();
|
||||
return original_ClearRenderTargetView(access.target(), rtv, color, rect_count, rects);
|
||||
}
|
||||
|
||||
using Fn_ClearUnorderedAccessViewUint = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, D3D12_GPU_DESCRIPTOR_HANDLE gpu_handle, D3D12_CPU_DESCRIPTOR_HANDLE cpu_handle, ID3D12Resource *resource, const UINT values[4], UINT rect_count, const D3D12_RECT *rects);
|
||||
static Fn_ClearUnorderedAccessViewUint original_ClearUnorderedAccessViewUint = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_ClearUnorderedAccessViewUint(ID3D12GraphicsCommandList* self, D3D12_GPU_DESCRIPTOR_HANDLE gpu_handle, D3D12_CPU_DESCRIPTOR_HANDLE cpu_handle, ID3D12Resource *resource, const UINT values[4], UINT rect_count, const D3D12_RECT *rects) {
|
||||
Access access(self);
|
||||
access.driver_call();
|
||||
return original_ClearUnorderedAccessViewUint(access.target(), gpu_handle, cpu_handle, resource, values, rect_count, rects);
|
||||
}
|
||||
|
||||
using Fn_ClearUnorderedAccessViewFloat = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, D3D12_GPU_DESCRIPTOR_HANDLE gpu_handle, D3D12_CPU_DESCRIPTOR_HANDLE cpu_handle, ID3D12Resource *resource, const float values[4], UINT rect_count, const D3D12_RECT *rects);
|
||||
static Fn_ClearUnorderedAccessViewFloat original_ClearUnorderedAccessViewFloat = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_ClearUnorderedAccessViewFloat(ID3D12GraphicsCommandList* self, D3D12_GPU_DESCRIPTOR_HANDLE gpu_handle, D3D12_CPU_DESCRIPTOR_HANDLE cpu_handle, ID3D12Resource *resource, const float values[4], UINT rect_count, const D3D12_RECT *rects) {
|
||||
Access access(self);
|
||||
access.driver_call();
|
||||
return original_ClearUnorderedAccessViewFloat(access.target(), gpu_handle, cpu_handle, resource, values, rect_count, rects);
|
||||
}
|
||||
|
||||
using Fn_DiscardResource = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, ID3D12Resource *resource, const D3D12_DISCARD_REGION *region);
|
||||
static Fn_DiscardResource original_DiscardResource = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_DiscardResource(ID3D12GraphicsCommandList* self, ID3D12Resource *resource, const D3D12_DISCARD_REGION *region) {
|
||||
Access access(self);
|
||||
access.driver_call();
|
||||
return original_DiscardResource(access.target(), resource, region);
|
||||
}
|
||||
|
||||
using Fn_BeginQuery = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, ID3D12QueryHeap *heap, D3D12_QUERY_TYPE type, UINT index);
|
||||
static Fn_BeginQuery original_BeginQuery = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_BeginQuery(ID3D12GraphicsCommandList* self, ID3D12QueryHeap *heap, D3D12_QUERY_TYPE type, UINT index);
|
||||
|
||||
using Fn_EndQuery = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, ID3D12QueryHeap *heap, D3D12_QUERY_TYPE type, UINT index);
|
||||
static Fn_EndQuery original_EndQuery = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_EndQuery(ID3D12GraphicsCommandList* self, ID3D12QueryHeap *heap, D3D12_QUERY_TYPE type, UINT index);
|
||||
|
||||
using Fn_ResolveQueryData = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, ID3D12QueryHeap *heap, D3D12_QUERY_TYPE type, UINT start_index, UINT query_count, ID3D12Resource *dst_buffer, UINT64 aligned_dst_buffer_offset);
|
||||
static Fn_ResolveQueryData original_ResolveQueryData = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_ResolveQueryData(ID3D12GraphicsCommandList* self, ID3D12QueryHeap *heap, D3D12_QUERY_TYPE type, UINT start_index, UINT query_count, ID3D12Resource *dst_buffer, UINT64 aligned_dst_buffer_offset) {
|
||||
Access access(self);
|
||||
access.driver_call();
|
||||
return original_ResolveQueryData(access.target(), heap, type, start_index, query_count, dst_buffer, aligned_dst_buffer_offset);
|
||||
}
|
||||
|
||||
using Fn_SetPredication = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, ID3D12Resource *buffer, UINT64 aligned_buffer_offset, D3D12_PREDICATION_OP operation);
|
||||
static Fn_SetPredication original_SetPredication = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_SetPredication(ID3D12GraphicsCommandList* self, ID3D12Resource *buffer, UINT64 aligned_buffer_offset, D3D12_PREDICATION_OP operation);
|
||||
|
||||
using Fn_SetMarker = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, UINT metadata, const void *data, UINT size);
|
||||
static Fn_SetMarker original_SetMarker = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_SetMarker(ID3D12GraphicsCommandList* self, UINT metadata, const void *data, UINT size) {
|
||||
Access access(self);
|
||||
access.driver_call();
|
||||
return original_SetMarker(access.target(), metadata, data, size);
|
||||
}
|
||||
|
||||
using Fn_BeginEvent = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, UINT metadata, const void *data, UINT size);
|
||||
static Fn_BeginEvent original_BeginEvent = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_BeginEvent(ID3D12GraphicsCommandList* self, UINT metadata, const void *data, UINT size);
|
||||
|
||||
using Fn_EndEvent = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self);
|
||||
static Fn_EndEvent original_EndEvent = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_EndEvent(ID3D12GraphicsCommandList* self);
|
||||
|
||||
using Fn_ExecuteIndirect = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, ID3D12CommandSignature *command_signature, UINT max_command_count, ID3D12Resource *arg_buffer, UINT64 arg_buffer_offset, ID3D12Resource *count_buffer, UINT64 count_buffer_offset);
|
||||
static Fn_ExecuteIndirect original_ExecuteIndirect = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_ExecuteIndirect(ID3D12GraphicsCommandList* self, ID3D12CommandSignature *command_signature, UINT max_command_count, ID3D12Resource *arg_buffer, UINT64 arg_buffer_offset, ID3D12Resource *count_buffer, UINT64 count_buffer_offset);
|
||||
|
||||
using Fn_AtomicCopyBufferUINT = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, ID3D12Resource *dst_buffer, UINT64 dst_offset, ID3D12Resource *src_buffer, UINT64 src_offset, UINT dependent_resource_count, ID3D12Resource *const *dependent_resources, const D3D12_SUBRESOURCE_RANGE_UINT64 *dependent_sub_resource_ranges);
|
||||
static Fn_AtomicCopyBufferUINT original_AtomicCopyBufferUINT = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_AtomicCopyBufferUINT(ID3D12GraphicsCommandList* self, ID3D12Resource *dst_buffer, UINT64 dst_offset, ID3D12Resource *src_buffer, UINT64 src_offset, UINT dependent_resource_count, ID3D12Resource *const *dependent_resources, const D3D12_SUBRESOURCE_RANGE_UINT64 *dependent_sub_resource_ranges) {
|
||||
Access access(self);
|
||||
access.driver_call();
|
||||
return original_AtomicCopyBufferUINT(access.target(), dst_buffer, dst_offset, src_buffer, src_offset, dependent_resource_count, dependent_resources, dependent_sub_resource_ranges);
|
||||
}
|
||||
|
||||
using Fn_AtomicCopyBufferUINT64 = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, ID3D12Resource *dst_buffer, UINT64 dst_offset, ID3D12Resource *src_buffer, UINT64 src_offset, UINT dependent_resource_count, ID3D12Resource *const *dependent_resources, const D3D12_SUBRESOURCE_RANGE_UINT64 *dependent_sub_resource_ranges);
|
||||
static Fn_AtomicCopyBufferUINT64 original_AtomicCopyBufferUINT64 = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_AtomicCopyBufferUINT64(ID3D12GraphicsCommandList* self, ID3D12Resource *dst_buffer, UINT64 dst_offset, ID3D12Resource *src_buffer, UINT64 src_offset, UINT dependent_resource_count, ID3D12Resource *const *dependent_resources, const D3D12_SUBRESOURCE_RANGE_UINT64 *dependent_sub_resource_ranges) {
|
||||
Access access(self);
|
||||
access.driver_call();
|
||||
return original_AtomicCopyBufferUINT64(access.target(), dst_buffer, dst_offset, src_buffer, src_offset, dependent_resource_count, dependent_resources, dependent_sub_resource_ranges);
|
||||
}
|
||||
|
||||
using Fn_OMSetDepthBounds = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, FLOAT min, FLOAT max);
|
||||
static Fn_OMSetDepthBounds original_OMSetDepthBounds = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_OMSetDepthBounds(ID3D12GraphicsCommandList* self, FLOAT min, FLOAT max) {
|
||||
Access access(self);
|
||||
if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable {
|
||||
original_OMSetDepthBounds(target, min, max);
|
||||
});
|
||||
access.driver_call();
|
||||
return original_OMSetDepthBounds(access.target(), min, max);
|
||||
}
|
||||
|
||||
using Fn_SetSamplePositions = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, UINT sample_count, UINT pixel_count, D3D12_SAMPLE_POSITION *sample_positions);
|
||||
static Fn_SetSamplePositions original_SetSamplePositions = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_SetSamplePositions(ID3D12GraphicsCommandList* self, UINT sample_count, UINT pixel_count, D3D12_SAMPLE_POSITION *sample_positions) {
|
||||
Access access(self);
|
||||
auto saved_sample_positions = copy_values(sample_positions, sample_count * pixel_count);
|
||||
if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable {
|
||||
original_SetSamplePositions(target, sample_count, pixel_count, values_pointer(saved_sample_positions));
|
||||
});
|
||||
access.driver_call();
|
||||
return original_SetSamplePositions(access.target(), sample_count, pixel_count, sample_positions);
|
||||
}
|
||||
|
||||
using Fn_ResolveSubresourceRegion = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, ID3D12Resource *dst_resource, UINT dst_sub_resource_idx, UINT dst_x, UINT dst_y, ID3D12Resource *src_resource, UINT src_sub_resource_idx, D3D12_RECT *src_rect, DXGI_FORMAT format, D3D12_RESOLVE_MODE mode);
|
||||
static Fn_ResolveSubresourceRegion original_ResolveSubresourceRegion = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_ResolveSubresourceRegion(ID3D12GraphicsCommandList* self, ID3D12Resource *dst_resource, UINT dst_sub_resource_idx, UINT dst_x, UINT dst_y, ID3D12Resource *src_resource, UINT src_sub_resource_idx, D3D12_RECT *src_rect, DXGI_FORMAT format, D3D12_RESOLVE_MODE mode) {
|
||||
Access access(self);
|
||||
access.driver_call();
|
||||
return original_ResolveSubresourceRegion(access.target(), dst_resource, dst_sub_resource_idx, dst_x, dst_y, src_resource, src_sub_resource_idx, src_rect, format, mode);
|
||||
}
|
||||
|
||||
using Fn_SetViewInstanceMask = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, UINT mask);
|
||||
static Fn_SetViewInstanceMask original_SetViewInstanceMask = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_SetViewInstanceMask(ID3D12GraphicsCommandList* self, UINT mask) {
|
||||
Access access(self);
|
||||
if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable {
|
||||
original_SetViewInstanceMask(target, mask);
|
||||
});
|
||||
access.driver_call();
|
||||
return original_SetViewInstanceMask(access.target(), mask);
|
||||
}
|
||||
|
||||
using Fn_WriteBufferImmediate = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, UINT count, const D3D12_WRITEBUFFERIMMEDIATE_PARAMETER *parameters, const D3D12_WRITEBUFFERIMMEDIATE_MODE *modes);
|
||||
static Fn_WriteBufferImmediate original_WriteBufferImmediate = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_WriteBufferImmediate(ID3D12GraphicsCommandList* self, UINT count, const D3D12_WRITEBUFFERIMMEDIATE_PARAMETER *parameters, const D3D12_WRITEBUFFERIMMEDIATE_MODE *modes) {
|
||||
Access access(self);
|
||||
access.driver_call();
|
||||
return original_WriteBufferImmediate(access.target(), count, parameters, modes);
|
||||
}
|
||||
|
||||
using Fn_SetProtectedResourceSession = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, ID3D12ProtectedResourceSession *protected_resource_session);
|
||||
static Fn_SetProtectedResourceSession original_SetProtectedResourceSession = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_SetProtectedResourceSession(ID3D12GraphicsCommandList* self, ID3D12ProtectedResourceSession *protected_resource_session) {
|
||||
Access access(self);
|
||||
auto saved_protected_resource_session = retain(protected_resource_session);
|
||||
if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable {
|
||||
original_SetProtectedResourceSession(target, saved_protected_resource_session.Get());
|
||||
});
|
||||
access.driver_call();
|
||||
return original_SetProtectedResourceSession(access.target(), protected_resource_session);
|
||||
}
|
||||
|
||||
using Fn_BeginRenderPass = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, UINT render_targets_count, const D3D12_RENDER_PASS_RENDER_TARGET_DESC *render_targets, const D3D12_RENDER_PASS_DEPTH_STENCIL_DESC *depth_stencil, D3D12_RENDER_PASS_FLAGS flags);
|
||||
static Fn_BeginRenderPass original_BeginRenderPass = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_BeginRenderPass(ID3D12GraphicsCommandList* self, UINT render_targets_count, const D3D12_RENDER_PASS_RENDER_TARGET_DESC *render_targets, const D3D12_RENDER_PASS_DEPTH_STENCIL_DESC *depth_stencil, D3D12_RENDER_PASS_FLAGS flags);
|
||||
|
||||
using Fn_EndRenderPass = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self);
|
||||
static Fn_EndRenderPass original_EndRenderPass = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_EndRenderPass(ID3D12GraphicsCommandList* self);
|
||||
|
||||
using Fn_InitializeMetaCommand = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, ID3D12MetaCommand *meta_command, const void *initialization_parameters_data, SIZE_T initialization_parameters_data_size_in_bytes);
|
||||
static Fn_InitializeMetaCommand original_InitializeMetaCommand = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_InitializeMetaCommand(ID3D12GraphicsCommandList* self, ID3D12MetaCommand *meta_command, const void *initialization_parameters_data, SIZE_T initialization_parameters_data_size_in_bytes) {
|
||||
Access access(self);
|
||||
access.driver_call();
|
||||
return original_InitializeMetaCommand(access.target(), meta_command, initialization_parameters_data, initialization_parameters_data_size_in_bytes);
|
||||
}
|
||||
|
||||
using Fn_ExecuteMetaCommand = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, ID3D12MetaCommand *meta_command, const void *execution_parameters_data, SIZE_T execution_parameters_data_size_in_bytes);
|
||||
static Fn_ExecuteMetaCommand original_ExecuteMetaCommand = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_ExecuteMetaCommand(ID3D12GraphicsCommandList* self, ID3D12MetaCommand *meta_command, const void *execution_parameters_data, SIZE_T execution_parameters_data_size_in_bytes) {
|
||||
Access access(self);
|
||||
access.driver_call();
|
||||
return original_ExecuteMetaCommand(access.target(), meta_command, execution_parameters_data, execution_parameters_data_size_in_bytes);
|
||||
}
|
||||
|
||||
using Fn_BuildRaytracingAccelerationStructure = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, const D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_DESC *desc, UINT postbuild_info_descs_count, const D3D12_RAYTRACING_ACCELERATION_STRUCTURE_POSTBUILD_INFO_DESC *postbuild_info_descs);
|
||||
static Fn_BuildRaytracingAccelerationStructure original_BuildRaytracingAccelerationStructure = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_BuildRaytracingAccelerationStructure(ID3D12GraphicsCommandList* self, const D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_DESC *desc, UINT postbuild_info_descs_count, const D3D12_RAYTRACING_ACCELERATION_STRUCTURE_POSTBUILD_INFO_DESC *postbuild_info_descs) {
|
||||
Access access(self);
|
||||
access.driver_call();
|
||||
return original_BuildRaytracingAccelerationStructure(access.target(), desc, postbuild_info_descs_count, postbuild_info_descs);
|
||||
}
|
||||
|
||||
using Fn_EmitRaytracingAccelerationStructurePostbuildInfo = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, const D3D12_RAYTRACING_ACCELERATION_STRUCTURE_POSTBUILD_INFO_DESC *desc, UINT src_acceleration_structures_count, const D3D12_GPU_VIRTUAL_ADDRESS *src_acceleration_structure_data);
|
||||
static Fn_EmitRaytracingAccelerationStructurePostbuildInfo original_EmitRaytracingAccelerationStructurePostbuildInfo = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_EmitRaytracingAccelerationStructurePostbuildInfo(ID3D12GraphicsCommandList* self, const D3D12_RAYTRACING_ACCELERATION_STRUCTURE_POSTBUILD_INFO_DESC *desc, UINT src_acceleration_structures_count, const D3D12_GPU_VIRTUAL_ADDRESS *src_acceleration_structure_data) {
|
||||
Access access(self);
|
||||
access.driver_call();
|
||||
return original_EmitRaytracingAccelerationStructurePostbuildInfo(access.target(), desc, src_acceleration_structures_count, src_acceleration_structure_data);
|
||||
}
|
||||
|
||||
using Fn_CopyRaytracingAccelerationStructure = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, D3D12_GPU_VIRTUAL_ADDRESS dst_acceleration_structure_data, D3D12_GPU_VIRTUAL_ADDRESS src_acceleration_structure_data, D3D12_RAYTRACING_ACCELERATION_STRUCTURE_COPY_MODE mode);
|
||||
static Fn_CopyRaytracingAccelerationStructure original_CopyRaytracingAccelerationStructure = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_CopyRaytracingAccelerationStructure(ID3D12GraphicsCommandList* self, D3D12_GPU_VIRTUAL_ADDRESS dst_acceleration_structure_data, D3D12_GPU_VIRTUAL_ADDRESS src_acceleration_structure_data, D3D12_RAYTRACING_ACCELERATION_STRUCTURE_COPY_MODE mode) {
|
||||
Access access(self);
|
||||
access.driver_call();
|
||||
return original_CopyRaytracingAccelerationStructure(access.target(), dst_acceleration_structure_data, src_acceleration_structure_data, mode);
|
||||
}
|
||||
|
||||
using Fn_SetPipelineState1 = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, ID3D12StateObject *state_object);
|
||||
static Fn_SetPipelineState1 original_SetPipelineState1 = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_SetPipelineState1(ID3D12GraphicsCommandList* self, ID3D12StateObject *state_object) {
|
||||
Access access(self);
|
||||
auto saved_state_object = retain(state_object);
|
||||
if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable {
|
||||
original_SetPipelineState1(target, saved_state_object.Get());
|
||||
});
|
||||
access.driver_call();
|
||||
return original_SetPipelineState1(access.target(), state_object);
|
||||
}
|
||||
|
||||
using Fn_DispatchRays = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, const D3D12_DISPATCH_RAYS_DESC *desc);
|
||||
static Fn_DispatchRays original_DispatchRays = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_DispatchRays(ID3D12GraphicsCommandList* self, const D3D12_DISPATCH_RAYS_DESC *desc) {
|
||||
Access access(self);
|
||||
access.driver_call();
|
||||
return original_DispatchRays(access.target(), desc);
|
||||
}
|
||||
|
||||
using Fn_RSSetShadingRate = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, D3D12_SHADING_RATE base_shading_rate, const D3D12_SHADING_RATE_COMBINER *combiners);
|
||||
static Fn_RSSetShadingRate original_RSSetShadingRate = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_RSSetShadingRate(ID3D12GraphicsCommandList* self, D3D12_SHADING_RATE base_shading_rate, const D3D12_SHADING_RATE_COMBINER *combiners) {
|
||||
Access access(self);
|
||||
auto saved_combiners = copy_values(combiners, 2);
|
||||
if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable {
|
||||
original_RSSetShadingRate(target, base_shading_rate, values_pointer(saved_combiners));
|
||||
});
|
||||
access.driver_call();
|
||||
return original_RSSetShadingRate(access.target(), base_shading_rate, combiners);
|
||||
}
|
||||
|
||||
using Fn_RSSetShadingRateImage = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, ID3D12Resource *shading_rate_image);
|
||||
static Fn_RSSetShadingRateImage original_RSSetShadingRateImage = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_RSSetShadingRateImage(ID3D12GraphicsCommandList* self, ID3D12Resource *shading_rate_image) {
|
||||
Access access(self);
|
||||
auto saved_shading_rate_image = retain(shading_rate_image);
|
||||
if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable {
|
||||
original_RSSetShadingRateImage(target, saved_shading_rate_image.Get());
|
||||
});
|
||||
access.driver_call();
|
||||
return original_RSSetShadingRateImage(access.target(), shading_rate_image);
|
||||
}
|
||||
|
||||
using Fn_DispatchMesh = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, UINT thread_group_count_x, UINT thread_group_count_y, UINT thread_group_count_z);
|
||||
static Fn_DispatchMesh original_DispatchMesh = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_DispatchMesh(ID3D12GraphicsCommandList* self, UINT thread_group_count_x, UINT thread_group_count_y, UINT thread_group_count_z) {
|
||||
Access access(self);
|
||||
access.driver_call();
|
||||
return original_DispatchMesh(access.target(), thread_group_count_x, thread_group_count_y, thread_group_count_z);
|
||||
}
|
||||
|
||||
using Fn_Barrier = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, UINT32 barrier_groups_count, const D3D12_BARRIER_GROUP *barrier_groups);
|
||||
static Fn_Barrier original_Barrier = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_Barrier(ID3D12GraphicsCommandList* self, UINT32 barrier_groups_count, const D3D12_BARRIER_GROUP *barrier_groups);
|
||||
|
||||
using Fn_OMSetFrontAndBackStencilRef = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, UINT front_stencil, UINT back_stencil);
|
||||
static Fn_OMSetFrontAndBackStencilRef original_OMSetFrontAndBackStencilRef = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_OMSetFrontAndBackStencilRef(ID3D12GraphicsCommandList* self, UINT front_stencil, UINT back_stencil) {
|
||||
Access access(self);
|
||||
if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) {
|
||||
original_OMSetFrontAndBackStencilRef(target, front_stencil, back_stencil);
|
||||
});
|
||||
access.driver_call();
|
||||
original_OMSetFrontAndBackStencilRef(access.target(), front_stencil, back_stencil);
|
||||
}
|
||||
|
||||
using Fn_RSSetDepthBias = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, FLOAT depth_bias, FLOAT clamp, FLOAT slope);
|
||||
static Fn_RSSetDepthBias original_RSSetDepthBias = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_RSSetDepthBias(ID3D12GraphicsCommandList* self, FLOAT depth_bias, FLOAT clamp, FLOAT slope) {
|
||||
Access access(self);
|
||||
if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) {
|
||||
original_RSSetDepthBias(target, depth_bias, clamp, slope);
|
||||
});
|
||||
access.driver_call();
|
||||
original_RSSetDepthBias(access.target(), depth_bias, clamp, slope);
|
||||
}
|
||||
|
||||
using Fn_IASetIndexBufferStripCutValue = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, D3D12_INDEX_BUFFER_STRIP_CUT_VALUE value);
|
||||
static Fn_IASetIndexBufferStripCutValue original_IASetIndexBufferStripCutValue = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_IASetIndexBufferStripCutValue(ID3D12GraphicsCommandList* self, D3D12_INDEX_BUFFER_STRIP_CUT_VALUE value) {
|
||||
Access access(self);
|
||||
if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) {
|
||||
original_IASetIndexBufferStripCutValue(target, value);
|
||||
});
|
||||
access.driver_call();
|
||||
original_IASetIndexBufferStripCutValue(access.target(), value);
|
||||
}
|
||||
|
||||
using Fn_SetProgram = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, const NewProgramDescription* desc);
|
||||
static Fn_SetProgram original_SetProgram = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_SetProgram(ID3D12GraphicsCommandList* self, const NewProgramDescription* desc);
|
||||
|
||||
using Fn_DispatchGraph = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, const void* desc);
|
||||
static Fn_DispatchGraph original_DispatchGraph = nullptr;
|
||||
static void STDMETHODCALLTYPE hook_DispatchGraph(ID3D12GraphicsCommandList* self, const void* desc) {
|
||||
Access access(self);
|
||||
access.driver_call();
|
||||
original_DispatchGraph(access.target(), desc);
|
||||
}
|
||||
|
||||
static void attach_recording_methods(void** table, unsigned version) {
|
||||
if (version >= 0) attach_method(table[9], reinterpret_cast<void*>(hook_Close), reinterpret_cast<void**>(&original_Close), "Close");
|
||||
if (version >= 0) attach_method(table[10], reinterpret_cast<void*>(hook_Reset), reinterpret_cast<void**>(&original_Reset), "Reset");
|
||||
if (version >= 0) attach_method(table[11], reinterpret_cast<void*>(hook_ClearState), reinterpret_cast<void**>(&original_ClearState), "ClearState");
|
||||
if (version >= 0) attach_method(table[12], reinterpret_cast<void*>(hook_DrawInstanced), reinterpret_cast<void**>(&original_DrawInstanced), "DrawInstanced");
|
||||
if (version >= 0) attach_method(table[13], reinterpret_cast<void*>(hook_DrawIndexedInstanced), reinterpret_cast<void**>(&original_DrawIndexedInstanced), "DrawIndexedInstanced");
|
||||
if (version >= 0) attach_method(table[14], reinterpret_cast<void*>(hook_Dispatch), reinterpret_cast<void**>(&original_Dispatch), "Dispatch");
|
||||
if (version >= 0) attach_method(table[15], reinterpret_cast<void*>(hook_CopyBufferRegion), reinterpret_cast<void**>(&original_CopyBufferRegion), "CopyBufferRegion");
|
||||
if (version >= 0) attach_method(table[16], reinterpret_cast<void*>(hook_CopyTextureRegion), reinterpret_cast<void**>(&original_CopyTextureRegion), "CopyTextureRegion");
|
||||
if (version >= 0) attach_method(table[17], reinterpret_cast<void*>(hook_CopyResource), reinterpret_cast<void**>(&original_CopyResource), "CopyResource");
|
||||
if (version >= 0) attach_method(table[18], reinterpret_cast<void*>(hook_CopyTiles), reinterpret_cast<void**>(&original_CopyTiles), "CopyTiles");
|
||||
if (version >= 0) attach_method(table[19], reinterpret_cast<void*>(hook_ResolveSubresource), reinterpret_cast<void**>(&original_ResolveSubresource), "ResolveSubresource");
|
||||
if (version >= 0) attach_method(table[20], reinterpret_cast<void*>(hook_IASetPrimitiveTopology), reinterpret_cast<void**>(&original_IASetPrimitiveTopology), "IASetPrimitiveTopology");
|
||||
if (version >= 0) attach_method(table[21], reinterpret_cast<void*>(hook_RSSetViewports), reinterpret_cast<void**>(&original_RSSetViewports), "RSSetViewports");
|
||||
if (version >= 0) attach_method(table[22], reinterpret_cast<void*>(hook_RSSetScissorRects), reinterpret_cast<void**>(&original_RSSetScissorRects), "RSSetScissorRects");
|
||||
if (version >= 0) attach_method(table[23], reinterpret_cast<void*>(hook_OMSetBlendFactor), reinterpret_cast<void**>(&original_OMSetBlendFactor), "OMSetBlendFactor");
|
||||
if (version >= 0) attach_method(table[24], reinterpret_cast<void*>(hook_OMSetStencilRef), reinterpret_cast<void**>(&original_OMSetStencilRef), "OMSetStencilRef");
|
||||
if (version >= 0) attach_method(table[25], reinterpret_cast<void*>(hook_SetPipelineState), reinterpret_cast<void**>(&original_SetPipelineState), "SetPipelineState");
|
||||
if (version >= 0) attach_method(table[26], reinterpret_cast<void*>(hook_ResourceBarrier), reinterpret_cast<void**>(&original_ResourceBarrier), "ResourceBarrier");
|
||||
if (version >= 0) attach_method(table[27], reinterpret_cast<void*>(hook_ExecuteBundle), reinterpret_cast<void**>(&original_ExecuteBundle), "ExecuteBundle");
|
||||
if (version >= 0) attach_method(table[28], reinterpret_cast<void*>(hook_SetDescriptorHeaps), reinterpret_cast<void**>(&original_SetDescriptorHeaps), "SetDescriptorHeaps");
|
||||
if (version >= 0) attach_method(table[29], reinterpret_cast<void*>(hook_SetComputeRootSignature), reinterpret_cast<void**>(&original_SetComputeRootSignature), "SetComputeRootSignature");
|
||||
if (version >= 0) attach_method(table[30], reinterpret_cast<void*>(hook_SetGraphicsRootSignature), reinterpret_cast<void**>(&original_SetGraphicsRootSignature), "SetGraphicsRootSignature");
|
||||
if (version >= 0) attach_method(table[31], reinterpret_cast<void*>(hook_SetComputeRootDescriptorTable), reinterpret_cast<void**>(&original_SetComputeRootDescriptorTable), "SetComputeRootDescriptorTable");
|
||||
if (version >= 0) attach_method(table[32], reinterpret_cast<void*>(hook_SetGraphicsRootDescriptorTable), reinterpret_cast<void**>(&original_SetGraphicsRootDescriptorTable), "SetGraphicsRootDescriptorTable");
|
||||
if (version >= 0) attach_method(table[33], reinterpret_cast<void*>(hook_SetComputeRoot32BitConstant), reinterpret_cast<void**>(&original_SetComputeRoot32BitConstant), "SetComputeRoot32BitConstant");
|
||||
if (version >= 0) attach_method(table[34], reinterpret_cast<void*>(hook_SetGraphicsRoot32BitConstant), reinterpret_cast<void**>(&original_SetGraphicsRoot32BitConstant), "SetGraphicsRoot32BitConstant");
|
||||
if (version >= 0) attach_method(table[35], reinterpret_cast<void*>(hook_SetComputeRoot32BitConstants), reinterpret_cast<void**>(&original_SetComputeRoot32BitConstants), "SetComputeRoot32BitConstants");
|
||||
if (version >= 0) attach_method(table[36], reinterpret_cast<void*>(hook_SetGraphicsRoot32BitConstants), reinterpret_cast<void**>(&original_SetGraphicsRoot32BitConstants), "SetGraphicsRoot32BitConstants");
|
||||
if (version >= 0) attach_method(table[37], reinterpret_cast<void*>(hook_SetComputeRootConstantBufferView), reinterpret_cast<void**>(&original_SetComputeRootConstantBufferView), "SetComputeRootConstantBufferView");
|
||||
if (version >= 0) attach_method(table[38], reinterpret_cast<void*>(hook_SetGraphicsRootConstantBufferView), reinterpret_cast<void**>(&original_SetGraphicsRootConstantBufferView), "SetGraphicsRootConstantBufferView");
|
||||
if (version >= 0) attach_method(table[39], reinterpret_cast<void*>(hook_SetComputeRootShaderResourceView), reinterpret_cast<void**>(&original_SetComputeRootShaderResourceView), "SetComputeRootShaderResourceView");
|
||||
if (version >= 0) attach_method(table[40], reinterpret_cast<void*>(hook_SetGraphicsRootShaderResourceView), reinterpret_cast<void**>(&original_SetGraphicsRootShaderResourceView), "SetGraphicsRootShaderResourceView");
|
||||
if (version >= 0) attach_method(table[41], reinterpret_cast<void*>(hook_SetComputeRootUnorderedAccessView), reinterpret_cast<void**>(&original_SetComputeRootUnorderedAccessView), "SetComputeRootUnorderedAccessView");
|
||||
if (version >= 0) attach_method(table[42], reinterpret_cast<void*>(hook_SetGraphicsRootUnorderedAccessView), reinterpret_cast<void**>(&original_SetGraphicsRootUnorderedAccessView), "SetGraphicsRootUnorderedAccessView");
|
||||
if (version >= 0) attach_method(table[43], reinterpret_cast<void*>(hook_IASetIndexBuffer), reinterpret_cast<void**>(&original_IASetIndexBuffer), "IASetIndexBuffer");
|
||||
if (version >= 0) attach_method(table[44], reinterpret_cast<void*>(hook_IASetVertexBuffers), reinterpret_cast<void**>(&original_IASetVertexBuffers), "IASetVertexBuffers");
|
||||
if (version >= 0) attach_method(table[45], reinterpret_cast<void*>(hook_SOSetTargets), reinterpret_cast<void**>(&original_SOSetTargets), "SOSetTargets");
|
||||
if (version >= 0) attach_method(table[46], reinterpret_cast<void*>(hook_OMSetRenderTargets), reinterpret_cast<void**>(&original_OMSetRenderTargets), "OMSetRenderTargets");
|
||||
if (version >= 0) attach_method(table[47], reinterpret_cast<void*>(hook_ClearDepthStencilView), reinterpret_cast<void**>(&original_ClearDepthStencilView), "ClearDepthStencilView");
|
||||
if (version >= 0) attach_method(table[48], reinterpret_cast<void*>(hook_ClearRenderTargetView), reinterpret_cast<void**>(&original_ClearRenderTargetView), "ClearRenderTargetView");
|
||||
if (version >= 0) attach_method(table[49], reinterpret_cast<void*>(hook_ClearUnorderedAccessViewUint), reinterpret_cast<void**>(&original_ClearUnorderedAccessViewUint), "ClearUnorderedAccessViewUint");
|
||||
if (version >= 0) attach_method(table[50], reinterpret_cast<void*>(hook_ClearUnorderedAccessViewFloat), reinterpret_cast<void**>(&original_ClearUnorderedAccessViewFloat), "ClearUnorderedAccessViewFloat");
|
||||
if (version >= 0) attach_method(table[51], reinterpret_cast<void*>(hook_DiscardResource), reinterpret_cast<void**>(&original_DiscardResource), "DiscardResource");
|
||||
if (version >= 0) attach_method(table[52], reinterpret_cast<void*>(hook_BeginQuery), reinterpret_cast<void**>(&original_BeginQuery), "BeginQuery");
|
||||
if (version >= 0) attach_method(table[53], reinterpret_cast<void*>(hook_EndQuery), reinterpret_cast<void**>(&original_EndQuery), "EndQuery");
|
||||
if (version >= 0) attach_method(table[54], reinterpret_cast<void*>(hook_ResolveQueryData), reinterpret_cast<void**>(&original_ResolveQueryData), "ResolveQueryData");
|
||||
if (version >= 0) attach_method(table[55], reinterpret_cast<void*>(hook_SetPredication), reinterpret_cast<void**>(&original_SetPredication), "SetPredication");
|
||||
if (version >= 0) attach_method(table[56], reinterpret_cast<void*>(hook_SetMarker), reinterpret_cast<void**>(&original_SetMarker), "SetMarker");
|
||||
if (version >= 0) attach_method(table[57], reinterpret_cast<void*>(hook_BeginEvent), reinterpret_cast<void**>(&original_BeginEvent), "BeginEvent");
|
||||
if (version >= 0) attach_method(table[58], reinterpret_cast<void*>(hook_EndEvent), reinterpret_cast<void**>(&original_EndEvent), "EndEvent");
|
||||
if (version >= 0) attach_method(table[59], reinterpret_cast<void*>(hook_ExecuteIndirect), reinterpret_cast<void**>(&original_ExecuteIndirect), "ExecuteIndirect");
|
||||
if (version >= 1) attach_method(table[60], reinterpret_cast<void*>(hook_AtomicCopyBufferUINT), reinterpret_cast<void**>(&original_AtomicCopyBufferUINT), "AtomicCopyBufferUINT");
|
||||
if (version >= 1) attach_method(table[61], reinterpret_cast<void*>(hook_AtomicCopyBufferUINT64), reinterpret_cast<void**>(&original_AtomicCopyBufferUINT64), "AtomicCopyBufferUINT64");
|
||||
if (version >= 1) attach_method(table[62], reinterpret_cast<void*>(hook_OMSetDepthBounds), reinterpret_cast<void**>(&original_OMSetDepthBounds), "OMSetDepthBounds");
|
||||
if (version >= 1) attach_method(table[63], reinterpret_cast<void*>(hook_SetSamplePositions), reinterpret_cast<void**>(&original_SetSamplePositions), "SetSamplePositions");
|
||||
if (version >= 1) attach_method(table[64], reinterpret_cast<void*>(hook_ResolveSubresourceRegion), reinterpret_cast<void**>(&original_ResolveSubresourceRegion), "ResolveSubresourceRegion");
|
||||
if (version >= 1) attach_method(table[65], reinterpret_cast<void*>(hook_SetViewInstanceMask), reinterpret_cast<void**>(&original_SetViewInstanceMask), "SetViewInstanceMask");
|
||||
if (version >= 2) attach_method(table[66], reinterpret_cast<void*>(hook_WriteBufferImmediate), reinterpret_cast<void**>(&original_WriteBufferImmediate), "WriteBufferImmediate");
|
||||
if (version >= 3) attach_method(table[67], reinterpret_cast<void*>(hook_SetProtectedResourceSession), reinterpret_cast<void**>(&original_SetProtectedResourceSession), "SetProtectedResourceSession");
|
||||
if (version >= 4) attach_method(table[68], reinterpret_cast<void*>(hook_BeginRenderPass), reinterpret_cast<void**>(&original_BeginRenderPass), "BeginRenderPass");
|
||||
if (version >= 4) attach_method(table[69], reinterpret_cast<void*>(hook_EndRenderPass), reinterpret_cast<void**>(&original_EndRenderPass), "EndRenderPass");
|
||||
if (version >= 4) attach_method(table[70], reinterpret_cast<void*>(hook_InitializeMetaCommand), reinterpret_cast<void**>(&original_InitializeMetaCommand), "InitializeMetaCommand");
|
||||
if (version >= 4) attach_method(table[71], reinterpret_cast<void*>(hook_ExecuteMetaCommand), reinterpret_cast<void**>(&original_ExecuteMetaCommand), "ExecuteMetaCommand");
|
||||
if (version >= 4) attach_method(table[72], reinterpret_cast<void*>(hook_BuildRaytracingAccelerationStructure), reinterpret_cast<void**>(&original_BuildRaytracingAccelerationStructure), "BuildRaytracingAccelerationStructure");
|
||||
if (version >= 4) attach_method(table[73], reinterpret_cast<void*>(hook_EmitRaytracingAccelerationStructurePostbuildInfo), reinterpret_cast<void**>(&original_EmitRaytracingAccelerationStructurePostbuildInfo), "EmitRaytracingAccelerationStructurePostbuildInfo");
|
||||
if (version >= 4) attach_method(table[74], reinterpret_cast<void*>(hook_CopyRaytracingAccelerationStructure), reinterpret_cast<void**>(&original_CopyRaytracingAccelerationStructure), "CopyRaytracingAccelerationStructure");
|
||||
if (version >= 4) attach_method(table[75], reinterpret_cast<void*>(hook_SetPipelineState1), reinterpret_cast<void**>(&original_SetPipelineState1), "SetPipelineState1");
|
||||
if (version >= 4) attach_method(table[76], reinterpret_cast<void*>(hook_DispatchRays), reinterpret_cast<void**>(&original_DispatchRays), "DispatchRays");
|
||||
if (version >= 5) attach_method(table[77], reinterpret_cast<void*>(hook_RSSetShadingRate), reinterpret_cast<void**>(&original_RSSetShadingRate), "RSSetShadingRate");
|
||||
if (version >= 5) attach_method(table[78], reinterpret_cast<void*>(hook_RSSetShadingRateImage), reinterpret_cast<void**>(&original_RSSetShadingRateImage), "RSSetShadingRateImage");
|
||||
if (version >= 6) attach_method(table[79], reinterpret_cast<void*>(hook_DispatchMesh), reinterpret_cast<void**>(&original_DispatchMesh), "DispatchMesh");
|
||||
if (version >= 7) attach_method(table[80], reinterpret_cast<void*>(hook_Barrier), reinterpret_cast<void**>(&original_Barrier), "Barrier");
|
||||
if (version >= 8) attach_method(table[81], reinterpret_cast<void*>(hook_OMSetFrontAndBackStencilRef), reinterpret_cast<void**>(&original_OMSetFrontAndBackStencilRef), "OMSetFrontAndBackStencilRef");
|
||||
if (version >= 9) attach_method(table[82], reinterpret_cast<void*>(hook_RSSetDepthBias), reinterpret_cast<void**>(&original_RSSetDepthBias), "RSSetDepthBias");
|
||||
if (version >= 9) attach_method(table[83], reinterpret_cast<void*>(hook_IASetIndexBufferStripCutValue), reinterpret_cast<void**>(&original_IASetIndexBufferStripCutValue), "IASetIndexBufferStripCutValue");
|
||||
if (version >= 10) attach_method(table[84], reinterpret_cast<void*>(hook_SetProgram), reinterpret_cast<void**>(&original_SetProgram), "SetProgram");
|
||||
if (version >= 10) attach_method(table[85], reinterpret_cast<void*>(hook_DispatchGraph), reinterpret_cast<void**>(&original_DispatchGraph), "DispatchGraph");
|
||||
}
|
||||
|
||||
static constexpr unsigned device_signature_slot = 41;
|
||||
static constexpr unsigned device_command_list_slot = 12;
|
||||
static constexpr unsigned queue_signal_slot = 14;
|
||||
static constexpr unsigned queue_wait_slot = 15;
|
||||
@@ -1,24 +0,0 @@
|
||||
#pragma once
|
||||
#include "d3d12_external.h"
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
namespace d4r::win::commands {
|
||||
// Intercept only documented D3D12 COM methods on this device's runtime.
|
||||
// A boundary becomes prefix -> callback -> suffix at ExecuteCommandLists,
|
||||
// preserving preceding lists in the caller's batch and current-frame output.
|
||||
using Boundary = std::function<void(ID3D12CommandQueue*)>;
|
||||
void install(ID3D12Device* device);
|
||||
void record_boundary(ID3D12GraphicsCommandList* list, Boundary callback);
|
||||
std::string error();
|
||||
size_t live_recordings();
|
||||
D3D12_RESOURCE_STATES resource_state(ID3D12GraphicsCommandList* list, ID3D12Resource* resource, D3D12_RESOURCE_STATES fallback);
|
||||
ResourceAccess resource_access(ID3D12GraphicsCommandList* list, ID3D12Resource* resource, ResourceAccess fallback);
|
||||
ResourceAccess submitted_resource_access(ID3D12Resource* resource, ResourceAccess fallback);
|
||||
// Internal copies and callback submissions must bypass logical list routing.
|
||||
struct InternalScope {
|
||||
bool previous;
|
||||
InternalScope();
|
||||
~InternalScope();
|
||||
};
|
||||
}
|
||||
@@ -1,327 +0,0 @@
|
||||
#include "d3d12_external.h"
|
||||
#include <dxgi1_6.h>
|
||||
#include <d3dcompiler.h>
|
||||
#include <d3d12sdklayers.h>
|
||||
#include <array>
|
||||
|
||||
using namespace d4r::diag;
|
||||
using namespace d4r::win;
|
||||
#if defined(D4R_DIAGNOSTIC_AGILITY)
|
||||
extern "C" {
|
||||
__declspec(dllexport) extern const UINT D3D12SDKVersion = 619;
|
||||
__declspec(dllexport) extern const char* D3D12SDKPath = ".\\debug-d3d12\\";
|
||||
}
|
||||
#endif
|
||||
namespace {
|
||||
constexpr unsigned count = 256, bytes = count * sizeof(uint32_t), seed = 0x12345;
|
||||
void messages(ID3D12Device* device) {
|
||||
ComPtr<ID3D12InfoQueue> info;
|
||||
if (FAILED(device->QueryInterface(IID_PPV_ARGS(info.GetAddressOf())))) return;
|
||||
for (UINT64 i = 0; i < info->GetNumStoredMessagesAllowedByRetrievalFilter(); ++i) {
|
||||
SIZE_T size = 0; (void)info->GetMessage(i, nullptr, &size);
|
||||
std::vector<uint8_t> buffer(size);
|
||||
auto* value = reinterpret_cast<D3D12_MESSAGE*>(buffer.data());
|
||||
if (SUCCEEDED(info->GetMessage(i, value, &size)))
|
||||
std::fprintf(stderr, "D3D12_VALIDATION severity=%u id=%u %s\n", unsigned(value->Severity), unsigned(value->ID), value->pDescription);
|
||||
}
|
||||
info->ClearStoredMessages();
|
||||
}
|
||||
void drain(ID3D12Device* device, ID3D12CommandQueue* queue) {
|
||||
ComPtr<ID3D12Fence> fence; dx(device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(fence.GetAddressOf())), "Probe fence");
|
||||
Handle event; event.value = CreateEventW(nullptr, FALSE, FALSE, nullptr);
|
||||
if (!event.value) throw std::runtime_error("Probe event");
|
||||
dx(queue->Signal(fence.Get(), 1), "Probe signal");
|
||||
if (fence->GetCompletedValue() < 1) {
|
||||
dx(fence->SetEventOnCompletion(1, event.value), "Probe fence event");
|
||||
if (WaitForSingleObject(event.value, 30000) != WAIT_OBJECT_0) throw std::runtime_error("Probe GPU timeout");
|
||||
}
|
||||
}
|
||||
struct NativeList {
|
||||
ComPtr<ID3D12CommandAllocator> allocator;
|
||||
ComPtr<ID3D12GraphicsCommandList> list;
|
||||
explicit NativeList(ID3D12Device* device, ID3D12PipelineState* initial = nullptr) {
|
||||
dx(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(allocator.GetAddressOf())), "Probe allocator");
|
||||
dx(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, allocator.Get(), initial, IID_PPV_ARGS(list.GetAddressOf())), "Probe command list");
|
||||
}
|
||||
void submit(ID3D12Device* device, ID3D12CommandQueue* queue) {
|
||||
dx(list->Close(), "Close probe list"); ID3D12CommandList* lists[] = {list.Get()};
|
||||
queue->ExecuteCommandLists(1, lists); drain(device, queue);
|
||||
}
|
||||
};
|
||||
ComPtr<ID3D12Resource> output_buffer(ID3D12Device* device) {
|
||||
D3D12_HEAP_PROPERTIES heap{}; heap.Type = D3D12_HEAP_TYPE_DEFAULT; heap.CreationNodeMask = heap.VisibleNodeMask = 1;
|
||||
D3D12_RESOURCE_DESC desc{}; desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; desc.Width = bytes;
|
||||
desc.Height = desc.DepthOrArraySize = desc.MipLevels = desc.SampleDesc.Count = 1;
|
||||
desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
|
||||
ComPtr<ID3D12Resource> result;
|
||||
dx(device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
|
||||
nullptr, IID_PPV_ARGS(result.GetAddressOf())), "Probe UAV");
|
||||
return result;
|
||||
}
|
||||
void upload(ID3D12Resource* buffer, uint32_t multiplier, uint32_t addend) {
|
||||
void* data = nullptr; D3D12_RANGE noRead{}; dx(buffer->Map(0, &noRead, &data), "Probe upload map");
|
||||
auto* values = static_cast<uint32_t*>(data);
|
||||
for (unsigned i = 0; i < count; ++i) values[i] = i * multiplier + addend;
|
||||
buffer->Unmap(0, nullptr);
|
||||
}
|
||||
bool verify(ID3D12Resource* buffer, uint32_t multiplier, uint32_t addend, uint32_t root_seed = seed) {
|
||||
void* data = nullptr; D3D12_RANGE range{0, bytes}; dx(buffer->Map(0, &range, &data), "Probe verification map");
|
||||
const auto* values = static_cast<const uint32_t*>(data);
|
||||
bool passed = true;
|
||||
for (unsigned i = 0; i < count; ++i) if (values[i] != i * multiplier + addend + root_seed) {
|
||||
std::fprintf(stderr, "COMMAND_DIFFERENT index=%u expected=%u actual=%u\n", i, i * multiplier + addend + root_seed, values[i]);
|
||||
passed = false; break;
|
||||
}
|
||||
D3D12_RANGE noWrite{}; buffer->Unmap(0, &noWrite); return passed;
|
||||
}
|
||||
struct Callback {
|
||||
ID3D12Device* device;
|
||||
ID3D12Resource* input;
|
||||
ID3D12Resource* output;
|
||||
ID3D12Resource* replacement;
|
||||
ID3D12Resource* readback;
|
||||
uint32_t expected_seed = seed;
|
||||
bool prefix_verified = false;
|
||||
uint32_t expected_multiplier = 17, expected_addend = 10;
|
||||
};
|
||||
void WINAPI boundary(ID3D12CommandQueue* queue, void* context) {
|
||||
auto& callback = *static_cast<Callback*>(context);
|
||||
NativeList list(callback.device);
|
||||
transition(list.list.Get(), callback.output, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_COPY_SOURCE);
|
||||
list.list->CopyResource(callback.readback, callback.output);
|
||||
transition(list.list.Get(), callback.output, D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
|
||||
transition(list.list.Get(), callback.input, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, D3D12_RESOURCE_STATE_COPY_DEST);
|
||||
list.list->CopyResource(callback.input, callback.replacement);
|
||||
transition(list.list.Get(), callback.input, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
|
||||
list.submit(callback.device, queue);
|
||||
callback.prefix_verified = verify(callback.readback, callback.expected_multiplier,
|
||||
callback.expected_addend, callback.expected_seed);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
start();
|
||||
ComPtr<ID3D12Device> device;
|
||||
std::unique_ptr<Library> debug_layers;
|
||||
try {
|
||||
Args args(argc, argv);
|
||||
if (args.module.empty()) throw std::runtime_error("--module d4r_nvngx.dll is required; NVIDIA DLLs are not needed");
|
||||
if (std::getenv("D4R_COMMAND_DEBUG_SDK")) {
|
||||
wchar_t exe[32768]{}; GetModuleFileNameW(nullptr, exe, 32768);
|
||||
debug_layers = std::make_unique<Library>(std::filesystem::path(exe).parent_path() / L"debug-d3d12/d3d12SDKLayers.dll");
|
||||
ComPtr<ID3D12Debug> debug;
|
||||
auto get_interface = reinterpret_cast<decltype(&D3D12GetInterface)>(GetProcAddress(GetModuleHandleW(L"d3d12.dll"), "D3D12GetInterface"));
|
||||
if (!get_interface) throw std::runtime_error("D3D12GetInterface is unavailable");
|
||||
dx(get_interface(CLSID_D3D12Debug, IID_PPV_ARGS(debug.GetAddressOf())), "Diagnostic debug layer(public CLSID)");
|
||||
debug->EnableDebugLayer();
|
||||
}
|
||||
ComPtr<ID3D12DeviceRemovedExtendedDataSettings> dred_settings;
|
||||
if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(dred_settings.GetAddressOf())))) {
|
||||
dred_settings->SetAutoBreadcrumbsEnablement(D3D12_DRED_ENABLEMENT_FORCED_ON);
|
||||
dred_settings->SetPageFaultEnablement(D3D12_DRED_ENABLEMENT_FORCED_ON);
|
||||
}
|
||||
std::unique_ptr<HipApi> hip;
|
||||
hipDeviceProp_t props{};
|
||||
const bool no_hip = std::getenv("D4R_COMMAND_PROBE_NO_HIP") != nullptr;
|
||||
if (!no_hip) { hip = std::make_unique<HipApi>(args.hip_root); hip->select_gfx1201(args.device, props); }
|
||||
ComPtr<IDXGIFactory4> factory; dx(CreateDXGIFactory1(IID_PPV_ARGS(factory.GetAddressOf())), "Probe DXGI");
|
||||
ComPtr<IDXGIAdapter1> adapter;
|
||||
for (UINT i = 0; ; ++i) {
|
||||
dx(factory->EnumAdapters1(i, adapter.ReleaseAndGetAddressOf()), "Probe adapter");
|
||||
DXGI_ADAPTER_DESC1 desc{}; dx(adapter->GetDesc1(&desc), "Probe adapter desc");
|
||||
if (no_hip ? desc.VendorId == 0x1002 : !std::memcmp(&desc.AdapterLuid, props.luid, sizeof(LUID))) break;
|
||||
}
|
||||
if (std::getenv("D4R_COMMAND_PROBE_WARP")) {
|
||||
dx(factory->EnumWarpAdapter(IID_PPV_ARGS(adapter.ReleaseAndGetAddressOf())), "Probe WARP adapter");
|
||||
}
|
||||
dx(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(device.GetAddressOf())), "Probe D3D12");
|
||||
ComPtr<ID3D12CommandQueue> queue;
|
||||
D3D12_COMMAND_QUEUE_DESC qd{}; qd.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
|
||||
dx(device->CreateCommandQueue(&qd, IID_PPV_ARGS(queue.GetAddressOf())), "Probe queue");
|
||||
wchar_t system[32768]{}; if (!GetSystemDirectoryW(system, 32768)) throw std::runtime_error("System directory");
|
||||
Library compiler(std::filesystem::path(system) / L"d3dcompiler_47.dll");
|
||||
auto compile = compiler.symbol<decltype(&D3DCompile)>("D3DCompile");
|
||||
static constexpr char shader[] = "RWStructuredBuffer<uint> Output : register(u0); StructuredBuffer<uint> Input : register(t0); cbuffer Constants : register(b0) { uint Seed; }; [numthreads(64,1,1)] void main(uint3 t:SV_DispatchThreadID) { Output[t.x] = Input[t.x] + Seed; }";
|
||||
ComPtr<ID3DBlob> code, errors;
|
||||
dx(compile(shader, sizeof(shader)-1, nullptr, nullptr, nullptr, "main", "cs_5_1", D3DCOMPILE_OPTIMIZATION_LEVEL3,
|
||||
0, code.GetAddressOf(), errors.GetAddressOf()), "Probe HLSL");
|
||||
D3D12_ROOT_PARAMETER roots[3]{};
|
||||
roots[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_UAV; roots[0].Descriptor.ShaderRegister = 0;
|
||||
roots[1].ParameterType = D3D12_ROOT_PARAMETER_TYPE_SRV; roots[1].Descriptor.ShaderRegister = 0;
|
||||
roots[2].ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS; roots[2].Constants.Num32BitValues = 1;
|
||||
D3D12_ROOT_SIGNATURE_DESC signature{}; signature.NumParameters = 3; signature.pParameters = roots;
|
||||
ComPtr<ID3DBlob> serialized;
|
||||
dx(D3D12SerializeRootSignature(&signature, D3D_ROOT_SIGNATURE_VERSION_1, serialized.GetAddressOf(), errors.ReleaseAndGetAddressOf()), "Probe root signature serialize");
|
||||
ComPtr<ID3D12RootSignature> root;
|
||||
dx(device->CreateRootSignature(0, serialized->GetBufferPointer(), serialized->GetBufferSize(), IID_PPV_ARGS(root.GetAddressOf())), "Probe root signature");
|
||||
D3D12_COMPUTE_PIPELINE_STATE_DESC pipeline{};
|
||||
pipeline.pRootSignature = root.Get(); pipeline.CS = {code->GetBufferPointer(), code->GetBufferSize()};
|
||||
ComPtr<ID3D12PipelineState> pso; dx(device->CreateComputePipelineState(&pipeline, IID_PPV_ARGS(pso.GetAddressOf())), "Probe PSO");
|
||||
Library shim(wide(args.module));
|
||||
using Install = unsigned(*)(ID3D12Device*);
|
||||
using Record = unsigned(*)(ID3D12GraphicsCommandList*, void(WINAPI*)(ID3D12CommandQueue*, void*), void*);
|
||||
using Live = unsigned long long(*)();
|
||||
const bool no_hooks = std::getenv("D4R_COMMAND_PROBE_NO_HOOKS") != nullptr;
|
||||
if (!no_hooks && shim.symbol<Install>("d4r_D3D12_InstallCommandBackend")(device.Get()) != 1) throw std::runtime_error("Command backend installation failed");
|
||||
{
|
||||
ComPtr<IDXGIFactory2> newerFactory; dx(factory->QueryInterface(IID_PPV_ARGS(newerFactory.GetAddressOf())), "Resize regression factory");
|
||||
DXGI_SWAP_CHAIN_DESC1 desc{}; desc.Width = desc.Height = 64;
|
||||
desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; desc.SampleDesc.Count = 1;
|
||||
desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; desc.BufferCount = 2;
|
||||
desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; desc.AlphaMode = DXGI_ALPHA_MODE_IGNORE;
|
||||
ComPtr<IDXGISwapChain1> chain;
|
||||
dx(newerFactory->CreateSwapChainForComposition(queue.Get(), &desc, nullptr, chain.GetAddressOf()), "Resize regression swap chain");
|
||||
ComPtr<ID3D12Resource> backBuffer; dx(chain->GetBuffer(0, IID_PPV_ARGS(backBuffer.GetAddressOf())), "Resize regression back buffer");
|
||||
NativeList recorded(device.Get());
|
||||
transition(recorded.list.Get(), backBuffer.Get(), D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET);
|
||||
transition(recorded.list.Get(), backBuffer.Get(), D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT);
|
||||
recorded.submit(device.Get(), queue.Get()); backBuffer.Reset();
|
||||
// The submitted list remains alive and unreset during resize.
|
||||
dx(chain->ResizeBuffers(2, 96, 64, DXGI_FORMAT_UNKNOWN, 0), "ResizeBuffers with retained submitted list");
|
||||
std::printf("D3D12_RESIZE_REGRESSION submitted_list_alive=1 old_back_buffer_released=1\n");
|
||||
}
|
||||
auto record = shim.symbol<Record>("d4r_D3D12_RecordDiagnosticBoundary");
|
||||
auto live = shim.symbol<Live>("d4r_D3D12_LiveRecordings");
|
||||
const bool root_indirect = args.interop_mode == "indirect-root-reset";
|
||||
const bool initial_pso = args.interop_mode == "initial-pso";
|
||||
const bool indirect_enabled = args.interop_mode == "indirect" || root_indirect;
|
||||
if (args.interop_mode != "direct" && args.interop_mode != "basic" && args.interop_mode != "indirect" && !root_indirect && !initial_pso)
|
||||
throw std::runtime_error("Command probe mode must be basic, indirect, indirect-root-reset or initial-pso");
|
||||
D3D12_INDIRECT_ARGUMENT_DESC arguments[2]{};
|
||||
arguments[0].Type = D3D12_INDIRECT_ARGUMENT_TYPE_CONSTANT;
|
||||
arguments[0].Constant.RootParameterIndex = 2;
|
||||
arguments[0].Constant.Num32BitValuesToSet = 1;
|
||||
arguments[1].Type = D3D12_INDIRECT_ARGUMENT_TYPE_DISPATCH;
|
||||
D3D12_COMMAND_SIGNATURE_DESC indirect_desc{};
|
||||
indirect_desc.ByteStride = (root_indirect ? 4 : 3) * sizeof(uint32_t);
|
||||
indirect_desc.NumArgumentDescs = root_indirect ? 2 : 1;
|
||||
indirect_desc.pArgumentDescs = arguments + (root_indirect ? 0 : 1);
|
||||
ComPtr<ID3D12CommandSignature> indirect;
|
||||
dx(device->CreateCommandSignature(&indirect_desc, root_indirect ? root.Get() : nullptr, IID_PPV_ARGS(indirect.GetAddressOf())), "Probe indirect signature");
|
||||
auto indirect_upload = make_buffer(device.Get(), 4 * sizeof(uint32_t), D3D12_HEAP_TYPE_UPLOAD, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_GENERIC_READ);
|
||||
auto indirect_buffer = make_buffer(device.Get(), 4 * sizeof(uint32_t), D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_COPY_DEST);
|
||||
void* mapped = nullptr; D3D12_RANGE no_read{};
|
||||
dx(indirect_upload->Map(0, &no_read, &mapped), "Probe indirect arguments");
|
||||
const uint32_t indirect_seed = std::getenv("D4R_COMMAND_PROBE_SMALL_SEED") ? 1u : seed;
|
||||
const uint32_t dispatch[] = {indirect_seed, count / 64, 1, 1};
|
||||
std::memcpy(mapped, dispatch, sizeof(dispatch)); indirect_upload->Unmap(0, nullptr);
|
||||
{
|
||||
NativeList copy(device.Get()); copy.list->CopyResource(indirect_buffer.Get(), indirect_upload.Get());
|
||||
transition(copy.list.Get(), indirect_buffer.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT);
|
||||
copy.submit(device.Get(), queue.Get());
|
||||
}
|
||||
for (unsigned iteration = 0; iteration < args.iterations; ++iteration) {
|
||||
auto input = make_buffer(device.Get(), bytes, D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_COPY_DEST);
|
||||
auto output = output_buffer(device.Get());
|
||||
auto first = make_buffer(device.Get(), bytes, D3D12_HEAP_TYPE_UPLOAD, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_GENERIC_READ);
|
||||
auto second = make_buffer(device.Get(), bytes, D3D12_HEAP_TYPE_UPLOAD, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_GENERIC_READ);
|
||||
auto readback = make_buffer(device.Get(), bytes, D3D12_HEAP_TYPE_READBACK, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_COPY_DEST);
|
||||
auto prefix = make_buffer(device.Get(), bytes, D3D12_HEAP_TYPE_READBACK, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_COPY_DEST);
|
||||
input->SetName(L"Probe input"); output->SetName(L"Probe output"); indirect_buffer->SetName(L"Probe indirect");
|
||||
std::printf("PROBE_GPU iteration=%u input=0x%llx output=0x%llx indirect=0x%llx\n", iteration,
|
||||
input->GetGPUVirtualAddress(), output->GetGPUVirtualAddress(), indirect_buffer->GetGPUVirtualAddress());
|
||||
upload(first.Get(), 17, 10); upload(second.Get(), 25, 100);
|
||||
{
|
||||
NativeList a(device.Get()), b(device.Get(), initial_pso ? pso.Get() : nullptr), c(device.Get());
|
||||
a.list->CopyResource(input.Get(), first.Get());
|
||||
transition(a.list.Get(), input.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
|
||||
dx(a.list->Close(), "Close predecessor A");
|
||||
b.list->SetComputeRootSignature(root.Get()); if (!initial_pso) b.list->SetPipelineState(pso.Get());
|
||||
b.list->SetComputeRootUnorderedAccessView(0, output->GetGPUVirtualAddress());
|
||||
b.list->SetComputeRootShaderResourceView(1, input->GetGPUVirtualAddress());
|
||||
uint32_t temporary = seed;
|
||||
b.list->SetComputeRoot32BitConstants(2, 1, &temporary, 0);
|
||||
temporary = 0xdeadbeef; // Deep-copy check: replay must retain Seed.
|
||||
const uint32_t expected_seed = root_indirect && iteration % 2 ? 0 : seed;
|
||||
if (indirect_enabled && iteration % 2) {
|
||||
b.list->ExecuteIndirect(indirect.Get(), 1, indirect_buffer.Get(), root_indirect ? 0 : sizeof(uint32_t), nullptr, 0);
|
||||
D3D12_RESOURCE_BARRIER uav{}; uav.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV; uav.UAV.pResource = output.Get();
|
||||
b.list->ResourceBarrier(1, &uav);
|
||||
// Only the root-mutating signature resets Seed to zero.
|
||||
// A dispatch-only signature must preserve every binding.
|
||||
}
|
||||
b.list->Dispatch(count / 64, 1, 1);
|
||||
Callback callback{device.Get(), input.Get(), output.Get(), second.Get(), prefix.Get(), expected_seed};
|
||||
if (no_hooks) {
|
||||
dx(b.list->Close(), "Close native prefix");
|
||||
messages(device.Get());
|
||||
ID3D12CommandList* prefix_batch[] = {a.list.Get(), b.list.Get()};
|
||||
queue->ExecuteCommandLists(2, prefix_batch); drain(device.Get(), queue.Get());
|
||||
boundary(queue.Get(), &callback);
|
||||
dx(b.allocator->Reset(), "Reset native prefix allocator");
|
||||
dx(b.list->Reset(b.allocator.Get(), pso.Get()), "Reset native suffix");
|
||||
b.list->SetComputeRootSignature(root.Get());
|
||||
b.list->SetComputeRootUnorderedAccessView(0, output->GetGPUVirtualAddress());
|
||||
b.list->SetComputeRootShaderResourceView(1, input->GetGPUVirtualAddress());
|
||||
b.list->SetComputeRoot32BitConstant(2, expected_seed, 0);
|
||||
} else if (record(b.list.Get(), boundary, &callback) != 1) throw std::runtime_error("Record split failed");
|
||||
b.list->Dispatch(count / 64, 1, 1); // No rebinding after the split.
|
||||
dx(b.list->Close(), "Close logical list B");
|
||||
transition(c.list.Get(), output.Get(), D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_COPY_SOURCE);
|
||||
c.list->CopyResource(readback.Get(), output.Get());
|
||||
transition(c.list.Get(), output.Get(), D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
|
||||
dx(c.list->Close(), "Close consumer C");
|
||||
messages(device.Get());
|
||||
ID3D12CommandList* batch[] = {a.list.Get(), b.list.Get(), c.list.Get()};
|
||||
queue->ExecuteCommandLists(no_hooks ? 2 : 3, batch + (no_hooks ? 1 : 0)); drain(device.Get(), queue.Get());
|
||||
if (!callback.prefix_verified || !verify(readback.Get(), 25, 100, expected_seed)) throw std::runtime_error("Queue order or suffix root state mismatch");
|
||||
if (!no_hooks) for (unsigned reuse = 0; reuse < 2; ++reuse) {
|
||||
// Populate the same thread's routing cache with a split
|
||||
// list, Reset that exact COM object, then split again with
|
||||
// different root state. A cached previous Recording would
|
||||
// keep targeting the old suffix or replay the old Seed.
|
||||
dx(b.allocator->Reset(), "Reset reused allocator");
|
||||
dx(b.list->Reset(b.allocator.Get(), initial_pso ? pso.Get() : nullptr), "Reset reused logical list");
|
||||
b.list->SetComputeRootSignature(root.Get());
|
||||
if (!initial_pso) b.list->SetPipelineState(pso.Get());
|
||||
b.list->SetComputeRootUnorderedAccessView(0, output->GetGPUVirtualAddress());
|
||||
b.list->SetComputeRootShaderResourceView(1, input->GetGPUVirtualAddress());
|
||||
callback.expected_seed = seed + iteration + reuse + 1;
|
||||
callback.expected_multiplier = 25; callback.expected_addend = 100;
|
||||
callback.prefix_verified = false;
|
||||
b.list->SetComputeRoot32BitConstant(2, callback.expected_seed, 0);
|
||||
b.list->Dispatch(count / 64, 1, 1);
|
||||
if (record(b.list.Get(), boundary, &callback) != 1) throw std::runtime_error("Record reused split failed");
|
||||
b.list->Dispatch(count / 64, 1, 1);
|
||||
transition(b.list.Get(), output.Get(), D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_COPY_SOURCE);
|
||||
b.list->CopyResource(readback.Get(), output.Get());
|
||||
transition(b.list.Get(), output.Get(), D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
|
||||
b.submit(device.Get(), queue.Get());
|
||||
if (!callback.prefix_verified || !verify(readback.Get(), 25, 100, callback.expected_seed))
|
||||
throw std::runtime_error("Reset reused logical list retained stale routing/root state");
|
||||
}
|
||||
}
|
||||
if (live() != 0) throw std::runtime_error("D3D12 recording metadata leaked after command-list Release");
|
||||
}
|
||||
const bool warp = std::getenv("D4R_COMMAND_PROBE_WARP") != nullptr;
|
||||
std::printf("PASS D3D12_COMMAND_BACKEND adapter=%s architecture=%s backend=%u iterations=%u batch_order=1 root_state=1 deep_copy=1 indirect=%u indirect_reset=%u reset_reuse=%u live_recordings=0 frame_age=0\n",
|
||||
warp ? "WARP" : "AMD", warp ? "software" : no_hip ? "not_queried" : "gfx1201", !no_hooks,
|
||||
args.iterations, indirect_enabled, root_indirect, no_hooks ? 0 : 2 * args.iterations);
|
||||
return 0;
|
||||
} catch (const std::exception& failure) {
|
||||
std::fprintf(stderr, "FAIL D3D12_COMMAND_BACKEND %s\n", failure.what());
|
||||
if (device) {
|
||||
messages(device.Get());
|
||||
std::fprintf(stderr, "DEVICE_REMOVED result=0x%08x\n", unsigned(device->GetDeviceRemovedReason()));
|
||||
ComPtr<ID3D12DeviceRemovedExtendedData> dred;
|
||||
if (SUCCEEDED(device->QueryInterface(IID_PPV_ARGS(dred.GetAddressOf())))) {
|
||||
D3D12_DRED_AUTO_BREADCRUMBS_OUTPUT breadcrumbs{};
|
||||
if (SUCCEEDED(dred->GetAutoBreadcrumbsOutput(&breadcrumbs)))
|
||||
for (auto* node = breadcrumbs.pHeadAutoBreadcrumbNode; node; node = node->pNext) {
|
||||
UINT completed = node->pLastBreadcrumbValue ? *node->pLastBreadcrumbValue : 0;
|
||||
std::fprintf(stderr, "DRED list=%p completed=%u total=%u\n", node->pCommandList, completed, node->BreadcrumbCount);
|
||||
for (UINT i = completed > 2 ? completed - 2 : 0; i < std::min(node->BreadcrumbCount, completed + 3); ++i)
|
||||
std::fprintf(stderr, "DRED command=%u operation=%u\n", i, unsigned(node->pCommandHistory[i]));
|
||||
}
|
||||
D3D12_DRED_PAGE_FAULT_OUTPUT fault{};
|
||||
if (SUCCEEDED(dred->GetPageFaultAllocationOutput(&fault)))
|
||||
std::fprintf(stderr, "DRED fault_address=0x%llx\n", fault.PageFaultVA);
|
||||
for (auto* allocation = fault.pHeadExistingAllocationNode; allocation; allocation = allocation->pNext)
|
||||
std::fprintf(stderr, "DRED existing=%ls type=%u\n", allocation->ObjectNameW ? allocation->ObjectNameW : L"(unnamed)", unsigned(allocation->AllocationType));
|
||||
for (auto* allocation = fault.pHeadRecentFreedAllocationNode; allocation; allocation = allocation->pNext)
|
||||
std::fprintf(stderr, "DRED freed=%ls type=%u\n", allocation->ObjectNameW ? allocation->ObjectNameW : L"(unnamed)", unsigned(allocation->AllocationType));
|
||||
}
|
||||
}
|
||||
loaded_modules(); return 4;
|
||||
}
|
||||
}
|
||||
Loaded 100 of 143 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user