mirror of
https://github.com/sal063/AC6_recomp
synced 2026-05-23 15:02:02 -04:00
1481 lines
66 KiB
C++
1481 lines
66 KiB
C++
#include "ac6_native_graphics.h"
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cstring>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <utility>
|
|
|
|
#include <rex/cvar.h>
|
|
#include <rex/graphics/d3d12/graphics_system.h>
|
|
#include <rex/logging.h>
|
|
#include <rex/system/interfaces/graphics.h>
|
|
|
|
#include "d3d_hooks.h"
|
|
|
|
#if REX_HAS_D3D12
|
|
#include <native/ui/d3d12/d3d12_presenter.h>
|
|
#include <native/ui/d3d12/d3d12_provider.h>
|
|
#include <native/ui/d3d12/d3d12_submission_tracker.h>
|
|
#include <native/ui/d3d12/d3d12_util.h>
|
|
#include <native/ui/immediate_drawer.h>
|
|
#endif
|
|
|
|
REXCVAR_DEFINE_BOOL(ac6_native_graphics_bootstrap, true, "AC6/Render",
|
|
"Use the experimental native graphics bootstrap backend");
|
|
REXCVAR_DEFINE_BOOL(
|
|
ac6_native_graphics_placeholder_present, false, "AC6/Render",
|
|
"Allow the native graphics bootstrap backend to replace the legacy swap path with a "
|
|
"preview frame generated by the experimental native replay compositor");
|
|
REXCVAR_DEFINE_BOOL(
|
|
ac6_allow_gpu_trace_stream, false, "AC6/Render",
|
|
"Allow legacy GPU trace streaming during AC6 native graphics experiments");
|
|
|
|
namespace ac6::graphics {
|
|
namespace {
|
|
|
|
using rex::X_STATUS;
|
|
|
|
#if REX_HAS_D3D12
|
|
namespace shaders {
|
|
#include "thirdparty/rexglue-sdk/src/ui/shaders/bytecode/d3d12_5_1/immediate_ps.h"
|
|
#include "thirdparty/rexglue-sdk/src/ui/shaders/bytecode/d3d12_5_1/immediate_vs.h"
|
|
} // namespace shaders
|
|
#endif
|
|
|
|
std::mutex g_native_graphics_status_mutex;
|
|
NativeGraphicsStatusSnapshot g_native_graphics_status{};
|
|
|
|
struct CapturedFrameEvent {
|
|
enum class Type {
|
|
kDraw,
|
|
kClear,
|
|
kResolve,
|
|
};
|
|
|
|
uint32_t sequence = 0;
|
|
Type type = Type::kDraw;
|
|
const ac6::d3d::ShadowState* shadow_state = nullptr;
|
|
};
|
|
|
|
struct ReplayPassCandidate {
|
|
ac6::d3d::ShadowState binding{};
|
|
uint32_t start_sequence = 0;
|
|
uint32_t end_sequence = 0;
|
|
uint32_t draw_count = 0;
|
|
uint32_t clear_count = 0;
|
|
uint32_t resolve_count = 0;
|
|
};
|
|
|
|
struct ReplayCandidateKey {
|
|
uint32_t rt0 = 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;
|
|
};
|
|
|
|
constexpr uint32_t kSelectedPassPreviewColorSampleCount = 4;
|
|
constexpr uint32_t kSelectedPassPreviewStepCount = 4;
|
|
|
|
using FloatColor = std::array<float, 4>;
|
|
|
|
struct SelectedPassClearStep {
|
|
uint32_t color = 0;
|
|
uint32_t rect_count = 0;
|
|
std::array<ac6::d3d::ClearRect, ac6::d3d::kMaxClearRectsPerRecord> rects{};
|
|
};
|
|
|
|
struct SelectedPassPreviewData {
|
|
SelectedPassPreviewSummary summary{};
|
|
std::array<SelectedPassClearStep, kSelectedPassPreviewStepCount> clear_steps{};
|
|
uint32_t clear_step_count = 0;
|
|
};
|
|
|
|
bool SamePassBinding(const ac6::d3d::ShadowState& left,
|
|
const ac6::d3d::ShadowState& right) {
|
|
return left.render_targets[0] == right.render_targets[0] &&
|
|
left.depth_stencil == right.depth_stencil &&
|
|
left.viewport.width == right.viewport.width &&
|
|
left.viewport.height == right.viewport.height;
|
|
}
|
|
|
|
bool SameReplayCandidate(const ReplayCandidateKey& left, const ReplayCandidateKey& right) {
|
|
return left.rt0 == right.rt0 && left.depth_stencil == right.depth_stencil &&
|
|
left.viewport_x == right.viewport_x && left.viewport_y == right.viewport_y &&
|
|
left.viewport_width == right.viewport_width &&
|
|
left.viewport_height == right.viewport_height;
|
|
}
|
|
|
|
ReplayCandidateKey MakeReplayCandidateKey(const NativeReplayPlanSummary& replay_plan) {
|
|
return ReplayCandidateKey{replay_plan.present_candidate_rt0,
|
|
replay_plan.present_candidate_depth_stencil,
|
|
replay_plan.present_candidate_viewport_x,
|
|
replay_plan.present_candidate_viewport_y,
|
|
replay_plan.present_candidate_viewport_width,
|
|
replay_plan.present_candidate_viewport_height};
|
|
}
|
|
|
|
bool SequenceInSelectedPass(uint32_t sequence, const NativeReplayPlanSummary& replay_plan) {
|
|
return replay_plan.valid && sequence >= replay_plan.selected_pass_start_sequence &&
|
|
sequence <= replay_plan.selected_pass_end_sequence;
|
|
}
|
|
|
|
FloatColor DecodeArgbColor(uint32_t packed_color) {
|
|
return {float((packed_color >> 16) & 0xFFu) / 255.0f,
|
|
float((packed_color >> 8) & 0xFFu) / 255.0f,
|
|
float(packed_color & 0xFFu) / 255.0f, 1.0f};
|
|
}
|
|
|
|
FloatColor MixColors(const FloatColor& left, const FloatColor& right, float t) {
|
|
const float clamped_t = std::clamp(t, 0.0f, 1.0f);
|
|
const float inverse_t = 1.0f - clamped_t;
|
|
return {left[0] * inverse_t + right[0] * clamped_t,
|
|
left[1] * inverse_t + right[1] * clamped_t,
|
|
left[2] * inverse_t + right[2] * clamped_t, 1.0f};
|
|
}
|
|
|
|
uint32_t PackImmediateColor(const FloatColor& color) {
|
|
const auto pack_channel = [](float value) -> uint32_t {
|
|
return uint32_t(std::clamp(value, 0.0f, 1.0f) * 255.0f + 0.5f);
|
|
};
|
|
const uint32_t r = pack_channel(color[0]);
|
|
const uint32_t g = pack_channel(color[1]);
|
|
const uint32_t b = pack_channel(color[2]);
|
|
const uint32_t a = pack_channel(color[3]);
|
|
return r | (g << 8) | (b << 16) | (a << 24);
|
|
}
|
|
|
|
uint32_t ScoreReplayPass(const ReplayPassCandidate& pass,
|
|
const rex::system::GraphicsSwapSubmission& submission,
|
|
bool is_last_pass) {
|
|
uint32_t score = 0;
|
|
if (pass.binding.viewport.width == submission.frontbuffer_width &&
|
|
pass.binding.viewport.height == submission.frontbuffer_height) {
|
|
score += 100;
|
|
}
|
|
if (pass.draw_count) {
|
|
score += 40 + std::min<uint32_t>(pass.draw_count, 64);
|
|
}
|
|
if (pass.resolve_count) {
|
|
score += 20;
|
|
}
|
|
if (pass.binding.render_targets[0]) {
|
|
score += 10;
|
|
}
|
|
if (is_last_pass) {
|
|
score += 25;
|
|
}
|
|
return score;
|
|
}
|
|
|
|
NativeReplayPlanSummary AnalyzeReplayPlan(
|
|
const ac6::d3d::FrameCaptureSnapshot& frame_capture,
|
|
const rex::system::GraphicsSwapSubmission& submission) {
|
|
NativeReplayPlanSummary summary;
|
|
summary.frame_index = frame_capture.frame_index;
|
|
|
|
std::vector<CapturedFrameEvent> events;
|
|
events.reserve(frame_capture.draws.size() + frame_capture.clears.size() +
|
|
frame_capture.resolves.size());
|
|
|
|
for (const auto& draw : frame_capture.draws) {
|
|
events.push_back(
|
|
{draw.sequence, CapturedFrameEvent::Type::kDraw, &draw.shadow_state});
|
|
}
|
|
for (const auto& clear : frame_capture.clears) {
|
|
events.push_back(
|
|
{clear.sequence, CapturedFrameEvent::Type::kClear, &clear.shadow_state});
|
|
}
|
|
for (const auto& resolve : frame_capture.resolves) {
|
|
events.push_back(
|
|
{resolve.sequence, CapturedFrameEvent::Type::kResolve, &resolve.shadow_state});
|
|
}
|
|
|
|
if (events.empty()) {
|
|
return summary;
|
|
}
|
|
|
|
std::sort(events.begin(), events.end(),
|
|
[](const CapturedFrameEvent& left, const CapturedFrameEvent& right) {
|
|
return left.sequence < right.sequence;
|
|
});
|
|
|
|
std::vector<ReplayPassCandidate> passes;
|
|
ReplayPassCandidate current_pass;
|
|
bool current_pass_valid = false;
|
|
auto flush_pass = [&]() {
|
|
if (!current_pass_valid) {
|
|
return;
|
|
}
|
|
passes.push_back(current_pass);
|
|
current_pass = {};
|
|
current_pass_valid = false;
|
|
};
|
|
|
|
for (const CapturedFrameEvent& event : events) {
|
|
if (!event.shadow_state) {
|
|
continue;
|
|
}
|
|
if (!current_pass_valid || !SamePassBinding(current_pass.binding, *event.shadow_state)) {
|
|
flush_pass();
|
|
current_pass.binding = *event.shadow_state;
|
|
current_pass.start_sequence = event.sequence;
|
|
current_pass_valid = true;
|
|
}
|
|
current_pass.end_sequence = event.sequence;
|
|
|
|
switch (event.type) {
|
|
case CapturedFrameEvent::Type::kDraw:
|
|
++summary.draw_event_count;
|
|
++current_pass.draw_count;
|
|
break;
|
|
case CapturedFrameEvent::Type::kClear:
|
|
++summary.clear_event_count;
|
|
++current_pass.clear_count;
|
|
break;
|
|
case CapturedFrameEvent::Type::kResolve:
|
|
++summary.resolve_event_count;
|
|
++current_pass.resolve_count;
|
|
break;
|
|
}
|
|
}
|
|
|
|
flush_pass();
|
|
|
|
summary.pass_count = static_cast<uint32_t>(passes.size());
|
|
if (!passes.empty()) {
|
|
summary.first_pass_draw_count = passes.front().draw_count;
|
|
summary.last_pass_draw_count = passes.back().draw_count;
|
|
}
|
|
|
|
uint32_t best_score = 0;
|
|
uint32_t best_index = 0;
|
|
bool best_index_valid = false;
|
|
for (uint32_t i = 0; i < passes.size(); ++i) {
|
|
const ReplayPassCandidate& pass = passes[i];
|
|
summary.largest_pass_draw_count =
|
|
std::max(summary.largest_pass_draw_count, pass.draw_count);
|
|
bool matches_swap_size = pass.binding.viewport.width == submission.frontbuffer_width &&
|
|
pass.binding.viewport.height == submission.frontbuffer_height;
|
|
if (matches_swap_size) {
|
|
++summary.swap_size_match_pass_count;
|
|
}
|
|
bool is_last_pass = (i + 1) == passes.size();
|
|
uint32_t score = ScoreReplayPass(pass, submission, is_last_pass);
|
|
if (!best_index_valid || score > best_score ||
|
|
(score == best_score && i > best_index)) {
|
|
best_index_valid = true;
|
|
best_index = i;
|
|
best_score = score;
|
|
}
|
|
}
|
|
|
|
if (best_index_valid) {
|
|
const ReplayPassCandidate& selected_pass = passes[best_index];
|
|
summary.selected_pass_index = best_index;
|
|
summary.selected_pass_score = best_score;
|
|
summary.selected_pass_start_sequence = selected_pass.start_sequence;
|
|
summary.selected_pass_end_sequence = selected_pass.end_sequence;
|
|
summary.selected_pass_draw_count = selected_pass.draw_count;
|
|
summary.selected_pass_clear_count = selected_pass.clear_count;
|
|
summary.selected_pass_resolve_count = selected_pass.resolve_count;
|
|
summary.selected_pass_is_last = (best_index + 1) == passes.size();
|
|
summary.selected_pass_has_resolve = selected_pass.resolve_count != 0;
|
|
summary.present_candidate_rt0 = selected_pass.binding.render_targets[0];
|
|
summary.present_candidate_depth_stencil = selected_pass.binding.depth_stencil;
|
|
summary.present_candidate_viewport_x = selected_pass.binding.viewport.x;
|
|
summary.present_candidate_viewport_y = selected_pass.binding.viewport.y;
|
|
summary.present_candidate_viewport_width = selected_pass.binding.viewport.width;
|
|
summary.present_candidate_viewport_height = selected_pass.binding.viewport.height;
|
|
summary.present_candidate_matches_swap_size =
|
|
summary.present_candidate_viewport_width == submission.frontbuffer_width &&
|
|
summary.present_candidate_viewport_height == submission.frontbuffer_height;
|
|
summary.valid = true;
|
|
}
|
|
return summary;
|
|
}
|
|
|
|
SelectedPassPreviewData BuildSelectedPassPreview(
|
|
const ac6::d3d::FrameCaptureSnapshot& frame_capture,
|
|
const NativeReplayPlanSummary& replay_plan) {
|
|
SelectedPassPreviewData preview;
|
|
if (!replay_plan.valid) {
|
|
return preview;
|
|
}
|
|
|
|
preview.summary.valid = true;
|
|
preview.summary.draw_count = replay_plan.selected_pass_draw_count;
|
|
preview.summary.clear_count = replay_plan.selected_pass_clear_count;
|
|
preview.summary.resolve_count = replay_plan.selected_pass_resolve_count;
|
|
|
|
bool has_first_clear = false;
|
|
for (const auto& clear : frame_capture.clears) {
|
|
if (!SequenceInSelectedPass(clear.sequence, replay_plan)) {
|
|
continue;
|
|
}
|
|
|
|
if (!has_first_clear) {
|
|
preview.summary.first_clear_color = clear.color;
|
|
has_first_clear = true;
|
|
}
|
|
preview.summary.last_clear_color = clear.color;
|
|
preview.summary.using_clear_fill = true;
|
|
|
|
preview.summary.sampled_clear_rect_count += clear.captured_rect_count;
|
|
if (preview.clear_step_count < preview.clear_steps.size()) {
|
|
SelectedPassClearStep& step = preview.clear_steps[preview.clear_step_count++];
|
|
step.color = clear.color;
|
|
step.rect_count = clear.captured_rect_count;
|
|
step.rects = clear.rects;
|
|
} else {
|
|
SelectedPassClearStep& step = preview.clear_steps.back();
|
|
step.color = clear.color;
|
|
step.rect_count = clear.captured_rect_count;
|
|
step.rects = clear.rects;
|
|
}
|
|
}
|
|
|
|
preview.summary.sampled_clear_color_count = preview.clear_step_count;
|
|
return preview;
|
|
}
|
|
|
|
#if REX_HAS_D3D12
|
|
class Ac6NativeGraphicsSystem final : public rex::graphics::d3d12::D3D12GraphicsSystem {
|
|
public:
|
|
X_STATUS Setup(rex::runtime::FunctionDispatcher* function_dispatcher,
|
|
rex::system::KernelState* kernel_state,
|
|
rex::ui::WindowedAppContext* app_context,
|
|
bool with_presentation) override {
|
|
X_STATUS status = rex::graphics::d3d12::D3D12GraphicsSystem::Setup(
|
|
function_dispatcher, kernel_state, app_context, with_presentation);
|
|
if (XFAILED(status)) {
|
|
return status;
|
|
}
|
|
|
|
d3d12_provider_ = static_cast<rex::ui::d3d12::D3D12Provider*>(provider());
|
|
d3d12_presenter_ = with_presentation
|
|
? static_cast<rex::ui::d3d12::D3D12Presenter*>(presenter())
|
|
: nullptr;
|
|
if (!d3d12_provider_) {
|
|
REXLOG_ERROR("AC6 native graphics bootstrap failed to acquire the D3D12 provider");
|
|
rex::graphics::d3d12::D3D12GraphicsSystem::Shutdown();
|
|
return X_STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_native_graphics_status_mutex);
|
|
g_native_graphics_status.backend_active = true;
|
|
g_native_graphics_status.provider_ready = d3d12_provider_ != nullptr;
|
|
g_native_graphics_status.presenter_ready = d3d12_presenter_ != nullptr;
|
|
}
|
|
|
|
if (REXCVAR_GET(ac6_native_graphics_placeholder_present)) {
|
|
REXLOG_WARN(
|
|
"AC6 native graphics bootstrap backend is active. The legacy D3D12 "
|
|
"graphics core remains enabled for compatibility, while direct swap "
|
|
"presentation is intercepted by the native replay preview path.");
|
|
} else {
|
|
REXLOG_WARN(
|
|
"AC6 native graphics bootstrap backend is active. The legacy D3D12 "
|
|
"graphics core remains enabled for compatibility, and direct swap "
|
|
"submissions are observed natively while legacy PM4 presentation "
|
|
"remains authoritative.");
|
|
}
|
|
return X_STATUS_SUCCESS;
|
|
}
|
|
|
|
void Shutdown() override {
|
|
ShutdownPlaceholderRefreshResources();
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_native_graphics_status_mutex);
|
|
g_native_graphics_status.backend_active = false;
|
|
g_native_graphics_status.provider_ready = false;
|
|
g_native_graphics_status.presenter_ready = false;
|
|
g_native_graphics_status.placeholder_resources_initialized = false;
|
|
g_native_graphics_status.last_swap_intercepted = false;
|
|
g_native_graphics_status.last_swap_fell_back = false;
|
|
}
|
|
d3d12_presenter_ = nullptr;
|
|
d3d12_provider_ = nullptr;
|
|
last_swap_submission_ = {};
|
|
swap_count_ = 0;
|
|
last_replay_plan_ = {};
|
|
last_selected_pass_preview_ = {};
|
|
last_selected_candidate_ = {};
|
|
last_selected_candidate_valid_ = false;
|
|
selected_candidate_streak_ = 0;
|
|
logged_first_swap_ = false;
|
|
logged_first_present_ = false;
|
|
logged_passthrough_swap_ = false;
|
|
logged_refresh_failure_ = false;
|
|
last_present_used_raster_replay_ = false;
|
|
rex::graphics::d3d12::D3D12GraphicsSystem::Shutdown();
|
|
}
|
|
|
|
bool HandleVideoSwap(const rex::system::GraphicsSwapSubmission& submission) override {
|
|
last_swap_submission_ = submission;
|
|
++swap_count_;
|
|
last_present_used_raster_replay_ = false;
|
|
ac6::d3d::FrameCaptureSnapshot frame_capture = ac6::d3d::GetFrameCapture();
|
|
NativeReplayPlanSummary replay_plan = AnalyzeReplayPlan(frame_capture, submission);
|
|
if (replay_plan.valid) {
|
|
ReplayCandidateKey candidate_key = MakeReplayCandidateKey(replay_plan);
|
|
if (last_selected_candidate_valid_ &&
|
|
SameReplayCandidate(last_selected_candidate_, candidate_key)) {
|
|
++selected_candidate_streak_;
|
|
} else {
|
|
last_selected_candidate_ = candidate_key;
|
|
last_selected_candidate_valid_ = true;
|
|
selected_candidate_streak_ = 1;
|
|
}
|
|
replay_plan.selected_pass_streak = selected_candidate_streak_;
|
|
replay_plan.selected_pass_is_stable = selected_candidate_streak_ >= 3;
|
|
} else {
|
|
last_selected_candidate_valid_ = false;
|
|
selected_candidate_streak_ = 0;
|
|
}
|
|
last_replay_plan_ = replay_plan;
|
|
last_selected_pass_preview_ = BuildSelectedPassPreview(frame_capture, replay_plan);
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_native_graphics_status_mutex);
|
|
g_native_graphics_status.total_swap_count = swap_count_;
|
|
g_native_graphics_status.last_frontbuffer_virtual_address =
|
|
submission.frontbuffer_virtual_address;
|
|
g_native_graphics_status.last_frontbuffer_physical_address =
|
|
submission.frontbuffer_physical_address;
|
|
g_native_graphics_status.last_frontbuffer_width = submission.frontbuffer_width;
|
|
g_native_graphics_status.last_frontbuffer_height = submission.frontbuffer_height;
|
|
g_native_graphics_status.last_texture_format = submission.texture_format;
|
|
g_native_graphics_status.last_color_space = submission.color_space;
|
|
g_native_graphics_status.capture_summary = ac6::d3d::GetFrameCaptureSummary();
|
|
g_native_graphics_status.replay_plan = replay_plan;
|
|
g_native_graphics_status.selected_pass_preview = last_selected_pass_preview_.summary;
|
|
g_native_graphics_status.last_swap_intercepted = false;
|
|
g_native_graphics_status.last_swap_fell_back = false;
|
|
}
|
|
|
|
if (!logged_first_swap_) {
|
|
logged_first_swap_ = true;
|
|
REXLOG_WARN(
|
|
"AC6 native graphics bootstrap received the first direct swap "
|
|
"(fb_va={:08X}, fb_pa={:08X}, {}x{}, fmt={:08X})",
|
|
submission.frontbuffer_virtual_address, submission.frontbuffer_physical_address,
|
|
submission.frontbuffer_width, submission.frontbuffer_height, submission.texture_format);
|
|
}
|
|
|
|
if (!REXCVAR_GET(ac6_native_graphics_placeholder_present)) {
|
|
if (!logged_passthrough_swap_) {
|
|
logged_passthrough_swap_ = true;
|
|
REXLOG_WARN(
|
|
"AC6 native graphics bootstrap is leaving direct swap presentation "
|
|
"on the legacy PM4 path because ac6_native_graphics_placeholder_present=false");
|
|
}
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_native_graphics_status_mutex);
|
|
++g_native_graphics_status.fallback_swap_count;
|
|
g_native_graphics_status.last_swap_fell_back = true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (!EnsurePlaceholderRefreshResources()) {
|
|
if (!logged_refresh_failure_) {
|
|
logged_refresh_failure_ = true;
|
|
REXLOG_WARN(
|
|
"AC6 native graphics bootstrap could not initialize diagnostic "
|
|
"direct-swap replay resources; falling back to the legacy PM4 swap path");
|
|
}
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_native_graphics_status_mutex);
|
|
++g_native_graphics_status.fallback_swap_count;
|
|
g_native_graphics_status.last_swap_fell_back = true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (!RefreshPlaceholderFrame(submission)) {
|
|
if (!logged_refresh_failure_) {
|
|
logged_refresh_failure_ = true;
|
|
REXLOG_WARN(
|
|
"AC6 native graphics bootstrap could not refresh the diagnostic "
|
|
"direct-swap replay frame; falling back to the legacy PM4 swap path");
|
|
}
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_native_graphics_status_mutex);
|
|
++g_native_graphics_status.fallback_swap_count;
|
|
g_native_graphics_status.last_swap_fell_back = true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (!logged_first_present_) {
|
|
logged_first_present_ = true;
|
|
REXLOG_WARN(
|
|
"AC6 native graphics bootstrap is now presenting a raster replay "
|
|
"preview frame through the direct swap path");
|
|
}
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_native_graphics_status_mutex);
|
|
++g_native_graphics_status.intercepted_swap_count;
|
|
g_native_graphics_status.selected_pass_preview.using_raster_replay =
|
|
last_present_used_raster_replay_;
|
|
g_native_graphics_status.last_swap_intercepted = true;
|
|
}
|
|
DispatchInterruptCallback(0, 2);
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
static constexpr uint32_t kPlaceholderRefreshSlotCount = 3;
|
|
static constexpr uint32_t kPlaceholderHeartbeatPeriod = 120;
|
|
static constexpr uint32_t kRasterPreviewMaxRectCount = 64;
|
|
static constexpr uint32_t kRasterPreviewMaxVertexCount = kRasterPreviewMaxRectCount * 6;
|
|
|
|
struct PlaceholderRefreshSlot {
|
|
Microsoft::WRL::ComPtr<ID3D12CommandAllocator> command_allocator;
|
|
Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> shader_visible_uav_heap;
|
|
Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> cpu_uav_heap;
|
|
Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> rtv_heap;
|
|
Microsoft::WRL::ComPtr<ID3D12Resource> raster_render_target;
|
|
Microsoft::WRL::ComPtr<ID3D12Resource> vertex_upload_buffer;
|
|
uint8_t* vertex_upload_mapping = nullptr;
|
|
uint32_t raster_target_width = 0;
|
|
uint32_t raster_target_height = 0;
|
|
uint64_t last_submission = 0;
|
|
};
|
|
|
|
bool EnsurePlaceholderRefreshResources() {
|
|
if (placeholder_resources_initialized_) {
|
|
return true;
|
|
}
|
|
if (!InitializePlaceholderRefreshResources()) {
|
|
return false;
|
|
}
|
|
placeholder_resources_initialized_ = true;
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_native_graphics_status_mutex);
|
|
g_native_graphics_status.placeholder_resources_initialized = true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool InitializeRasterPreviewResources(ID3D12Device* device) {
|
|
D3D12_ROOT_PARAMETER root_parameters[3] = {};
|
|
D3D12_DESCRIPTOR_RANGE descriptor_range_texture = {};
|
|
D3D12_DESCRIPTOR_RANGE descriptor_range_sampler = {};
|
|
|
|
root_parameters[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
|
|
root_parameters[0].DescriptorTable.NumDescriptorRanges = 1;
|
|
root_parameters[0].DescriptorTable.pDescriptorRanges = &descriptor_range_texture;
|
|
root_parameters[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
|
|
descriptor_range_texture.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
|
|
descriptor_range_texture.NumDescriptors = 1;
|
|
descriptor_range_texture.BaseShaderRegister = 0;
|
|
descriptor_range_texture.RegisterSpace = 0;
|
|
descriptor_range_texture.OffsetInDescriptorsFromTableStart = 0;
|
|
|
|
root_parameters[1].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
|
|
root_parameters[1].DescriptorTable.NumDescriptorRanges = 1;
|
|
root_parameters[1].DescriptorTable.pDescriptorRanges = &descriptor_range_sampler;
|
|
root_parameters[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
|
|
descriptor_range_sampler.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER;
|
|
descriptor_range_sampler.NumDescriptors = 1;
|
|
descriptor_range_sampler.BaseShaderRegister = 0;
|
|
descriptor_range_sampler.RegisterSpace = 0;
|
|
descriptor_range_sampler.OffsetInDescriptorsFromTableStart = 0;
|
|
|
|
root_parameters[2].ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS;
|
|
root_parameters[2].Constants.ShaderRegister = 0;
|
|
root_parameters[2].Constants.RegisterSpace = 0;
|
|
root_parameters[2].Constants.Num32BitValues = 2;
|
|
root_parameters[2].ShaderVisibility = D3D12_SHADER_VISIBILITY_VERTEX;
|
|
|
|
D3D12_ROOT_SIGNATURE_DESC root_signature_desc = {};
|
|
root_signature_desc.NumParameters = uint32_t(std::size(root_parameters));
|
|
root_signature_desc.pParameters = root_parameters;
|
|
root_signature_desc.NumStaticSamplers = 0;
|
|
root_signature_desc.pStaticSamplers = nullptr;
|
|
root_signature_desc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;
|
|
raster_preview_root_signature_.Attach(
|
|
rex::ui::d3d12::util::CreateRootSignature(*d3d12_provider_, root_signature_desc));
|
|
if (!raster_preview_root_signature_) {
|
|
REXLOG_ERROR(
|
|
"AC6 native graphics bootstrap failed to create the raster replay "
|
|
"root signature");
|
|
return false;
|
|
}
|
|
|
|
D3D12_GRAPHICS_PIPELINE_STATE_DESC pipeline_desc = {};
|
|
pipeline_desc.pRootSignature = raster_preview_root_signature_.Get();
|
|
pipeline_desc.VS.pShaderBytecode = shaders::immediate_vs;
|
|
pipeline_desc.VS.BytecodeLength = sizeof(shaders::immediate_vs);
|
|
pipeline_desc.PS.pShaderBytecode = shaders::immediate_ps;
|
|
pipeline_desc.PS.BytecodeLength = sizeof(shaders::immediate_ps);
|
|
D3D12_RENDER_TARGET_BLEND_DESC& blend_desc = pipeline_desc.BlendState.RenderTarget[0];
|
|
blend_desc.BlendEnable = TRUE;
|
|
blend_desc.SrcBlend = D3D12_BLEND_SRC_ALPHA;
|
|
blend_desc.DestBlend = D3D12_BLEND_INV_SRC_ALPHA;
|
|
blend_desc.BlendOp = D3D12_BLEND_OP_ADD;
|
|
blend_desc.SrcBlendAlpha = D3D12_BLEND_ONE;
|
|
blend_desc.DestBlendAlpha = D3D12_BLEND_ONE;
|
|
blend_desc.BlendOpAlpha = D3D12_BLEND_OP_ADD;
|
|
blend_desc.RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL;
|
|
pipeline_desc.SampleMask = UINT_MAX;
|
|
pipeline_desc.RasterizerState.FillMode = D3D12_FILL_MODE_SOLID;
|
|
pipeline_desc.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;
|
|
pipeline_desc.RasterizerState.FrontCounterClockwise = FALSE;
|
|
pipeline_desc.RasterizerState.DepthClipEnable = TRUE;
|
|
D3D12_INPUT_ELEMENT_DESC input_elements[3] = {};
|
|
input_elements[0].SemanticName = "POSITION";
|
|
input_elements[0].Format = DXGI_FORMAT_R32G32_FLOAT;
|
|
input_elements[0].AlignedByteOffset = offsetof(rex::ui::ImmediateVertex, x);
|
|
input_elements[1].SemanticName = "TEXCOORD";
|
|
input_elements[1].Format = DXGI_FORMAT_R32G32_FLOAT;
|
|
input_elements[1].AlignedByteOffset = offsetof(rex::ui::ImmediateVertex, u);
|
|
input_elements[2].SemanticName = "COLOR";
|
|
input_elements[2].Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
|
input_elements[2].AlignedByteOffset = offsetof(rex::ui::ImmediateVertex, color);
|
|
pipeline_desc.InputLayout.pInputElementDescs = input_elements;
|
|
pipeline_desc.InputLayout.NumElements = uint32_t(std::size(input_elements));
|
|
pipeline_desc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
|
|
pipeline_desc.NumRenderTargets = 1;
|
|
pipeline_desc.RTVFormats[0] = rex::ui::d3d12::D3D12Presenter::kGuestOutputFormat;
|
|
pipeline_desc.SampleDesc.Count = 1;
|
|
if (FAILED(device->CreateGraphicsPipelineState(&pipeline_desc,
|
|
IID_PPV_ARGS(&raster_preview_pipeline_)))) {
|
|
REXLOG_ERROR(
|
|
"AC6 native graphics bootstrap failed to create the raster replay "
|
|
"pipeline");
|
|
return false;
|
|
}
|
|
|
|
D3D12_DESCRIPTOR_HEAP_DESC view_heap_desc = {};
|
|
view_heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
|
|
view_heap_desc.NumDescriptors = 1;
|
|
view_heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
|
|
if (FAILED(device->CreateDescriptorHeap(&view_heap_desc,
|
|
IID_PPV_ARGS(&raster_preview_view_heap_)))) {
|
|
REXLOG_ERROR(
|
|
"AC6 native graphics bootstrap failed to create the raster replay "
|
|
"view heap");
|
|
return false;
|
|
}
|
|
|
|
D3D12_SHADER_RESOURCE_VIEW_DESC texture_view_desc = {};
|
|
texture_view_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
|
texture_view_desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
|
|
texture_view_desc.Shader4ComponentMapping =
|
|
D3D12_ENCODE_SHADER_4_COMPONENT_MAPPING(
|
|
D3D12_SHADER_COMPONENT_MAPPING_FORCE_VALUE_1,
|
|
D3D12_SHADER_COMPONENT_MAPPING_FORCE_VALUE_1,
|
|
D3D12_SHADER_COMPONENT_MAPPING_FORCE_VALUE_1,
|
|
D3D12_SHADER_COMPONENT_MAPPING_FORCE_VALUE_1);
|
|
texture_view_desc.Texture2D.MostDetailedMip = 0;
|
|
texture_view_desc.Texture2D.MipLevels = 1;
|
|
texture_view_desc.Texture2D.PlaneSlice = 0;
|
|
texture_view_desc.Texture2D.ResourceMinLODClamp = 0.0f;
|
|
device->CreateShaderResourceView(
|
|
nullptr, &texture_view_desc,
|
|
raster_preview_view_heap_->GetCPUDescriptorHandleForHeapStart());
|
|
|
|
D3D12_DESCRIPTOR_HEAP_DESC sampler_heap_desc = {};
|
|
sampler_heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER;
|
|
sampler_heap_desc.NumDescriptors = 1;
|
|
sampler_heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
|
|
if (FAILED(device->CreateDescriptorHeap(&sampler_heap_desc,
|
|
IID_PPV_ARGS(&raster_preview_sampler_heap_)))) {
|
|
REXLOG_ERROR(
|
|
"AC6 native graphics bootstrap failed to create the raster replay "
|
|
"sampler heap");
|
|
return false;
|
|
}
|
|
|
|
D3D12_SAMPLER_DESC sampler_desc = {};
|
|
sampler_desc.Filter = D3D12_FILTER_MIN_MAG_MIP_POINT;
|
|
sampler_desc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
|
|
sampler_desc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
|
|
sampler_desc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
|
|
sampler_desc.MaxAnisotropy = 1;
|
|
device->CreateSampler(&sampler_desc,
|
|
raster_preview_sampler_heap_->GetCPUDescriptorHandleForHeapStart());
|
|
|
|
D3D12_DESCRIPTOR_HEAP_DESC rtv_heap_desc = {};
|
|
rtv_heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
|
|
rtv_heap_desc.NumDescriptors = 1;
|
|
rtv_heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
|
|
rtv_heap_desc.NodeMask = 0;
|
|
|
|
D3D12_RESOURCE_DESC upload_buffer_desc = {};
|
|
rex::ui::d3d12::util::FillBufferResourceDesc(
|
|
upload_buffer_desc, sizeof(rex::ui::ImmediateVertex) * kRasterPreviewMaxVertexCount,
|
|
D3D12_RESOURCE_FLAG_NONE);
|
|
|
|
for (PlaceholderRefreshSlot& slot : placeholder_refresh_slots_) {
|
|
if (FAILED(device->CreateDescriptorHeap(&rtv_heap_desc, IID_PPV_ARGS(&slot.rtv_heap)))) {
|
|
REXLOG_ERROR(
|
|
"AC6 native graphics bootstrap failed to create a raster replay "
|
|
"RTV heap");
|
|
return false;
|
|
}
|
|
if (FAILED(device->CreateCommittedResource(
|
|
&rex::ui::d3d12::util::kHeapPropertiesUpload, D3D12_HEAP_FLAG_NONE,
|
|
&upload_buffer_desc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr,
|
|
IID_PPV_ARGS(&slot.vertex_upload_buffer)))) {
|
|
REXLOG_ERROR(
|
|
"AC6 native graphics bootstrap failed to create a raster replay "
|
|
"vertex upload buffer");
|
|
return false;
|
|
}
|
|
D3D12_RANGE read_range = {0, 0};
|
|
if (FAILED(slot.vertex_upload_buffer->Map(0, &read_range,
|
|
reinterpret_cast<void**>(&slot.vertex_upload_mapping)))) {
|
|
REXLOG_ERROR(
|
|
"AC6 native graphics bootstrap failed to map a raster replay "
|
|
"vertex upload buffer");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool EnsureRasterPreviewRenderTarget(PlaceholderRefreshSlot& slot, uint32_t width,
|
|
uint32_t height) {
|
|
if (slot.raster_render_target && slot.raster_target_width == width &&
|
|
slot.raster_target_height == height) {
|
|
return true;
|
|
}
|
|
|
|
slot.raster_render_target.Reset();
|
|
slot.raster_target_width = 0;
|
|
slot.raster_target_height = 0;
|
|
|
|
D3D12_CLEAR_VALUE clear_value = {};
|
|
clear_value.Format = rex::ui::d3d12::D3D12Presenter::kGuestOutputFormat;
|
|
clear_value.Color[0] = 0.0f;
|
|
clear_value.Color[1] = 0.0f;
|
|
clear_value.Color[2] = 0.0f;
|
|
clear_value.Color[3] = 1.0f;
|
|
|
|
D3D12_RESOURCE_DESC render_target_desc = {};
|
|
render_target_desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
|
|
render_target_desc.Alignment = 0;
|
|
render_target_desc.Width = width;
|
|
render_target_desc.Height = height;
|
|
render_target_desc.DepthOrArraySize = 1;
|
|
render_target_desc.MipLevels = 1;
|
|
render_target_desc.Format = rex::ui::d3d12::D3D12Presenter::kGuestOutputFormat;
|
|
render_target_desc.SampleDesc.Count = 1;
|
|
render_target_desc.SampleDesc.Quality = 0;
|
|
render_target_desc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
|
|
render_target_desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
|
|
if (FAILED(d3d12_provider_->GetDevice()->CreateCommittedResource(
|
|
&rex::ui::d3d12::util::kHeapPropertiesDefault,
|
|
d3d12_provider_->GetHeapFlagCreateNotZeroed(), &render_target_desc,
|
|
D3D12_RESOURCE_STATE_RENDER_TARGET, &clear_value,
|
|
IID_PPV_ARGS(&slot.raster_render_target)))) {
|
|
REXLOG_ERROR(
|
|
"AC6 native graphics bootstrap failed to create a {}x{} raster replay "
|
|
"render target",
|
|
width, height);
|
|
return false;
|
|
}
|
|
|
|
d3d12_provider_->GetDevice()->CreateRenderTargetView(
|
|
slot.raster_render_target.Get(), nullptr, slot.rtv_heap->GetCPUDescriptorHandleForHeapStart());
|
|
slot.raster_target_width = width;
|
|
slot.raster_target_height = height;
|
|
return true;
|
|
}
|
|
|
|
bool InitializePlaceholderRefreshResources() {
|
|
auto fail = [this](const char* message) {
|
|
REXLOG_ERROR("{}", message);
|
|
ShutdownPlaceholderRefreshResources();
|
|
return false;
|
|
};
|
|
|
|
ID3D12Device* device = d3d12_provider_->GetDevice();
|
|
ID3D12CommandQueue* direct_queue = d3d12_provider_->GetDirectQueue();
|
|
if (!device || !direct_queue) {
|
|
return fail("AC6 native graphics bootstrap D3D12 provider is missing device or queue");
|
|
}
|
|
|
|
if (!placeholder_submission_tracker_.Initialize(device, direct_queue)) {
|
|
return fail("AC6 native graphics bootstrap failed to create the placeholder submission tracker");
|
|
}
|
|
|
|
D3D12_DESCRIPTOR_HEAP_DESC uav_heap_desc = {};
|
|
uav_heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
|
|
uav_heap_desc.NumDescriptors = 1;
|
|
uav_heap_desc.NodeMask = 0;
|
|
|
|
for (uint32_t i = 0; i < kPlaceholderRefreshSlotCount; ++i) {
|
|
PlaceholderRefreshSlot& slot = placeholder_refresh_slots_[i];
|
|
if (FAILED(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT,
|
|
IID_PPV_ARGS(&slot.command_allocator)))) {
|
|
return fail("AC6 native graphics bootstrap failed to create a placeholder command allocator");
|
|
}
|
|
|
|
uav_heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
|
|
if (FAILED(device->CreateDescriptorHeap(&uav_heap_desc,
|
|
IID_PPV_ARGS(&slot.shader_visible_uav_heap)))) {
|
|
return fail(
|
|
"AC6 native graphics bootstrap failed to create a shader-visible UAV heap");
|
|
}
|
|
|
|
uav_heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
|
|
if (FAILED(device->CreateDescriptorHeap(&uav_heap_desc, IID_PPV_ARGS(&slot.cpu_uav_heap)))) {
|
|
return fail("AC6 native graphics bootstrap failed to create a CPU UAV heap");
|
|
}
|
|
}
|
|
|
|
if (FAILED(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT,
|
|
placeholder_refresh_slots_[0].command_allocator.Get(),
|
|
nullptr, IID_PPV_ARGS(&placeholder_command_list_)))) {
|
|
return fail("AC6 native graphics bootstrap failed to create the placeholder command list");
|
|
}
|
|
if (FAILED(placeholder_command_list_->Close())) {
|
|
return fail("AC6 native graphics bootstrap failed to close the placeholder command list");
|
|
}
|
|
|
|
if (!InitializeRasterPreviewResources(device)) {
|
|
return fail("AC6 native graphics bootstrap failed to initialize raster replay resources");
|
|
}
|
|
|
|
placeholder_refresh_slot_index_ = 0;
|
|
return true;
|
|
}
|
|
|
|
void ShutdownPlaceholderRefreshResources() {
|
|
placeholder_submission_tracker_.Shutdown();
|
|
placeholder_command_list_.Reset();
|
|
raster_preview_pipeline_.Reset();
|
|
raster_preview_root_signature_.Reset();
|
|
raster_preview_view_heap_.Reset();
|
|
raster_preview_sampler_heap_.Reset();
|
|
placeholder_refresh_slot_index_ = 0;
|
|
placeholder_resources_initialized_ = false;
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_native_graphics_status_mutex);
|
|
g_native_graphics_status.placeholder_resources_initialized = false;
|
|
}
|
|
for (PlaceholderRefreshSlot& slot : placeholder_refresh_slots_) {
|
|
slot.last_submission = 0;
|
|
if (slot.vertex_upload_buffer && slot.vertex_upload_mapping) {
|
|
slot.vertex_upload_buffer->Unmap(0, nullptr);
|
|
}
|
|
slot.vertex_upload_mapping = nullptr;
|
|
slot.vertex_upload_buffer.Reset();
|
|
slot.raster_render_target.Reset();
|
|
slot.rtv_heap.Reset();
|
|
slot.raster_target_width = 0;
|
|
slot.raster_target_height = 0;
|
|
slot.cpu_uav_heap.Reset();
|
|
slot.shader_visible_uav_heap.Reset();
|
|
slot.command_allocator.Reset();
|
|
}
|
|
}
|
|
|
|
bool RefreshPlaceholderFrame(const rex::system::GraphicsSwapSubmission& submission) {
|
|
if (!d3d12_presenter_ || !submission.frontbuffer_width || !submission.frontbuffer_height) {
|
|
return false;
|
|
}
|
|
|
|
return d3d12_presenter_->RefreshGuestOutput(
|
|
submission.frontbuffer_width, submission.frontbuffer_height,
|
|
submission.frontbuffer_width, submission.frontbuffer_height,
|
|
[this, submission](rex::ui::Presenter::GuestOutputRefreshContext& context) {
|
|
return RecordPlaceholderFrame(
|
|
static_cast<rex::ui::d3d12::D3D12Presenter::D3D12GuestOutputRefreshContext&>(
|
|
context),
|
|
submission);
|
|
});
|
|
}
|
|
|
|
bool SubmitPlaceholderCommandList(PlaceholderRefreshSlot& slot) {
|
|
if (FAILED(placeholder_command_list_->Close())) {
|
|
REXLOG_ERROR("AC6 native graphics bootstrap failed to close the placeholder command list");
|
|
return false;
|
|
}
|
|
|
|
ID3D12CommandList* execute_command_list = placeholder_command_list_.Get();
|
|
d3d12_provider_->GetDirectQueue()->ExecuteCommandLists(1, &execute_command_list);
|
|
slot.last_submission = placeholder_submission_tracker_.GetCurrentSubmission();
|
|
if (!placeholder_submission_tracker_.NextSubmission()) {
|
|
REXLOG_WARN(
|
|
"AC6 native graphics bootstrap could not signal the placeholder "
|
|
"refresh fence immediately");
|
|
}
|
|
placeholder_refresh_slot_index_ =
|
|
(placeholder_refresh_slot_index_ + 1) % kPlaceholderRefreshSlotCount;
|
|
return true;
|
|
}
|
|
|
|
bool TryRecordRasterPlaceholderFrame(
|
|
PlaceholderRefreshSlot& slot, ID3D12Resource* guest_output_resource,
|
|
const rex::system::GraphicsSwapSubmission& submission) {
|
|
if (!raster_preview_pipeline_ || !raster_preview_root_signature_ || !raster_preview_view_heap_ ||
|
|
!raster_preview_sampler_heap_ || !slot.rtv_heap || !slot.vertex_upload_mapping ||
|
|
!guest_output_resource) {
|
|
return false;
|
|
}
|
|
if (!EnsureRasterPreviewRenderTarget(slot, submission.frontbuffer_width,
|
|
submission.frontbuffer_height)) {
|
|
return false;
|
|
}
|
|
|
|
const UINT width = submission.frontbuffer_width;
|
|
const UINT height = submission.frontbuffer_height;
|
|
const UINT title_band_height = std::max<UINT>(1, height / 8);
|
|
const UINT status_band_height = std::max<UINT>(1, height / 20);
|
|
const UINT heartbeat_width =
|
|
std::max<UINT>(1, UINT((uint64_t(width) *
|
|
(((swap_count_ - 1) % kPlaceholderHeartbeatPeriod) + 1)) /
|
|
kPlaceholderHeartbeatPeriod));
|
|
|
|
const NativeReplayPlanSummary& replay_plan = last_replay_plan_;
|
|
const SelectedPassPreviewData& selected_pass_preview = last_selected_pass_preview_;
|
|
const uint32_t candidate_seed =
|
|
replay_plan.present_candidate_rt0 ^ (replay_plan.present_candidate_depth_stencil * 33u) ^
|
|
(replay_plan.selected_pass_score * 2654435761u);
|
|
auto channel = [candidate_seed](uint32_t shift, float low, float high) {
|
|
const float t = float((candidate_seed >> shift) & 0xFFu) / 255.0f;
|
|
return low + (high - low) * t;
|
|
};
|
|
|
|
const FloatColor background_color = {replay_plan.valid ? 0.03f : 0.08f,
|
|
replay_plan.valid ? 0.04f : 0.06f,
|
|
replay_plan.valid ? 0.07f : 0.10f, 1.0f};
|
|
const FloatColor title_band_color = {
|
|
replay_plan.selected_pass_is_stable ? 0.18f
|
|
: (replay_plan.present_candidate_matches_swap_size ? 0.18f : 0.55f),
|
|
replay_plan.selected_pass_is_stable ? 0.62f
|
|
: (replay_plan.present_candidate_matches_swap_size ? 0.56f : 0.22f),
|
|
replay_plan.selected_pass_is_stable ? 0.46f
|
|
: (replay_plan.present_candidate_matches_swap_size ? 0.24f : 0.18f), 1.0f};
|
|
const FloatColor fallback_candidate_color = {channel(0, 0.20f, 0.92f),
|
|
channel(8, 0.18f, 0.75f),
|
|
channel(16, 0.16f, 0.88f), 1.0f};
|
|
const FloatColor resolve_color = {0.98f, 0.95f, 0.28f, 0.92f};
|
|
const FloatColor heartbeat_color = {0.96f, 0.92f, 0.22f, 1.0f};
|
|
const FloatColor candidate_base_color =
|
|
selected_pass_preview.summary.using_clear_fill
|
|
? DecodeArgbColor(selected_pass_preview.summary.last_clear_color)
|
|
: fallback_candidate_color;
|
|
const FloatColor stripe_color = MixColors(candidate_base_color, title_band_color, 0.40f);
|
|
const FloatColor candidate_outline_color =
|
|
MixColors(candidate_base_color, heartbeat_color, 0.55f);
|
|
const FloatColor score_color = MixColors(candidate_outline_color, heartbeat_color, 0.35f);
|
|
|
|
UINT candidate_left = 0;
|
|
UINT candidate_top = title_band_height;
|
|
UINT candidate_width = width;
|
|
UINT candidate_height = std::max<UINT>(1, height - title_band_height - status_band_height);
|
|
if (replay_plan.valid && replay_plan.present_candidate_viewport_width &&
|
|
replay_plan.present_candidate_viewport_height) {
|
|
candidate_left = std::min<UINT>(width - 1, replay_plan.present_candidate_viewport_x);
|
|
candidate_top = std::min<UINT>(height - 1, replay_plan.present_candidate_viewport_y);
|
|
candidate_width =
|
|
std::max<UINT>(1, std::min<UINT>(replay_plan.present_candidate_viewport_width,
|
|
width - candidate_left));
|
|
candidate_height =
|
|
std::max<UINT>(1, std::min<UINT>(replay_plan.present_candidate_viewport_height,
|
|
height - candidate_top));
|
|
}
|
|
const UINT candidate_right = std::min(width, candidate_left + candidate_width);
|
|
const UINT candidate_bottom = std::min(height, candidate_top + candidate_height);
|
|
|
|
std::array<rex::ui::ImmediateVertex, kRasterPreviewMaxVertexCount> vertices = {};
|
|
uint32_t vertex_count = 0;
|
|
auto push_rect = [&](float left, float top, float right, float bottom, FloatColor color) {
|
|
left = std::clamp(left, 0.0f, float(width));
|
|
top = std::clamp(top, 0.0f, float(height));
|
|
right = std::clamp(right, 0.0f, float(width));
|
|
bottom = std::clamp(bottom, 0.0f, float(height));
|
|
if (left >= right || top >= bottom || vertex_count + 6 > vertices.size()) {
|
|
return;
|
|
}
|
|
const uint32_t packed_color = PackImmediateColor(color);
|
|
auto emit_vertex = [&](float x, float y) {
|
|
rex::ui::ImmediateVertex& vertex = vertices[vertex_count++];
|
|
vertex.x = x;
|
|
vertex.y = y;
|
|
vertex.u = 0.0f;
|
|
vertex.v = 0.0f;
|
|
vertex.color = packed_color;
|
|
};
|
|
emit_vertex(left, top);
|
|
emit_vertex(right, top);
|
|
emit_vertex(right, bottom);
|
|
emit_vertex(left, top);
|
|
emit_vertex(right, bottom);
|
|
emit_vertex(left, bottom);
|
|
};
|
|
|
|
push_rect(float(candidate_left), float(candidate_top), float(candidate_right),
|
|
float(candidate_bottom), candidate_base_color);
|
|
|
|
const FloatColor clear_overlay_tint = {1.0f, 1.0f, 1.0f, 0.92f};
|
|
for (uint32_t i = 0; i < selected_pass_preview.clear_step_count; ++i) {
|
|
const SelectedPassClearStep& step = selected_pass_preview.clear_steps[i];
|
|
FloatColor step_color = DecodeArgbColor(step.color);
|
|
step_color[3] = clear_overlay_tint[3];
|
|
if (step.rect_count) {
|
|
for (uint32_t rect_index = 0; rect_index < step.rect_count; ++rect_index) {
|
|
const ac6::d3d::ClearRect& rect = step.rects[rect_index];
|
|
push_rect(float(rect.left), float(rect.top), float(rect.right), float(rect.bottom),
|
|
step_color);
|
|
}
|
|
} else {
|
|
push_rect(float(candidate_left), float(candidate_top), float(candidate_right),
|
|
float(candidate_bottom), step_color);
|
|
}
|
|
}
|
|
|
|
UINT resolve_band_height = 0;
|
|
if (replay_plan.valid && replay_plan.selected_pass_has_resolve) {
|
|
resolve_band_height = std::max<UINT>(1, candidate_height / 10);
|
|
push_rect(float(candidate_left), float(candidate_bottom - resolve_band_height),
|
|
float(candidate_right), float(candidate_bottom), resolve_color);
|
|
}
|
|
|
|
if (selected_pass_preview.summary.draw_count && candidate_width > 8 && candidate_height > 8) {
|
|
const UINT stripe_count =
|
|
std::min<UINT>(12, std::max<UINT>(1, 1 + (selected_pass_preview.summary.draw_count / 6)));
|
|
const UINT stripe_width =
|
|
std::max<UINT>(1, candidate_width / std::max<UINT>(24, stripe_count * 5));
|
|
const UINT stripe_top = candidate_top;
|
|
const UINT stripe_bottom =
|
|
candidate_bottom > resolve_band_height ? (candidate_bottom - resolve_band_height)
|
|
: candidate_bottom;
|
|
const UINT stripe_area_height =
|
|
stripe_bottom > stripe_top ? (stripe_bottom - stripe_top) : 0;
|
|
for (UINT i = 0; i < stripe_count && stripe_area_height; ++i) {
|
|
const UINT stripe_center_x =
|
|
candidate_left + ((i + 1) * candidate_width) / (stripe_count + 1);
|
|
const UINT stripe_left = stripe_center_x > stripe_width / 2
|
|
? stripe_center_x - stripe_width / 2
|
|
: candidate_left;
|
|
const UINT stripe_right = std::min(candidate_right, stripe_left + stripe_width);
|
|
const UINT stripe_height =
|
|
std::max<UINT>(1, stripe_area_height * (45 + ((i * 13) % 40)) / 100);
|
|
const UINT stripe_start = stripe_bottom > stripe_height ? (stripe_bottom - stripe_height)
|
|
: stripe_top;
|
|
FloatColor lane_color =
|
|
(i & 1u) ? MixColors(stripe_color, heartbeat_color, 0.22f)
|
|
: MixColors(stripe_color, background_color, 0.10f);
|
|
lane_color[3] = 0.42f;
|
|
push_rect(float(stripe_left), float(stripe_start), float(stripe_right),
|
|
float(stripe_bottom), lane_color);
|
|
}
|
|
}
|
|
|
|
if (candidate_width > 2 && candidate_height > 2) {
|
|
const UINT outline_thickness =
|
|
std::max<UINT>(1, std::min<UINT>(4, std::min(candidate_width, candidate_height) / 96 + 1));
|
|
const UINT bottom_outline_top =
|
|
candidate_bottom > outline_thickness ? (candidate_bottom - outline_thickness)
|
|
: candidate_top;
|
|
push_rect(float(candidate_left), float(candidate_top), float(candidate_right),
|
|
float(std::min(candidate_bottom, candidate_top + outline_thickness)),
|
|
candidate_outline_color);
|
|
push_rect(float(candidate_left), float(bottom_outline_top), float(candidate_right),
|
|
float(candidate_bottom), candidate_outline_color);
|
|
push_rect(float(candidate_left), float(candidate_top),
|
|
float(std::min(candidate_right, candidate_left + outline_thickness)),
|
|
float(candidate_bottom), candidate_outline_color);
|
|
push_rect(float(candidate_right > outline_thickness ? (candidate_right - outline_thickness)
|
|
: candidate_left),
|
|
float(candidate_top), float(candidate_right), float(candidate_bottom),
|
|
candidate_outline_color);
|
|
}
|
|
|
|
if (replay_plan.valid && replay_plan.pass_count) {
|
|
const UINT score_width = std::max<UINT>(
|
|
1, UINT((uint64_t(width) * std::min<uint32_t>(replay_plan.selected_pass_score, 220u)) /
|
|
220u));
|
|
push_rect(0.0f, float(title_band_height), float(score_width),
|
|
float(std::min(height, title_band_height + status_band_height)), score_color);
|
|
}
|
|
push_rect(0.0f, 0.0f, float(width), float(title_band_height), title_band_color);
|
|
push_rect(0.0f, float(height - status_band_height), float(heartbeat_width), float(height),
|
|
heartbeat_color);
|
|
|
|
D3D12_VIEWPORT viewport = {};
|
|
viewport.TopLeftX = 0.0f;
|
|
viewport.TopLeftY = 0.0f;
|
|
viewport.Width = float(width);
|
|
viewport.Height = float(height);
|
|
viewport.MinDepth = 0.0f;
|
|
viewport.MaxDepth = 1.0f;
|
|
D3D12_RECT scissor = {0, 0, LONG(width), LONG(height)};
|
|
placeholder_command_list_->RSSetViewports(1, &viewport);
|
|
placeholder_command_list_->RSSetScissorRects(1, &scissor);
|
|
|
|
const D3D12_CPU_DESCRIPTOR_HANDLE rtv =
|
|
slot.rtv_heap->GetCPUDescriptorHandleForHeapStart();
|
|
placeholder_command_list_->OMSetRenderTargets(1, &rtv, TRUE, nullptr);
|
|
placeholder_command_list_->ClearRenderTargetView(rtv, background_color.data(), 0, nullptr);
|
|
|
|
if (vertex_count) {
|
|
std::memcpy(slot.vertex_upload_mapping, vertices.data(),
|
|
sizeof(rex::ui::ImmediateVertex) * vertex_count);
|
|
D3D12_VERTEX_BUFFER_VIEW vertex_buffer_view = {};
|
|
vertex_buffer_view.BufferLocation = slot.vertex_upload_buffer->GetGPUVirtualAddress();
|
|
vertex_buffer_view.SizeInBytes = UINT(sizeof(rex::ui::ImmediateVertex) * vertex_count);
|
|
vertex_buffer_view.StrideInBytes = UINT(sizeof(rex::ui::ImmediateVertex));
|
|
|
|
ID3D12DescriptorHeap* descriptor_heaps[] = {raster_preview_view_heap_.Get(),
|
|
raster_preview_sampler_heap_.Get()};
|
|
placeholder_command_list_->SetDescriptorHeaps(uint32_t(std::size(descriptor_heaps)),
|
|
descriptor_heaps);
|
|
placeholder_command_list_->SetGraphicsRootSignature(raster_preview_root_signature_.Get());
|
|
const float coordinate_space_size_inv[2] = {1.0f / float(width), 1.0f / float(height)};
|
|
placeholder_command_list_->SetGraphicsRoot32BitConstants(2, 2, coordinate_space_size_inv, 0);
|
|
placeholder_command_list_->SetGraphicsRootDescriptorTable(
|
|
0, raster_preview_view_heap_->GetGPUDescriptorHandleForHeapStart());
|
|
placeholder_command_list_->SetGraphicsRootDescriptorTable(
|
|
1, raster_preview_sampler_heap_->GetGPUDescriptorHandleForHeapStart());
|
|
placeholder_command_list_->SetPipelineState(raster_preview_pipeline_.Get());
|
|
placeholder_command_list_->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
|
placeholder_command_list_->IASetVertexBuffers(0, 1, &vertex_buffer_view);
|
|
placeholder_command_list_->DrawInstanced(vertex_count, 1, 0, 0);
|
|
}
|
|
|
|
D3D12_RESOURCE_BARRIER barriers[3] = {};
|
|
barriers[0].Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
|
|
barriers[0].Transition.pResource = slot.raster_render_target.Get();
|
|
barriers[0].Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
|
|
barriers[0].Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
|
|
barriers[0].Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_SOURCE;
|
|
barriers[1].Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
|
|
barriers[1].Transition.pResource = guest_output_resource;
|
|
barriers[1].Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
|
|
barriers[1].Transition.StateBefore = rex::ui::d3d12::D3D12Presenter::kGuestOutputInternalState;
|
|
barriers[1].Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_DEST;
|
|
placeholder_command_list_->ResourceBarrier(2, barriers);
|
|
|
|
D3D12_TEXTURE_COPY_LOCATION copy_dest = {};
|
|
copy_dest.pResource = guest_output_resource;
|
|
copy_dest.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
|
|
copy_dest.SubresourceIndex = 0;
|
|
D3D12_TEXTURE_COPY_LOCATION copy_source = {};
|
|
copy_source.pResource = slot.raster_render_target.Get();
|
|
copy_source.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
|
|
copy_source.SubresourceIndex = 0;
|
|
placeholder_command_list_->CopyTextureRegion(©_dest, 0, 0, 0, ©_source, nullptr);
|
|
|
|
barriers[0].Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
|
|
barriers[0].Transition.StateAfter = rex::ui::d3d12::D3D12Presenter::kGuestOutputInternalState;
|
|
barriers[0].Transition.pResource = guest_output_resource;
|
|
barriers[1].Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_SOURCE;
|
|
barriers[1].Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
|
|
barriers[1].Transition.pResource = slot.raster_render_target.Get();
|
|
placeholder_command_list_->ResourceBarrier(2, barriers);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool RecordPlaceholderFrame(
|
|
rex::ui::d3d12::D3D12Presenter::D3D12GuestOutputRefreshContext& context,
|
|
const rex::system::GraphicsSwapSubmission& submission) {
|
|
std::lock_guard<std::mutex> lock(placeholder_refresh_mutex_);
|
|
|
|
if (!d3d12_provider_ || !placeholder_command_list_) {
|
|
return false;
|
|
}
|
|
|
|
PlaceholderRefreshSlot& slot = placeholder_refresh_slots_[placeholder_refresh_slot_index_];
|
|
if (slot.last_submission &&
|
|
!placeholder_submission_tracker_.AwaitSubmissionCompletion(slot.last_submission)) {
|
|
REXLOG_ERROR(
|
|
"AC6 native graphics bootstrap failed to await placeholder refresh "
|
|
"slot {}", placeholder_refresh_slot_index_);
|
|
return false;
|
|
}
|
|
|
|
ID3D12Resource* guest_output_resource = context.resource_uav_capable();
|
|
if (!guest_output_resource) {
|
|
return false;
|
|
}
|
|
|
|
if (FAILED(slot.command_allocator->Reset())) {
|
|
REXLOG_ERROR("AC6 native graphics bootstrap failed to reset a placeholder command allocator");
|
|
return false;
|
|
}
|
|
if (FAILED(placeholder_command_list_->Reset(slot.command_allocator.Get(), nullptr))) {
|
|
REXLOG_ERROR("AC6 native graphics bootstrap failed to reset the placeholder command list");
|
|
return false;
|
|
}
|
|
|
|
last_present_used_raster_replay_ = false;
|
|
if (TryRecordRasterPlaceholderFrame(slot, guest_output_resource, submission)) {
|
|
context.SetIs8bpc(true);
|
|
last_present_used_raster_replay_ = true;
|
|
return SubmitPlaceholderCommandList(slot);
|
|
}
|
|
if (FAILED(placeholder_command_list_->Reset(slot.command_allocator.Get(), nullptr))) {
|
|
REXLOG_ERROR(
|
|
"AC6 native graphics bootstrap failed to reset the placeholder command list "
|
|
"for the legacy fallback path");
|
|
return false;
|
|
}
|
|
|
|
ID3D12Device* device = d3d12_provider_->GetDevice();
|
|
D3D12_UNORDERED_ACCESS_VIEW_DESC uav_desc = {};
|
|
uav_desc.Format = rex::ui::d3d12::D3D12Presenter::kGuestOutputFormat;
|
|
uav_desc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D;
|
|
uav_desc.Texture2D.MipSlice = 0;
|
|
uav_desc.Texture2D.PlaneSlice = 0;
|
|
|
|
D3D12_CPU_DESCRIPTOR_HANDLE uav_cpu_visible =
|
|
slot.shader_visible_uav_heap->GetCPUDescriptorHandleForHeapStart();
|
|
D3D12_GPU_DESCRIPTOR_HANDLE uav_gpu_visible =
|
|
slot.shader_visible_uav_heap->GetGPUDescriptorHandleForHeapStart();
|
|
D3D12_CPU_DESCRIPTOR_HANDLE uav_cpu =
|
|
slot.cpu_uav_heap->GetCPUDescriptorHandleForHeapStart();
|
|
device->CreateUnorderedAccessView(guest_output_resource, nullptr, &uav_desc, uav_cpu_visible);
|
|
device->CreateUnorderedAccessView(guest_output_resource, nullptr, &uav_desc, uav_cpu);
|
|
|
|
ID3D12DescriptorHeap* descriptor_heaps[] = {slot.shader_visible_uav_heap.Get()};
|
|
placeholder_command_list_->SetDescriptorHeaps(1, descriptor_heaps);
|
|
|
|
D3D12_RESOURCE_BARRIER barrier = {};
|
|
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
|
|
barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
|
|
barrier.Transition.pResource = guest_output_resource;
|
|
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
|
|
barrier.Transition.StateBefore = rex::ui::d3d12::D3D12Presenter::kGuestOutputInternalState;
|
|
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
|
|
placeholder_command_list_->ResourceBarrier(1, &barrier);
|
|
|
|
context.SetIs8bpc(true);
|
|
|
|
const UINT width = submission.frontbuffer_width;
|
|
const UINT height = submission.frontbuffer_height;
|
|
const UINT title_band_height = std::max<UINT>(1, height / 8);
|
|
const UINT status_band_height = std::max<UINT>(1, height / 20);
|
|
const UINT heartbeat_width =
|
|
std::max<UINT>(1, UINT((uint64_t(width) *
|
|
(((swap_count_ - 1) % kPlaceholderHeartbeatPeriod) + 1)) /
|
|
kPlaceholderHeartbeatPeriod));
|
|
|
|
const NativeReplayPlanSummary& replay_plan = last_replay_plan_;
|
|
const SelectedPassPreviewData& selected_pass_preview = last_selected_pass_preview_;
|
|
const uint32_t candidate_seed =
|
|
replay_plan.present_candidate_rt0 ^ (replay_plan.present_candidate_depth_stencil * 33u) ^
|
|
(replay_plan.selected_pass_score * 2654435761u);
|
|
auto channel = [candidate_seed](uint32_t shift, float low, float high) {
|
|
float t = float((candidate_seed >> shift) & 0xFFu) / 255.0f;
|
|
return low + (high - low) * t;
|
|
};
|
|
|
|
const FloatColor background_color = {replay_plan.valid ? 0.04f : 0.08f,
|
|
replay_plan.valid ? 0.05f : 0.06f,
|
|
replay_plan.valid ? 0.08f : 0.10f, 1.0f};
|
|
const FloatColor title_band_color = {
|
|
replay_plan.selected_pass_is_stable ? 0.18f
|
|
: (replay_plan.present_candidate_matches_swap_size ? 0.18f : 0.55f),
|
|
replay_plan.selected_pass_is_stable ? 0.62f
|
|
: (replay_plan.present_candidate_matches_swap_size ? 0.56f : 0.22f),
|
|
replay_plan.selected_pass_is_stable ? 0.46f
|
|
: (replay_plan.present_candidate_matches_swap_size ? 0.24f : 0.18f), 1.0f};
|
|
const FloatColor fallback_candidate_color = {channel(0, 0.20f, 0.92f),
|
|
channel(8, 0.18f, 0.75f),
|
|
channel(16, 0.16f, 0.88f), 1.0f};
|
|
const FloatColor resolve_color = {0.96f, 0.94f, 0.30f, 1.0f};
|
|
const FloatColor heartbeat_color = {0.95f, 0.92f, 0.24f, 1.0f};
|
|
const FloatColor candidate_base_color =
|
|
selected_pass_preview.summary.using_clear_fill
|
|
? DecodeArgbColor(selected_pass_preview.summary.last_clear_color)
|
|
: fallback_candidate_color;
|
|
const FloatColor candidate_outline_color = MixColors(candidate_base_color, heartbeat_color, 0.5f);
|
|
const FloatColor stripe_color = MixColors(candidate_base_color, title_band_color, 0.4f);
|
|
|
|
auto clear_rect = [&](const FloatColor& color, UINT left, UINT top, UINT right, UINT bottom) {
|
|
if (left >= right || top >= bottom) {
|
|
return;
|
|
}
|
|
D3D12_RECT rect = {LONG(left), LONG(top), LONG(right), LONG(bottom)};
|
|
placeholder_command_list_->ClearUnorderedAccessViewFloat(
|
|
uav_gpu_visible, uav_cpu, guest_output_resource, color.data(), 1, &rect);
|
|
};
|
|
|
|
placeholder_command_list_->ClearUnorderedAccessViewFloat(
|
|
uav_gpu_visible, uav_cpu, guest_output_resource, background_color.data(), 0, nullptr);
|
|
|
|
UINT candidate_left = 0;
|
|
UINT candidate_top = title_band_height;
|
|
UINT candidate_width = width;
|
|
UINT candidate_height = std::max<UINT>(1, height - title_band_height - status_band_height);
|
|
if (replay_plan.valid && replay_plan.present_candidate_viewport_width &&
|
|
replay_plan.present_candidate_viewport_height) {
|
|
candidate_left = std::min<UINT>(width - 1, replay_plan.present_candidate_viewport_x);
|
|
candidate_top = std::min<UINT>(height - 1, replay_plan.present_candidate_viewport_y);
|
|
candidate_width =
|
|
std::max<UINT>(1, std::min<UINT>(replay_plan.present_candidate_viewport_width,
|
|
width - candidate_left));
|
|
candidate_height =
|
|
std::max<UINT>(1, std::min<UINT>(replay_plan.present_candidate_viewport_height,
|
|
height - candidate_top));
|
|
}
|
|
D3D12_RECT center_block_rect = {LONG(candidate_left), LONG(candidate_top),
|
|
LONG(std::min(width, candidate_left + candidate_width)),
|
|
LONG(std::min(height, candidate_top + candidate_height))};
|
|
placeholder_command_list_->ClearUnorderedAccessViewFloat(
|
|
uav_gpu_visible, uav_cpu, guest_output_resource, candidate_base_color.data(), 1,
|
|
¢er_block_rect);
|
|
|
|
const UINT candidate_right = std::min(width, candidate_left + candidate_width);
|
|
const UINT candidate_bottom = std::min(height, candidate_top + candidate_height);
|
|
|
|
UINT sampled_clear_band_height = 0;
|
|
if (selected_pass_preview.clear_step_count && candidate_height > 2) {
|
|
sampled_clear_band_height =
|
|
std::max<UINT>(1, std::min<UINT>(candidate_height / 12, height / 32 + 1));
|
|
for (uint32_t i = 0; i < selected_pass_preview.clear_step_count; ++i) {
|
|
const UINT band_top = std::min(candidate_bottom, candidate_top + sampled_clear_band_height * i);
|
|
const UINT band_bottom =
|
|
std::min(candidate_bottom, band_top + sampled_clear_band_height);
|
|
clear_rect(DecodeArgbColor(selected_pass_preview.clear_steps[i].color), candidate_left,
|
|
band_top, candidate_right, band_bottom);
|
|
}
|
|
}
|
|
|
|
UINT resolve_band_height = 0;
|
|
if (replay_plan.valid && replay_plan.selected_pass_has_resolve) {
|
|
resolve_band_height = std::max<UINT>(1, candidate_height / 10);
|
|
clear_rect(resolve_color, candidate_left,
|
|
std::min(candidate_bottom, candidate_bottom - resolve_band_height),
|
|
candidate_right, candidate_bottom);
|
|
}
|
|
|
|
if (selected_pass_preview.summary.draw_count && candidate_width > 8 && candidate_height > 8) {
|
|
const UINT stripe_count =
|
|
std::min<UINT>(12, std::max<UINT>(1, 1 + (selected_pass_preview.summary.draw_count / 6)));
|
|
const UINT stripe_width =
|
|
std::max<UINT>(1, candidate_width / std::max<UINT>(24, stripe_count * 5));
|
|
const UINT stripe_top = std::min(candidate_bottom, candidate_top + sampled_clear_band_height);
|
|
const UINT stripe_bottom =
|
|
candidate_bottom > resolve_band_height ? (candidate_bottom - resolve_band_height)
|
|
: candidate_bottom;
|
|
const UINT stripe_area_height =
|
|
stripe_bottom > stripe_top ? (stripe_bottom - stripe_top) : 0;
|
|
for (UINT i = 0; i < stripe_count && stripe_area_height; ++i) {
|
|
const UINT stripe_center_x =
|
|
candidate_left + ((i + 1) * candidate_width) / (stripe_count + 1);
|
|
const UINT stripe_left = stripe_center_x > stripe_width / 2
|
|
? stripe_center_x - stripe_width / 2
|
|
: candidate_left;
|
|
const UINT stripe_right = std::min(candidate_right, stripe_left + stripe_width);
|
|
const UINT stripe_height =
|
|
std::max<UINT>(1, stripe_area_height * (45 + ((i * 13) % 40)) / 100);
|
|
const UINT stripe_start = stripe_bottom > stripe_height ? (stripe_bottom - stripe_height)
|
|
: stripe_top;
|
|
const FloatColor lane_color =
|
|
(i & 1u) ? MixColors(stripe_color, heartbeat_color, 0.22f)
|
|
: MixColors(stripe_color, background_color, 0.10f);
|
|
clear_rect(lane_color, stripe_left, stripe_start, stripe_right, stripe_bottom);
|
|
}
|
|
}
|
|
|
|
if (candidate_width > 2 && candidate_height > 2) {
|
|
const UINT outline_thickness =
|
|
std::max<UINT>(1, std::min<UINT>(4, std::min(candidate_width, candidate_height) / 96 + 1));
|
|
const UINT bottom_outline_top =
|
|
candidate_bottom > outline_thickness ? (candidate_bottom - outline_thickness)
|
|
: candidate_top;
|
|
clear_rect(candidate_outline_color, candidate_left, candidate_top, candidate_right,
|
|
std::min(candidate_bottom, candidate_top + outline_thickness));
|
|
clear_rect(candidate_outline_color, candidate_left, bottom_outline_top, candidate_right,
|
|
candidate_bottom);
|
|
clear_rect(candidate_outline_color, candidate_left, candidate_top,
|
|
std::min(candidate_right, candidate_left + outline_thickness), candidate_bottom);
|
|
clear_rect(candidate_outline_color,
|
|
candidate_right > outline_thickness ? (candidate_right - outline_thickness)
|
|
: candidate_left,
|
|
candidate_top, candidate_right, candidate_bottom);
|
|
}
|
|
|
|
if (replay_plan.valid && replay_plan.pass_count) {
|
|
const UINT score_width = std::max<UINT>(
|
|
1, UINT((uint64_t(width) * std::min<uint32_t>(replay_plan.selected_pass_score, 220u)) /
|
|
220u));
|
|
clear_rect(candidate_outline_color, 0, title_band_height, score_width,
|
|
std::min(height, title_band_height + status_band_height));
|
|
}
|
|
|
|
clear_rect(title_band_color, 0, 0, width, title_band_height);
|
|
clear_rect(heartbeat_color, 0, height - status_band_height, heartbeat_width, height);
|
|
|
|
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
|
|
barrier.Transition.StateAfter = rex::ui::d3d12::D3D12Presenter::kGuestOutputInternalState;
|
|
placeholder_command_list_->ResourceBarrier(1, &barrier);
|
|
|
|
return SubmitPlaceholderCommandList(slot);
|
|
}
|
|
|
|
rex::ui::d3d12::D3D12Provider* d3d12_provider_ = nullptr;
|
|
rex::ui::d3d12::D3D12Presenter* d3d12_presenter_ = nullptr;
|
|
std::mutex placeholder_refresh_mutex_;
|
|
rex::ui::d3d12::D3D12SubmissionTracker placeholder_submission_tracker_;
|
|
std::array<PlaceholderRefreshSlot, kPlaceholderRefreshSlotCount> placeholder_refresh_slots_;
|
|
Microsoft::WRL::ComPtr<ID3D12GraphicsCommandList> placeholder_command_list_;
|
|
Microsoft::WRL::ComPtr<ID3D12RootSignature> raster_preview_root_signature_;
|
|
Microsoft::WRL::ComPtr<ID3D12PipelineState> raster_preview_pipeline_;
|
|
Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> raster_preview_view_heap_;
|
|
Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> raster_preview_sampler_heap_;
|
|
uint32_t placeholder_refresh_slot_index_ = 0;
|
|
rex::system::GraphicsSwapSubmission last_swap_submission_;
|
|
NativeReplayPlanSummary last_replay_plan_{};
|
|
SelectedPassPreviewData last_selected_pass_preview_{};
|
|
ReplayCandidateKey last_selected_candidate_{};
|
|
bool last_selected_candidate_valid_ = false;
|
|
uint32_t selected_candidate_streak_ = 0;
|
|
uint64_t swap_count_ = 0;
|
|
bool logged_first_swap_ = false;
|
|
bool logged_first_present_ = false;
|
|
bool logged_passthrough_swap_ = false;
|
|
bool logged_refresh_failure_ = false;
|
|
bool last_present_used_raster_replay_ = false;
|
|
bool placeholder_resources_initialized_ = false;
|
|
};
|
|
#endif
|
|
|
|
} // namespace
|
|
|
|
void ConfigureGraphicsBackend(rex::RuntimeConfig& config) {
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_native_graphics_status_mutex);
|
|
g_native_graphics_status = {};
|
|
g_native_graphics_status.bootstrap_enabled = REXCVAR_GET(ac6_native_graphics_bootstrap);
|
|
g_native_graphics_status.placeholder_present_enabled =
|
|
REXCVAR_GET(ac6_native_graphics_placeholder_present);
|
|
}
|
|
#if REX_HAS_D3D12
|
|
if (!REXCVAR_GET(ac6_native_graphics_bootstrap)) {
|
|
return;
|
|
}
|
|
|
|
config.graphics = std::make_unique<Ac6NativeGraphicsSystem>();
|
|
#else
|
|
(void)config;
|
|
#endif
|
|
}
|
|
|
|
NativeGraphicsStatusSnapshot GetNativeGraphicsStatus() {
|
|
std::lock_guard<std::mutex> lock(g_native_graphics_status_mutex);
|
|
NativeGraphicsStatusSnapshot snapshot = g_native_graphics_status;
|
|
snapshot.bootstrap_enabled = REXCVAR_GET(ac6_native_graphics_bootstrap);
|
|
snapshot.placeholder_present_enabled = REXCVAR_GET(ac6_native_graphics_placeholder_present);
|
|
return snapshot;
|
|
}
|
|
|
|
} // namespace ac6::graphics
|