mirror of
https://github.com/sal063/AC6_recomp
synced 2026-08-02 00:34:20 -04:00
Fix graphics diagnostics and restore default runtime settings
This commit is contained in:
+171
-2
@@ -1,10 +1,13 @@
|
||||
#include "ac6_native_graphics.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
|
||||
#include <native/audio/audio_system.h>
|
||||
#include <rex/cvar.h>
|
||||
#include <rex/graphics/flags.h>
|
||||
#include <rex/graphics/graphics_system.h>
|
||||
#include <rex/logging.h>
|
||||
#include <rex/system/kernel_state.h>
|
||||
@@ -38,8 +41,11 @@ namespace ac6::graphics {
|
||||
namespace {
|
||||
|
||||
ac6::renderer::NativeRenderer g_native_renderer;
|
||||
ac6::renderer::Ac6RenderFrontend g_capture_frontend;
|
||||
ac6::renderer::FramePlanner g_capture_frame_planner;
|
||||
NativeGraphicsRuntimeStatus g_runtime_status{};
|
||||
ac6::renderer::D3D12Backend* g_d3d12_backend = nullptr;
|
||||
std::atomic<rex::memory::Memory*> g_captured_memory{nullptr};
|
||||
|
||||
GraphicsRuntimeMode ParseGraphicsMode(std::string_view value) {
|
||||
if (value == "disabled") {
|
||||
@@ -77,12 +83,10 @@ void ResetReplayStatus() {
|
||||
g_runtime_status.replay_frames_built = 0;
|
||||
g_runtime_status.active_backend = ac6::renderer::BackendType::kUnknown;
|
||||
g_runtime_status.renderer_stats = {};
|
||||
g_runtime_status.frontend_summary = {};
|
||||
g_runtime_status.replay_summary = {};
|
||||
g_runtime_status.execution_summary = {};
|
||||
g_runtime_status.executor_summary = {};
|
||||
g_runtime_status.backend_executor_status = {};
|
||||
g_runtime_status.frame_plan = {};
|
||||
g_runtime_status.latest_renderer_frame_index = 0;
|
||||
g_runtime_status.last_meaningful_renderer_frame_index = 0;
|
||||
g_runtime_status.showing_latched_snapshot = false;
|
||||
@@ -92,6 +96,24 @@ void SyncRuntimeFlags() {
|
||||
g_runtime_status.enabled = REXCVAR_GET(ac6_native_graphics_enabled);
|
||||
g_runtime_status.mode = ParseGraphicsMode(REXCVAR_GET(ac6_graphics_mode));
|
||||
g_runtime_status.capture_enabled = REXCVAR_GET(ac6_render_capture);
|
||||
const uint32_t shared_scale =
|
||||
static_cast<uint32_t>(std::max(INT32_C(1), REXCVAR_GET(resolution_scale)));
|
||||
const bool use_shared_scale = rex::cvar::HasNonDefaultValue("resolution_scale");
|
||||
const int32_t configured_scale_x =
|
||||
use_shared_scale && !rex::cvar::HasNonDefaultValue("draw_resolution_scale_x")
|
||||
? static_cast<int32_t>(shared_scale)
|
||||
: REXCVAR_GET(draw_resolution_scale_x);
|
||||
const int32_t configured_scale_y =
|
||||
use_shared_scale && !rex::cvar::HasNonDefaultValue("draw_resolution_scale_y")
|
||||
? static_cast<int32_t>(shared_scale)
|
||||
: REXCVAR_GET(draw_resolution_scale_y);
|
||||
g_runtime_status.draw_resolution_scale_x =
|
||||
static_cast<uint32_t>(std::max(INT32_C(1), configured_scale_x));
|
||||
g_runtime_status.draw_resolution_scale_y =
|
||||
static_cast<uint32_t>(std::max(INT32_C(1), configured_scale_y));
|
||||
g_runtime_status.direct_host_resolve = REXCVAR_GET(direct_host_resolve);
|
||||
g_runtime_status.draw_resolution_scaled_texture_offsets =
|
||||
REXCVAR_GET(draw_resolution_scaled_texture_offsets);
|
||||
g_runtime_status.authoritative_renderer_active =
|
||||
g_runtime_status.enabled &&
|
||||
g_runtime_status.mode != GraphicsRuntimeMode::kDisabled;
|
||||
@@ -131,6 +153,142 @@ bool IsMeaningfulRendererSnapshot(
|
||||
backend_status.resolve_command_count != 0;
|
||||
}
|
||||
|
||||
uint32_t ScoreObservedPassForDiagnostics(const ac6::renderer::ObservedPassDesc& pass) {
|
||||
const uint64_t viewport_area =
|
||||
uint64_t(pass.viewport_width) * uint64_t(pass.viewport_height);
|
||||
uint32_t score = 0;
|
||||
score += pass.selected_for_present ? 160u : 0u;
|
||||
score += pass.matches_frame_end_viewport ? 120u : 0u;
|
||||
score += pass.resolve_count * 80u;
|
||||
score += pass.draw_count * 4u;
|
||||
score += pass.clear_count * 6u;
|
||||
score += pass.max_texture_count * 3u;
|
||||
score += pass.max_stream_count * 2u;
|
||||
score += pass.max_sampler_count * 2u;
|
||||
score += static_cast<uint32_t>(std::min<uint64_t>(viewport_area / 32768u, 120u));
|
||||
switch (pass.kind) {
|
||||
case ac6::renderer::ObservedPassKind::kScene:
|
||||
score += 40u;
|
||||
break;
|
||||
case ac6::renderer::ObservedPassKind::kPostProcess:
|
||||
score += 50u;
|
||||
break;
|
||||
case ac6::renderer::ObservedPassKind::kUiComposite:
|
||||
score += 20u;
|
||||
break;
|
||||
case ac6::renderer::ObservedPassKind::kUnknown:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return score;
|
||||
}
|
||||
|
||||
void RefreshPassDiagnostics(const std::vector<ac6::renderer::ObservedPassDesc>& passes) {
|
||||
g_runtime_status.pass_diagnostics_count = 0;
|
||||
g_runtime_status.pass_diagnostics = {};
|
||||
if (passes.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct RankedPass {
|
||||
uint32_t pass_index = 0;
|
||||
uint32_t score = 0;
|
||||
};
|
||||
|
||||
std::vector<RankedPass> ranked;
|
||||
ranked.reserve(passes.size());
|
||||
for (uint32_t i = 0; i < passes.size(); ++i) {
|
||||
const auto& pass = passes[i];
|
||||
ranked.push_back({i, ScoreObservedPassForDiagnostics(pass)});
|
||||
}
|
||||
|
||||
std::sort(ranked.begin(), ranked.end(), [&](const RankedPass& left, const RankedPass& right) {
|
||||
if (left.score != right.score) {
|
||||
return left.score > right.score;
|
||||
}
|
||||
return left.pass_index < right.pass_index;
|
||||
});
|
||||
|
||||
const uint32_t count = std::min<uint32_t>(static_cast<uint32_t>(ranked.size()),
|
||||
static_cast<uint32_t>(g_runtime_status.pass_diagnostics.size()));
|
||||
g_runtime_status.pass_diagnostics_count = count;
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
const auto& ranked_pass = ranked[i];
|
||||
const auto& pass = passes[ranked_pass.pass_index];
|
||||
g_runtime_status.pass_diagnostics[i] = {
|
||||
.valid = true,
|
||||
.pass_index = ranked_pass.pass_index,
|
||||
.kind = pass.kind,
|
||||
.score = ranked_pass.score,
|
||||
.render_target_0 = pass.render_target_0,
|
||||
.depth_stencil = pass.depth_stencil,
|
||||
.viewport_x = pass.viewport_x,
|
||||
.viewport_y = pass.viewport_y,
|
||||
.viewport_width = pass.viewport_width,
|
||||
.viewport_height = pass.viewport_height,
|
||||
.draw_count = pass.draw_count,
|
||||
.clear_count = pass.clear_count,
|
||||
.resolve_count = pass.resolve_count,
|
||||
.max_texture_count = pass.max_texture_count,
|
||||
.max_stream_count = pass.max_stream_count,
|
||||
.max_sampler_count = pass.max_sampler_count,
|
||||
.max_fetch_constant_count = pass.max_fetch_constant_count,
|
||||
.max_shader_gpr_alloc = pass.max_shader_gpr_alloc,
|
||||
.pass_signature = pass.pass_signature,
|
||||
.first_texture_fetch_layout_signature =
|
||||
pass.first_texture_fetch_layout_signature,
|
||||
.last_texture_fetch_layout_signature =
|
||||
pass.last_texture_fetch_layout_signature,
|
||||
.first_resource_binding_signature =
|
||||
pass.first_resource_binding_signature,
|
||||
.last_resource_binding_signature =
|
||||
pass.last_resource_binding_signature,
|
||||
.first_textures = {.count = pass.first_textures.count,
|
||||
.slots = pass.first_textures.slots,
|
||||
.values = pass.first_textures.values},
|
||||
.last_textures = {.count = pass.last_textures.count,
|
||||
.slots = pass.last_textures.slots,
|
||||
.values = pass.last_textures.values},
|
||||
.first_fetch_constants = {.count = pass.first_fetch_constants.count,
|
||||
.slots = pass.first_fetch_constants.slots,
|
||||
.values = pass.first_fetch_constants.values},
|
||||
.last_fetch_constants = {.count = pass.last_fetch_constants.count,
|
||||
.slots = pass.last_fetch_constants.slots,
|
||||
.values = pass.last_fetch_constants.values},
|
||||
.selected_for_present = pass.selected_for_present,
|
||||
.matches_frame_end_viewport = pass.matches_frame_end_viewport,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void RefreshResolveDiagnostics(const ac6::d3d::FrameCaptureSnapshot& frame_capture) {
|
||||
g_runtime_status.resolve_diagnostics_count = 0;
|
||||
g_runtime_status.resolve_diagnostics = {};
|
||||
if (frame_capture.resolves.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t total_resolves = static_cast<uint32_t>(frame_capture.resolves.size());
|
||||
const uint32_t count =
|
||||
std::min<uint32_t>(total_resolves,
|
||||
static_cast<uint32_t>(g_runtime_status.resolve_diagnostics.size()));
|
||||
const uint32_t start_index = total_resolves - count;
|
||||
g_runtime_status.resolve_diagnostics_count = count;
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
const auto& resolve = frame_capture.resolves[start_index + i];
|
||||
g_runtime_status.resolve_diagnostics[i] = {
|
||||
.valid = true,
|
||||
.sequence = resolve.sequence,
|
||||
.render_target_0 = resolve.shadow_state.render_targets[0],
|
||||
.depth_stencil = resolve.shadow_state.depth_stencil,
|
||||
.viewport_width = resolve.shadow_state.viewport.width,
|
||||
.viewport_height = resolve.shadow_state.viewport.height,
|
||||
.args = resolve.args,
|
||||
.depth_or_scale = resolve.depth_or_scale,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void ShutdownReplayRenderer() {
|
||||
g_d3d12_backend = nullptr;
|
||||
if (!g_runtime_status.initialized) {
|
||||
@@ -213,6 +371,7 @@ std::string_view ToString(const GraphicsRuntimeMode mode) {
|
||||
|
||||
void OnFrameBoundary(rex::memory::Memory* memory) {
|
||||
SyncRuntimeFlags();
|
||||
g_captured_memory.store(memory, std::memory_order_release);
|
||||
|
||||
if (!g_runtime_status.enabled || g_runtime_status.mode == GraphicsRuntimeMode::kDisabled) {
|
||||
ShutdownReplayRenderer();
|
||||
@@ -238,6 +397,11 @@ void OnFrameBoundary(rex::memory::Memory* memory) {
|
||||
++g_runtime_status.analysis_frames_observed;
|
||||
g_runtime_status.capture_summary = capture_summary;
|
||||
g_runtime_status.latest_capture_frame_index = capture_summary.frame_index;
|
||||
g_runtime_status.frontend_summary = g_capture_frontend.BuildFromCapture(frame_capture);
|
||||
RefreshPassDiagnostics(g_capture_frontend.passes());
|
||||
RefreshResolveDiagnostics(frame_capture);
|
||||
g_runtime_status.frame_plan = g_capture_frame_planner.Build(
|
||||
g_runtime_status.frontend_summary, g_capture_frontend.passes());
|
||||
if (capture_summary.draw_count || capture_summary.clear_count ||
|
||||
capture_summary.resolve_count) {
|
||||
g_runtime_status.last_meaningful_capture_frame_index = capture_summary.frame_index;
|
||||
@@ -318,6 +482,7 @@ void OnFrameBoundary(rex::memory::Memory* memory) {
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
g_captured_memory.store(nullptr, std::memory_order_release);
|
||||
ShutdownReplayRenderer();
|
||||
}
|
||||
|
||||
@@ -337,4 +502,8 @@ ID3D12Resource* GetNativeOutputTexture() {
|
||||
return g_d3d12_backend ? g_d3d12_backend->GetOutputTexture() : nullptr;
|
||||
}
|
||||
|
||||
rex::memory::Memory* GetCapturedMemory() {
|
||||
return g_captured_memory.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
} // namespace ac6::graphics
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
|
||||
@@ -28,11 +29,64 @@ enum class GraphicsRuntimeMode : uint8_t {
|
||||
std::string_view ToString(GraphicsRuntimeMode mode);
|
||||
|
||||
struct NativeGraphicsRuntimeStatus {
|
||||
struct PassDiagnosticsEntry {
|
||||
struct BoundResourceSample {
|
||||
uint32_t count = 0;
|
||||
std::array<uint32_t, 4> slots{};
|
||||
std::array<uint32_t, 4> values{};
|
||||
};
|
||||
|
||||
bool valid = false;
|
||||
uint32_t pass_index = 0;
|
||||
ac6::renderer::ObservedPassKind kind = ac6::renderer::ObservedPassKind::kUnknown;
|
||||
uint32_t score = 0;
|
||||
uint32_t render_target_0 = 0;
|
||||
uint32_t depth_stencil = 0;
|
||||
uint32_t viewport_x = 0;
|
||||
uint32_t viewport_y = 0;
|
||||
uint32_t viewport_width = 0;
|
||||
uint32_t viewport_height = 0;
|
||||
uint32_t draw_count = 0;
|
||||
uint32_t clear_count = 0;
|
||||
uint32_t resolve_count = 0;
|
||||
uint32_t max_texture_count = 0;
|
||||
uint32_t max_stream_count = 0;
|
||||
uint32_t max_sampler_count = 0;
|
||||
uint32_t max_fetch_constant_count = 0;
|
||||
uint32_t max_shader_gpr_alloc = 0;
|
||||
uint64_t pass_signature = 0;
|
||||
uint64_t first_texture_fetch_layout_signature = 0;
|
||||
uint64_t last_texture_fetch_layout_signature = 0;
|
||||
uint64_t first_resource_binding_signature = 0;
|
||||
uint64_t last_resource_binding_signature = 0;
|
||||
BoundResourceSample first_textures{};
|
||||
BoundResourceSample last_textures{};
|
||||
BoundResourceSample first_fetch_constants{};
|
||||
BoundResourceSample last_fetch_constants{};
|
||||
bool selected_for_present = false;
|
||||
bool matches_frame_end_viewport = false;
|
||||
};
|
||||
|
||||
struct ResolveDiagnosticsEntry {
|
||||
bool valid = false;
|
||||
uint32_t sequence = 0;
|
||||
uint32_t render_target_0 = 0;
|
||||
uint32_t depth_stencil = 0;
|
||||
uint32_t viewport_width = 0;
|
||||
uint32_t viewport_height = 0;
|
||||
std::array<uint32_t, 7> args{};
|
||||
float depth_or_scale = 0.0f;
|
||||
};
|
||||
|
||||
bool enabled = false;
|
||||
GraphicsRuntimeMode mode = GraphicsRuntimeMode::kHybridBackendFixes;
|
||||
bool capture_enabled = false;
|
||||
bool authoritative_renderer_active = false;
|
||||
bool experimental_replay_present = false;
|
||||
uint32_t draw_resolution_scale_x = 1;
|
||||
uint32_t draw_resolution_scale_y = 1;
|
||||
bool direct_host_resolve = true;
|
||||
bool draw_resolution_scaled_texture_offsets = true;
|
||||
bool initialized = false;
|
||||
bool had_init_failure = false;
|
||||
bool showing_latched_snapshot = false;
|
||||
@@ -56,6 +110,10 @@ struct NativeGraphicsRuntimeStatus {
|
||||
ac6::d3d::FrameCaptureSummary capture_summary{};
|
||||
ac6::backend::BackendDiagnosticsSnapshot backend_diagnostics{};
|
||||
ac6::renderer::NativeFramePlan frame_plan{};
|
||||
uint32_t pass_diagnostics_count = 0;
|
||||
std::array<PassDiagnosticsEntry, 6> pass_diagnostics{};
|
||||
uint32_t resolve_diagnostics_count = 0;
|
||||
std::array<ResolveDiagnosticsEntry, 8> resolve_diagnostics{};
|
||||
};
|
||||
|
||||
void OnFrameBoundary(rex::memory::Memory* memory);
|
||||
@@ -63,5 +121,6 @@ void Shutdown();
|
||||
|
||||
NativeGraphicsRuntimeStatus GetRuntimeStatus();
|
||||
ID3D12Resource* GetNativeOutputTexture();
|
||||
rex::memory::Memory* GetCapturedMemory();
|
||||
|
||||
} // namespace ac6::graphics
|
||||
|
||||
@@ -1,10 +1,359 @@
|
||||
#include "ac6_native_graphics_overlay.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <rex/graphics/pipeline/texture/info.h>
|
||||
#include <rex/graphics/xenos.h>
|
||||
#include <rex/memory.h>
|
||||
|
||||
#include "ac6_native_graphics.h"
|
||||
|
||||
namespace ac6::graphics {
|
||||
namespace {
|
||||
|
||||
struct DecodedTextureFetch {
|
||||
bool valid = false;
|
||||
uint32_t header_offset = 0;
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
uint32_t depth = 0;
|
||||
uint32_t base_address = 0;
|
||||
const char* format_name = nullptr;
|
||||
rex::graphics::xenos::DataDimension dimension =
|
||||
rex::graphics::xenos::DataDimension::k2DOrStacked;
|
||||
bool tiled = false;
|
||||
};
|
||||
|
||||
template <size_t N>
|
||||
struct DecodedSampleSet {
|
||||
uint32_t count = 0;
|
||||
std::array<DecodedTextureFetch, N> entries{};
|
||||
};
|
||||
|
||||
bool IsReadableGuestRange(rex::memory::Memory* memory, uint32_t guest_ptr, uint32_t size) {
|
||||
if (memory == nullptr || guest_ptr == 0 || size == 0) {
|
||||
return false;
|
||||
}
|
||||
const uint32_t guest_end = guest_ptr + size - 1;
|
||||
if (guest_end < guest_ptr) {
|
||||
return false;
|
||||
}
|
||||
auto* start_heap = memory->LookupHeap(guest_ptr);
|
||||
if (start_heap == nullptr) {
|
||||
return false;
|
||||
}
|
||||
auto* end_heap = memory->LookupHeap(guest_end);
|
||||
if (end_heap != start_heap) {
|
||||
return false;
|
||||
}
|
||||
const auto access = start_heap->QueryRangeAccess(guest_ptr, guest_end);
|
||||
return access == rex::memory::PageAccess::kReadOnly ||
|
||||
access == rex::memory::PageAccess::kReadWrite ||
|
||||
access == rex::memory::PageAccess::kExecuteReadOnly ||
|
||||
access == rex::memory::PageAccess::kExecuteReadWrite;
|
||||
}
|
||||
|
||||
bool LoadTextureFetchAt(rex::memory::Memory* memory, uint32_t guest_ptr,
|
||||
rex::graphics::xenos::xe_gpu_texture_fetch_t& fetch) {
|
||||
if (!IsReadableGuestRange(memory, guest_ptr, sizeof(fetch))) {
|
||||
return false;
|
||||
}
|
||||
const uint8_t* fetch_base = memory->TranslateVirtual<const uint8_t*>(guest_ptr);
|
||||
if (fetch_base == nullptr) {
|
||||
return false;
|
||||
}
|
||||
fetch.dword_0 = rex::memory::load_and_swap<uint32_t>(fetch_base + 0);
|
||||
fetch.dword_1 = rex::memory::load_and_swap<uint32_t>(fetch_base + 4);
|
||||
fetch.dword_2 = rex::memory::load_and_swap<uint32_t>(fetch_base + 8);
|
||||
fetch.dword_3 = rex::memory::load_and_swap<uint32_t>(fetch_base + 12);
|
||||
fetch.dword_4 = rex::memory::load_and_swap<uint32_t>(fetch_base + 16);
|
||||
fetch.dword_5 = rex::memory::load_and_swap<uint32_t>(fetch_base + 20);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsPlausibleTextureFetchHeader(const rex::graphics::xenos::xe_gpu_texture_fetch_t& fetch) {
|
||||
using rex::graphics::xenos::DataDimension;
|
||||
|
||||
if (static_cast<uint32_t>(fetch.format) >= 64) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (fetch.dimension) {
|
||||
case DataDimension::k1D:
|
||||
case DataDimension::k3D:
|
||||
return !fetch.stacked;
|
||||
case DataDimension::k2DOrStacked:
|
||||
return true;
|
||||
case DataDimension::kCube:
|
||||
return !fetch.stacked && fetch.size_2d.stack_depth == 5;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsPlausibleTextureInfo(const rex::graphics::TextureInfo& info) {
|
||||
const uint32_t width = info.width + 1;
|
||||
const uint32_t height = info.height + 1;
|
||||
const uint32_t depth = info.depth + 1;
|
||||
return width >= 1 && width <= 8192 && height >= 1 && height <= 8192 && depth >= 1 &&
|
||||
depth <= 2048 && info.memory.base_address != 0 && info.format_info() != nullptr;
|
||||
}
|
||||
|
||||
bool DecodeTextureFetch(uint32_t guest_ptr, DecodedTextureFetch& out) {
|
||||
rex::memory::Memory* memory = GetCapturedMemory();
|
||||
if (!IsReadableGuestRange(memory, guest_ptr, sizeof(rex::graphics::xenos::xe_gpu_texture_fetch_t))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr uint32_t kProbeLimit = 0x80;
|
||||
for (uint32_t offset = 0; offset + sizeof(rex::graphics::xenos::xe_gpu_texture_fetch_t) <=
|
||||
kProbeLimit;
|
||||
offset += 4) {
|
||||
if (!IsReadableGuestRange(memory, guest_ptr + offset,
|
||||
sizeof(rex::graphics::xenos::xe_gpu_texture_fetch_t))) {
|
||||
continue;
|
||||
}
|
||||
rex::graphics::xenos::xe_gpu_texture_fetch_t fetch{};
|
||||
if (!LoadTextureFetchAt(memory, guest_ptr + offset, fetch)) {
|
||||
continue;
|
||||
}
|
||||
if (fetch.type != rex::graphics::xenos::FetchConstantType::kTexture) {
|
||||
continue;
|
||||
}
|
||||
if (!IsPlausibleTextureFetchHeader(fetch)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
rex::graphics::TextureInfo texture_info;
|
||||
if (!rex::graphics::TextureInfo::Prepare(fetch, &texture_info) ||
|
||||
!IsPlausibleTextureInfo(texture_info)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
out.valid = true;
|
||||
out.header_offset = offset;
|
||||
out.dimension = texture_info.dimension;
|
||||
out.tiled = texture_info.is_tiled;
|
||||
out.width = texture_info.width + 1;
|
||||
out.height = texture_info.height + 1;
|
||||
out.depth = texture_info.depth + 1;
|
||||
out.base_address = texture_info.memory.base_address;
|
||||
out.format_name = texture_info.format_info()->name;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string FormatDecodedTextureObject(uint32_t guest_ptr) {
|
||||
if (guest_ptr == 0) {
|
||||
return "none";
|
||||
}
|
||||
DecodedTextureFetch decoded;
|
||||
if (!DecodeTextureFetch(guest_ptr, decoded) || !decoded.valid) {
|
||||
char buffer[32];
|
||||
std::snprintf(buffer, sizeof(buffer), "%08X", guest_ptr);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
char buffer[128];
|
||||
const char* tiled_tag = decoded.tiled ? "t" : "l";
|
||||
if (decoded.dimension == rex::graphics::xenos::DataDimension::k3D || decoded.depth > 1) {
|
||||
std::snprintf(buffer, sizeof(buffer), "%08X+%02X[%ux%ux%u %s @%08X %s]", guest_ptr,
|
||||
decoded.header_offset, decoded.width, decoded.height, decoded.depth, tiled_tag,
|
||||
decoded.base_address, decoded.format_name ? decoded.format_name : "?");
|
||||
} else {
|
||||
std::snprintf(buffer, sizeof(buffer), "%08X+%02X[%ux%u %s @%08X %s]", guest_ptr,
|
||||
decoded.header_offset, decoded.width, decoded.height, tiled_tag,
|
||||
decoded.base_address, decoded.format_name ? decoded.format_name : "?");
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
template <typename Sample>
|
||||
DecodedSampleSet<4> DecodeSampleSet(const Sample& sample) {
|
||||
DecodedSampleSet<4> decoded_set;
|
||||
const uint32_t shown =
|
||||
std::min<uint32_t>(sample.count, static_cast<uint32_t>(sample.values.size()));
|
||||
for (uint32_t i = 0; i < shown; ++i) {
|
||||
DecodedTextureFetch decoded;
|
||||
if (!DecodeTextureFetch(sample.values[i], decoded) || !decoded.valid) {
|
||||
continue;
|
||||
}
|
||||
decoded_set.entries[decoded_set.count++] = decoded;
|
||||
}
|
||||
return decoded_set;
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
bool SampleUsesBaseAddress(const DecodedSampleSet<N>& sample_set, uint32_t base_address) {
|
||||
if (base_address == 0) {
|
||||
return false;
|
||||
}
|
||||
for (uint32_t i = 0; i < sample_set.count; ++i) {
|
||||
if (sample_set.entries[i].base_address == base_address) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string FormatResolveArgCandidates(
|
||||
const NativeGraphicsRuntimeStatus::ResolveDiagnosticsEntry& resolve) {
|
||||
std::string formatted;
|
||||
for (uint32_t i = 0; i < resolve.args.size(); ++i) {
|
||||
if (resolve.args[i] == 0) {
|
||||
continue;
|
||||
}
|
||||
DecodedTextureFetch decoded;
|
||||
if (!DecodeTextureFetch(resolve.args[i], decoded) || !decoded.valid) {
|
||||
continue;
|
||||
}
|
||||
char buffer[96];
|
||||
std::snprintf(buffer, sizeof(buffer), "r%u=%08X+%02X[%ux%u @%08X %s]", 4u + i,
|
||||
resolve.args[i], decoded.header_offset, decoded.width, decoded.height,
|
||||
decoded.base_address, decoded.format_name ? decoded.format_name : "?");
|
||||
if (!formatted.empty()) {
|
||||
formatted.append(", ");
|
||||
}
|
||||
formatted.append(buffer);
|
||||
}
|
||||
return formatted.empty() ? "none" : formatted;
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
std::string FormatPassSources(const DecodedSampleSet<N>& first_fetch,
|
||||
const DecodedSampleSet<N>& last_fetch,
|
||||
const NativeGraphicsRuntimeStatus& status, uint32_t self_index) {
|
||||
std::string formatted;
|
||||
for (uint32_t other_index = 0; other_index < status.pass_diagnostics_count; ++other_index) {
|
||||
if (other_index == self_index) {
|
||||
continue;
|
||||
}
|
||||
const auto& other_pass = status.pass_diagnostics[other_index];
|
||||
if (!other_pass.valid || other_pass.render_target_0 == 0) {
|
||||
continue;
|
||||
}
|
||||
DecodedTextureFetch decoded_rt0;
|
||||
if (!DecodeTextureFetch(other_pass.render_target_0, decoded_rt0) || !decoded_rt0.valid ||
|
||||
decoded_rt0.base_address == 0) {
|
||||
continue;
|
||||
}
|
||||
if (!SampleUsesBaseAddress(first_fetch, decoded_rt0.base_address) &&
|
||||
!SampleUsesBaseAddress(last_fetch, decoded_rt0.base_address)) {
|
||||
continue;
|
||||
}
|
||||
char buffer[48];
|
||||
std::snprintf(buffer, sizeof(buffer), "#%u(rt0 #%u %ux%u @%08X)", other_index,
|
||||
other_pass.pass_index, decoded_rt0.width, decoded_rt0.height,
|
||||
decoded_rt0.base_address);
|
||||
if (!formatted.empty()) {
|
||||
formatted.append(", ");
|
||||
}
|
||||
formatted.append(buffer);
|
||||
}
|
||||
return formatted.empty() ? "none" : formatted;
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
std::string FormatResolveSources(const DecodedSampleSet<N>& first_fetch,
|
||||
const DecodedSampleSet<N>& last_fetch,
|
||||
const NativeGraphicsRuntimeStatus& status) {
|
||||
std::string formatted;
|
||||
for (uint32_t resolve_index = 0; resolve_index < status.resolve_diagnostics_count;
|
||||
++resolve_index) {
|
||||
const auto& resolve = status.resolve_diagnostics[resolve_index];
|
||||
if (!resolve.valid) {
|
||||
continue;
|
||||
}
|
||||
for (uint32_t arg_index = 0; arg_index < resolve.args.size(); ++arg_index) {
|
||||
if (resolve.args[arg_index] == 0) {
|
||||
continue;
|
||||
}
|
||||
DecodedTextureFetch decoded;
|
||||
if (!DecodeTextureFetch(resolve.args[arg_index], decoded) || !decoded.valid ||
|
||||
decoded.base_address == 0) {
|
||||
continue;
|
||||
}
|
||||
if (!SampleUsesBaseAddress(first_fetch, decoded.base_address) &&
|
||||
!SampleUsesBaseAddress(last_fetch, decoded.base_address)) {
|
||||
continue;
|
||||
}
|
||||
char buffer[72];
|
||||
std::snprintf(buffer, sizeof(buffer), "#%u:r%u[%ux%u @%08X]", resolve_index, 4u + arg_index,
|
||||
decoded.width, decoded.height, decoded.base_address);
|
||||
if (!formatted.empty()) {
|
||||
formatted.append(", ");
|
||||
}
|
||||
formatted.append(buffer);
|
||||
}
|
||||
}
|
||||
return formatted.empty() ? "none" : formatted;
|
||||
}
|
||||
|
||||
template <typename Sample>
|
||||
std::string FormatBoundResourceSample(const Sample& sample) {
|
||||
if (sample.count == 0) {
|
||||
return "none";
|
||||
}
|
||||
std::string formatted;
|
||||
const uint32_t shown = std::min<uint32_t>(sample.count, static_cast<uint32_t>(sample.values.size()));
|
||||
for (uint32_t i = 0; i < shown; ++i) {
|
||||
if (!formatted.empty()) {
|
||||
formatted.append(", ");
|
||||
}
|
||||
char buffer[32];
|
||||
std::snprintf(buffer, sizeof(buffer), "%u:%08X", sample.slots[i], sample.values[i]);
|
||||
formatted.append(buffer);
|
||||
}
|
||||
if (sample.count > shown) {
|
||||
formatted.append(", ...");
|
||||
}
|
||||
return formatted;
|
||||
}
|
||||
|
||||
template <typename Sample>
|
||||
std::string FormatFetchConstantSample(const Sample& sample) {
|
||||
if (sample.count == 0) {
|
||||
return "none";
|
||||
}
|
||||
std::string formatted;
|
||||
const uint32_t shown = std::min<uint32_t>(sample.count, static_cast<uint32_t>(sample.values.size()));
|
||||
for (uint32_t i = 0; i < shown; ++i) {
|
||||
if (!formatted.empty()) {
|
||||
formatted.append(", ");
|
||||
}
|
||||
char buffer[96];
|
||||
DecodedTextureFetch decoded;
|
||||
if (DecodeTextureFetch(sample.values[i], decoded) && decoded.valid) {
|
||||
const char* tiled_tag = decoded.tiled ? "t" : "l";
|
||||
if (decoded.dimension == rex::graphics::xenos::DataDimension::k3D ||
|
||||
decoded.depth > 1) {
|
||||
std::snprintf(buffer, sizeof(buffer),
|
||||
"%u:%08X+%02X[%ux%ux%u %s @%08X %s]", sample.slots[i], sample.values[i],
|
||||
decoded.header_offset, decoded.width, decoded.height, decoded.depth,
|
||||
tiled_tag, decoded.base_address,
|
||||
decoded.format_name ? decoded.format_name : "?");
|
||||
} else {
|
||||
std::snprintf(buffer, sizeof(buffer), "%u:%08X+%02X[%ux%u %s @%08X %s]",
|
||||
sample.slots[i], sample.values[i], decoded.header_offset, decoded.width,
|
||||
decoded.height, tiled_tag, decoded.base_address,
|
||||
decoded.format_name ? decoded.format_name : "?");
|
||||
}
|
||||
} else {
|
||||
std::snprintf(buffer, sizeof(buffer), "%u:%08X", sample.slots[i], sample.values[i]);
|
||||
}
|
||||
formatted.append(buffer);
|
||||
}
|
||||
if (sample.count > shown) {
|
||||
formatted.append(", ...");
|
||||
}
|
||||
return formatted;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
NativeGraphicsStatusDialog::NativeGraphicsStatusDialog(rex::ui::ImGuiDrawer* imgui_drawer)
|
||||
: ImGuiDialog(imgui_drawer) {}
|
||||
@@ -33,6 +382,11 @@ void NativeGraphicsStatusDialog::OnDraw(ImGuiIO& io) {
|
||||
status.authoritative_renderer_active ? "RexGlue/Xenia D3D12 backend"
|
||||
: "disabled");
|
||||
ImGui::Text("capture active: %s", status.capture_enabled ? "yes" : "no");
|
||||
ImGui::Text("draw resolution scale: %ux%u", status.draw_resolution_scale_x,
|
||||
status.draw_resolution_scale_y);
|
||||
ImGui::Text("scaled tex offsets / direct host resolve: %s / %s",
|
||||
status.draw_resolution_scaled_texture_offsets ? "on" : "off",
|
||||
status.direct_host_resolve ? "on" : "off");
|
||||
ImGui::Text("experimental replay present override: %s",
|
||||
status.experimental_replay_present ? "enabled" : "disabled");
|
||||
ImGui::Text("analysis frames / replay frames: %llu / %llu",
|
||||
@@ -55,6 +409,109 @@ void NativeGraphicsStatusDialog::OnDraw(ImGuiIO& io) {
|
||||
ImGui::Text("frame-end viewport: %ux%u",
|
||||
status.capture_summary.frame_end_viewport_width,
|
||||
status.capture_summary.frame_end_viewport_height);
|
||||
ImGui::Text("frontend passes scene/post/ui: %u / %u / %u / %u",
|
||||
status.frontend_summary.pass_count, status.frontend_summary.scene_pass_count,
|
||||
status.frontend_summary.post_process_pass_count,
|
||||
status.frontend_summary.ui_pass_count);
|
||||
if (status.frame_plan.present_stage.valid) {
|
||||
ImGui::Text(
|
||||
"present stage: #%u %s vp=%ux%u rt0=%08X depth=%08X draws/resolves=%u/%u",
|
||||
status.frame_plan.present_stage.pass_index,
|
||||
ac6::renderer::ToString(status.frame_plan.present_stage.kind),
|
||||
status.frame_plan.present_stage.viewport_width,
|
||||
status.frame_plan.present_stage.viewport_height,
|
||||
status.frame_plan.present_stage.render_target_0,
|
||||
status.frame_plan.present_stage.depth_stencil,
|
||||
status.frame_plan.present_stage.draw_count,
|
||||
status.frame_plan.present_stage.resolve_count);
|
||||
}
|
||||
if (status.frame_plan.scene_stage.valid) {
|
||||
ImGui::Text(
|
||||
"scene stage: #%u %s vp=%ux%u rt0=%08X depth=%08X draws/resolves=%u/%u",
|
||||
status.frame_plan.scene_stage.pass_index,
|
||||
ac6::renderer::ToString(status.frame_plan.scene_stage.kind),
|
||||
status.frame_plan.scene_stage.viewport_width,
|
||||
status.frame_plan.scene_stage.viewport_height,
|
||||
status.frame_plan.scene_stage.render_target_0,
|
||||
status.frame_plan.scene_stage.depth_stencil,
|
||||
status.frame_plan.scene_stage.draw_count,
|
||||
status.frame_plan.scene_stage.resolve_count);
|
||||
}
|
||||
if (status.frame_plan.post_process_stage.valid) {
|
||||
ImGui::Text(
|
||||
"post stage: #%u %s vp=%ux%u rt0=%08X depth=%08X draws/resolves=%u/%u",
|
||||
status.frame_plan.post_process_stage.pass_index,
|
||||
ac6::renderer::ToString(status.frame_plan.post_process_stage.kind),
|
||||
status.frame_plan.post_process_stage.viewport_width,
|
||||
status.frame_plan.post_process_stage.viewport_height,
|
||||
status.frame_plan.post_process_stage.render_target_0,
|
||||
status.frame_plan.post_process_stage.depth_stencil,
|
||||
status.frame_plan.post_process_stage.draw_count,
|
||||
status.frame_plan.post_process_stage.resolve_count);
|
||||
}
|
||||
if (status.frame_plan.ui_stage.valid) {
|
||||
ImGui::Text(
|
||||
"ui stage: #%u %s vp=%ux%u rt0=%08X depth=%08X draws/resolves=%u/%u",
|
||||
status.frame_plan.ui_stage.pass_index,
|
||||
ac6::renderer::ToString(status.frame_plan.ui_stage.kind),
|
||||
status.frame_plan.ui_stage.viewport_width,
|
||||
status.frame_plan.ui_stage.viewport_height,
|
||||
status.frame_plan.ui_stage.render_target_0,
|
||||
status.frame_plan.ui_stage.depth_stencil,
|
||||
status.frame_plan.ui_stage.draw_count,
|
||||
status.frame_plan.ui_stage.resolve_count);
|
||||
}
|
||||
for (uint32_t i = 0; i < status.pass_diagnostics_count; ++i) {
|
||||
const auto& pass = status.pass_diagnostics[i];
|
||||
if (!pass.valid) {
|
||||
continue;
|
||||
}
|
||||
ImGui::Text(
|
||||
"pass[%u]: #%u %s score=%u vp=%ux%u+%u+%u rt0=%08X depth=%08X d/c/r=%u/%u/%u tex/str/samp/fetch=%u/%u/%u/%u gpr=%u %s%s",
|
||||
i, pass.pass_index, ac6::renderer::ToString(pass.kind), pass.score,
|
||||
pass.viewport_width, pass.viewport_height, pass.viewport_x, pass.viewport_y,
|
||||
pass.render_target_0, pass.depth_stencil, pass.draw_count, pass.clear_count,
|
||||
pass.resolve_count, pass.max_texture_count, pass.max_stream_count,
|
||||
pass.max_sampler_count, pass.max_fetch_constant_count, pass.max_shader_gpr_alloc,
|
||||
pass.selected_for_present ? "present " : "",
|
||||
pass.matches_frame_end_viewport ? "frame_end" : "");
|
||||
ImGui::Text(
|
||||
" sig=%016llX tfetch=%016llX->%016llX bind=%016llX->%016llX",
|
||||
static_cast<unsigned long long>(pass.pass_signature),
|
||||
static_cast<unsigned long long>(pass.first_texture_fetch_layout_signature),
|
||||
static_cast<unsigned long long>(pass.last_texture_fetch_layout_signature),
|
||||
static_cast<unsigned long long>(pass.first_resource_binding_signature),
|
||||
static_cast<unsigned long long>(pass.last_resource_binding_signature));
|
||||
const std::string first_textures = FormatBoundResourceSample(pass.first_textures);
|
||||
const std::string last_textures = FormatBoundResourceSample(pass.last_textures);
|
||||
const std::string first_fetch = FormatFetchConstantSample(pass.first_fetch_constants);
|
||||
const std::string last_fetch = FormatFetchConstantSample(pass.last_fetch_constants);
|
||||
const std::string rt0_object = FormatDecodedTextureObject(pass.render_target_0);
|
||||
const std::string depth_object = FormatDecodedTextureObject(pass.depth_stencil);
|
||||
const DecodedSampleSet<4> decoded_first_fetch = DecodeSampleSet(pass.first_fetch_constants);
|
||||
const DecodedSampleSet<4> decoded_last_fetch = DecodeSampleSet(pass.last_fetch_constants);
|
||||
const std::string source_passes =
|
||||
FormatPassSources(decoded_first_fetch, decoded_last_fetch, status, i);
|
||||
const std::string resolve_sources =
|
||||
FormatResolveSources(decoded_first_fetch, decoded_last_fetch, status);
|
||||
ImGui::Text(" tex %s -> %s", first_textures.c_str(), last_textures.c_str());
|
||||
ImGui::Text(" fet %s -> %s", first_fetch.c_str(), last_fetch.c_str());
|
||||
ImGui::Text(" rt0 %s depth %s", rt0_object.c_str(), depth_object.c_str());
|
||||
ImGui::Text(" src %s", source_passes.c_str());
|
||||
ImGui::Text(" rslv %s", resolve_sources.c_str());
|
||||
}
|
||||
for (uint32_t i = 0; i < status.resolve_diagnostics_count; ++i) {
|
||||
const auto& resolve = status.resolve_diagnostics[i];
|
||||
if (!resolve.valid) {
|
||||
continue;
|
||||
}
|
||||
ImGui::Text(
|
||||
"resolve[%u]: seq=%u vp=%ux%u rt0=%08X depth=%08X f1=%.3f", i, resolve.sequence,
|
||||
resolve.viewport_width, resolve.viewport_height, resolve.render_target_0,
|
||||
resolve.depth_stencil, resolve.depth_or_scale);
|
||||
const std::string candidates = FormatResolveArgCandidates(resolve);
|
||||
ImGui::Text(" %s", candidates.c_str());
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Text("swap source: %s", ac6::backend::ToString(diagnostics.swap_source));
|
||||
@@ -90,10 +547,41 @@ void NativeGraphicsStatusDialog::OnDraw(ImGuiIO& io) {
|
||||
diagnostics.latest_signature.quarter_res_like ? "yes" : "no",
|
||||
diagnostics.latest_signature.point_sprite_like ? "yes" : "no",
|
||||
diagnostics.latest_signature.additive_like ? "yes" : "no");
|
||||
ImGui::Text("sampler hints: point min/mip=%u/%u linear min/mip=%u/%u mip clamp=%u max mip=%u",
|
||||
diagnostics.latest_signature.point_min_sampler_count,
|
||||
diagnostics.latest_signature.point_mip_sampler_count,
|
||||
diagnostics.latest_signature.linear_min_sampler_count,
|
||||
diagnostics.latest_signature.linear_mip_sampler_count,
|
||||
diagnostics.latest_signature.mip_clamp_sampler_count,
|
||||
diagnostics.latest_signature.max_sampler_mip_level);
|
||||
ImGui::TextWrapped("signature tags: %s",
|
||||
diagnostics.latest_signature_tags.empty()
|
||||
? "none"
|
||||
: diagnostics.latest_signature_tags.c_str());
|
||||
const bool draw_scale_is_native =
|
||||
status.draw_resolution_scale_x <= 1 && status.draw_resolution_scale_y <= 1;
|
||||
const bool draw_scale_is_scaled =
|
||||
status.draw_resolution_scale_x > 1 || status.draw_resolution_scale_y > 1;
|
||||
const bool likely_point_filtered =
|
||||
diagnostics.latest_signature.point_min_sampler_count != 0 ||
|
||||
diagnostics.latest_signature.point_mip_sampler_count != 0;
|
||||
if (draw_scale_is_native) {
|
||||
ImGui::TextWrapped(
|
||||
"likely cause: guest output is still native 720p/1x, so particles and low-res effect "
|
||||
"passes will look blocky on higher-resolution displays.");
|
||||
} else if (!diagnostics.swap_source_scaled) {
|
||||
ImGui::TextWrapped(
|
||||
"likely cause: draw scaling is enabled, but the presented swap source is still "
|
||||
"unscaled. This points to an unscaled resolve / swap-texture path.");
|
||||
} else if (status.direct_host_resolve) {
|
||||
ImGui::TextWrapped(
|
||||
"likely cause: draw scaling is active and the swap source is scaled, so the failure is "
|
||||
"likely in the scaled resolve path rather than scaling being ignored.");
|
||||
} else if (diagnostics.latest_signature.point_sprite_like && likely_point_filtered) {
|
||||
ImGui::TextWrapped(
|
||||
"likely cause: the active effect pass looks like point-sprite rendering with point "
|
||||
"sampling, so close sprites can stay pixelated even when they are near the camera.");
|
||||
}
|
||||
ImGui::Text("authoritative VS / PS: %016llX / %016llX",
|
||||
static_cast<unsigned long long>(diagnostics.active_vertex_shader_hash),
|
||||
static_cast<unsigned long long>(diagnostics.active_pixel_shader_hash));
|
||||
|
||||
@@ -5,6 +5,17 @@
|
||||
namespace ac6::renderer {
|
||||
namespace {
|
||||
|
||||
void HashU32(uint64_t& hash, uint32_t value) {
|
||||
constexpr uint64_t kFnvPrime = 1099511628211ull;
|
||||
hash ^= value;
|
||||
hash *= kFnvPrime;
|
||||
}
|
||||
|
||||
void HashU64(uint64_t& hash, uint64_t value) {
|
||||
HashU32(hash, uint32_t(value & 0xFFFFFFFFull));
|
||||
HashU32(hash, uint32_t(value >> 32));
|
||||
}
|
||||
|
||||
template <typename Container>
|
||||
uint32_t CountNonZeroEntries(const Container& values) {
|
||||
uint32_t count = 0;
|
||||
@@ -16,6 +27,23 @@ uint32_t CountNonZeroEntries(const Container& values) {
|
||||
return count;
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
ObservedPassDesc::BoundResourceSample SampleBoundEntries(
|
||||
const std::array<uint32_t, N>& values) {
|
||||
ObservedPassDesc::BoundResourceSample sample;
|
||||
for (uint32_t slot = 0; slot < values.size(); ++slot) {
|
||||
if (!values[slot]) {
|
||||
continue;
|
||||
}
|
||||
if (sample.count < sample.values.size()) {
|
||||
sample.slots[sample.count] = slot;
|
||||
sample.values[sample.count] = values[slot];
|
||||
}
|
||||
++sample.count;
|
||||
}
|
||||
return sample;
|
||||
}
|
||||
|
||||
uint32_t CountBoundStreams(
|
||||
const std::array<ac6::d3d::StreamBinding, ac6::d3d::kMaxStreams>& streams) {
|
||||
uint32_t count = 0;
|
||||
@@ -273,9 +301,44 @@ FrontendFrameSummary Ac6RenderFrontend::BuildFromCapture(
|
||||
current_pass.viewport_y = event.shadow_state->viewport.y;
|
||||
current_pass.viewport_width = event.shadow_state->viewport.width;
|
||||
current_pass.viewport_height = event.shadow_state->viewport.height;
|
||||
current_pass.first_vertex_fetch_layout_signature =
|
||||
event.shadow_state->vertex_fetch_layout_signature;
|
||||
current_pass.last_vertex_fetch_layout_signature =
|
||||
event.shadow_state->vertex_fetch_layout_signature;
|
||||
current_pass.first_texture_fetch_layout_signature =
|
||||
event.shadow_state->texture_fetch_layout_signature;
|
||||
current_pass.last_texture_fetch_layout_signature =
|
||||
event.shadow_state->texture_fetch_layout_signature;
|
||||
current_pass.first_resource_binding_signature =
|
||||
event.shadow_state->resource_binding_signature;
|
||||
current_pass.last_resource_binding_signature =
|
||||
event.shadow_state->resource_binding_signature;
|
||||
current_pass.first_textures = SampleBoundEntries(event.shadow_state->textures);
|
||||
current_pass.last_textures = current_pass.first_textures;
|
||||
current_pass.first_fetch_constants =
|
||||
SampleBoundEntries(event.shadow_state->texture_fetch_ptrs);
|
||||
current_pass.last_fetch_constants = current_pass.first_fetch_constants;
|
||||
constexpr uint64_t kFnvOffsetBasis = 1469598103934665603ull;
|
||||
current_pass.pass_signature = kFnvOffsetBasis;
|
||||
}
|
||||
|
||||
current_pass.end_sequence = event.sequence;
|
||||
HashU32(current_pass.pass_signature, static_cast<uint32_t>(event.type));
|
||||
HashU32(current_pass.pass_signature, event.sequence);
|
||||
HashU64(current_pass.pass_signature, event.shadow_state->vertex_fetch_layout_signature);
|
||||
HashU64(current_pass.pass_signature, event.shadow_state->texture_fetch_layout_signature);
|
||||
HashU64(current_pass.pass_signature, event.shadow_state->resource_binding_signature);
|
||||
current_pass.last_vertex_fetch_layout_signature =
|
||||
event.shadow_state->vertex_fetch_layout_signature;
|
||||
current_pass.last_texture_fetch_layout_signature =
|
||||
event.shadow_state->texture_fetch_layout_signature;
|
||||
current_pass.last_resource_binding_signature =
|
||||
event.shadow_state->resource_binding_signature;
|
||||
current_pass.last_textures = SampleBoundEntries(event.shadow_state->textures);
|
||||
current_pass.last_fetch_constants =
|
||||
SampleBoundEntries(event.shadow_state->texture_fetch_ptrs);
|
||||
current_pass.max_shader_gpr_alloc =
|
||||
std::max(current_pass.max_shader_gpr_alloc, event.shadow_state->shader_gpr_alloc);
|
||||
switch (event.type) {
|
||||
case CapturedFrameEventType::kDraw:
|
||||
++current_pass.draw_count;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
@@ -47,7 +48,14 @@ struct ObservedCommandDesc {
|
||||
};
|
||||
|
||||
struct ObservedPassDesc {
|
||||
struct BoundResourceSample {
|
||||
uint32_t count = 0;
|
||||
std::array<uint32_t, 4> slots{};
|
||||
std::array<uint32_t, 4> values{};
|
||||
};
|
||||
|
||||
ObservedPassKind kind = ObservedPassKind::kUnknown;
|
||||
uint64_t pass_signature = 0;
|
||||
uint32_t start_sequence = 0;
|
||||
uint32_t end_sequence = 0;
|
||||
uint32_t draw_count = 0;
|
||||
@@ -61,12 +69,23 @@ struct ObservedPassDesc {
|
||||
uint32_t max_sampler_count = 0;
|
||||
uint32_t max_fetch_constant_count = 0;
|
||||
uint32_t max_draw_call_count = 0;
|
||||
uint32_t max_shader_gpr_alloc = 0;
|
||||
uint32_t render_target_0 = 0;
|
||||
uint32_t depth_stencil = 0;
|
||||
uint32_t viewport_x = 0;
|
||||
uint32_t viewport_y = 0;
|
||||
uint32_t viewport_width = 0;
|
||||
uint32_t viewport_height = 0;
|
||||
uint64_t first_vertex_fetch_layout_signature = 0;
|
||||
uint64_t last_vertex_fetch_layout_signature = 0;
|
||||
uint64_t first_texture_fetch_layout_signature = 0;
|
||||
uint64_t last_texture_fetch_layout_signature = 0;
|
||||
uint64_t first_resource_binding_signature = 0;
|
||||
uint64_t last_resource_binding_signature = 0;
|
||||
BoundResourceSample first_textures{};
|
||||
BoundResourceSample last_textures{};
|
||||
BoundResourceSample first_fetch_constants{};
|
||||
BoundResourceSample last_fetch_constants{};
|
||||
bool selected_for_present = false;
|
||||
bool matches_frame_end_viewport = false;
|
||||
std::vector<ObservedCommandDesc> commands;
|
||||
|
||||
+12
-2
@@ -315,12 +315,22 @@ void CaptureClear(uint8_t* base, uint32_t device, uint32_t rect_count, uint32_t
|
||||
g_live_clears.push_back(std::move(record));
|
||||
}
|
||||
|
||||
void CaptureResolve(uint32_t device) {
|
||||
void CaptureResolve(uint32_t device, const PPCContext& ctx) {
|
||||
if (!REXCVAR_GET(ac6_render_capture)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ac6::d3d::ResolveRecord record;
|
||||
record.args = {
|
||||
ctx.r4.u32,
|
||||
ctx.r5.u32,
|
||||
ctx.r6.u32,
|
||||
ctx.r7.u32,
|
||||
ctx.r8.u32,
|
||||
ctx.r9.u32,
|
||||
ctx.r10.u32,
|
||||
};
|
||||
record.depth_or_scale = static_cast<float>(ctx.f1.f64);
|
||||
record.shadow_state = SnapshotShadowState(device);
|
||||
|
||||
std::unique_lock<std::shared_mutex> lock(g_capture_mutex);
|
||||
@@ -530,7 +540,7 @@ PPC_FUNC_IMPL(rex_sub_821E2BB8) {
|
||||
|
||||
RememberDevice(ctx.r3.u32);
|
||||
g_live_stats.resolve_calls.fetch_add(1, std::memory_order_relaxed);
|
||||
CaptureResolve(ctx.r3.u32);
|
||||
CaptureResolve(ctx.r3.u32, ctx);
|
||||
|
||||
if (REXCVAR_GET(ac6_d3d_trace)) {
|
||||
REXLOG_CAT_TRACE(kLogGPU, "Resolve");
|
||||
|
||||
@@ -149,6 +149,8 @@ struct ClearRecord {
|
||||
|
||||
struct ResolveRecord {
|
||||
uint32_t sequence{0};
|
||||
std::array<uint32_t, 7> args{};
|
||||
float depth_or_scale{0.0f};
|
||||
ShadowState shadow_state{};
|
||||
};
|
||||
|
||||
|
||||
+1
-6
@@ -45,12 +45,7 @@ std::unique_ptr<rex::ui::WindowedApp> Ac6recompAppCreate(rex::ui::WindowedAppCon
|
||||
// Force SDK logging to a file as well
|
||||
REXCVAR_SET(log_file, "ac6recomp.log");
|
||||
REXCVAR_SET(log_level, "info");
|
||||
REXCVAR_SET(ac6_native_graphics_enabled, true);
|
||||
REXCVAR_SET(ac6_graphics_mode, "hybrid_backend_fixes");
|
||||
REXCVAR_SET(ac6_experimental_replay_present, false);
|
||||
REXCVAR_SET(ac6_render_capture, true);
|
||||
REXCVAR_SET(ac6_timing_hooks_enabled, true);
|
||||
REXCVAR_SET(ac6_unlock_fps, true);
|
||||
REXCVAR_SET(ac6_unlock_fps, false);
|
||||
|
||||
REXLOG_INFO("Ac6recompAppCreate: graphics mode={} replay_present={} capture={}",
|
||||
REXCVAR_GET(ac6_graphics_mode),
|
||||
|
||||
@@ -561,6 +561,10 @@ class TextureCache {
|
||||
|
||||
void UpdateTexturesTotalHostMemoryUsage(uint64_t add, uint64_t subtract);
|
||||
|
||||
// Updates the scaled-resolve bit in the key based on the current resolved
|
||||
// guest-memory page tracking.
|
||||
void ApplyScaledResolveState(TextureKey& key);
|
||||
|
||||
// Shared memory callback for texture data invalidation.
|
||||
static void WatchCallback(const std::unique_lock<std::recursive_mutex>& global_lock,
|
||||
void* context, void* data, uint64_t argument, bool invalidated_by_gpu);
|
||||
|
||||
+21
-12
@@ -487,6 +487,9 @@ void TextureCache::RequestTextures(uint32_t used_texture_mask) {
|
||||
TextureKey old_key = binding.key;
|
||||
uint8_t old_swizzled_signs = binding.swizzled_signs;
|
||||
BindingInfoFromFetchConstant(fetch, binding.key, &binding.swizzled_signs);
|
||||
if (binding.key.is_valid) {
|
||||
ApplyScaledResolveState(binding.key);
|
||||
}
|
||||
texture_bindings_in_sync_ |= index_bit;
|
||||
if (!binding.key.is_valid) {
|
||||
if (old_key.is_valid) {
|
||||
@@ -754,20 +757,26 @@ void TextureCache::DestroyAllTextures(bool from_destructor) {
|
||||
COUNT_profile_set("gpu/texture_cache/textures", 0);
|
||||
}
|
||||
|
||||
TextureCache::Texture* TextureCache::FindOrCreateTexture(TextureKey key) {
|
||||
// Check if the texture is a scaled resolve texture.
|
||||
if (IsDrawResolutionScaled() && key.tiled && IsScaledResolveSupportedForFormat(key)) {
|
||||
texture_util::TextureGuestLayout scaled_resolve_guest_layout = key.GetGuestLayout();
|
||||
if ((scaled_resolve_guest_layout.base.level_data_extent_bytes &&
|
||||
IsRangeScaledResolved(key.base_page << 12,
|
||||
scaled_resolve_guest_layout.base.level_data_extent_bytes)) ||
|
||||
(scaled_resolve_guest_layout.mips_total_extent_bytes &&
|
||||
IsRangeScaledResolved(key.mip_page << 12,
|
||||
scaled_resolve_guest_layout.mips_total_extent_bytes))) {
|
||||
key.scaled_resolve = 1;
|
||||
}
|
||||
void TextureCache::ApplyScaledResolveState(TextureKey& key) {
|
||||
key.scaled_resolve = 0;
|
||||
if (!IsDrawResolutionScaled() || !key.tiled || !IsScaledResolveSupportedForFormat(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
texture_util::TextureGuestLayout scaled_resolve_guest_layout = key.GetGuestLayout();
|
||||
if ((scaled_resolve_guest_layout.base.level_data_extent_bytes &&
|
||||
IsRangeScaledResolved(key.base_page << 12,
|
||||
scaled_resolve_guest_layout.base.level_data_extent_bytes)) ||
|
||||
(scaled_resolve_guest_layout.mips_total_extent_bytes &&
|
||||
IsRangeScaledResolved(key.mip_page << 12,
|
||||
scaled_resolve_guest_layout.mips_total_extent_bytes))) {
|
||||
key.scaled_resolve = 1;
|
||||
}
|
||||
}
|
||||
|
||||
TextureCache::Texture* TextureCache::FindOrCreateTexture(TextureKey key) {
|
||||
ApplyScaledResolveState(key);
|
||||
|
||||
auto get_host_extent = [this, &key](uint32_t& host_width_out, uint32_t& host_height_out,
|
||||
uint32_t& host_depth_or_array_size_out) {
|
||||
host_width_out = key.GetWidth();
|
||||
|
||||
Reference in New Issue
Block a user