From 5bdc93d0551940fb36feb3be2269305e4176d647 Mon Sep 17 00:00:00 2001 From: salh Date: Fri, 17 Apr 2026 21:47:18 +0300 Subject: [PATCH] feat: wire AC6 native graphics runtime and bootstrap configure path Run the native renderer every frame from AC6 present timing hooks with a visible runtime status overlay so migration progress is observable in-game. Also add a tracked CMake bootstrap include and migration mapping docs so fresh clones can configure before generated glue exists. Made-with: Cursor --- CMakeLists.txt | 8 +- cmake/rexglue_bootstrap.cmake | 62 ++++++++ ...NATIVE_GRAPHICS_CONFIG_MIGRATION_MATRIX.md | 131 ++++++++++++++++ ..._NATIVE_GRAPHICS_RENDER_API_REMOVAL_MAP.md | 71 +++++++++ src/ac6_native_graphics.cpp | 143 +++++++++++++++++- src/ac6_native_graphics.h | 33 +++- src/ac6_native_graphics_overlay.cpp | 58 ++++++- src/ac6_native_graphics_overlay.h | 1 + src/ac6recomp_app.h | 31 +++- src/d3d_hooks.h | 20 ++- src/render_hooks.cpp | 2 + 11 files changed, 554 insertions(+), 6 deletions(-) create mode 100644 cmake/rexglue_bootstrap.cmake create mode 100644 docs/native_graphics_migration/AC6_NATIVE_GRAPHICS_CONFIG_MIGRATION_MATRIX.md create mode 100644 docs/native_graphics_migration/AC6_NATIVE_GRAPHICS_RENDER_API_REMOVAL_MAP.md diff --git a/CMakeLists.txt b/CMakeLists.txt index 4772bcb3..ca45c57e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,11 +9,17 @@ project(ac6recomp LANGUAGES CXX) set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) -include(generated/rexglue.cmake) +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/generated/rexglue.cmake") + include(generated/rexglue.cmake) +else() + include(cmake/rexglue_bootstrap.cmake) +endif() # Sources set(AC6RECOMP_SOURCES src/main.cpp + src/d3d_hooks.cpp + src/render_hooks.cpp src/ac6_native_graphics.cpp src/ac6_native_graphics_overlay.cpp src/ac6_native_renderer/ac6_render_frontend.cpp diff --git a/cmake/rexglue_bootstrap.cmake b/cmake/rexglue_bootstrap.cmake new file mode 100644 index 00000000..ef3787b2 --- /dev/null +++ b/cmake/rexglue_bootstrap.cmake @@ -0,0 +1,62 @@ +# Bootstrap ReXGlue integration for fresh clones where generated/rexglue.cmake +# is not present yet. +# +# This file is intentionally tracked in git so first-time configure works on +# all platforms before running codegen. + +# SDK version +# Set REXSDK_VERSION to pin a specific SDK version. +# Otherwise, the version bundled with this bootstrap is used. +set(REXSDK_VERSION "" CACHE STRING "Override SDK version (leave empty for default)") + +# Find SDK +set(REXSDK_DIR "" CACHE PATH "Path to rexglue-sdk source tree") +if(REXSDK_DIR) + add_subdirectory("${REXSDK_DIR}" rexglue-sdk) +else() + if(REXSDK_VERSION) + find_package(rexglue ${REXSDK_VERSION} EXACT QUIET CONFIG) + else() + find_package(rexglue 0.7.4 QUIET CONFIG) + endif() + if(NOT rexglue_FOUND) + message(FATAL_ERROR + "ReXGlue SDK not found. Either:\n" + " - Set REXSDK_DIR to the rexglue-sdk source tree (e.g. thirdparty/rexglue-sdk)\n" + " - Install the SDK package and ensure it is on CMAKE_PREFIX_PATH") + endif() +endif() + +# Include generated code if codegen has been run. +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/generated/sources.cmake") + include(generated/sources.cmake) +endif() + +# Configure a rexglue target with SDK libraries and platform settings. +# Call after add_executable() in your CMakeLists.txt. +# Usage: rexglue_setup_target() +macro(rexglue_setup_target target_name) + target_sources(${target_name} PRIVATE ${GENERATED_SOURCES}) + target_include_directories(${target_name} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR}/generated + ) + target_link_libraries(${target_name} PRIVATE + rex::core + rex::system + rex::kernel + rex::graphics + rex::ui + ) + rexglue_configure_target(${target_name}) +endmacro() + +# Codegen target - run: +# cmake --build . --target ac6recomp_codegen +add_custom_target(ac6recomp_codegen + COMMAND $ codegen ${CMAKE_CURRENT_SOURCE_DIR}/ac6recomp_config.toml + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "Generating recompiled code for ac6recomp" + VERBATIM +) diff --git a/docs/native_graphics_migration/AC6_NATIVE_GRAPHICS_CONFIG_MIGRATION_MATRIX.md b/docs/native_graphics_migration/AC6_NATIVE_GRAPHICS_CONFIG_MIGRATION_MATRIX.md new file mode 100644 index 00000000..14221353 --- /dev/null +++ b/docs/native_graphics_migration/AC6_NATIVE_GRAPHICS_CONFIG_MIGRATION_MATRIX.md @@ -0,0 +1,131 @@ +# AC6 Native Graphics Config Migration Matrix + +Date: 2026-04-17 +Scope: `AC6 recomp` graphics configuration migration +Status: initial actionable matrix (to be extended as new graphics keys are discovered) + +Related planning docs: + +- `AC6_NATIVE_GRAPHICS_MIGRATION_STRATEGY_2026-04-17.md` +- `AC6_NATIVE_GRAPHICS_API_DEPRECATION_TIMELINE.md` +- `AC6_NATIVE_GRAPHICS_EXECUTION_PLAN_2026-04-17.md` + +## Purpose + +This document converts deprecation policy into concrete key-by-key actions. It defines: + +- what legacy graphics keys do today +- whether each key is kept, renamed, made dev-only, or removed +- when enforcement happens (`M2` through `M6`) +- what migration behavior and CI checks are required + +## Enforcement Legend + +- `keep`: remains valid in production native renderer +- `rename`: replaced by native key; legacy alias can exist temporarily +- `dev-only`: blocked in production builds; permitted in developer builds +- `remove`: deleted from AC6 production path after milestone gate + +## Key Migration Matrix + +| Legacy key | Current meaning | Native replacement / behavior | Action | Enforcement milestone | User migration note | CI / linter rule | +| --- | --- | --- | --- | --- | --- | --- | +| `vsync` | controls vertical sync pacing in emulation-era GPU path | `native.present.vsync` | rename | M4 | migrate to native present setting; same semantic intent | error on legacy key in production config after M4 | +| `guest_vblank_sync_to_refresh` | keeps guest vblank cadence tied to guest refresh | `compat.timing.guest_vblank_sync_to_refresh` | dev-only | M2 | keep only for timing investigations | fail production build if enabled | +| `swap_post_effect` | applies post effect at emulated swap stage (`none`, `fxaa`, `fxaa_extreme`) | `native.postfx.swap_effect` | rename | M4 | same options initially, later unified into post-FX stack config | warn on legacy key in M3, error in M4 | +| `resolution` | emulation-era output resolution selector | `native.output.resolution` | rename | M4 | migrate value directly | auto-fix mapping in config migration tool | +| `resolution_scale` | global emulation resolution scale | `native.render.scale` | rename | M3 | use native scale with subsystem routing support | error if both keys present | +| `draw_resolution_scale_x` | emulation draw-scale X | `native.render.scale_x` | rename | M3 | migrate only if non-default | warning if legacy key present in M3+ | +| `draw_resolution_scale_y` | emulation draw-scale Y | `native.render.scale_y` | rename | M3 | migrate only if non-default | warning if legacy key present in M3+ | +| `draw_resolution_scaled_texture_offsets` | emulation compensation for scaled texture offsets | native render graph handles coordinate transforms explicitly | remove | M5 | no user replacement; behavior internalized | error if key present after M5 | +| `resolve_resolution_scale_fill_half_pixel_offset` | half-pixel handling during emulated resolve scale fill | handled by native resolve pass shaders | remove | M5 | no replacement needed | error on key after M5 | +| `readback_resolve` | CPU readback mode for render-to-texture resolve | `compat.readback.resolve_mode` (dev-only) | dev-only | M2 | use only during parity/debug bring-up | fail production build if not default | +| `readback_resolve_half_pixel_offset` | resolve readback sampling offset tweak | `compat.readback.resolve_half_pixel_offset` (dev-only) | dev-only | M2 | debug-only parity aid | fail production build if enabled | +| `readback_memexport` | CPU readback for shader memexport coherency | `compat.readback.memexport` (dev-only) | dev-only | M2 | needed only while legacy bridge remains | fail production build if enabled | +| `readback_memexport_fast` | fast memexport readback mode | `compat.readback.memexport_fast` (dev-only) | dev-only | M2 | debug/perf experiment only | fail production build if enabled | +| `vulkan_readback_resolve` | Vulkan legacy alias for readback resolve | none (legacy alias removed) | remove | M3 | migrate to shared compat key if needed in dev builds | reject key always in production | +| `vulkan_readback_memexport` | Vulkan legacy alias for memexport readback | none (legacy alias removed) | remove | M3 | migrate to shared compat key if needed in dev builds | reject key always in production | +| `d3d12_readback_resolve` | D3D12 legacy alias for readback resolve | none (legacy alias removed) | remove | M3 | migrate to shared compat key if needed in dev builds | reject key always in production | +| `d3d12_readback_memexport` | D3D12 legacy alias for memexport readback | none (legacy alias removed) | remove | M3 | migrate to shared compat key if needed in dev builds | reject key always in production | +| `async_shader_compilation` | async runtime shader/pipeline compilation in emulation path | `native.shader.runtime_async_compile` (dev-only fallback) | dev-only | M2 | native path uses offline compiled shaders; runtime path debug-only | fail production config if runtime compile enabled | +| `dump_shaders` | dumps runtime translated shaders | `native.debug.dump_shaders` (dev-only) | dev-only | M2 | for migration diagnostics only | block in production config | +| `dxbc_switch` | legacy DXBC translator switch | none | remove | M5 | no replacement; offline shader pipeline supersedes | error on key after M5 | +| `dxbc_source_map` | legacy translator source map output | none | remove | M5 | no replacement; use offline reflection artifacts | error on key after M5 | +| `vfetch_index_rounding_bias` | Xenos translator behavior knob | none | remove | M5 | no replacement on native path | error on key after M5 | +| `texture_cache_memory_limit_render_to_texture` | emulated texture cache memory cap for RTT | `native.streaming.texture_budget_mb` | rename | M4 | migrate to native residency budget | warning when legacy key used | +| `texture_cache_memory_limit_soft` | soft emulated texture cache limit | `native.streaming.texture_budget_soft_mb` | rename | M4 | migrate value with documented unit conversion | linter auto-fix if units are valid | +| `texture_cache_memory_limit_hard` | hard emulated texture cache limit | `native.streaming.texture_budget_hard_mb` | rename | M4 | migrate value with documented unit conversion | linter auto-fix if units are valid | +| `texture_cache_memory_limit_soft_lifetime` | cache residency lifetime heuristic | native streamer residency policy tables | remove | M5 | no user replacement | error on key after M5 | +| `gpu_3d_to_2d_texture` | emulated texture workaround | `compat.texture.legacy_3d_to_2d` | dev-only | M3 | only for parity triage | fail production if enabled | +| `gpu_allow_invalid_fetch_constants` | tolerate invalid fetch constants | `compat.validation.allow_invalid_fetch_constants` | dev-only | M2 | debug-only escape hatch | fail production if true | +| `non_seamless_cube_map` | compatibility toggle for cube map sampling | `native.sampling.non_seamless_cube_map` | keep | M4 | kept as hardware compatibility option | ensure default distribution value documented | +| `anisotropic_override` | forced anisotropic filtering level | `native.quality.anisotropy_override` | rename | M4 | migrate directly; same value range | validate range in config linter | +| `occlusion_query_enable` | enable host occlusion query handling | `native.query.occlusion.enable` | rename | M4 | migrate directly | error if both old and new keys set | +| `query_occlusion_fake_sample_count` | fake sample count for emulated queries | `compat.query.fake_sample_count` | dev-only | M3 | debugging only; not for production | fail production if non-default | +| `depth_float24_round` | depth precision workaround | `compat.depth.float24_round` | dev-only | M3 | only use for parity troubleshooting | fail production if true | +| `depth_float24_convert_in_pixel_shader` | depth conversion workaround | `compat.depth.float24_ps_convert` | dev-only | M3 | debug-only parity aid | fail production if true | +| `depth_transfer_not_equal_test` | depth transfer compare workaround | `compat.depth.transfer_not_equal_test` | dev-only | M3 | debug-only parity aid | fail production if non-default | +| `native_stencil_value_output` | stencil output behavior toggle | `native.depth_stencil.stencil_output` | keep | M4 | stays as backend compatibility toggle | validate supported backend combinations | +| `native_stencil_value_output_d3d12_intel` | Intel-specific stencil behavior toggle | `native.depth_stencil.d3d12_intel_stencil_output` | keep | M4 | keep platform-specific escape hatch | limit to Windows+D3D12 in linter | +| `gamma_render_target_as_unorm16` | gamma RT format behavior | `native.render_target.gamma_unorm16` | keep | M4 | retained as quality/compat setting | ensure default value parity-tested | +| `native_2x_msaa` | native 2x MSAA toggle | `native.quality.msaa_2x` | rename | M4 | migrate directly | warn if legacy key used after M4 | +| `snorm16_render_target_full_range` | emulated RT format behavior toggle | `compat.render_target.snorm16_full_range` | dev-only | M3 | parity bring-up only | fail production if enabled | +| `mrt_edram_used_range_clamp_to_min` | EDRAM-era MRT behavior workaround | none | remove | M5 | no replacement in native render graph | error on key after M5 | +| `direct_host_resolve` | direct host resolve path toggle | none (native resolve passes are authoritative) | remove | M5 | no replacement | error on key after M5 | +| `execute_unclipped_draw_vs_on_cpu` | CPU fallback for draw processing | none | remove | M5 | no replacement on native path | error on key after M5 | +| `execute_unclipped_draw_vs_on_cpu_for_psi_render_backend` | backend-specific CPU fallback | none | remove | M5 | no replacement on native path | error on key after M5 | +| `execute_unclipped_draw_vs_on_cpu_with_scissor` | CPU fallback variant | none | remove | M5 | no replacement on native path | error on key after M5 | +| `force_convert_line_loops_to_strips` | primitive conversion workaround | `compat.primitive.line_loop_to_strip` | dev-only | M3 | debug-only compatibility fallback | fail production if enabled | +| `force_convert_quad_lists_to_triangle_lists` | primitive conversion workaround | `compat.primitive.quad_to_tri` | dev-only | M3 | debug-only compatibility fallback | fail production if enabled | +| `force_convert_triangle_fans_to_lists` | primitive conversion workaround | `compat.primitive.fan_to_list` | dev-only | M3 | debug-only compatibility fallback | fail production if enabled | +| `primitive_processor_cache_min_indices` | primitive cache threshold | `native.frontend.primitive_cache_min_indices` | rename | M4 | migrate directly after native frontend cutover | range-check in linter | +| `trace_gpu_prefix` | trace output prefix for GPU traces | `native.debug.trace_prefix` (dev-only) | dev-only | M2 | diagnostics only | fail production if non-empty | +| `trace_gpu_stream` | stream GPU trace continuously | `native.debug.trace_stream` (dev-only) | dev-only | M2 | diagnostics only | fail production if true | +| `gpu_debug_markers` | GPU markers for tools like PIX/RenderDoc | `native.debug.gpu_markers` | keep | M4 | supported for dev and optionally production troubleshooting | allow but default off in release config | +| `vulkan_sparse_shared_memory` | Vulkan shared-memory emulation mode | none | remove | M5 | no native replacement | error on key after M5 | +| `vulkan_submit_on_primary_buffer_end` | Vulkan emulation submit timing behavior | `native.vulkan.submit_policy` | rename | M4 | migrate to native queue submit policy | warn then error after M4 | +| `vulkan_dynamic_rendering` | Vulkan dynamic rendering toggle | `native.vulkan.dynamic_rendering` | keep | M4 | retained as backend capability switch | validate backend support | +| `vulkan_async_skip_incomplete_frames` | Vulkan frame skip behavior | `native.vulkan.allow_incomplete_frame_skip` | rename | M4 | retain as backend tuning option | validate only on Vulkan backend | +| `vulkan_pipeline_creation_threads` | Vulkan runtime pipeline thread count | `native.vulkan.pipeline_threads` | rename | M4 | used for native pipeline library management | range validation | +| `vulkan_tessellation_wireframe` | Vulkan tessellation wireframe debug mode | `native.debug.vulkan.tess_wireframe` | dev-only | M2 | debug-only mode | fail production if true | +| `vulkan_force_expand_point_sprites_in_vs` | Vulkan compatibility workaround | `compat.vulkan.expand_point_sprites_in_vs` | dev-only | M3 | parity fallback only | fail production if true | +| `vulkan_force_expand_rectangle_lists_in_vs` | Vulkan compatibility workaround | `compat.vulkan.expand_rect_lists_in_vs` | dev-only | M3 | parity fallback only | fail production if true | +| `vulkan_force_convert_quad_lists_to_triangle_lists` | Vulkan primitive conversion workaround | `compat.vulkan.quad_to_tri` | dev-only | M3 | parity fallback only | fail production if true | +| `render_target_path_vulkan` | Vulkan render target debug dump path | `native.debug.vulkan.render_target_path` (dev-only) | dev-only | M2 | debug output only | fail production if non-empty | +| `d3d12_bindless` | D3D12 bindless toggle | `native.d3d12.bindless` | keep | M4 | retained as backend feature flag | validate support on hardware tier | +| `d3d12_submit_on_primary_buffer_end` | D3D12 emulation submit timing behavior | `native.d3d12.submit_policy` | rename | M4 | migrate to native queue submit policy | warn then error after M4 | +| `d3d12_dxbc_disasm` | DXBC disassembly diagnostics | `native.debug.d3d12.dxbc_disasm` (dev-only) | dev-only | M2 | diagnostics only | fail production if true | +| `d3d12_dxbc_disasm_dxilconv` | DXIL conversion disassembly diagnostics | `native.debug.d3d12.dxbc_disasm_dxilconv` (dev-only) | dev-only | M2 | diagnostics only | fail production if true | +| `d3d12_pipeline_creation_threads` | D3D12 runtime pipeline thread count | `native.d3d12.pipeline_threads` | rename | M4 | migrate directly | range validation | +| `d3d12_tessellation_wireframe` | D3D12 tessellation wireframe debug mode | `native.debug.d3d12.tess_wireframe` | dev-only | M2 | diagnostics only | fail production if true | +| `d3d12_tiled_shared_memory` | D3D12 tiled shared-memory emulation toggle | none | remove | M5 | no replacement | error on key after M5 | +| `render_target_path_d3d12` | D3D12 render target debug dump path | `native.debug.d3d12.render_target_path` (dev-only) | dev-only | M2 | diagnostics only | fail production if non-empty | + +## Mechanical Migration Rules + +1. Apply all `rename` mappings first. +2. If both legacy and native key are present, native key wins and linter emits warning in `M2-M3`, error in `M4+`. +3. `dev-only` keys: + - allowed in local and CI debug/dev profiles + - blocked in production distribution artifacts immediately at the listed milestone +4. `remove` keys: + - warning one milestone before removal + - hard error at removal milestone and later + +## Required Tooling + +- Config migration script: + - input: existing user/developer config + - output: rewritten config with `rename` rules applied and deprecation report +- Config linter modes: + - `warn` mode for pre-enforcement milestones + - `enforce` mode for release pipelines +- CI checks: + - fail if default shipping config contains `dev-only` or `remove` keys past enforcement milestone + - fail if unknown graphics keys are present + +## Open Items + +- confirm final native key namespace before M3 freeze (`native.*` versus split subsystem namespaces) +- define unit conversion policy for memory-budget key migrations where old/new units differ +- attach examples for common user migration paths (`performance`, `debug`, `capture`) in README updates diff --git a/docs/native_graphics_migration/AC6_NATIVE_GRAPHICS_RENDER_API_REMOVAL_MAP.md b/docs/native_graphics_migration/AC6_NATIVE_GRAPHICS_RENDER_API_REMOVAL_MAP.md new file mode 100644 index 00000000..57afb1ea --- /dev/null +++ b/docs/native_graphics_migration/AC6_NATIVE_GRAPHICS_RENDER_API_REMOVAL_MAP.md @@ -0,0 +1,71 @@ +# AC6 Native Graphics Render API Removal Map + +Date: 2026-04-17 +Scope: AC6 render-path symbols, interfaces, and build linkage + +Purpose: convert the migration strategy and audit into a concrete, file-referenced retirement map for legacy PM4/Xenos render interfaces. + +Related docs: + +- `AC6_NATIVE_GRAPHICS_EMULATION_AUDIT_2026-04-17.md` +- `AC6_NATIVE_GRAPHICS_EXECUTION_PLAN_2026-04-17.md` +- `AC6_NATIVE_GRAPHICS_API_DEPRECATION_TIMELINE.md` +- `AC6_NATIVE_GRAPHICS_CONFIG_MIGRATION_MATRIX.md` + +## Map Rules + +- This map is AC6-scope only; non-graphics kernel compatibility exports may remain. +- `M3` means hybrid migration and routing controls. +- `M4` means native present authority. +- `M5` means render linkage removal from AC6 targets. +- Every `remove` entry must have a native replacement and validation artifact before deletion. + +## Removal Matrix + +| Legacy API / symbol / module | Current references (primary files) | Native replacement | Action | Target milestone | Compatibility-shim notes | Required validation artifact | +| --- | --- | --- | --- | --- | --- | --- | +| `rex::graphics::GraphicsSystem` as render owner | `thirdparty/rexglue-sdk/include/rex/graphics/graphics_system.h`, `thirdparty/rexglue-sdk/src/graphics/graphics_system.cpp` | `ac6_native_renderer` ownership split across `RenderDevice`, `RenderGraph`, `FrameScheduler` | remove from AC6 render route | M5 | retain minimal interrupt/timing bridge only | boot + frame submission logs showing AC6 render no longer instantiates legacy graphics owner for production | +| `rex::graphics::CommandProcessor` PM4 execution core | `thirdparty/rexglue-sdk/include/rex/graphics/command_processor.h`, `thirdparty/rexglue-sdk/src/graphics/command_processor.cpp` | native render graph compilation and submission from `Ac6RenderFrontend` | remove | M5 | no PM4 parsing in production route | link report proving `command_processor*` objects excluded from AC6 shipping target | +| PM4 packet path including `PM4_XE_SWAP` | `thirdparty/rexglue-sdk/src/graphics/packet_disassembler.cpp`, `thirdparty/rexglue-sdk/src/graphics/command_processor.cpp` | native frame boundary and present path | remove | M5 | temporary parity hooks allowed pre-M5 only | CI check proving no production frame depends on PM4 swap synthesis | +| `VdSwap_entry` PM4 swap synthesis behavior | `thirdparty/rexglue-sdk/src/kernel/xboxkrnl/xboxkrnl_video.cpp` | `VdSwap` as native frame boundary only | narrow then remove PM4 synthesis | M4->M5 | keep interrupt contract if required by title timing | contract doc + canary telemetry confirming native present authority | +| D3D12 emulation command processor (`D3D12CommandProcessor`) | `thirdparty/rexglue-sdk/include/rex/graphics/d3d12/command_processor.h`, `thirdparty/rexglue-sdk/src/graphics/d3d12/command_processor.cpp` | `ac6_native_renderer/backends/d3d12_*` | remove from AC6 link | M5 | none for render; backend native module remains | object-link exclusion report for emulation D3D12 command processor | +| Vulkan emulation command processor (`VulkanCommandProcessor`) | `thirdparty/rexglue-sdk/include/rex/graphics/vulkan/command_processor.h`, `thirdparty/rexglue-sdk/src/graphics/vulkan/command_processor.cpp` | `ac6_native_renderer/backends/vulkan_*` | remove from AC6 link | M5 | none for render; backend native module remains | object-link exclusion report for emulation Vulkan command processor | +| Emulated shared-memory render model | `thirdparty/rexglue-sdk/src/graphics/shared_memory.cpp`, `thirdparty/rexglue-sdk/src/graphics/d3d12/shared_memory.cpp`, `thirdparty/rexglue-sdk/src/graphics/vulkan/shared_memory.cpp` | host-native resource allocator + transient/upload arenas | remove | M5 | none | memory validation showing native allocator path only | +| Texture cache + untiling runtime conversion | `thirdparty/rexglue-sdk/src/graphics/pipeline/texture/cache.cpp`, `thirdparty/rexglue-sdk/src/graphics/pipeline/texture/conversion.cpp`, backend texture caches | cooked texture packages + streaming manager | remove | M5 | dynamic texture bridge allowed during M3-M4 only | parity and perf reports showing no hot-path guest untiling | +| Render-target cache and EDRAM emulation | `thirdparty/rexglue-sdk/src/graphics/pipeline/render_target/cache.cpp`, backend render target caches | explicit native render graph attachments/history buffers | remove | M5 | none for production render route | pass graph captures showing native attachment ownership | +| Runtime shader translation (`dxbc`/`spirv` translators) | `thirdparty/rexglue-sdk/src/graphics/pipeline/shader/translator.cpp`, `dxbc_translator*.cpp`, `spirv_translator*.cpp` | authored HLSL + offline DXIL/SPIR-V/MSL pipeline | remove | M5 | dev-only diagnostics may remain out of production path | CI shader manifest proving no runtime translation on native path | +| Xenos register/render state ownership | `thirdparty/rexglue-sdk/src/graphics/registers.cpp`, `register_file.cpp`, `xenos.cpp` | native render/material descriptors | remove from render-authoritative path | M5 | limited compatibility reads may remain outside render | code-level ownership audit showing no render pass built from Xenos register file | +| Emulation trace writer / PM4-centric diagnostics | `thirdparty/rexglue-sdk/src/graphics/trace_writer.cpp` and trace protocol stack | native capture + pass timeline dumps + backend markers | dev-only then remove from production | M6 | keep dev capture tooling if isolated from shipping path | production package scan with no PM4/Xenos trace dependencies | + +## Keep And Adapt (Not Removed) + +| Component | Primary files | Native migration disposition | +| --- | --- | --- | +| presenter / window / provider | `thirdparty/rexglue-sdk/src/native/ui/presenter.cpp`, `thirdparty/rexglue-sdk/src/native/ui/d3d12/d3d12_presenter.cpp`, provider and window modules | keep and adapt to consume native final-color output | +| runtime graphics injection seam | `thirdparty/rexglue-sdk/src/native/ui/rex_app.cpp`, `thirdparty/rexglue-sdk/src/system/runtime.cpp`, `thirdparty/rexglue-sdk/include/rex/system/interfaces/graphics.h` | keep and expand for native renderer lifecycle, telemetry, and kill switches | +| AC6-local migration bridge and diagnostics | `src/ac6_native_graphics.cpp`, `src/ac6_native_graphics_overlay.cpp`, `src/ac6_native_renderer/*` | keep during migration; progressively reduce legacy bridge responsibilities | + +## Build/CI Retirement Checks + +At minimum, AC6 Phase 5 completion must include: + +1. link artifact proof that AC6 shipping target excludes: + - `command_processor` objects + - shader translator objects + - shared-memory render resources + - guest-state texture and render-target caches +2. config linter enforcement from `AC6_NATIVE_GRAPHICS_CONFIG_MIGRATION_MATRIX.md` for removed/dev-only legacy keys +3. parity, perf, and memory gates green on benchmark matrix after legacy linkage removal + +## Sequencing Notes + +- `M3`: route subsystems via native/legacy toggles while parity hardens +- `M4`: transfer present authority; `VdSwap` reduced to boundary semantics +- `M5`: remove legacy render linkage from AC6 production +- `M6`: clean dead flags and PM4/Xenos-only diagnostic dependencies from production deliverables + +## Open Items + +- confirm final AC6 shipping target(s) that CI link-exclusion checks run against +- add exact object/library names once build graph emits deterministic link manifests +- tie each matrix row to issue IDs in the migration epic tracker diff --git a/src/ac6_native_graphics.cpp b/src/ac6_native_graphics.cpp index e02abfc9..c22faf4d 100644 --- a/src/ac6_native_graphics.cpp +++ b/src/ac6_native_graphics.cpp @@ -1 +1,142 @@ - +#include "ac6_native_graphics.h" + +#include +#include + +#include +#include + +#include "ac6_native_renderer/native_renderer.h" +#include "d3d_hooks.h" + +REXCVAR_DEFINE_BOOL(ac6_native_graphics_enabled, true, "AC6/NativeGraphics", + "Enable AC6 native renderer frame-plan execution from captured D3D state"); +REXCVAR_DEFINE_BOOL(ac6_native_graphics_require_capture, true, "AC6/NativeGraphics", + "Force render-capture on while native graphics execution is enabled"); +REXCVAR_DEFINE_STRING(ac6_native_graphics_backend, "auto", "AC6/NativeGraphics", + "Preferred native backend: auto, d3d12, vulkan, metal") + .allowed({"auto", "d3d12", "vulkan", "metal"}); +REXCVAR_DEFINE_STRING(ac6_native_graphics_feature_level, "scene_submission", "AC6/NativeGraphics", + "Native renderer feature level: bootstrap, scene_submission, parity_validation, shipping") + .allowed({"bootstrap", "scene_submission", "parity_validation", "shipping"}); +REXCVAR_DEFINE_INT32(ac6_native_graphics_frames_in_flight, 2, "AC6/NativeGraphics", + "Native renderer max frames in flight") + .range(1, 4); + +namespace ac6::graphics { +namespace { + +std::mutex g_native_graphics_mutex; +ac6::renderer::NativeRenderer g_native_renderer; +NativeGraphicsRuntimeStatus g_runtime_status{}; + +ac6::renderer::BackendType ParseBackend(std::string_view value) { + if (value == "d3d12") { + return ac6::renderer::BackendType::kD3D12; + } + if (value == "vulkan") { + return ac6::renderer::BackendType::kVulkan; + } + if (value == "metal") { + return ac6::renderer::BackendType::kMetal; + } + return ac6::renderer::BackendType::kUnknown; +} + +ac6::renderer::FeatureLevel ParseFeatureLevel(std::string_view value) { + if (value == "bootstrap") { + return ac6::renderer::FeatureLevel::kBootstrap; + } + if (value == "parity_validation") { + return ac6::renderer::FeatureLevel::kParityValidation; + } + if (value == "shipping") { + return ac6::renderer::FeatureLevel::kShipping; + } + return ac6::renderer::FeatureLevel::kSceneSubmission; +} + +ac6::renderer::NativeRendererConfig BuildRendererConfig() { + ac6::renderer::NativeRendererConfig config; + config.preferred_backend = ParseBackend(REXCVAR_GET(ac6_native_graphics_backend)); + config.feature_level = ParseFeatureLevel(REXCVAR_GET(ac6_native_graphics_feature_level)); + config.max_frames_in_flight = static_cast(REXCVAR_GET(ac6_native_graphics_frames_in_flight)); + config.enable_debug_markers = true; + config.enable_validation = true; + return config; +} + +bool EnsureInitialized() { + if (g_runtime_status.initialized) { + return true; + } + + ++g_runtime_status.init_attempts; + const ac6::renderer::NativeRendererConfig config = BuildRendererConfig(); + if (!g_native_renderer.Initialize(config)) { + g_runtime_status.had_init_failure = true; + REXLOG_ERROR("AC6 native graphics failed to initialize backend={}", + ac6::renderer::ToString(ac6::renderer::ResolveBackend(config.preferred_backend))); + return false; + } + + g_runtime_status.initialized = true; + g_runtime_status.had_init_failure = false; + ++g_runtime_status.init_successes; + g_runtime_status.feature_level = config.feature_level; + return true; +} + +void UpdateStatusFromRendererUnlocked() { + g_runtime_status.renderer_stats = g_native_renderer.GetStats(); + g_runtime_status.active_backend = g_runtime_status.renderer_stats.active_backend; + g_runtime_status.frame_plan = g_native_renderer.frame_plan(); +} + +} // namespace + +void OnFrameBoundary() { + std::scoped_lock lock(g_native_graphics_mutex); + + g_runtime_status.enabled = REXCVAR_GET(ac6_native_graphics_enabled); + if (!g_runtime_status.enabled) { + if (g_runtime_status.initialized) { + g_native_renderer.Shutdown(); + g_runtime_status.initialized = false; + } + return; + } + + if (REXCVAR_GET(ac6_native_graphics_require_capture) && !REXCVAR_GET(ac6_render_capture)) { + REXCVAR_SET(ac6_render_capture, true); + } + + if (!EnsureInitialized()) { + return; + } + + const ac6::d3d::FrameCaptureSnapshot frame_capture = ac6::d3d::GetFrameCapture(); + g_runtime_status.capture_summary = ac6::d3d::GetFrameCaptureSummary(); + + g_native_renderer.BeginFrame(); + g_native_renderer.BuildCapturedFrame(frame_capture); + ++g_runtime_status.frames_built; + UpdateStatusFromRendererUnlocked(); +} + +void Shutdown() { + std::scoped_lock lock(g_native_graphics_mutex); + if (!g_runtime_status.initialized) { + return; + } + g_native_renderer.Shutdown(); + g_runtime_status.initialized = false; +} + +NativeGraphicsRuntimeStatus GetRuntimeStatus() { + std::scoped_lock lock(g_native_graphics_mutex); + return g_runtime_status; +} + +} // namespace ac6::graphics + diff --git a/src/ac6_native_graphics.h b/src/ac6_native_graphics.h index e02abfc9..bc9c2c9b 100644 --- a/src/ac6_native_graphics.h +++ b/src/ac6_native_graphics.h @@ -1 +1,32 @@ - +#pragma once + +#include + +#include "ac6_native_renderer/frame_plan.h" +#include "ac6_native_renderer/types.h" +#include "d3d_state.h" + +namespace ac6::graphics { + +struct NativeGraphicsRuntimeStatus { + bool enabled = false; + bool initialized = false; + bool had_init_failure = false; + uint64_t init_attempts = 0; + uint64_t init_successes = 0; + uint64_t frames_built = 0; + + ac6::renderer::BackendType active_backend = ac6::renderer::BackendType::kUnknown; + ac6::renderer::FeatureLevel feature_level = ac6::renderer::FeatureLevel::kBootstrap; + ac6::renderer::NativeRendererStats renderer_stats{}; + ac6::d3d::FrameCaptureSummary capture_summary{}; + ac6::renderer::NativeFramePlan frame_plan{}; +}; + +void OnFrameBoundary(); +void Shutdown(); + +NativeGraphicsRuntimeStatus GetRuntimeStatus(); + +} // namespace ac6::graphics + diff --git a/src/ac6_native_graphics_overlay.cpp b/src/ac6_native_graphics_overlay.cpp index e02abfc9..0b7b688a 100644 --- a/src/ac6_native_graphics_overlay.cpp +++ b/src/ac6_native_graphics_overlay.cpp @@ -1 +1,57 @@ - +#include "ac6_native_graphics_overlay.h" + +#include + +#include "ac6_native_graphics.h" + +namespace ac6::graphics { + +NativeGraphicsStatusDialog::NativeGraphicsStatusDialog(rex::ui::ImGuiDrawer* imgui_drawer) + : ImGuiDialog(imgui_drawer) {} + +NativeGraphicsStatusDialog::~NativeGraphicsStatusDialog() = default; + +void NativeGraphicsStatusDialog::OnDraw(ImGuiIO& io) { + (void)io; + if (!visible_) { + return; + } + + if (!ImGui::Begin("AC6 Native Graphics##status", &visible_, ImGuiWindowFlags_NoCollapse)) { + ImGui::End(); + return; + } + + const NativeGraphicsRuntimeStatus status = GetRuntimeStatus(); + ImGui::Text("enabled: %s", status.enabled ? "true" : "false"); + ImGui::Text("initialized: %s", status.initialized ? "true" : "false"); + ImGui::Text("init failures seen: %s", status.had_init_failure ? "true" : "false"); + ImGui::Text("init attempts/successes: %llu / %llu", + static_cast(status.init_attempts), + static_cast(status.init_successes)); + ImGui::Text("frames built: %llu", static_cast(status.frames_built)); + ImGui::Separator(); + ImGui::Text("backend: %s", ac6::renderer::ToString(status.active_backend).data()); + ImGui::Text("feature level: %s", ac6::renderer::ToString(status.feature_level).data()); + ImGui::Text("renderer frames: %llu", + static_cast(status.renderer_stats.frame_count)); + ImGui::Text("render passes built: %llu", + static_cast(status.renderer_stats.built_pass_count)); + ImGui::Separator(); + ImGui::Text("capture frame: %llu", + static_cast(status.capture_summary.frame_index)); + ImGui::Text("capture draws/clears/resolves: %u / %u / %u", + status.capture_summary.draw_count, status.capture_summary.clear_count, + status.capture_summary.resolve_count); + ImGui::Text("planned output: %ux%u", status.frame_plan.output_width, + status.frame_plan.output_height); + ImGui::Text("stages scene/post/ui: %s / %s / %s", + status.frame_plan.has_scene_stage ? "yes" : "no", + status.frame_plan.has_post_process_stage ? "yes" : "no", + status.frame_plan.has_ui_stage ? "yes" : "no"); + + ImGui::End(); +} + +} // namespace ac6::graphics + diff --git a/src/ac6_native_graphics_overlay.h b/src/ac6_native_graphics_overlay.h index eba57a8d..4de30238 100644 --- a/src/ac6_native_graphics_overlay.h +++ b/src/ac6_native_graphics_overlay.h @@ -15,6 +15,7 @@ class NativeGraphicsStatusDialog final : public rex::ui::ImGuiDialog { explicit NativeGraphicsStatusDialog(rex::ui::ImGuiDrawer* imgui_drawer); ~NativeGraphicsStatusDialog(); + void Show() { visible_ = true; } void ToggleVisible() { visible_ = !visible_; } bool IsVisible() const { return visible_; } diff --git a/src/ac6recomp_app.h b/src/ac6recomp_app.h index e02abfc9..9933524d 100644 --- a/src/ac6recomp_app.h +++ b/src/ac6recomp_app.h @@ -1 +1,30 @@ - +#pragma once + +#include + +#include + +#include "ac6_native_graphics_overlay.h" +#include "generated/ac6recomp_config.h" + +class Ac6recompApp : public rex::ReXApp { + public: + using rex::ReXApp::ReXApp; + + static std::unique_ptr Create( + rex::ui::WindowedAppContext& ctx) { + return std::unique_ptr(new Ac6recompApp(ctx, "ac6recomp", PPCImageConfig)); + } + + protected: + void OnCreateDialogs(rex::ui::ImGuiDrawer* drawer) override { + rex::ReXApp::OnCreateDialogs(drawer); + native_graphics_status_dialog_ = + std::make_unique(drawer); + native_graphics_status_dialog_->Show(); + } + + private: + std::unique_ptr native_graphics_status_dialog_; +}; + diff --git a/src/d3d_hooks.h b/src/d3d_hooks.h index e02abfc9..cd4a540a 100644 --- a/src/d3d_hooks.h +++ b/src/d3d_hooks.h @@ -1 +1,19 @@ - +#pragma once + +#include + +#include "d3d_state.h" + +REXCVAR_DECLARE(bool, ac6_render_capture); + +namespace ac6::d3d { + +void OnFrameBoundary(); + +DrawStatsSnapshot GetDrawStats(); +FrameCaptureSnapshot GetFrameCapture(); +FrameCaptureSummary GetFrameCaptureSummary(); +ShadowState GetShadowState(); + +} // namespace ac6::d3d + diff --git a/src/render_hooks.cpp b/src/render_hooks.cpp index 1cf05557..24015e51 100644 --- a/src/render_hooks.cpp +++ b/src/render_hooks.cpp @@ -1,5 +1,6 @@ #include "render_hooks.h" #include "d3d_hooks.h" +#include "ac6_native_graphics.h" #include #include @@ -47,6 +48,7 @@ void ac6DeltaDivisorHook(PPCRegister& r29) { void ac6PresentTimingHook(PPCRegister& /*r31*/) { ac6::d3d::OnFrameBoundary(); + ac6::graphics::OnFrameBoundary(); const auto now = Clock::now(); double frame_time_ms = 0.0;