improve Vulkan texture and presentation handling
This commit is contained in:
parent
3184592404
commit
5b6b87bcea
30 changed files with 1998 additions and 62 deletions
|
|
@ -19,11 +19,11 @@
|
|||
|
||||
namespace rex::filesystem {
|
||||
|
||||
class NullEntry;
|
||||
class Entry;
|
||||
|
||||
class NullFile : public File {
|
||||
public:
|
||||
NullFile(uint32_t file_access, NullEntry* entry);
|
||||
NullFile(uint32_t file_access, Entry* entry);
|
||||
~NullFile() override;
|
||||
|
||||
void Destroy() override;
|
||||
|
|
|
|||
|
|
@ -15,3 +15,5 @@
|
|||
|
||||
REXCVAR_DECLARE(std::string, dump_source);
|
||||
REXCVAR_DECLARE(std::string, dump_path);
|
||||
REXCVAR_DECLARE(int32_t, filesystem_debug_log_fe_asset_ops_remaining);
|
||||
REXCVAR_DECLARE(int32_t, filesystem_debug_log_team_profile_background_remaining);
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ enum class ReadbackResolveMode {
|
|||
kFast,
|
||||
kSome,
|
||||
kFull,
|
||||
kAuto,
|
||||
};
|
||||
|
||||
struct SwapState {
|
||||
|
|
|
|||
|
|
@ -328,9 +328,6 @@ class D3D12TextureCache final : public TextureCache {
|
|||
ID3D12Resource* resource() const { return resource_.Get(); }
|
||||
D3D12_RESOURCE_STATES SetResourceState(D3D12_RESOURCE_STATES new_state) {
|
||||
D3D12_RESOURCE_STATES old_state = resource_state_;
|
||||
if (old_state == D3D12_RESOURCE_STATE_UNORDERED_ACCESS) {
|
||||
uav_barrier_pending_ = false;
|
||||
}
|
||||
resource_state_ = new_state;
|
||||
return old_state;
|
||||
}
|
||||
|
|
@ -342,6 +339,11 @@ class D3D12TextureCache final : public TextureCache {
|
|||
}
|
||||
// After an aliasing barrier (which is even stronger than an UAV barrier).
|
||||
void ClearUAVBarrierPending() { uav_barrier_pending_ = false; }
|
||||
bool ConsumeUAVBarrierPending() {
|
||||
bool pending = uav_barrier_pending_;
|
||||
uav_barrier_pending_ = false;
|
||||
return pending;
|
||||
}
|
||||
|
||||
private:
|
||||
Microsoft::WRL::ComPtr<ID3D12Resource> resource_;
|
||||
|
|
|
|||
|
|
@ -29,6 +29,10 @@ REXCVAR_DECLARE(int32_t, draw_resolution_scale_x);
|
|||
REXCVAR_DECLARE(int32_t, draw_resolution_scale_y);
|
||||
REXCVAR_DECLARE(bool, resolve_resolution_scale_fill_half_pixel_offset);
|
||||
REXCVAR_DECLARE(bool, draw_resolution_scaled_texture_offsets);
|
||||
REXCVAR_DECLARE(bool, scaled_resolve_texture_loads_from_shared_memory);
|
||||
REXCVAR_DECLARE(int32_t, scaled_resolve_texture_loads_from_shared_memory_max_pixels);
|
||||
REXCVAR_DECLARE(bool, scaled_resolve_texture_loads_update_guest_memory);
|
||||
REXCVAR_DECLARE(int32_t, scaled_resolve_small_texture_readback_max_length);
|
||||
REXCVAR_DECLARE(std::string, readback_resolve);
|
||||
REXCVAR_DECLARE(bool, readback_resolve_half_pixel_offset);
|
||||
REXCVAR_DECLARE(bool, readback_memexport);
|
||||
|
|
@ -87,6 +91,18 @@ REXCVAR_DECLARE(bool, vulkan_sparse_shared_memory);
|
|||
REXCVAR_DECLARE(bool, vulkan_submit_on_primary_buffer_end);
|
||||
REXCVAR_DECLARE(bool, vulkan_dynamic_rendering);
|
||||
REXCVAR_DECLARE(bool, vulkan_async_skip_incomplete_frames);
|
||||
REXCVAR_DECLARE(int32_t, vulkan_async_placeholder_max_consecutive_skips);
|
||||
REXCVAR_DECLARE(int32_t, vulkan_debug_log_frame_summaries_remaining);
|
||||
REXCVAR_DECLARE(int32_t, vulkan_debug_frame_summary_interval_frames);
|
||||
REXCVAR_DECLARE(int32_t, vulkan_debug_log_resolve_decisions_remaining);
|
||||
REXCVAR_DECLARE(int32_t, vulkan_debug_log_team_profile_background_candidates_remaining);
|
||||
REXCVAR_DECLARE(int32_t, vulkan_debug_log_team_profile_background_bindings_remaining);
|
||||
REXCVAR_DECLARE(int32_t, vulkan_debug_log_team_profile_background_draws_remaining);
|
||||
REXCVAR_DECLARE(bool, vulkan_sync_after_resolve);
|
||||
REXCVAR_DECLARE(bool, vulkan_barrier_after_scaled_resolve);
|
||||
REXCVAR_DECLARE(int32_t, vulkan_readback_resolve_max_length);
|
||||
REXCVAR_DECLARE(int32_t, vulkan_readback_resolve_min_address);
|
||||
REXCVAR_DECLARE(int32_t, vulkan_readback_resolve_max_address);
|
||||
REXCVAR_DECLARE(int32_t, vulkan_pipeline_creation_threads);
|
||||
REXCVAR_DECLARE(bool, vulkan_tessellation_wireframe);
|
||||
REXCVAR_DECLARE(bool, vulkan_force_expand_point_sprites_in_vs);
|
||||
|
|
|
|||
|
|
@ -88,6 +88,11 @@ class TextureCache {
|
|||
uint32_t /*length_scaled_alignment_log2*/ = 0) {
|
||||
return false;
|
||||
}
|
||||
virtual bool MaterializeScaledResolveToSharedMemory(uint32_t /*start_unscaled*/,
|
||||
uint32_t /*length_unscaled*/,
|
||||
uint32_t /*pixel_size_log2*/) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static uint32_t GuestToHostSwizzle(uint32_t guest_swizzle, uint32_t host_format_swizzle);
|
||||
|
||||
|
|
@ -110,6 +115,16 @@ class TextureCache {
|
|||
}
|
||||
|
||||
virtual void RequestTextures(uint32_t used_texture_mask);
|
||||
void DebugRecordResolveReadback(uint32_t start, uint32_t length, bool scaled);
|
||||
void DebugClearRecentResolveReadbacks();
|
||||
void DebugRecordTeamProfileBackgroundResolveRange(uint32_t start, uint32_t length, bool scaled,
|
||||
uint64_t scaled_start,
|
||||
uint64_t scaled_length);
|
||||
bool DebugFindTeamProfileBackgroundBinding(uint32_t used_texture_mask,
|
||||
uint32_t& fetch_index_out,
|
||||
bool& signed_view_out,
|
||||
uint32_t* texture_base_out = nullptr,
|
||||
uint32_t* texture_length_out = nullptr) const;
|
||||
|
||||
// "ActiveTexture" means as of the latest RequestTextures call.
|
||||
|
||||
|
|
@ -228,7 +243,8 @@ class TextureCache {
|
|||
}
|
||||
void MakeUpToDateAndWatch(const std::unique_lock<std::recursive_mutex>& global_lock);
|
||||
|
||||
void WatchCallback(const std::unique_lock<std::recursive_mutex>& global_lock, bool is_mip);
|
||||
void WatchCallback(const std::unique_lock<std::recursive_mutex>& global_lock, bool is_mip,
|
||||
bool invalidated_by_gpu);
|
||||
|
||||
// For LRU caching - updates the last usage frame and moves the texture to
|
||||
// the end of the usage queue. Must be called any time the texture is
|
||||
|
|
@ -525,6 +541,10 @@ class TextureCache {
|
|||
// into the texture object.
|
||||
virtual bool LoadTextureDataFromResidentMemoryImpl(Texture& texture, bool load_base,
|
||||
bool load_mips) = 0;
|
||||
bool DebugTextureOverlapsRecentTeamProfileBackgroundResolve(
|
||||
const Texture& texture, bool mips, uint32_t& resolve_serial_out,
|
||||
bool& resolve_scaled_out, uint64_t& resolve_scaled_start_out,
|
||||
uint64_t& resolve_scaled_length_out) const;
|
||||
|
||||
// Converts a texture fetch constant to a texture key, normalizing and
|
||||
// validating the values, or creating an invalid key, and also gets the
|
||||
|
|
@ -544,6 +564,7 @@ class TextureCache {
|
|||
// Called when something in a texture binding is changed for the
|
||||
// implementation to update the internal dependencies of the binding.
|
||||
virtual void UpdateTextureBindingsImpl(uint32_t /*fetch_constant_mask*/) {}
|
||||
bool DebugIsTeamProfileBackgroundTexture(const Texture& texture) const;
|
||||
|
||||
private:
|
||||
struct PendingTextureLoad {
|
||||
|
|
@ -554,11 +575,21 @@ class TextureCache {
|
|||
struct PendingSharedMemoryRange {
|
||||
uint32_t start = 0;
|
||||
uint32_t length = 0;
|
||||
bool materialize_scaled_resolve = false;
|
||||
uint32_t pixel_size_log2 = 0;
|
||||
};
|
||||
bool PrepareTextureLoad(Texture& texture, PendingTextureLoad& pending_load_out,
|
||||
PendingSharedMemoryRange* pending_ranges_out,
|
||||
size_t& pending_range_count_out);
|
||||
bool CommitPreparedTextureLoad(const PendingTextureLoad& pending_load);
|
||||
void DebugLogScaledTextureBindings(uint32_t used_texture_mask);
|
||||
bool DebugRangeOverlapsRecentScaledResolveReadback(uint32_t start, uint32_t length) const;
|
||||
bool DebugTextureOverlapsRecentResolveReadback(const Texture& texture, bool mips,
|
||||
uint32_t& readback_serial_out,
|
||||
bool& readback_scaled_out) const;
|
||||
static bool DebugIsTeamProfileBackgroundCandidateKey(const TextureKey& key);
|
||||
void DebugLogTeamProfileBackgroundTextureCandidate(const char* action, uint32_t fetch_index,
|
||||
const Texture& texture);
|
||||
|
||||
void UpdateTexturesTotalHostMemoryUsage(uint64_t add, uint64_t subtract);
|
||||
|
||||
|
|
@ -569,6 +600,8 @@ class TextureCache {
|
|||
// Checks if there are any pages that contain scaled resolve data within the
|
||||
// range.
|
||||
bool IsRangeScaledResolved(uint32_t start_unscaled, uint32_t length_unscaled);
|
||||
// Checks if every page touched by the range contains scaled resolve data.
|
||||
bool IsRangeFullyScaledResolved(uint32_t start_unscaled, uint32_t length_unscaled);
|
||||
// Global shared memory invalidation callback for invalidating scaled resolved
|
||||
// texture data.
|
||||
static void ScaledResolveGlobalWatchCallbackThunk(
|
||||
|
|
@ -617,6 +650,32 @@ class TextureCache {
|
|||
// Bit vector with bits reset on fetch constant writes to avoid parsing fetch
|
||||
// constants again and again.
|
||||
uint32_t texture_bindings_in_sync_ = 0;
|
||||
|
||||
int32_t debug_scaled_texture_bindings_remaining_seen_ = 0;
|
||||
uint32_t debug_scaled_texture_binding_logs_this_sample_ = 0;
|
||||
struct DebugResolveReadbackRange {
|
||||
uint32_t start = 0;
|
||||
uint32_t length = 0;
|
||||
uint32_t serial = 0;
|
||||
bool scaled = false;
|
||||
};
|
||||
std::array<DebugResolveReadbackRange, 32> debug_recent_resolve_readbacks_;
|
||||
uint32_t debug_recent_resolve_readback_next_ = 0;
|
||||
uint32_t debug_recent_resolve_readback_count_ = 0;
|
||||
uint32_t debug_resolve_readback_serial_ = 0;
|
||||
struct DebugTeamProfileResolveRange {
|
||||
uint32_t start = 0;
|
||||
uint32_t length = 0;
|
||||
uint32_t serial = 0;
|
||||
bool scaled = false;
|
||||
uint64_t scaled_start = 0;
|
||||
uint64_t scaled_length = 0;
|
||||
};
|
||||
std::array<DebugTeamProfileResolveRange, 64> debug_recent_team_profile_resolves_;
|
||||
uint32_t debug_recent_team_profile_resolve_next_ = 0;
|
||||
uint32_t debug_recent_team_profile_resolve_count_ = 0;
|
||||
uint32_t debug_team_profile_resolve_serial_ = 0;
|
||||
uint32_t debug_team_profile_resolve_texture_logs_remaining_ = 0;
|
||||
};
|
||||
|
||||
} // namespace rex::graphics
|
||||
|
|
|
|||
|
|
@ -80,6 +80,10 @@ class SharedMemory {
|
|||
bool RequestRanges(const std::pair<uint32_t, uint32_t>* ranges, size_t count);
|
||||
bool RequestRange(uint32_t start, uint32_t length);
|
||||
|
||||
const uint8_t* DebugTranslatePhysical(uint32_t address) const {
|
||||
return memory_.TranslatePhysical<const uint8_t*>(address);
|
||||
}
|
||||
|
||||
// Marks the range and, if not exact_range, potentially its surroundings
|
||||
// (to up to the first GPU-written page, as an access violation exception
|
||||
// count optimization) as modified by the CPU, also invalidating GPU-written
|
||||
|
|
|
|||
|
|
@ -278,6 +278,8 @@ class VulkanCommandProcessor : public CommandProcessor {
|
|||
void InitializeTrace() override;
|
||||
|
||||
private:
|
||||
friend class VulkanTextureCache;
|
||||
|
||||
struct CommandBuffer {
|
||||
VkCommandPool pool;
|
||||
VkCommandBuffer buffer;
|
||||
|
|
@ -433,9 +435,12 @@ class VulkanCommandProcessor : public CommandProcessor {
|
|||
// Keep primary-buffer-end submit behavior aligned with D3D12: only submit
|
||||
// when immediate submission is safe.
|
||||
bool CanEndSubmissionImmediately() const;
|
||||
void DebugRecordResolve(uint32_t address, uint32_t length, bool scaled);
|
||||
|
||||
void ClearTransientDescriptorPools();
|
||||
bool IssueCopy_ReadbackResolvePath();
|
||||
bool DownscaleScaledResolveToSharedMemory(uint32_t start_unscaled, uint32_t length_unscaled,
|
||||
uint32_t pixel_size_log2);
|
||||
bool IssueDraw_MemexportReadbackFullPath(uint32_t total_size);
|
||||
bool IssueDraw_MemexportReadbackFastPath(uint32_t total_size);
|
||||
|
||||
|
|
@ -553,6 +558,24 @@ class VulkanCommandProcessor : public CommandProcessor {
|
|||
// Tracks whether any draw in the current frame used an async placeholder
|
||||
// graphics pipeline and may have produced incomplete output.
|
||||
bool frame_used_async_placeholder_pipeline_ = false;
|
||||
uint32_t async_placeholder_consecutive_frame_skips_ = 0;
|
||||
bool async_placeholder_skip_fallback_logged_ = false;
|
||||
uint32_t debug_frame_draws_ = 0;
|
||||
uint32_t debug_frame_copy_resolves_ = 0;
|
||||
uint32_t debug_frame_raster_draws_ = 0;
|
||||
uint32_t debug_frame_color_draws_ = 0;
|
||||
uint32_t debug_frame_depth_stencil_draws_ = 0;
|
||||
uint32_t debug_frame_texture_draws_ = 0;
|
||||
uint32_t debug_frame_placeholder_draws_ = 0;
|
||||
uint32_t debug_frame_no_effect_draws_ = 0;
|
||||
uint32_t debug_frame_memexport_draws_ = 0;
|
||||
uint32_t debug_frame_resolve_ranges_ = 0;
|
||||
uint32_t debug_frame_scaled_resolve_ranges_ = 0;
|
||||
uint64_t debug_frame_resolve_bytes_ = 0;
|
||||
static constexpr uint32_t kDebugFrameResolveRangeSamples = 6;
|
||||
std::array<uint32_t, kDebugFrameResolveRangeSamples> debug_frame_resolve_addresses_ = {};
|
||||
std::array<uint32_t, kDebugFrameResolveRangeSamples> debug_frame_resolve_lengths_ = {};
|
||||
std::array<bool, kDebugFrameResolveRangeSamples> debug_frame_resolve_scaled_ = {};
|
||||
// Guest frame index, since some transient resources can be reused across
|
||||
// submissions. Values updated in the beginning of a frame.
|
||||
uint64_t frame_current_ = 1;
|
||||
|
|
|
|||
|
|
@ -108,7 +108,9 @@ class VulkanRenderTargetCache final : public RenderTargetCache {
|
|||
// frame for calling.
|
||||
bool Resolve(const memory::Memory& memory, VulkanSharedMemory& shared_memory,
|
||||
VulkanTextureCache& texture_cache, uint32_t& written_address_out,
|
||||
uint32_t& written_length_out);
|
||||
uint32_t& written_length_out,
|
||||
bool debug_log_team_profile_background_trace = false);
|
||||
void DebugLogLastUpdateRenderTargetsForTeamProfileBackgroundDraw(uint64_t frame) const;
|
||||
|
||||
// Returns true if any downloads were submitted to the command processor.
|
||||
bool InitializeTraceSubmitDownloads();
|
||||
|
|
|
|||
|
|
@ -145,12 +145,19 @@ class VulkanTextureCache final : public TextureCache {
|
|||
|
||||
bool EnsureScaledResolveMemoryCommitted(uint32_t start_unscaled, uint32_t length_unscaled,
|
||||
uint32_t length_scaled_alignment_log2 = 0) override;
|
||||
bool MaterializeScaledResolveToSharedMemory(uint32_t start_unscaled, uint32_t length_unscaled,
|
||||
uint32_t pixel_size_log2) override;
|
||||
|
||||
bool LoadTextureDataFromResidentMemoryImpl(Texture& texture, bool load_base,
|
||||
bool load_mips) override;
|
||||
|
||||
void UpdateTextureBindingsImpl(uint32_t fetch_constant_mask) override;
|
||||
|
||||
void DebugLogTeamProfileBackgroundGpuEvent(const char* action, uint32_t fetch_index,
|
||||
const Texture& texture, bool signed_view = false,
|
||||
uint32_t host_swizzle = 0,
|
||||
VkImageView image_view = VK_NULL_HANDLE) const;
|
||||
|
||||
private:
|
||||
enum LoadDescriptorSetIndex {
|
||||
kLoadDescriptorSetIndexDestination,
|
||||
|
|
@ -201,6 +208,7 @@ class VulkanTextureCache final : public TextureCache {
|
|||
~VulkanTexture();
|
||||
|
||||
VkImage image() const { return image_; }
|
||||
Usage usage() const { return usage_; }
|
||||
|
||||
// Doesn't transition (the caller must insert the barrier).
|
||||
Usage SetUsage(Usage new_usage) {
|
||||
|
|
@ -271,6 +279,8 @@ class VulkanTextureCache final : public TextureCache {
|
|||
VkImageView image_view_3d_as_2d_signed_ = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
static const char* DebugVulkanTextureUsageName(VulkanTexture::Usage usage);
|
||||
|
||||
struct VulkanTextureBinding {
|
||||
VkImageView image_view_unsigned;
|
||||
VkImageView image_view_signed;
|
||||
|
|
|
|||
|
|
@ -290,6 +290,9 @@ class KernelState {
|
|||
void UnregisterNotifyListener(XNotifyListener* listener);
|
||||
void BroadcastNotification(XNotificationID id, uint32_t data);
|
||||
|
||||
void SetUserContext(uint32_t user_index, uint32_t context_id, uint32_t value);
|
||||
std::optional<uint32_t> GetUserContext(uint32_t user_index, uint32_t context_id) const;
|
||||
|
||||
util::NativeList* dpc_list() { return &dpc_list_; }
|
||||
|
||||
void CompleteOverlapped(uint32_t overlapped_ptr, X_RESULT result);
|
||||
|
|
@ -346,6 +349,9 @@ class KernelState {
|
|||
std::vector<object_ref<XNotifyListener>> notify_listeners_;
|
||||
bool has_notified_startup_ = false;
|
||||
|
||||
mutable std::mutex user_contexts_mutex_;
|
||||
std::unordered_map<uint64_t, uint32_t> user_contexts_;
|
||||
|
||||
// Protected by global_critical_region_.
|
||||
std::unordered_map<uint32_t, FiberInfo> fiber_map_;
|
||||
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ class ImGuiDrawer : public WindowInputListener, public UIDrawer {
|
|||
~ImGuiDrawer();
|
||||
|
||||
ImGuiIO& GetIO();
|
||||
bool HasDialogs() const { return !dialogs_.empty(); }
|
||||
|
||||
void AddDialog(ImGuiDialog* dialog);
|
||||
void RemoveDialog(ImGuiDialog* dialog);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include <rex/filesystem/devices/host_path_entry.h>
|
||||
#include <rex/filesystem/devices/host_path_file.h>
|
||||
#include <rex/filesystem/devices/null_file.h>
|
||||
|
||||
#include <rex/filesystem.h>
|
||||
#include <rex/filesystem/device.h>
|
||||
|
|
@ -56,6 +57,10 @@ X_STATUS HostPathEntry::Open(uint32_t desired_access, File** out_file) {
|
|||
REXFS_ERROR("Attempting to open file for write access on read-only device");
|
||||
return X_STATUS_ACCESS_DENIED;
|
||||
}
|
||||
if (attributes_ & kFileAttributeDirectory) {
|
||||
*out_file = new NullFile(desired_access, this);
|
||||
return X_STATUS_SUCCESS;
|
||||
}
|
||||
auto file_handle = rex::filesystem::FileHandle::OpenExisting(host_path_, desired_access);
|
||||
if (!file_handle) {
|
||||
// TODO(benvanik): pick correct response.
|
||||
|
|
|
|||
|
|
@ -12,8 +12,63 @@
|
|||
#include <rex/filesystem/devices/host_path_entry.h>
|
||||
#include <rex/filesystem/devices/host_path_file.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <rex/cvar.h>
|
||||
#include <rex/filesystem/entry.h>
|
||||
#include <rex/filesystem/flags.h>
|
||||
#include <rex/logging.h>
|
||||
|
||||
namespace rex::filesystem {
|
||||
|
||||
namespace {
|
||||
|
||||
bool LooksLikeSkaterPreviewAssetPath(std::string_view path) {
|
||||
std::string lower(path);
|
||||
std::transform(lower.begin(), lower.end(), lower.begin(),
|
||||
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
static constexpr std::string_view kNeedles[] = {
|
||||
"import_skater", "team_management", "preset", "skater",
|
||||
"preview", "fedynamic", "fedata", "fetexture",
|
||||
"createacharacter", "db.big", "data/fe", "data\\fe",
|
||||
};
|
||||
for (std::string_view needle : kNeedles) {
|
||||
if (lower.find(needle) != std::string::npos) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LooksLikeTeamProfileBackgroundPath(std::string_view path) {
|
||||
std::string lower(path);
|
||||
std::transform(lower.begin(), lower.end(), lower.begin(),
|
||||
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
return lower.find("team_profile_background_0") != std::string::npos;
|
||||
}
|
||||
|
||||
bool ConsumeFeAssetLogBudget() {
|
||||
const int32_t remaining = REXCVAR_GET(filesystem_debug_log_fe_asset_ops_remaining);
|
||||
if (remaining <= 0) {
|
||||
return false;
|
||||
}
|
||||
REXCVAR_SET(filesystem_debug_log_fe_asset_ops_remaining, remaining - 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConsumeTeamProfileBackgroundLogBudget() {
|
||||
const int32_t remaining = REXCVAR_GET(filesystem_debug_log_team_profile_background_remaining);
|
||||
if (remaining <= 0) {
|
||||
return false;
|
||||
}
|
||||
REXCVAR_SET(filesystem_debug_log_team_profile_background_remaining, remaining - 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
HostPathFile::HostPathFile(uint32_t file_access, HostPathEntry* entry,
|
||||
std::unique_ptr<rex::filesystem::FileHandle> file_handle)
|
||||
: File(file_access, entry), file_handle_(std::move(file_handle)) {}
|
||||
|
|
@ -31,6 +86,20 @@ X_STATUS HostPathFile::ReadSync(std::span<uint8_t> buffer, size_t byte_offset,
|
|||
}
|
||||
|
||||
if (file_handle_->Read(byte_offset, buffer.data(), buffer.size(), out_bytes_read)) {
|
||||
if (entry_ && LooksLikeTeamProfileBackgroundPath(entry_->absolute_path()) &&
|
||||
ConsumeTeamProfileBackgroundLogBudget()) {
|
||||
REXFS_WARN(
|
||||
"Team profile BG diagnostic: op=read, path='{}', offset={:X}, requested={:X}, read={:X}",
|
||||
entry_->absolute_path(), byte_offset, buffer.size(),
|
||||
out_bytes_read ? *out_bytes_read : 0);
|
||||
}
|
||||
if (entry_ && LooksLikeSkaterPreviewAssetPath(entry_->absolute_path()) &&
|
||||
ConsumeFeAssetLogBudget()) {
|
||||
REXFS_WARN(
|
||||
"FE asset diagnostic: op=read, path='{}', offset={:X}, requested={:X}, read={:X}",
|
||||
entry_->absolute_path(), byte_offset, buffer.size(),
|
||||
out_bytes_read ? *out_bytes_read : 0);
|
||||
}
|
||||
return X_STATUS_SUCCESS;
|
||||
} else {
|
||||
return X_STATUS_END_OF_FILE;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
namespace rex::filesystem {
|
||||
|
||||
NullFile::NullFile(uint32_t file_access, NullEntry* entry) : File(file_access, entry) {}
|
||||
NullFile::NullFile(uint32_t file_access, Entry* entry) : File(file_access, entry) {}
|
||||
|
||||
NullFile::~NullFile() = default;
|
||||
|
||||
|
|
@ -23,6 +23,9 @@ void NullFile::Destroy() {
|
|||
}
|
||||
|
||||
X_STATUS NullFile::ReadSync(std::span<uint8_t> buffer, size_t byte_offset, size_t* out_bytes_read) {
|
||||
(void)buffer;
|
||||
(void)byte_offset;
|
||||
(void)out_bytes_read;
|
||||
if (!(file_access_ & FileAccess::kFileReadData)) {
|
||||
return X_STATUS_ACCESS_DENIED;
|
||||
}
|
||||
|
|
@ -32,6 +35,9 @@ X_STATUS NullFile::ReadSync(std::span<uint8_t> buffer, size_t byte_offset, size_
|
|||
|
||||
X_STATUS NullFile::WriteSync(std::span<const uint8_t> buffer, size_t byte_offset,
|
||||
size_t* out_bytes_written) {
|
||||
(void)buffer;
|
||||
(void)byte_offset;
|
||||
(void)out_bytes_written;
|
||||
if (!(file_access_ & (FileAccess::kFileWriteData | FileAccess::kFileAppendData))) {
|
||||
return X_STATUS_ACCESS_DENIED;
|
||||
}
|
||||
|
|
@ -40,6 +46,7 @@ X_STATUS NullFile::WriteSync(std::span<const uint8_t> buffer, size_t byte_offset
|
|||
}
|
||||
|
||||
X_STATUS NullFile::SetLength(size_t length) {
|
||||
(void)length;
|
||||
if (!(file_access_ & FileAccess::kFileWriteData)) {
|
||||
return X_STATUS_ACCESS_DENIED;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,10 @@
|
|||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <rex/filesystem/vfs.h>
|
||||
#include <rex/logging.h>
|
||||
#include <rex/string.h>
|
||||
|
|
@ -20,8 +24,67 @@ REXCVAR_DEFINE_BOOL(allow_game_relative_writes, false, "Filesystem",
|
|||
"relative to game://. Used for "
|
||||
"generating test data to compare with original hardware.");
|
||||
|
||||
REXCVAR_DEFINE_INT32(filesystem_debug_log_fe_asset_ops_remaining, 0, "Filesystem",
|
||||
"Log FE/import-skater-related file opens and reads for this many operations")
|
||||
.range(0, 10000)
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload)
|
||||
.debug_only();
|
||||
|
||||
REXCVAR_DEFINE_INT32(filesystem_debug_log_team_profile_background_remaining, 200, "Filesystem",
|
||||
"Log team_profile_background_0.rx2 file opens and reads for this many "
|
||||
"operations")
|
||||
.range(0, 10000)
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload)
|
||||
.debug_only();
|
||||
|
||||
namespace rex::filesystem {
|
||||
|
||||
namespace {
|
||||
|
||||
bool LooksLikeSkaterPreviewAssetPath(std::string_view path) {
|
||||
std::string lower(path);
|
||||
std::transform(lower.begin(), lower.end(), lower.begin(),
|
||||
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
static constexpr std::string_view kNeedles[] = {
|
||||
"import_skater", "team_management", "preset", "skater",
|
||||
"preview", "fedynamic", "fedata", "fetexture",
|
||||
"createacharacter", "db.big", "data/fe", "data\\fe",
|
||||
};
|
||||
for (std::string_view needle : kNeedles) {
|
||||
if (lower.find(needle) != std::string::npos) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LooksLikeTeamProfileBackgroundPath(std::string_view path) {
|
||||
std::string lower(path);
|
||||
std::transform(lower.begin(), lower.end(), lower.begin(),
|
||||
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
return lower.find("team_profile_background_0") != std::string::npos;
|
||||
}
|
||||
|
||||
bool ConsumeFeAssetLogBudget() {
|
||||
const int32_t remaining = REXCVAR_GET(filesystem_debug_log_fe_asset_ops_remaining);
|
||||
if (remaining <= 0) {
|
||||
return false;
|
||||
}
|
||||
REXCVAR_SET(filesystem_debug_log_fe_asset_ops_remaining, remaining - 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConsumeTeamProfileBackgroundLogBudget() {
|
||||
const int32_t remaining = REXCVAR_GET(filesystem_debug_log_team_profile_background_remaining);
|
||||
if (remaining <= 0) {
|
||||
return false;
|
||||
}
|
||||
REXCVAR_SET(filesystem_debug_log_team_profile_background_remaining, remaining - 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
VirtualFileSystem::VirtualFileSystem() {}
|
||||
|
||||
VirtualFileSystem::~VirtualFileSystem() {
|
||||
|
|
@ -225,6 +288,15 @@ X_STATUS VirtualFileSystem::OpenFile(Entry* root_entry, const std::string_view p
|
|||
|
||||
auto file_name = rex::string::utf8_find_name_from_guest_path(path);
|
||||
entry = parent_entry->GetChild(file_name);
|
||||
if (!entry && !root_entry) {
|
||||
// Some virtual overlays are registered for a full path that doesn't exist
|
||||
// under the real parent device. Resolve the full path too so those
|
||||
// symlinks can satisfy opens like d:\data\scene\.
|
||||
entry = ResolvePath(path);
|
||||
if (entry) {
|
||||
parent_entry = entry->parent();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
entry = !root_entry ? ResolvePath(path) : root_entry->GetChild(path);
|
||||
}
|
||||
|
|
@ -279,10 +351,7 @@ X_STATUS VirtualFileSystem::OpenFile(Entry* root_entry, const std::string_view p
|
|||
desired_access = FileAccess::kGenericRead | FileAccess::kFileReadData;
|
||||
}
|
||||
|
||||
bool created = false;
|
||||
if (!entry) {
|
||||
// Remember that we are creating this new, instead of replacing.
|
||||
created = true;
|
||||
*out_action = FileAction::kCreated;
|
||||
} else {
|
||||
// May need to delete, if it exists.
|
||||
|
|
@ -327,6 +396,20 @@ X_STATUS VirtualFileSystem::OpenFile(Entry* root_entry, const std::string_view p
|
|||
|
||||
// Open.
|
||||
auto result = entry->Open(desired_access, out_file);
|
||||
if (LooksLikeTeamProfileBackgroundPath(path) && ConsumeTeamProfileBackgroundLogBudget()) {
|
||||
REXFS_WARN(
|
||||
"Team profile BG diagnostic: op=open, path='{}', access={:#x}, disposition={}, action={}, "
|
||||
"status={:#x}, entry='{}'",
|
||||
path, desired_access, static_cast<int>(creation_disposition), static_cast<int>(*out_action),
|
||||
result, entry ? entry->absolute_path() : std::string_view("<null>"));
|
||||
}
|
||||
if (LooksLikeSkaterPreviewAssetPath(path) && ConsumeFeAssetLogBudget()) {
|
||||
REXFS_WARN(
|
||||
"FE asset diagnostic: op=open, path='{}', access={:#x}, disposition={}, action={}, "
|
||||
"status={:#x}, entry='{}'",
|
||||
path, desired_access, static_cast<int>(creation_disposition), static_cast<int>(*out_action),
|
||||
result, entry ? entry->absolute_path() : std::string_view("<null>"));
|
||||
}
|
||||
if (XFAILED(result)) {
|
||||
*out_action = FileAction::kDoesNotExist;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,21 @@ REXCVAR_DEFINE_BOOL(clear_memory_page_state, false, "GPU",
|
|||
"Disable for minor CPU overhead reduction, but may break memory coherency.")
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
|
||||
REXCVAR_DEFINE_BOOL(draw_resolution_scaled_texture_offsets, true, "GPU/Shader",
|
||||
"Scale texture offsets with draw resolution");
|
||||
|
||||
REXCVAR_DEFINE_BOOL(scaled_resolve_texture_loads_from_shared_memory, false, "GPU/Texture",
|
||||
"Materialize scaled render-to-texture resolves into guest memory on the GPU "
|
||||
"before loading them as regular textures")
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
|
||||
REXCVAR_DEFINE_INT32(scaled_resolve_texture_loads_from_shared_memory_max_pixels, 1152 * 640,
|
||||
"GPU/Texture",
|
||||
"Maximum unscaled pixel count for GPU materialization of scaled "
|
||||
"render-to-texture resolves into guest memory")
|
||||
.range(1, 8192 * 8192)
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
|
||||
REXCVAR_DEFINE_BOOL(occlusion_query_enable, true, "GPU", "Enable host occlusion query handling")
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
|
||||
|
|
@ -50,8 +65,9 @@ REXCVAR_DEFINE_STRING(readback_resolve, "none", "GPU",
|
|||
" none: Disable readback (default)\n"
|
||||
" fast: Read previous frame (delayed, copy every frame)\n"
|
||||
" some: Read previous frame (delayed, copy on cache miss)\n"
|
||||
" full: Immediate sync readback (accurate but stalls)")
|
||||
.allowed({"none", "fast", "some", "full"})
|
||||
" full: Immediate sync readback (accurate but stalls)\n"
|
||||
" auto: Backend-specific adaptive policy")
|
||||
.allowed({"none", "fast", "some", "full", "auto"})
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
|
||||
REXCVAR_DEFINE_BOOL(readback_resolve_half_pixel_offset, false, "GPU",
|
||||
|
|
@ -59,6 +75,19 @@ REXCVAR_DEFINE_BOOL(readback_resolve_half_pixel_offset, false, "GPU",
|
|||
"scaled block during resolve readback downscale")
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
|
||||
REXCVAR_DEFINE_BOOL(scaled_resolve_texture_loads_update_guest_memory, true, "GPU",
|
||||
"When scaled resolve texture loads are materialized into shared memory, also "
|
||||
"mirror the downscaled data into guest memory for CPU-visible coherency")
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
|
||||
REXCVAR_DEFINE_INT32(scaled_resolve_small_texture_readback_max_length, 0x2D0000, "GPU/Texture",
|
||||
"Maximum unscaled byte length of a scaled render-to-texture resolve that "
|
||||
"will be immediately read back even when readback_resolve is disabled. "
|
||||
"This is intended for small UI thumbnails that depend on CPU-visible "
|
||||
"resolved data without forcing full-screen resolve readbacks.")
|
||||
.range(0, 16 * 1024 * 1024)
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
|
||||
REXCVAR_DEFINE_BOOL(readback_memexport, false, "GPU",
|
||||
"Enable CPU readback of shader memexport writes for guest memory "
|
||||
"coherency (can reduce correctness issues, but may add GPU/CPU sync cost)")
|
||||
|
|
@ -96,6 +125,9 @@ ReadbackResolveMode ParseReadbackResolveMode(std::string_view value) {
|
|||
if (value == "full") {
|
||||
return ReadbackResolveMode::kFull;
|
||||
}
|
||||
if (value == "auto") {
|
||||
return ReadbackResolveMode::kAuto;
|
||||
}
|
||||
return ReadbackResolveMode::kDisabled;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
#include <rex/kernel/xboxkrnl/video.h>
|
||||
#include <rex/logging.h>
|
||||
#include <rex/memory/utils.h>
|
||||
#include <rex/system/kernel_state.h>
|
||||
#include <rex/ui/d3d12/d3d12_presenter.h>
|
||||
#include <rex/ui/d3d12/d3d12_util.h>
|
||||
|
||||
|
|
@ -52,6 +53,14 @@ namespace rex::graphics::d3d12 {
|
|||
|
||||
namespace {
|
||||
|
||||
bool IsGameplayStateActive(const system::KernelState* kernel_state) {
|
||||
if (!kernel_state) {
|
||||
return false;
|
||||
}
|
||||
auto gameplay_context = kernel_state->GetUserContext(0, 0x8001);
|
||||
return gameplay_context && *gameplay_context == 1;
|
||||
}
|
||||
|
||||
#ifdef REXGLUE_ENABLE_PERF_COUNTERS
|
||||
class ScopedDrawStageTimer {
|
||||
public:
|
||||
|
|
@ -3134,7 +3143,9 @@ bool D3D12CommandProcessor::IssueCopy() {
|
|||
return false;
|
||||
}
|
||||
ReadbackResolveMode readback_mode = GetReadbackResolveMode(REXCVAR_GET(d3d12_readback_resolve));
|
||||
if (readback_mode == ReadbackResolveMode::kDisabled) {
|
||||
const bool gameplay_state_active = IsGameplayStateActive(kernel_state_);
|
||||
if (readback_mode == ReadbackResolveMode::kDisabled &&
|
||||
(!texture_cache_->IsDrawResolutionScaled() || gameplay_state_active)) {
|
||||
uint32_t written_address, written_length;
|
||||
return render_target_cache_->Resolve(*memory_, *shared_memory_, *texture_cache_,
|
||||
written_address, written_length);
|
||||
|
|
@ -3160,6 +3171,20 @@ bool D3D12CommandProcessor::IssueCopy_ReadbackResolvePath() {
|
|||
}
|
||||
|
||||
bool is_scaled = texture_cache_->IsDrawResolutionScaled();
|
||||
ReadbackResolveMode readback_mode = GetReadbackResolveMode(REXCVAR_GET(d3d12_readback_resolve));
|
||||
if (readback_mode == ReadbackResolveMode::kDisabled) {
|
||||
constexpr uint32_t kImportSkaterPreviewResolveAddress = UINT32_C(0x04911000);
|
||||
constexpr uint32_t kImportSkaterPreviewResolveLength = UINT32_C(0x2D0000);
|
||||
const bool force_scaled_resolve_cpu_copy =
|
||||
!IsGameplayStateActive(kernel_state_) && is_scaled &&
|
||||
written_address == kImportSkaterPreviewResolveAddress &&
|
||||
written_length == kImportSkaterPreviewResolveLength;
|
||||
if (!force_scaled_resolve_cpu_copy) {
|
||||
return true;
|
||||
}
|
||||
readback_mode = ReadbackResolveMode::kFull;
|
||||
}
|
||||
|
||||
uint64_t resolve_key = MakeReadbackResolveKey(written_address, written_length);
|
||||
ReadbackBuffer& rb = readback_buffers_[resolve_key];
|
||||
rb.last_used_frame = frame_current_;
|
||||
|
|
@ -3327,7 +3352,6 @@ bool D3D12CommandProcessor::IssueCopy_ReadbackResolvePath() {
|
|||
written_address, written_length);
|
||||
}
|
||||
|
||||
ReadbackResolveMode readback_mode = GetReadbackResolveMode(REXCVAR_GET(d3d12_readback_resolve));
|
||||
bool use_delayed_sync =
|
||||
readback_mode == ReadbackResolveMode::kFast || readback_mode == ReadbackResolveMode::kSome;
|
||||
uint32_t read_index = write_index;
|
||||
|
|
|
|||
|
|
@ -1333,6 +1333,9 @@ bool D3D12TextureCache::MakeScaledResolveRangeCurrent(uint32_t start_unscaled,
|
|||
void D3D12TextureCache::TransitionCurrentScaledResolveRange(D3D12_RESOURCE_STATES new_state) {
|
||||
assert_true(IsDrawResolutionScaled());
|
||||
ScaledResolveVirtualBuffer& buffer = GetCurrentScaledResolveBuffer();
|
||||
if (buffer.ConsumeUAVBarrierPending()) {
|
||||
command_processor_.PushUAVBarrier(buffer.resource());
|
||||
}
|
||||
command_processor_.PushTransitionBarrier(buffer.resource(), buffer.SetResourceState(new_state),
|
||||
new_state);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,9 +24,6 @@
|
|||
#include <rex/math.h>
|
||||
#include <rex/string.h>
|
||||
|
||||
REXCVAR_DEFINE_BOOL(draw_resolution_scaled_texture_offsets, true, "GPU/Shader",
|
||||
"Scale texture offsets with draw resolution");
|
||||
|
||||
namespace rex::graphics {
|
||||
using namespace ucode;
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@
|
|||
#include <fmt/format.h>
|
||||
|
||||
#include <rex/assert.h>
|
||||
#include <rex/cvar.h>
|
||||
#include <rex/graphics/flags.h>
|
||||
#include <rex/graphics/pipeline/shader/spirv_translator.h>
|
||||
#include <rex/math.h>
|
||||
|
||||
|
|
@ -975,7 +977,10 @@ void SpirvShaderTranslator::ProcessTextureFetchInstruction(
|
|||
}
|
||||
spv::Id texture_resolution_scaled = spv::NoResult;
|
||||
uint32_t revert_resolution_scale_axes =
|
||||
uint32_t(draw_resolution_scale_x_ > 1) | (uint32_t(draw_resolution_scale_y_ > 1) << 1);
|
||||
REXCVAR_GET(draw_resolution_scaled_texture_offsets)
|
||||
? uint32_t(draw_resolution_scale_x_ > 1) |
|
||||
(uint32_t(draw_resolution_scale_y_ > 1) << 1)
|
||||
: 0;
|
||||
spv::Id const_texture_resolution_scale[] = {spv::NoResult, spv::NoResult};
|
||||
spv::Id const_texture_resolution_scale_reciprocal[] = {spv::NoResult, spv::NoResult};
|
||||
if (revert_resolution_scale_axes) {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#include <rex/assert.h>
|
||||
|
|
@ -129,6 +130,8 @@ REXCVAR_DEFINE_BOOL(pre_mask_resolve_l2_block, true, "GPU",
|
|||
|
||||
namespace rex::graphics {
|
||||
|
||||
constexpr uint64_t kDebugTeamProfileBackgroundHash = UINT64_C(0x2C31D77D35F4EC6F);
|
||||
|
||||
const TextureCache::LoadShaderInfo TextureCache::load_shader_info_[kLoadShaderCount] = {
|
||||
// k8bpb
|
||||
{3, 4, 1, 4},
|
||||
|
|
@ -464,6 +467,7 @@ bool TextureCache::CommitPreparedTextureLoad(const PendingTextureLoad& pending_l
|
|||
// not up to date anymore.
|
||||
texture.MakeUpToDateAndWatch(global_critical_region_.Acquire());
|
||||
texture.LogAction("Loaded");
|
||||
DebugLogTeamProfileBackgroundTextureCandidate("loaded", UINT32_MAX, texture);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -490,6 +494,7 @@ void TextureCache::RequestTextures(uint32_t used_texture_mask) {
|
|||
|
||||
uint32_t textures_remaining = used_texture_mask & ~texture_bindings_in_sync_;
|
||||
if (!textures_remaining) {
|
||||
DebugLogScaledTextureBindings(used_texture_mask);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -633,6 +638,10 @@ void TextureCache::RequestTextures(uint32_t used_texture_mask) {
|
|||
|
||||
if (batched_shared_memory_request_succeeded) {
|
||||
for (size_t i = 0; i < pending_texture_load_count; ++i) {
|
||||
if (pending_texture_loads[i].texture != nullptr) {
|
||||
DebugLogTeamProfileBackgroundTextureCandidate("commit-load", UINT32_MAX,
|
||||
*pending_texture_loads[i].texture);
|
||||
}
|
||||
CommitPreparedTextureLoad(pending_texture_loads[i]);
|
||||
}
|
||||
} else {
|
||||
|
|
@ -653,6 +662,390 @@ void TextureCache::RequestTextures(uint32_t used_texture_mask) {
|
|||
#endif
|
||||
UpdateTextureBindingsImpl(bindings_changed);
|
||||
}
|
||||
DebugLogScaledTextureBindings(used_texture_mask);
|
||||
}
|
||||
|
||||
void TextureCache::DebugRecordResolveReadback(uint32_t start, uint32_t length, bool scaled) {
|
||||
if (!length) {
|
||||
return;
|
||||
}
|
||||
|
||||
DebugResolveReadbackRange& range =
|
||||
debug_recent_resolve_readbacks_[debug_recent_resolve_readback_next_];
|
||||
range.start = start;
|
||||
range.length = length;
|
||||
range.serial = ++debug_resolve_readback_serial_;
|
||||
range.scaled = scaled;
|
||||
debug_recent_resolve_readback_next_ =
|
||||
(debug_recent_resolve_readback_next_ + 1) % debug_recent_resolve_readbacks_.size();
|
||||
debug_recent_resolve_readback_count_ =
|
||||
std::min<uint32_t>(debug_recent_resolve_readback_count_ + 1,
|
||||
uint32_t(debug_recent_resolve_readbacks_.size()));
|
||||
|
||||
int32_t remaining = REXCVAR_GET(vulkan_debug_log_frame_summaries_remaining);
|
||||
int32_t interval = std::max(1, REXCVAR_GET(vulkan_debug_frame_summary_interval_frames));
|
||||
bool log_this_sample = remaining == 36000 || remaining == 3600 || (remaining % interval) == 0;
|
||||
if (remaining > 0 && log_this_sample) {
|
||||
REXGPU_WARN("Vulkan debug resolve readback full copy: serial={}, scaled={}, range={:08X}+{:X}",
|
||||
range.serial, scaled, start, length);
|
||||
}
|
||||
}
|
||||
|
||||
void TextureCache::DebugClearRecentResolveReadbacks() {
|
||||
for (DebugResolveReadbackRange& range : debug_recent_resolve_readbacks_) {
|
||||
range = {};
|
||||
}
|
||||
debug_recent_resolve_readback_next_ = 0;
|
||||
debug_recent_resolve_readback_count_ = 0;
|
||||
}
|
||||
|
||||
void TextureCache::DebugRecordTeamProfileBackgroundResolveRange(uint32_t start, uint32_t length,
|
||||
bool scaled,
|
||||
uint64_t scaled_start,
|
||||
uint64_t scaled_length) {
|
||||
if (!length) {
|
||||
return;
|
||||
}
|
||||
|
||||
DebugTeamProfileResolveRange& range =
|
||||
debug_recent_team_profile_resolves_[debug_recent_team_profile_resolve_next_];
|
||||
range.start = start;
|
||||
range.length = length;
|
||||
range.serial = ++debug_team_profile_resolve_serial_;
|
||||
range.scaled = scaled;
|
||||
range.scaled_start = scaled_start;
|
||||
range.scaled_length = scaled_length;
|
||||
debug_recent_team_profile_resolve_next_ =
|
||||
(debug_recent_team_profile_resolve_next_ + 1) %
|
||||
debug_recent_team_profile_resolves_.size();
|
||||
debug_recent_team_profile_resolve_count_ =
|
||||
std::min<uint32_t>(debug_recent_team_profile_resolve_count_ + 1,
|
||||
uint32_t(debug_recent_team_profile_resolves_.size()));
|
||||
if (!debug_team_profile_resolve_texture_logs_remaining_) {
|
||||
debug_team_profile_resolve_texture_logs_remaining_ = 32;
|
||||
}
|
||||
|
||||
REXGPU_WARN(
|
||||
"Team profile BG resolve range recorded: serial={}, scaled={}, range={:08X}+{:X}, "
|
||||
"scaled_range={:X}+{:X}",
|
||||
range.serial, scaled, start, length, scaled_start, scaled_length);
|
||||
}
|
||||
|
||||
bool TextureCache::DebugIsTeamProfileBackgroundCandidateKey(const TextureKey& key) {
|
||||
return key.dimension == xenos::DataDimension::k2DOrStacked && key.GetWidth() == 128 &&
|
||||
key.GetHeight() == 256 && key.GetDepthOrArraySize() == 1 &&
|
||||
key.format == xenos::TextureFormat::k_DXT4_5;
|
||||
}
|
||||
|
||||
bool TextureCache::DebugIsTeamProfileBackgroundTexture(const Texture& texture) const {
|
||||
const TextureKey& key = texture.key();
|
||||
if (!DebugIsTeamProfileBackgroundCandidateKey(key) || key.scaled_resolve) {
|
||||
return false;
|
||||
}
|
||||
const uint8_t* data = shared_memory().DebugTranslatePhysical(key.base_page << 12);
|
||||
const uint32_t sample_length = std::min<uint32_t>(texture.GetGuestBaseSize(), UINT32_C(0x8000));
|
||||
if (data == nullptr || sample_length == 0) {
|
||||
return false;
|
||||
}
|
||||
uint64_t fnv1a = UINT64_C(14695981039346656037);
|
||||
for (uint32_t i = 0; i < sample_length; ++i) {
|
||||
fnv1a ^= data[i];
|
||||
fnv1a *= UINT64_C(1099511628211);
|
||||
}
|
||||
return fnv1a == kDebugTeamProfileBackgroundHash;
|
||||
}
|
||||
|
||||
bool TextureCache::DebugFindTeamProfileBackgroundBinding(uint32_t used_texture_mask,
|
||||
uint32_t& fetch_index_out,
|
||||
bool& signed_view_out,
|
||||
uint32_t* texture_base_out,
|
||||
uint32_t* texture_length_out) const {
|
||||
uint32_t textures_remaining = used_texture_mask;
|
||||
uint32_t index = 0;
|
||||
while (rex::bit_scan_forward(textures_remaining, &index)) {
|
||||
textures_remaining &= ~(UINT32_C(1) << index);
|
||||
const TextureBinding* binding = GetValidTextureBinding(index);
|
||||
if (binding == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (binding->texture != nullptr && DebugIsTeamProfileBackgroundTexture(*binding->texture)) {
|
||||
fetch_index_out = index;
|
||||
signed_view_out = false;
|
||||
if (texture_base_out != nullptr) {
|
||||
*texture_base_out = binding->texture->key().base_page << 12;
|
||||
}
|
||||
if (texture_length_out != nullptr) {
|
||||
*texture_length_out = binding->texture->GetGuestBaseSize();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (binding->texture_signed != nullptr &&
|
||||
DebugIsTeamProfileBackgroundTexture(*binding->texture_signed)) {
|
||||
fetch_index_out = index;
|
||||
signed_view_out = true;
|
||||
if (texture_base_out != nullptr) {
|
||||
*texture_base_out = binding->texture_signed->key().base_page << 12;
|
||||
}
|
||||
if (texture_length_out != nullptr) {
|
||||
*texture_length_out = binding->texture_signed->GetGuestBaseSize();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void TextureCache::DebugLogTeamProfileBackgroundTextureCandidate(const char* action,
|
||||
uint32_t fetch_index,
|
||||
const Texture& texture) {
|
||||
const TextureKey& key = texture.key();
|
||||
if (!DebugIsTeamProfileBackgroundCandidateKey(key)) {
|
||||
return;
|
||||
}
|
||||
const bool is_bind_action = std::string_view(action).starts_with("bind");
|
||||
const int32_t remaining = is_bind_action
|
||||
? REXCVAR_GET(
|
||||
vulkan_debug_log_team_profile_background_bindings_remaining)
|
||||
: REXCVAR_GET(
|
||||
vulkan_debug_log_team_profile_background_candidates_remaining);
|
||||
if (remaining <= 0) {
|
||||
return;
|
||||
}
|
||||
if (is_bind_action) {
|
||||
REXCVAR_SET(vulkan_debug_log_team_profile_background_bindings_remaining, remaining - 1);
|
||||
} else {
|
||||
REXCVAR_SET(vulkan_debug_log_team_profile_background_candidates_remaining, remaining - 1);
|
||||
}
|
||||
|
||||
uint32_t sample_length = 0;
|
||||
uint32_t nonzero_count = 0;
|
||||
uint64_t fnv1a = UINT64_C(14695981039346656037);
|
||||
uint8_t head[8] = {};
|
||||
if (!is_bind_action) {
|
||||
const uint8_t* data = shared_memory().DebugTranslatePhysical(key.base_page << 12);
|
||||
sample_length = std::min<uint32_t>(texture.GetGuestBaseSize(), UINT32_C(0x8000));
|
||||
if (data != nullptr && sample_length != 0) {
|
||||
for (uint32_t i = 0; i < sample_length; ++i) {
|
||||
uint8_t value = data[i];
|
||||
nonzero_count += value != 0;
|
||||
fnv1a ^= value;
|
||||
fnv1a *= UINT64_C(1099511628211);
|
||||
}
|
||||
std::memcpy(head, data, std::min<size_t>(sizeof(head), sample_length));
|
||||
}
|
||||
}
|
||||
|
||||
REXGPU_WARN(
|
||||
"Team profile BG texture candidate: action={}, fetch={}, scaled={}, {} {}x{}x{} {}, "
|
||||
"base={:08X}+{:X}, mips={:08X}+{:X}, pitch={}, mip_max={}, packed_mips={}, "
|
||||
"sample_len={:X}, nonzero={}, hash={:016X}, head={:02X}{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}",
|
||||
action, fetch_index, bool(key.scaled_resolve), key.tiled ? "tiled" : "linear",
|
||||
key.GetWidth(), key.GetHeight(), key.GetDepthOrArraySize(),
|
||||
FormatInfo::Get(key.format)->name, key.base_page << 12, texture.GetGuestBaseSize(),
|
||||
key.mip_page << 12, texture.GetGuestMipsSize(), key.pitch << 5, key.mip_max_level,
|
||||
bool(key.packed_mips), sample_length, nonzero_count, fnv1a, head[0], head[1], head[2],
|
||||
head[3], head[4], head[5], head[6], head[7]);
|
||||
}
|
||||
|
||||
bool TextureCache::DebugTextureOverlapsRecentResolveReadback(
|
||||
const Texture& texture, bool mips, uint32_t& readback_serial_out,
|
||||
bool& readback_scaled_out) const {
|
||||
const TextureKey& key = texture.key();
|
||||
uint32_t texture_start = (mips ? key.mip_page : key.base_page) << 12;
|
||||
uint32_t texture_length = mips ? texture.GetGuestMipsSize() : texture.GetGuestBaseSize();
|
||||
if (!texture_length) {
|
||||
return false;
|
||||
}
|
||||
uint64_t texture_end = uint64_t(texture_start) + texture_length;
|
||||
for (uint32_t i = 0; i < debug_recent_resolve_readback_count_; ++i) {
|
||||
const DebugResolveReadbackRange& readback = debug_recent_resolve_readbacks_[i];
|
||||
if (!readback.length) {
|
||||
continue;
|
||||
}
|
||||
uint64_t readback_end = uint64_t(readback.start) + readback.length;
|
||||
if (uint64_t(texture_start) < readback_end && uint64_t(readback.start) < texture_end) {
|
||||
readback_serial_out = readback.serial;
|
||||
readback_scaled_out = readback.scaled;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TextureCache::DebugRangeOverlapsRecentScaledResolveReadback(uint32_t start,
|
||||
uint32_t length) const {
|
||||
if (!length) {
|
||||
return false;
|
||||
}
|
||||
uint64_t end = uint64_t(start) + length;
|
||||
for (uint32_t i = 0; i < debug_recent_resolve_readback_count_; ++i) {
|
||||
const DebugResolveReadbackRange& readback = debug_recent_resolve_readbacks_[i];
|
||||
if (!readback.length || !readback.scaled) {
|
||||
continue;
|
||||
}
|
||||
uint64_t readback_end = uint64_t(readback.start) + readback.length;
|
||||
if (uint64_t(start) < readback_end && uint64_t(readback.start) < end) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TextureCache::DebugTextureOverlapsRecentTeamProfileBackgroundResolve(
|
||||
const Texture& texture, bool mips, uint32_t& resolve_serial_out, bool& resolve_scaled_out,
|
||||
uint64_t& resolve_scaled_start_out, uint64_t& resolve_scaled_length_out) const {
|
||||
const TextureKey& key = texture.key();
|
||||
uint32_t texture_start = (mips ? key.mip_page : key.base_page) << 12;
|
||||
uint32_t texture_length = mips ? texture.GetGuestMipsSize() : texture.GetGuestBaseSize();
|
||||
if (!texture_length) {
|
||||
return false;
|
||||
}
|
||||
uint64_t texture_end = uint64_t(texture_start) + texture_length;
|
||||
bool found = false;
|
||||
for (uint32_t i = 0; i < debug_recent_team_profile_resolve_count_; ++i) {
|
||||
const DebugTeamProfileResolveRange& resolve = debug_recent_team_profile_resolves_[i];
|
||||
if (!resolve.length) {
|
||||
continue;
|
||||
}
|
||||
uint64_t resolve_end = uint64_t(resolve.start) + resolve.length;
|
||||
if (uint64_t(texture_start) < resolve_end && uint64_t(resolve.start) < texture_end) {
|
||||
if (found && resolve.serial <= resolve_serial_out) {
|
||||
continue;
|
||||
}
|
||||
resolve_serial_out = resolve.serial;
|
||||
resolve_scaled_out = resolve.scaled;
|
||||
resolve_scaled_start_out = resolve.scaled_start;
|
||||
resolve_scaled_length_out = resolve.scaled_length;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
void TextureCache::DebugLogScaledTextureBindings(uint32_t used_texture_mask) {
|
||||
if (used_texture_mask == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (REXCVAR_GET(vulkan_debug_log_team_profile_background_candidates_remaining) > 0 ||
|
||||
REXCVAR_GET(vulkan_debug_log_team_profile_background_bindings_remaining) > 0) {
|
||||
uint32_t textures_remaining = used_texture_mask;
|
||||
uint32_t index = 0;
|
||||
while (rex::bit_scan_forward(textures_remaining, &index)) {
|
||||
textures_remaining &= ~(UINT32_C(1) << index);
|
||||
const TextureBinding* binding = GetValidTextureBinding(index);
|
||||
if (binding == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (binding->texture) {
|
||||
DebugLogTeamProfileBackgroundTextureCandidate("bind", index, *binding->texture);
|
||||
}
|
||||
if (binding->texture_signed) {
|
||||
DebugLogTeamProfileBackgroundTextureCandidate("bind-signed", index,
|
||||
*binding->texture_signed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32_t remaining = REXCVAR_GET(vulkan_debug_log_frame_summaries_remaining);
|
||||
bool log_team_profile_overlaps = debug_team_profile_resolve_texture_logs_remaining_ != 0;
|
||||
if (remaining <= 0 && !log_team_profile_overlaps) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t interval = std::max(1, REXCVAR_GET(vulkan_debug_frame_summary_interval_frames));
|
||||
bool log_this_sample = remaining == 36000 || remaining == 3600 || (remaining % interval) == 0;
|
||||
if (remaining <= 0) {
|
||||
log_this_sample = false;
|
||||
}
|
||||
if (!log_this_sample && !log_team_profile_overlaps) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (remaining != debug_scaled_texture_bindings_remaining_seen_) {
|
||||
debug_scaled_texture_bindings_remaining_seen_ = remaining;
|
||||
debug_scaled_texture_binding_logs_this_sample_ = 0;
|
||||
}
|
||||
constexpr uint32_t kMaxScaledTextureBindingLogsPerSample = 64;
|
||||
if (debug_scaled_texture_binding_logs_this_sample_ >= kMaxScaledTextureBindingLogsPerSample) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto log_texture = [&](uint32_t fetch_index, const char* view_name, const Texture* texture) {
|
||||
if (texture == nullptr ||
|
||||
debug_scaled_texture_binding_logs_this_sample_ >= kMaxScaledTextureBindingLogsPerSample ||
|
||||
(!log_this_sample && debug_team_profile_resolve_texture_logs_remaining_ == 0)) {
|
||||
return;
|
||||
}
|
||||
const TextureKey& key = texture->key();
|
||||
uint32_t base_readback_serial = 0;
|
||||
uint32_t mips_readback_serial = 0;
|
||||
bool base_readback_scaled = false;
|
||||
bool mips_readback_scaled = false;
|
||||
bool overlaps_base_readback = DebugTextureOverlapsRecentResolveReadback(
|
||||
*texture, false, base_readback_serial, base_readback_scaled);
|
||||
bool overlaps_mips_readback = DebugTextureOverlapsRecentResolveReadback(
|
||||
*texture, true, mips_readback_serial, mips_readback_scaled);
|
||||
uint32_t base_team_profile_serial = 0;
|
||||
uint32_t mips_team_profile_serial = 0;
|
||||
bool base_team_profile_scaled = false;
|
||||
bool mips_team_profile_scaled = false;
|
||||
uint64_t base_team_profile_scaled_start = 0;
|
||||
uint64_t base_team_profile_scaled_length = 0;
|
||||
uint64_t mips_team_profile_scaled_start = 0;
|
||||
uint64_t mips_team_profile_scaled_length = 0;
|
||||
bool overlaps_base_team_profile = DebugTextureOverlapsRecentTeamProfileBackgroundResolve(
|
||||
*texture, false, base_team_profile_serial, base_team_profile_scaled,
|
||||
base_team_profile_scaled_start, base_team_profile_scaled_length);
|
||||
bool overlaps_mips_team_profile = DebugTextureOverlapsRecentTeamProfileBackgroundResolve(
|
||||
*texture, true, mips_team_profile_serial, mips_team_profile_scaled,
|
||||
mips_team_profile_scaled_start, mips_team_profile_scaled_length);
|
||||
if (!key.scaled_resolve && !overlaps_base_readback && !overlaps_mips_readback &&
|
||||
!overlaps_base_team_profile && !overlaps_mips_team_profile) {
|
||||
return;
|
||||
}
|
||||
if (!log_this_sample && !(overlaps_base_team_profile || overlaps_mips_team_profile)) {
|
||||
return;
|
||||
}
|
||||
++debug_scaled_texture_binding_logs_this_sample_;
|
||||
if (overlaps_base_team_profile || overlaps_mips_team_profile) {
|
||||
--debug_team_profile_resolve_texture_logs_remaining_;
|
||||
}
|
||||
REXGPU_WARN(
|
||||
"Vulkan debug texture binding: remaining={}, fetch={}, view={}, scaled={}, {} {}x{}x{} "
|
||||
"{}, base={:08X}+{:X}, mips={:08X}+{:X}, pitch={}, host={}x{}, readback_base=#{} {}, "
|
||||
"readback_mips=#{} {}, team_profile_base=#{} {} {:X}+{:X}, "
|
||||
"team_profile_mips=#{} {} {:X}+{:X}",
|
||||
remaining, fetch_index, view_name, bool(key.scaled_resolve),
|
||||
key.tiled ? "tiled" : "linear", key.GetWidth(), key.GetHeight(),
|
||||
key.GetDepthOrArraySize(), FormatInfo::Get(key.format)->name,
|
||||
key.base_page << 12, texture->GetGuestBaseSize(), key.mip_page << 12,
|
||||
texture->GetGuestMipsSize(), key.pitch << 5,
|
||||
key.GetWidth() * (key.scaled_resolve ? draw_resolution_scale_x_ : 1),
|
||||
key.GetHeight() * (key.scaled_resolve ? draw_resolution_scale_y_ : 1),
|
||||
base_readback_serial, overlaps_base_readback ? (base_readback_scaled ? "scaled" : "1x")
|
||||
: "none",
|
||||
mips_readback_serial, overlaps_mips_readback ? (mips_readback_scaled ? "scaled" : "1x")
|
||||
: "none",
|
||||
base_team_profile_serial,
|
||||
overlaps_base_team_profile ? (base_team_profile_scaled ? "scaled" : "1x") : "none",
|
||||
base_team_profile_scaled_start, base_team_profile_scaled_length,
|
||||
mips_team_profile_serial,
|
||||
overlaps_mips_team_profile ? (mips_team_profile_scaled ? "scaled" : "1x") : "none",
|
||||
mips_team_profile_scaled_start, mips_team_profile_scaled_length);
|
||||
};
|
||||
|
||||
uint32_t textures_remaining = used_texture_mask;
|
||||
uint32_t index = 0;
|
||||
while (rex::bit_scan_forward(textures_remaining, &index)) {
|
||||
textures_remaining &= ~(UINT32_C(1) << index);
|
||||
const TextureBinding* binding = GetValidTextureBinding(index);
|
||||
if (binding == nullptr) {
|
||||
continue;
|
||||
}
|
||||
log_texture(index, "unsigned", binding->texture);
|
||||
log_texture(index, "signed", binding->texture_signed);
|
||||
}
|
||||
}
|
||||
|
||||
const char* TextureCache::TextureKey::GetLogDimensionName(xenos::DataDimension dimension) {
|
||||
|
|
@ -795,7 +1188,12 @@ void TextureCache::Texture::MarkAsUsed() {
|
|||
}
|
||||
|
||||
void TextureCache::Texture::WatchCallback(
|
||||
[[maybe_unused]] const std::unique_lock<std::recursive_mutex>& global_lock, bool is_mip) {
|
||||
[[maybe_unused]] const std::unique_lock<std::recursive_mutex>& global_lock, bool is_mip,
|
||||
bool invalidated_by_gpu) {
|
||||
texture_cache().DebugLogTeamProfileBackgroundTextureCandidate(
|
||||
invalidated_by_gpu ? (is_mip ? "invalidate-gpu-mips" : "invalidate-gpu-base")
|
||||
: (is_mip ? "invalidate-cpu-mips" : "invalidate-cpu-base"),
|
||||
UINT32_MAX, *this);
|
||||
if (is_mip) {
|
||||
assert_not_zero(GetGuestMipsSize());
|
||||
mips_outdated_ = true;
|
||||
|
|
@ -813,7 +1211,7 @@ void TextureCache::WatchCallback(const std::unique_lock<std::recursive_mutex>& g
|
|||
void* context, void* data, uint64_t argument,
|
||||
bool invalidated_by_gpu) {
|
||||
Texture& texture = *static_cast<Texture*>(context);
|
||||
texture.WatchCallback(global_lock, argument != 0);
|
||||
texture.WatchCallback(global_lock, argument != 0, invalidated_by_gpu);
|
||||
texture.texture_cache().texture_became_outdated_.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
|
|
@ -923,6 +1321,8 @@ TextureCache::Texture* TextureCache::FindOrCreateTexture(TextureKey key) {
|
|||
// previously 0, now not 0, to save memory - common case in streaming.
|
||||
auto found_texture_it = textures_.find(key);
|
||||
if (found_texture_it != textures_.end()) {
|
||||
DebugLogTeamProfileBackgroundTextureCandidate("reuse", UINT32_MAX,
|
||||
*found_texture_it->second);
|
||||
return found_texture_it->second.get();
|
||||
}
|
||||
|
||||
|
|
@ -939,6 +1339,7 @@ TextureCache::Texture* TextureCache::FindOrCreateTexture(TextureKey key) {
|
|||
}
|
||||
COUNT_profile_set("gpu/texture_cache/textures", textures_.size());
|
||||
texture->LogAction("Created");
|
||||
DebugLogTeamProfileBackgroundTextureCandidate("create", UINT32_MAX, *texture);
|
||||
return texture;
|
||||
}
|
||||
|
||||
|
|
@ -958,6 +1359,7 @@ bool TextureCache::LoadTextureData(Texture& texture) {
|
|||
return false;
|
||||
}
|
||||
|
||||
DebugLogTeamProfileBackgroundTextureCandidate("load", UINT32_MAX, texture);
|
||||
return CommitPreparedTextureLoad(pending_load);
|
||||
}
|
||||
|
||||
|
|
@ -1125,6 +1527,37 @@ bool TextureCache::IsRangeScaledResolved(uint32_t start_unscaled, uint32_t lengt
|
|||
return false;
|
||||
}
|
||||
|
||||
bool TextureCache::IsRangeFullyScaledResolved(uint32_t start_unscaled, uint32_t length_unscaled) {
|
||||
if (!IsDrawResolutionScaled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
start_unscaled = std::min(start_unscaled, SharedMemory::kBufferSize);
|
||||
length_unscaled = std::min(length_unscaled, SharedMemory::kBufferSize - start_unscaled);
|
||||
if (!length_unscaled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t page_first = start_unscaled >> 12;
|
||||
uint32_t page_last = (start_unscaled + length_unscaled - 1) >> 12;
|
||||
uint32_t block_first = page_first >> 5;
|
||||
uint32_t block_last = page_last >> 5;
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
for (uint32_t block_index = block_first; block_index <= block_last; ++block_index) {
|
||||
uint32_t check_bits = UINT32_MAX;
|
||||
if (block_index == block_first) {
|
||||
check_bits &= ~((UINT32_C(1) << (page_first & 31)) - 1);
|
||||
}
|
||||
if (block_index == block_last && (page_last & 31) != 31) {
|
||||
check_bits &= (UINT32_C(1) << ((page_last & 31) + 1)) - 1;
|
||||
}
|
||||
if ((scaled_resolve_pages_[block_index] & check_bits) != check_bits) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void TextureCache::ScaledResolveGlobalWatchCallbackThunk(
|
||||
const std::unique_lock<std::recursive_mutex>& global_lock, void* context,
|
||||
uint32_t address_first, uint32_t address_last, bool invalidated_by_gpu) {
|
||||
|
|
|
|||
|
|
@ -11,13 +11,17 @@
|
|||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
|
|
@ -42,6 +46,7 @@
|
|||
#include <rex/graphics/vulkan/shared_memory.h>
|
||||
#include <rex/graphics/xenos.h>
|
||||
#include <rex/kernel/xboxkrnl/video.h>
|
||||
#include <rex/system/kernel_state.h>
|
||||
#include <rex/types.h>
|
||||
#include <rex/memory/utils.h>
|
||||
#include <rex/ui/flags.h>
|
||||
|
|
@ -62,6 +67,80 @@ REXCVAR_DEFINE_BOOL(vulkan_async_skip_incomplete_frames, true, "GPU/Vulkan",
|
|||
"used placeholder pipelines to avoid visible flashing")
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
|
||||
REXCVAR_DEFINE_INT32(vulkan_async_placeholder_max_consecutive_skips, 120, "GPU/Vulkan",
|
||||
"Maximum consecutive frames to skip because they used async placeholder "
|
||||
"pipelines before presenting incomplete output anyway (0 for unlimited)")
|
||||
.range(0, 3600)
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
|
||||
REXCVAR_DEFINE_INT32(vulkan_debug_log_frame_summaries_remaining, 0, "GPU/Vulkan",
|
||||
"Track Vulkan per-frame draw summaries for this many frames")
|
||||
.range(0, 36000)
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload)
|
||||
.debug_only();
|
||||
|
||||
REXCVAR_DEFINE_INT32(vulkan_debug_frame_summary_interval_frames, 30, "GPU/Vulkan",
|
||||
"Frame interval for Vulkan debug frame summary logging")
|
||||
.range(1, 600)
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload)
|
||||
.debug_only();
|
||||
|
||||
REXCVAR_DEFINE_INT32(vulkan_debug_log_resolve_decisions_remaining, 0, "GPU/Vulkan",
|
||||
"Log Vulkan render-target resolve/readback decisions for this many copy "
|
||||
"commands")
|
||||
.range(0, 10000)
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload)
|
||||
.debug_only();
|
||||
|
||||
REXCVAR_DEFINE_INT32(vulkan_debug_log_team_profile_background_candidates_remaining, 0,
|
||||
"GPU/Vulkan",
|
||||
"Log texture lifecycle events matching team_profile_background_0.rx2")
|
||||
.range(0, 10000)
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload)
|
||||
.debug_only();
|
||||
|
||||
REXCVAR_DEFINE_INT32(vulkan_debug_log_team_profile_background_bindings_remaining, 0,
|
||||
"GPU/Vulkan",
|
||||
"Log texture binding events matching team_profile_background_0.rx2")
|
||||
.range(0, 10000)
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload)
|
||||
.debug_only();
|
||||
|
||||
REXCVAR_DEFINE_INT32(vulkan_debug_log_team_profile_background_draws_remaining, 0,
|
||||
"GPU/Vulkan",
|
||||
"Log draws sampling the exact team_profile_background_0.rx2 texture")
|
||||
.range(0, 10000)
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload)
|
||||
.debug_only();
|
||||
|
||||
REXCVAR_DEFINE_BOOL(vulkan_sync_after_resolve, false, "GPU/Vulkan",
|
||||
"Synchronously wait for Vulkan render-target resolves without CPU readback")
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload)
|
||||
.debug_only();
|
||||
|
||||
REXCVAR_DEFINE_BOOL(vulkan_barrier_after_scaled_resolve, true, "GPU/Vulkan",
|
||||
"Submit a GPU memory barrier after scaled Vulkan render-target resolves so "
|
||||
"follow-up texture loads can read the scaled resolve buffer without CPU "
|
||||
"readback")
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
|
||||
REXCVAR_DEFINE_INT32(vulkan_readback_resolve_max_length, 0, "GPU/Vulkan",
|
||||
"Maximum resolve byte length eligible for Vulkan CPU readback "
|
||||
"(0 disables the length filter)")
|
||||
.range(0, 0x7FFFFFFF)
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
|
||||
REXCVAR_DEFINE_INT32(vulkan_readback_resolve_min_address, 0, "GPU/Vulkan",
|
||||
"Minimum guest address eligible for Vulkan CPU resolve readback")
|
||||
.range(0, 0x7FFFFFFF)
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
|
||||
REXCVAR_DEFINE_INT32(vulkan_readback_resolve_max_address, 0, "GPU/Vulkan",
|
||||
"Maximum exclusive guest address eligible for Vulkan CPU resolve readback "
|
||||
"(0 disables the address filter)")
|
||||
.range(0, 0x7FFFFFFF)
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
|
||||
REXCVAR_DEFINE_BOOL(vulkan_submit_on_primary_buffer_end, true, "GPU/Vulkan",
|
||||
"Submit command buffer when PM4 primary buffer ends")
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
|
|
@ -79,6 +158,58 @@ namespace rex::graphics::vulkan {
|
|||
|
||||
namespace {
|
||||
|
||||
const char* ReadbackResolveModeName(ReadbackResolveMode mode) {
|
||||
switch (mode) {
|
||||
case ReadbackResolveMode::kDisabled:
|
||||
return "none";
|
||||
case ReadbackResolveMode::kFast:
|
||||
return "fast";
|
||||
case ReadbackResolveMode::kSome:
|
||||
return "some";
|
||||
case ReadbackResolveMode::kFull:
|
||||
return "full";
|
||||
case ReadbackResolveMode::kAuto:
|
||||
return "auto";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
bool IsGameplayStateActive(const system::KernelState* kernel_state) {
|
||||
if (!kernel_state) {
|
||||
return false;
|
||||
}
|
||||
auto gameplay_context = kernel_state->GetUserContext(0, 0x8001);
|
||||
return gameplay_context && *gameplay_context == 1;
|
||||
}
|
||||
|
||||
bool IsInGameContextActive(const system::KernelState* kernel_state) {
|
||||
if (!kernel_state) {
|
||||
return false;
|
||||
}
|
||||
auto title_context = kernel_state->GetUserContext(0, 0x0);
|
||||
return title_context && *title_context != 0;
|
||||
}
|
||||
|
||||
ReadbackResolveMode ResolveAutoReadbackMode(ReadbackResolveMode mode,
|
||||
const system::KernelState* kernel_state) {
|
||||
if (mode != ReadbackResolveMode::kAuto) {
|
||||
return mode;
|
||||
}
|
||||
|
||||
return (!IsGameplayStateActive(kernel_state) && !IsInGameContextActive(kernel_state))
|
||||
? ReadbackResolveMode::kFull
|
||||
: ReadbackResolveMode::kDisabled;
|
||||
}
|
||||
|
||||
bool ConsumeResolveDecisionLogBudget() {
|
||||
const int32_t remaining = REXCVAR_GET(vulkan_debug_log_resolve_decisions_remaining);
|
||||
if (remaining <= 0) {
|
||||
return false;
|
||||
}
|
||||
REXCVAR_SET(vulkan_debug_log_resolve_decisions_remaining, remaining - 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
// glslang default built-in resource limits.
|
||||
constexpr TBuiltInResource kGlslangDefaultTBuiltInResource = {
|
||||
/* .maxLights = */ 32,
|
||||
|
|
@ -188,6 +319,60 @@ constexpr TBuiltInResource kGlslangDefaultTBuiltInResource = {
|
|||
},
|
||||
};
|
||||
|
||||
void DebugDumpResolveReadback(uint32_t address, uint32_t length, bool scaled, const void* data) {
|
||||
if (!data || !length || REXCVAR_GET(vulkan_debug_log_frame_summaries_remaining) <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr uint32_t kMaxDumps = 96;
|
||||
static std::mutex dump_mutex;
|
||||
static std::unordered_set<uint64_t> dumped_ranges;
|
||||
static uint32_t dump_count = 0;
|
||||
|
||||
const uint64_t key = (uint64_t(address) << 32) | length;
|
||||
std::lock_guard lock(dump_mutex);
|
||||
if (dump_count >= kMaxDumps || !dumped_ranges.insert(key).second) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
const std::filesystem::path dump_dir = std::filesystem::path("logs") / "texture_dumps" / "raw";
|
||||
std::filesystem::create_directories(dump_dir, ec);
|
||||
if (ec) {
|
||||
REXGPU_WARN("Failed to create Vulkan resolve readback dump directory: {}", ec.message());
|
||||
return;
|
||||
}
|
||||
|
||||
char filename[160];
|
||||
std::snprintf(filename, sizeof(filename), "resolve_%03u_addr_%08X_len_%X_%s.bin", dump_count,
|
||||
address, length, scaled ? "scaled" : "unscaled");
|
||||
const std::filesystem::path dump_path = dump_dir / filename;
|
||||
|
||||
std::ofstream dump_file(dump_path, std::ios::binary | std::ios::trunc);
|
||||
if (!dump_file) {
|
||||
REXGPU_WARN("Failed to open Vulkan resolve readback dump '{}'", dump_path.string());
|
||||
return;
|
||||
}
|
||||
dump_file.write(reinterpret_cast<const char*>(data), length);
|
||||
if (!dump_file) {
|
||||
REXGPU_WARN("Failed to write Vulkan resolve readback dump '{}'", dump_path.string());
|
||||
return;
|
||||
}
|
||||
|
||||
const std::filesystem::path manifest_path =
|
||||
std::filesystem::path("logs") / "texture_dumps" / "manifest.csv";
|
||||
std::ofstream manifest_file(manifest_path, std::ios::app);
|
||||
if (manifest_file) {
|
||||
manifest_file << dump_count << ",0x" << std::hex << std::uppercase << address << ",0x"
|
||||
<< length << "," << (scaled ? "scaled" : "unscaled") << "," << filename
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
REXGPU_INFO("Dumped Vulkan resolve readback texture {}: address={:08X}, length={:X}, scaled={}",
|
||||
dump_path.string(), address, length, scaled);
|
||||
++dump_count;
|
||||
}
|
||||
|
||||
const char* GetSwapFxaaComputeSource(bool extreme_quality) {
|
||||
return extreme_quality ? R"(#version 450
|
||||
layout(local_size_x = 16, local_size_y = 8, local_size_z = 1) in;
|
||||
|
|
@ -2313,16 +2498,75 @@ void VulkanCommandProcessor::IssueSwap(uint32_t frontbuffer_ptr, uint32_t frontb
|
|||
bool skip_present_due_async_placeholder = REXCVAR_GET(async_shader_compilation) &&
|
||||
REXCVAR_GET(vulkan_async_skip_incomplete_frames) &&
|
||||
frame_used_async_placeholder_pipeline_;
|
||||
if (skip_present_due_async_placeholder) {
|
||||
static bool skipped_incomplete_frame_logged = false;
|
||||
if (!skipped_incomplete_frame_logged) {
|
||||
skipped_incomplete_frame_logged = true;
|
||||
REXGPU_WARN(
|
||||
"Skipping Vulkan frame presentation due to async placeholder draw "
|
||||
"usage in this frame");
|
||||
auto log_debug_frame_summary = [&]() {
|
||||
int32_t remaining = REXCVAR_GET(vulkan_debug_log_frame_summaries_remaining);
|
||||
if (remaining <= 0) {
|
||||
return;
|
||||
}
|
||||
EndSubmission(true);
|
||||
return;
|
||||
const int32_t interval =
|
||||
std::max(1, REXCVAR_GET(vulkan_debug_frame_summary_interval_frames));
|
||||
const bool should_log = remaining == 36000 || remaining == 3600 ||
|
||||
(remaining % interval) == 0 ||
|
||||
skip_present_due_async_placeholder ||
|
||||
debug_frame_placeholder_draws_ != 0;
|
||||
if (should_log) {
|
||||
REXGPU_WARN(
|
||||
"Vulkan debug frame summary: frame={}, remaining={}, frontbuffer={:08X} {}x{}, "
|
||||
"draws={}, copy_resolves={}, raster={}, color={}, depth_stencil={}, textured={}, "
|
||||
"memexport={}, no_effect={}, placeholder={}, resolve_ranges={}, "
|
||||
"scaled_resolve_ranges={}, resolve_bytes={}, "
|
||||
"resolve0={:08X}+{:X}{}, resolve1={:08X}+{:X}{}, resolve2={:08X}+{:X}{}, "
|
||||
"resolve3={:08X}+{:X}{}, resolve4={:08X}+{:X}{}, resolve5={:08X}+{:X}{}, "
|
||||
"skip_present={}",
|
||||
frame_current_, remaining, frontbuffer_ptr, frontbuffer_width, frontbuffer_height,
|
||||
debug_frame_draws_, debug_frame_copy_resolves_, debug_frame_raster_draws_,
|
||||
debug_frame_color_draws_, debug_frame_depth_stencil_draws_, debug_frame_texture_draws_,
|
||||
debug_frame_memexport_draws_, debug_frame_no_effect_draws_,
|
||||
debug_frame_placeholder_draws_, debug_frame_resolve_ranges_,
|
||||
debug_frame_scaled_resolve_ranges_, debug_frame_resolve_bytes_,
|
||||
debug_frame_resolve_addresses_[0], debug_frame_resolve_lengths_[0],
|
||||
debug_frame_resolve_scaled_[0] ? "s" : "",
|
||||
debug_frame_resolve_addresses_[1], debug_frame_resolve_lengths_[1],
|
||||
debug_frame_resolve_scaled_[1] ? "s" : "",
|
||||
debug_frame_resolve_addresses_[2], debug_frame_resolve_lengths_[2],
|
||||
debug_frame_resolve_scaled_[2] ? "s" : "",
|
||||
debug_frame_resolve_addresses_[3], debug_frame_resolve_lengths_[3],
|
||||
debug_frame_resolve_scaled_[3] ? "s" : "",
|
||||
debug_frame_resolve_addresses_[4], debug_frame_resolve_lengths_[4],
|
||||
debug_frame_resolve_scaled_[4] ? "s" : "",
|
||||
debug_frame_resolve_addresses_[5], debug_frame_resolve_lengths_[5],
|
||||
debug_frame_resolve_scaled_[5] ? "s" : "", skip_present_due_async_placeholder);
|
||||
}
|
||||
REXCVAR_SET(vulkan_debug_log_frame_summaries_remaining, remaining - 1);
|
||||
};
|
||||
log_debug_frame_summary();
|
||||
if (skip_present_due_async_placeholder) {
|
||||
const uint32_t max_consecutive_skips = uint32_t(std::max(
|
||||
0, REXCVAR_GET(vulkan_async_placeholder_max_consecutive_skips)));
|
||||
if (!max_consecutive_skips ||
|
||||
async_placeholder_consecutive_frame_skips_ < max_consecutive_skips) {
|
||||
++async_placeholder_consecutive_frame_skips_;
|
||||
static bool skipped_incomplete_frame_logged = false;
|
||||
if (!skipped_incomplete_frame_logged) {
|
||||
skipped_incomplete_frame_logged = true;
|
||||
REXGPU_WARN(
|
||||
"Skipping Vulkan frame presentation due to async placeholder draw "
|
||||
"usage in this frame");
|
||||
}
|
||||
EndSubmission(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!async_placeholder_skip_fallback_logged_) {
|
||||
async_placeholder_skip_fallback_logged_ = true;
|
||||
REXGPU_WARN(
|
||||
"Presenting Vulkan frames with async placeholder draws after {} consecutive "
|
||||
"skips to avoid a persistent black screen",
|
||||
async_placeholder_consecutive_frame_skips_);
|
||||
}
|
||||
} else {
|
||||
async_placeholder_consecutive_frame_skips_ = 0;
|
||||
async_placeholder_skip_fallback_logged_ = false;
|
||||
}
|
||||
|
||||
SwapPostEffect swap_post_effect = GetActualSwapPostEffect();
|
||||
|
|
@ -3619,6 +3863,10 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType prim_type, uint32_t
|
|||
|
||||
const RegisterFile& regs = *register_file_;
|
||||
(void)index_buffer_info;
|
||||
const bool debug_log_frame = REXCVAR_GET(vulkan_debug_log_frame_summaries_remaining) > 0;
|
||||
if (debug_log_frame) {
|
||||
++debug_frame_draws_;
|
||||
}
|
||||
auto draw_fail = [&](const char* stage) {
|
||||
auto vgt_draw_initiator = regs.Get<reg::VGT_DRAW_INITIATOR>();
|
||||
REXGPU_ERROR(
|
||||
|
|
@ -3636,6 +3884,9 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType prim_type, uint32_t
|
|||
xenos::EdramMode edram_mode = regs.Get<reg::RB_MODECONTROL>().edram_mode;
|
||||
if (edram_mode == xenos::EdramMode::kCopy) {
|
||||
// Special copy handling.
|
||||
if (debug_log_frame) {
|
||||
++debug_frame_copy_resolves_;
|
||||
}
|
||||
rex::perf::ScopedCounterTimer resolve_timer(rex::perf::CounterId::kRtResolveUs);
|
||||
const uint32_t primitive_count = draw_util::EstimatePrimitiveCount(prim_type, index_count);
|
||||
PROFILE_DRAW_BUCKET(rex::perf::DrawBucket::kCopyResolve, index_count, primitive_count);
|
||||
|
|
@ -3688,6 +3939,9 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType prim_type, uint32_t
|
|||
if (surface_pitch_is_zero && is_rasterization_done) {
|
||||
// Doesn't actually draw.
|
||||
// Unlikely that zero would even really be legal though.
|
||||
if (debug_log_frame) {
|
||||
++debug_frame_no_effect_draws_;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
VulkanShader* pixel_shader = nullptr;
|
||||
|
|
@ -3708,6 +3962,9 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType prim_type, uint32_t
|
|||
// cache.
|
||||
if (!memexport_used_vertex) {
|
||||
// This draw has no effect.
|
||||
if (debug_log_frame) {
|
||||
++debug_frame_no_effect_draws_;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -3758,6 +4015,9 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType prim_type, uint32_t
|
|||
}
|
||||
if (!primitive_processing_result.host_draw_vertex_count) {
|
||||
// Nothing to draw.
|
||||
if (debug_log_frame) {
|
||||
++debug_frame_no_effect_draws_;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (primitive_processing_result.host_primitive_type == xenos::PrimitiveType::kTriangleFan) {
|
||||
|
|
@ -3876,6 +4136,9 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType prim_type, uint32_t
|
|||
if (REXCVAR_GET(vulkan_skip_inert_no_pixel_draws) && pixel_shader == nullptr &&
|
||||
normalized_color_mask == 0 && !normalized_depth_control.z_enable &&
|
||||
!normalized_depth_control.stencil_enable && !active_occlusion_query_.valid) {
|
||||
if (debug_log_frame) {
|
||||
++debug_frame_no_effect_draws_;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -3885,10 +4148,34 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType prim_type, uint32_t
|
|||
uint32_t used_texture_mask =
|
||||
vertex_shader->GetUsedTextureMaskAfterTranslation() |
|
||||
(pixel_shader != nullptr ? pixel_shader->GetUsedTextureMaskAfterTranslation() : 0);
|
||||
uint32_t debug_team_profile_bg_fetch_index = UINT32_MAX;
|
||||
bool debug_team_profile_bg_signed_view = false;
|
||||
uint32_t debug_team_profile_bg_texture_base = 0;
|
||||
uint32_t debug_team_profile_bg_texture_length = 0;
|
||||
if (debug_log_frame) {
|
||||
if (is_rasterization_done) {
|
||||
++debug_frame_raster_draws_;
|
||||
}
|
||||
if (normalized_color_mask) {
|
||||
++debug_frame_color_draws_;
|
||||
}
|
||||
if (normalized_depth_control.z_enable || normalized_depth_control.stencil_enable) {
|
||||
++debug_frame_depth_stencil_draws_;
|
||||
}
|
||||
if (used_texture_mask) {
|
||||
++debug_frame_texture_draws_;
|
||||
}
|
||||
if (!memexport_ranges_.empty()) {
|
||||
++debug_frame_memexport_draws_;
|
||||
}
|
||||
}
|
||||
{
|
||||
rex::perf::ScopedCounterTimer stage_timer(rex::perf::CounterId::kDrawStageTextureUs);
|
||||
texture_cache_->RequestTextures(used_texture_mask);
|
||||
}
|
||||
const bool debug_team_profile_bg_draw = texture_cache_->DebugFindTeamProfileBackgroundBinding(
|
||||
used_texture_mask, debug_team_profile_bg_fetch_index, debug_team_profile_bg_signed_view,
|
||||
&debug_team_profile_bg_texture_base, &debug_team_profile_bg_texture_length);
|
||||
|
||||
const VulkanPipelineCache::PipelineLayoutProvider* pipeline_layout_provider;
|
||||
// Set up the render targets - this may perform dispatches and draws.
|
||||
|
|
@ -3922,6 +4209,9 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType prim_type, uint32_t
|
|||
&pipeline_is_placeholder);
|
||||
if (REXCVAR_GET(async_shader_compilation) && pipeline_is_placeholder) {
|
||||
frame_used_async_placeholder_pipeline_ = true;
|
||||
if (debug_log_frame) {
|
||||
++debug_frame_placeholder_draws_;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (pipeline == VK_NULL_HANDLE || pipeline_layout_provider == nullptr) {
|
||||
|
|
@ -3998,6 +4288,35 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType prim_type, uint32_t
|
|||
UpdateDynamicState(viewport_info, primitive_polygonal, normalized_depth_control);
|
||||
}
|
||||
|
||||
if (debug_team_profile_bg_draw) {
|
||||
int32_t remaining = REXCVAR_GET(vulkan_debug_log_team_profile_background_draws_remaining);
|
||||
if (remaining > 0) {
|
||||
REXCVAR_SET(vulkan_debug_log_team_profile_background_draws_remaining, remaining - 1);
|
||||
render_target_cache_->DebugLogLastUpdateRenderTargetsForTeamProfileBackgroundDraw(
|
||||
frame_current_);
|
||||
const auto rb_surface_info = regs.Get<reg::RB_SURFACE_INFO>();
|
||||
const auto pa_sc_window_scissor_tl = regs.Get<reg::PA_SC_WINDOW_SCISSOR_TL>();
|
||||
const auto pa_sc_window_scissor_br = regs.Get<reg::PA_SC_WINDOW_SCISSOR_BR>();
|
||||
REXGPU_WARN(
|
||||
"Team profile BG Vulkan draw trace: frame={}, fetch={}, signed_view={}, "
|
||||
"prim={}, index_count={}, host_vertices={}, used_mask={:08X}, vs={:016X}, "
|
||||
"ps={:016X}, color_mask={:X}, depth={}, stencil={}, edram_mode={}, "
|
||||
"surface_pitch={}, viewport={}x{}+{},{} z={}..{}, scissor={}..{},{}..{}, "
|
||||
"texture_base={:08X}+{:X}",
|
||||
frame_current_, debug_team_profile_bg_fetch_index, debug_team_profile_bg_signed_view,
|
||||
uint32_t(prim_type), index_count, primitive_processing_result.host_draw_vertex_count,
|
||||
used_texture_mask, vertex_shader->ucode_data_hash(),
|
||||
pixel_shader != nullptr ? pixel_shader->ucode_data_hash() : 0, normalized_color_mask,
|
||||
bool(normalized_depth_control.z_enable), bool(normalized_depth_control.stencil_enable),
|
||||
uint32_t(edram_mode), rb_surface_info.surface_pitch, viewport_info.xy_extent[0],
|
||||
viewport_info.xy_extent[1], viewport_info.xy_offset[0], viewport_info.xy_offset[1],
|
||||
viewport_info.z_min, viewport_info.z_max, pa_sc_window_scissor_tl.tl_x,
|
||||
pa_sc_window_scissor_tl.tl_y, pa_sc_window_scissor_br.br_x,
|
||||
pa_sc_window_scissor_br.br_y, debug_team_profile_bg_texture_base,
|
||||
debug_team_profile_bg_texture_length);
|
||||
}
|
||||
}
|
||||
|
||||
auto vgt_draw_initiator = regs.Get<reg::VGT_DRAW_INITIATOR>();
|
||||
|
||||
// Whether to load the guest 32-bit (usually big-endian) vertex index
|
||||
|
|
@ -4527,16 +4846,294 @@ bool VulkanCommandProcessor::IssueCopy() {
|
|||
return false;
|
||||
}
|
||||
|
||||
ReadbackResolveMode readback_mode = GetReadbackResolveMode(REXCVAR_GET(vulkan_readback_resolve));
|
||||
if (readback_mode == ReadbackResolveMode::kDisabled) {
|
||||
uint32_t written_address, written_length;
|
||||
return render_target_cache_->Resolve(*memory_, *shared_memory_, *texture_cache_,
|
||||
written_address, written_length);
|
||||
ReadbackResolveMode readback_mode =
|
||||
ResolveAutoReadbackMode(GetReadbackResolveMode(REXCVAR_GET(vulkan_readback_resolve)),
|
||||
kernel_state_);
|
||||
const bool gameplay_state_active = IsGameplayStateActive(kernel_state_);
|
||||
if (readback_mode == ReadbackResolveMode::kDisabled &&
|
||||
(!texture_cache_->IsDrawResolutionScaled() || gameplay_state_active)) {
|
||||
uint32_t written_address = 0;
|
||||
uint32_t written_length = 0;
|
||||
bool resolved = render_target_cache_->Resolve(*memory_, *shared_memory_, *texture_cache_,
|
||||
written_address, written_length);
|
||||
const bool scaled = texture_cache_->IsDrawResolutionScaled();
|
||||
if (ConsumeResolveDecisionLogBudget()) {
|
||||
REXGPU_WARN(
|
||||
"Vulkan resolve diagnostic: frame={}, mode={}, path=resolve-only, result={}, "
|
||||
"address={:08X}, length={:X}, scaled={}",
|
||||
frame_current_, ReadbackResolveModeName(readback_mode), resolved, written_address,
|
||||
written_length, scaled);
|
||||
}
|
||||
if (resolved) {
|
||||
DebugRecordResolve(written_address, written_length, scaled);
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
return IssueCopy_ReadbackResolvePath();
|
||||
}
|
||||
|
||||
bool VulkanCommandProcessor::DownscaleScaledResolveToSharedMemory(uint32_t start_unscaled,
|
||||
uint32_t length_unscaled,
|
||||
uint32_t pixel_size_log2) {
|
||||
if (!texture_cache_ || !shared_memory_ || !resolve_downscale_pipeline_ ||
|
||||
!resolve_downscale_pipeline_layout_ || !length_unscaled || pixel_size_log2 > 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t pixel_size = uint32_t(1) << pixel_size_log2;
|
||||
uint32_t tile_size_1x = 32 * 32 * pixel_size;
|
||||
uint32_t tile_count = length_unscaled / tile_size_1x;
|
||||
if (!tile_count || tile_count * tile_size_1x != length_unscaled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!texture_cache_->CommitScaledResolveRange(start_unscaled, length_unscaled, 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t scaled_start = 0;
|
||||
uint64_t scaled_length = 0;
|
||||
if (!texture_cache_->GetScaledResolveRange(start_unscaled, length_unscaled, 0, scaled_start,
|
||||
scaled_length) ||
|
||||
!scaled_length || scaled_start > uint64_t(UINT32_MAX)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
VkBuffer scaled_resolve_buffer = texture_cache_->scaled_resolve_buffer();
|
||||
if (scaled_resolve_buffer == VK_NULL_HANDLE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool update_guest_memory = false;
|
||||
const uint32_t downscale_buffer_size = AlignReadbackBufferSize(length_unscaled);
|
||||
const ui::vulkan::VulkanDevice* const vulkan_device = GetVulkanDevice();
|
||||
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
|
||||
const VkDevice device = vulkan_device->device();
|
||||
if (downscale_buffer_size > resolve_downscale_buffer_size_) {
|
||||
VkBuffer new_buffer = VK_NULL_HANDLE;
|
||||
VkDeviceMemory new_memory = VK_NULL_HANDLE;
|
||||
if (!ui::vulkan::util::CreateDedicatedAllocationBuffer(
|
||||
vulkan_device, downscale_buffer_size,
|
||||
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
|
||||
ui::vulkan::util::MemoryPurpose::kDeviceLocal, new_buffer, new_memory)) {
|
||||
REXGPU_ERROR("Failed to create a {} MB Vulkan resolve downscale buffer",
|
||||
downscale_buffer_size >> 20);
|
||||
return false;
|
||||
}
|
||||
if (resolve_downscale_buffer_ != VK_NULL_HANDLE ||
|
||||
resolve_downscale_buffer_memory_ != VK_NULL_HANDLE) {
|
||||
if (!AwaitAllQueueOperationsCompletion()) {
|
||||
dfn.vkDestroyBuffer(device, new_buffer, nullptr);
|
||||
dfn.vkFreeMemory(device, new_memory, nullptr);
|
||||
return false;
|
||||
}
|
||||
if (resolve_downscale_buffer_ != VK_NULL_HANDLE) {
|
||||
dfn.vkDestroyBuffer(device, resolve_downscale_buffer_, nullptr);
|
||||
}
|
||||
if (resolve_downscale_buffer_memory_ != VK_NULL_HANDLE) {
|
||||
dfn.vkFreeMemory(device, resolve_downscale_buffer_memory_, nullptr);
|
||||
}
|
||||
}
|
||||
resolve_downscale_buffer_ = new_buffer;
|
||||
resolve_downscale_buffer_memory_ = new_memory;
|
||||
resolve_downscale_buffer_size_ = downscale_buffer_size;
|
||||
}
|
||||
if (resolve_downscale_buffer_ == VK_NULL_HANDLE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ReadbackBuffer* readback = nullptr;
|
||||
uint32_t readback_write_index = 0;
|
||||
if (update_guest_memory) {
|
||||
auto ensure_readback_slot = [&](ReadbackBuffer& readback_buffer, uint32_t index,
|
||||
uint32_t size) -> bool {
|
||||
if (readback_buffer.buffers[index] != VK_NULL_HANDLE &&
|
||||
size <= readback_buffer.sizes[index] && readback_buffer.mapped_data[index] != nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
VkBuffer new_buffer = VK_NULL_HANDLE;
|
||||
VkDeviceMemory new_memory = VK_NULL_HANDLE;
|
||||
if (!ui::vulkan::util::CreateDedicatedAllocationBuffer(
|
||||
vulkan_device, size, VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
||||
ui::vulkan::util::MemoryPurpose::kReadback, new_buffer, new_memory)) {
|
||||
REXGPU_ERROR("Failed to create a {} MB Vulkan materialized resolve readback buffer",
|
||||
size >> 20);
|
||||
return false;
|
||||
}
|
||||
|
||||
void* new_mapping = nullptr;
|
||||
if (dfn.vkMapMemory(device, new_memory, 0, VK_WHOLE_SIZE, 0, &new_mapping) != VK_SUCCESS) {
|
||||
REXGPU_ERROR("Failed to map a Vulkan materialized resolve readback buffer");
|
||||
dfn.vkDestroyBuffer(device, new_buffer, nullptr);
|
||||
dfn.vkFreeMemory(device, new_memory, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (readback_buffer.buffers[index] != VK_NULL_HANDLE ||
|
||||
readback_buffer.memories[index] != VK_NULL_HANDLE) {
|
||||
if (!AwaitAllQueueOperationsCompletion()) {
|
||||
dfn.vkUnmapMemory(device, new_memory);
|
||||
dfn.vkDestroyBuffer(device, new_buffer, nullptr);
|
||||
dfn.vkFreeMemory(device, new_memory, nullptr);
|
||||
return false;
|
||||
}
|
||||
if (readback_buffer.mapped_data[index] != nullptr &&
|
||||
readback_buffer.memories[index] != VK_NULL_HANDLE) {
|
||||
dfn.vkUnmapMemory(device, readback_buffer.memories[index]);
|
||||
}
|
||||
if (readback_buffer.buffers[index] != VK_NULL_HANDLE) {
|
||||
dfn.vkDestroyBuffer(device, readback_buffer.buffers[index], nullptr);
|
||||
}
|
||||
if (readback_buffer.memories[index] != VK_NULL_HANDLE) {
|
||||
dfn.vkFreeMemory(device, readback_buffer.memories[index], nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
readback_buffer.buffers[index] = new_buffer;
|
||||
readback_buffer.memories[index] = new_memory;
|
||||
readback_buffer.mapped_data[index] = new_mapping;
|
||||
readback_buffer.sizes[index] = size;
|
||||
return true;
|
||||
};
|
||||
|
||||
uint64_t readback_key =
|
||||
MakeReadbackResolveKey(start_unscaled, length_unscaled) ^ UINT64_C(0x8000000000000000);
|
||||
readback = &readback_buffers_[readback_key];
|
||||
readback->last_used_frame = frame_current_;
|
||||
readback_write_index = readback->current_index;
|
||||
if (!ensure_readback_slot(*readback, readback_write_index, downscale_buffer_size)) {
|
||||
readback = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
VkDescriptorSet descriptor_set = AllocateSingleTransientDescriptor(
|
||||
SingleTransientDescriptorLayout::kStorageBufferPairCompute);
|
||||
if (descriptor_set == VK_NULL_HANDLE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
VkDescriptorBufferInfo buffer_infos[2] = {};
|
||||
buffer_infos[0].buffer = scaled_resolve_buffer;
|
||||
buffer_infos[0].offset = 0;
|
||||
buffer_infos[0].range = VK_WHOLE_SIZE;
|
||||
buffer_infos[1].buffer = resolve_downscale_buffer_;
|
||||
buffer_infos[1].offset = 0;
|
||||
buffer_infos[1].range = length_unscaled;
|
||||
|
||||
VkWriteDescriptorSet descriptor_writes[2] = {};
|
||||
for (uint32_t i = 0; i < 2; ++i) {
|
||||
descriptor_writes[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
descriptor_writes[i].dstSet = descriptor_set;
|
||||
descriptor_writes[i].dstBinding = i;
|
||||
descriptor_writes[i].descriptorCount = 1;
|
||||
descriptor_writes[i].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
||||
descriptor_writes[i].pBufferInfo = &buffer_infos[i];
|
||||
}
|
||||
dfn.vkUpdateDescriptorSets(device, 2, descriptor_writes, 0, nullptr);
|
||||
|
||||
texture_cache_->UseScaledResolveBufferForRead();
|
||||
SubmitBarriers(true);
|
||||
|
||||
uint32_t resolve_downscale_timestamp_start =
|
||||
BeginGpuTimestampedRegion(rex::perf::DrawBucket::kResolveDownscale);
|
||||
|
||||
VkBufferMemoryBarrier pre_barrier = {};
|
||||
pre_barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
|
||||
pre_barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
|
||||
pre_barrier.dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
|
||||
pre_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
pre_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
pre_barrier.buffer = resolve_downscale_buffer_;
|
||||
pre_barrier.offset = 0;
|
||||
pre_barrier.size = length_unscaled;
|
||||
deferred_command_buffer_.CmdVkPipelineBarrier(
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 1, &pre_barrier, 0, nullptr);
|
||||
|
||||
BindExternalComputePipeline(resolve_downscale_pipeline_);
|
||||
|
||||
ResolveDownscaleConstants constants;
|
||||
constants.scale_x = texture_cache_->draw_resolution_scale_x();
|
||||
constants.scale_y = texture_cache_->draw_resolution_scale_y();
|
||||
constants.pixel_size_log2 = pixel_size_log2;
|
||||
constants.tile_count = tile_count;
|
||||
constants.source_offset_bytes = uint32_t(scaled_start);
|
||||
constants.half_pixel_offset =
|
||||
(REXCVAR_GET(readback_resolve_half_pixel_offset) &&
|
||||
(constants.scale_x > 1 || constants.scale_y > 1))
|
||||
? 1u
|
||||
: 0u;
|
||||
deferred_command_buffer_.CmdVkPushConstants(resolve_downscale_pipeline_layout_,
|
||||
VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(constants),
|
||||
&constants);
|
||||
deferred_command_buffer_.CmdVkBindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE,
|
||||
resolve_downscale_pipeline_layout_, 0, 1,
|
||||
&descriptor_set, 0, nullptr);
|
||||
deferred_command_buffer_.CmdVkDispatch(tile_count, 1, 1);
|
||||
|
||||
VkBufferMemoryBarrier downscale_barrier = {};
|
||||
downscale_barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
|
||||
downscale_barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
|
||||
downscale_barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||||
downscale_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
downscale_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
downscale_barrier.buffer = resolve_downscale_buffer_;
|
||||
downscale_barrier.offset = 0;
|
||||
downscale_barrier.size = length_unscaled;
|
||||
deferred_command_buffer_.CmdVkPipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 1,
|
||||
&downscale_barrier, 0, nullptr);
|
||||
|
||||
shared_memory_->Use(VulkanSharedMemory::Usage::kTransferDestination,
|
||||
std::make_pair(start_unscaled, length_unscaled));
|
||||
SubmitBarriers(true);
|
||||
|
||||
VkBufferCopy shared_memory_copy = {};
|
||||
shared_memory_copy.srcOffset = 0;
|
||||
shared_memory_copy.dstOffset = start_unscaled;
|
||||
shared_memory_copy.size = length_unscaled;
|
||||
deferred_command_buffer_.CmdVkCopyBuffer(resolve_downscale_buffer_, shared_memory_->buffer(), 1,
|
||||
&shared_memory_copy);
|
||||
|
||||
if (readback != nullptr && readback->buffers[readback_write_index] != VK_NULL_HANDLE) {
|
||||
VkBufferCopy readback_copy = {};
|
||||
readback_copy.srcOffset = 0;
|
||||
readback_copy.dstOffset = 0;
|
||||
readback_copy.size = length_unscaled;
|
||||
deferred_command_buffer_.CmdVkCopyBuffer(resolve_downscale_buffer_,
|
||||
readback->buffers[readback_write_index], 1,
|
||||
&readback_copy);
|
||||
PushBufferMemoryBarrier(readback->buffers[readback_write_index], 0, length_unscaled,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT,
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT);
|
||||
}
|
||||
|
||||
EndGpuTimestampedDraw(resolve_downscale_timestamp_start);
|
||||
if (readback != nullptr && readback->buffers[readback_write_index] != VK_NULL_HANDLE &&
|
||||
readback->mapped_data[readback_write_index] != nullptr) {
|
||||
if (!AwaitAllQueueOperationsCompletion()) {
|
||||
return false;
|
||||
}
|
||||
VkMappedMemoryRange readback_memory_range = {};
|
||||
readback_memory_range.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
|
||||
readback_memory_range.memory = readback->memories[readback_write_index];
|
||||
readback_memory_range.offset = 0;
|
||||
readback_memory_range.size = VK_WHOLE_SIZE;
|
||||
dfn.vkInvalidateMappedMemoryRanges(device, 1, &readback_memory_range);
|
||||
|
||||
if (uint8_t* destination = memory_->TranslatePhysical(start_unscaled)) {
|
||||
std::memcpy(destination, readback->mapped_data[readback_write_index], length_unscaled);
|
||||
}
|
||||
readback->current_index = 1 - readback->current_index;
|
||||
} else if (!AwaitAllQueueOperationsCompletion()) {
|
||||
return false;
|
||||
}
|
||||
shared_memory_->RangeWrittenByGpu(start_unscaled, length_unscaled);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VulkanCommandProcessor::IssueCopy_ReadbackResolvePath() {
|
||||
const ui::vulkan::VulkanDevice* const vulkan_device = GetVulkanDevice();
|
||||
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
|
||||
|
|
@ -4545,22 +5142,93 @@ bool VulkanCommandProcessor::IssueCopy_ReadbackResolvePath() {
|
|||
uint32_t written_address, written_length;
|
||||
if (!render_target_cache_->Resolve(*memory_, *shared_memory_, *texture_cache_, written_address,
|
||||
written_length)) {
|
||||
if (ConsumeResolveDecisionLogBudget()) {
|
||||
REXGPU_WARN("Vulkan resolve diagnostic: frame={}, mode=readback, path=readback, result=false",
|
||||
frame_current_);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool is_scaled = texture_cache_->IsDrawResolutionScaled();
|
||||
DebugRecordResolve(written_address, written_length, is_scaled);
|
||||
|
||||
auto log_readback_decision = [&](const char* decision, ReadbackResolveMode mode,
|
||||
bool cache_miss = false, bool should_copy = false,
|
||||
uint32_t extra = 0) {
|
||||
if (!ConsumeResolveDecisionLogBudget()) {
|
||||
return;
|
||||
}
|
||||
REXGPU_WARN(
|
||||
"Vulkan resolve diagnostic: frame={}, mode={}, path=readback, decision={}, "
|
||||
"address={:08X}, length={:X}, scaled={}, cache_miss={}, should_copy={}, extra={:X}",
|
||||
frame_current_, ReadbackResolveModeName(mode), decision, written_address, written_length,
|
||||
is_scaled, cache_miss, should_copy, extra);
|
||||
};
|
||||
|
||||
if (!written_length) {
|
||||
log_readback_decision("empty-range", ReadbackResolveMode::kDisabled);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!memory_->TranslatePhysical(written_address)) {
|
||||
log_readback_decision("unmapped-destination", ReadbackResolveMode::kDisabled);
|
||||
return true;
|
||||
}
|
||||
|
||||
ReadbackResolveMode readback_mode = GetReadbackResolveMode(REXCVAR_GET(vulkan_readback_resolve));
|
||||
ReadbackResolveMode readback_mode =
|
||||
ResolveAutoReadbackMode(GetReadbackResolveMode(REXCVAR_GET(vulkan_readback_resolve)),
|
||||
kernel_state_);
|
||||
bool force_scaled_resolve_sync_only = false;
|
||||
bool force_scaled_resolve_cpu_copy = false;
|
||||
if (readback_mode == ReadbackResolveMode::kDisabled) {
|
||||
constexpr uint32_t kImportSkaterPreviewResolveAddress = UINT32_C(0x04911000);
|
||||
constexpr uint32_t kImportSkaterPreviewResolveLength = UINT32_C(0x2D0000);
|
||||
force_scaled_resolve_cpu_copy =
|
||||
!IsGameplayStateActive(kernel_state_) && is_scaled &&
|
||||
written_address == kImportSkaterPreviewResolveAddress &&
|
||||
written_length == kImportSkaterPreviewResolveLength;
|
||||
if (!force_scaled_resolve_cpu_copy) {
|
||||
log_readback_decision("disabled-after-resolve", readback_mode);
|
||||
return true;
|
||||
}
|
||||
readback_mode = ReadbackResolveMode::kFull;
|
||||
force_scaled_resolve_sync_only = true;
|
||||
log_readback_decision("targeted-scaled-resolve-full", readback_mode, false, true,
|
||||
kImportSkaterPreviewResolveLength);
|
||||
}
|
||||
|
||||
const int32_t max_readback_length = REXCVAR_GET(vulkan_readback_resolve_max_length);
|
||||
if (max_readback_length > 0 && written_length > uint32_t(max_readback_length)) {
|
||||
log_readback_decision("length-filter", readback_mode, false, false,
|
||||
uint32_t(max_readback_length));
|
||||
if (REXCVAR_GET(vulkan_debug_log_frame_summaries_remaining) > 0) {
|
||||
REXGPU_WARN(
|
||||
"Vulkan resolve readback skipped by length filter: address={:08X}, length={:X}, "
|
||||
"max={:X}",
|
||||
written_address, written_length, uint32_t(max_readback_length));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const int32_t max_readback_address = REXCVAR_GET(vulkan_readback_resolve_max_address);
|
||||
if (max_readback_address > 0) {
|
||||
const uint32_t min_readback_address =
|
||||
uint32_t(std::max(0, REXCVAR_GET(vulkan_readback_resolve_min_address)));
|
||||
const uint64_t readback_end = uint64_t(written_address) + written_length;
|
||||
if (written_address < min_readback_address ||
|
||||
readback_end > uint32_t(max_readback_address)) {
|
||||
log_readback_decision("address-filter", readback_mode, false, false,
|
||||
uint32_t(max_readback_address));
|
||||
if (REXCVAR_GET(vulkan_debug_log_frame_summaries_remaining) > 0) {
|
||||
REXGPU_WARN(
|
||||
"Vulkan resolve readback skipped by address filter: address={:08X}, length={:X}, "
|
||||
"window={:08X}-{:08X}",
|
||||
written_address, written_length, min_readback_address,
|
||||
uint32_t(max_readback_address));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
auto ensure_readback_slot = [&](ReadbackBuffer& readback, uint32_t index, uint32_t size) -> bool {
|
||||
if (readback.buffers[index] != VK_NULL_HANDLE && size <= readback.sizes[index] &&
|
||||
readback.mapped_data[index] != nullptr) {
|
||||
|
|
@ -4609,7 +5277,6 @@ bool VulkanCommandProcessor::IssueCopy_ReadbackResolvePath() {
|
|||
return true;
|
||||
};
|
||||
|
||||
bool is_scaled = texture_cache_->IsDrawResolutionScaled();
|
||||
uint64_t resolve_key = MakeReadbackResolveKey(written_address, written_length);
|
||||
ReadbackBuffer& readback = readback_buffers_[resolve_key];
|
||||
readback.last_used_frame = frame_current_;
|
||||
|
|
@ -4810,7 +5477,11 @@ bool VulkanCommandProcessor::IssueCopy_ReadbackResolvePath() {
|
|||
}
|
||||
}
|
||||
|
||||
bool should_copy = (readback_mode == ReadbackResolveMode::kSome) ? is_cache_miss : true;
|
||||
bool should_copy =
|
||||
(!force_scaled_resolve_sync_only || force_scaled_resolve_cpu_copy) &&
|
||||
((readback_mode == ReadbackResolveMode::kSome) ? is_cache_miss : true);
|
||||
log_readback_decision(should_copy ? "copy-guest-memory" : "cache-hit-skip", readback_mode,
|
||||
is_cache_miss, should_copy, uint32_t(resolve_key));
|
||||
if (should_copy && readback.buffers[read_index] != VK_NULL_HANDLE &&
|
||||
written_length <= readback.sizes[read_index] && readback.mapped_data[read_index] != nullptr) {
|
||||
VkMappedMemoryRange readback_memory_range = {};
|
||||
|
|
@ -4823,6 +5494,11 @@ bool VulkanCommandProcessor::IssueCopy_ReadbackResolvePath() {
|
|||
uint8_t* destination = memory_->TranslatePhysical(written_address);
|
||||
if (destination) {
|
||||
std::memcpy(destination, readback.mapped_data[read_index], written_length);
|
||||
if (readback_mode == ReadbackResolveMode::kFull) {
|
||||
texture_cache_->DebugRecordResolveReadback(written_address, written_length, is_scaled);
|
||||
DebugDumpResolveReadback(written_address, written_length, is_scaled,
|
||||
readback.mapped_data[read_index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -5594,6 +6270,23 @@ bool VulkanCommandProcessor::CanEndSubmissionImmediately() const {
|
|||
return !submission_open_ || (!scratch_buffer_used_ && !pipeline_cache_->IsCreatingPipelines());
|
||||
}
|
||||
|
||||
void VulkanCommandProcessor::DebugRecordResolve(uint32_t address, uint32_t length, bool scaled) {
|
||||
if (REXCVAR_GET(vulkan_debug_log_frame_summaries_remaining) <= 0 || !length) {
|
||||
return;
|
||||
}
|
||||
const uint32_t sample_index = debug_frame_resolve_ranges_;
|
||||
if (sample_index < kDebugFrameResolveRangeSamples) {
|
||||
debug_frame_resolve_addresses_[sample_index] = address;
|
||||
debug_frame_resolve_lengths_[sample_index] = length;
|
||||
debug_frame_resolve_scaled_[sample_index] = scaled;
|
||||
}
|
||||
++debug_frame_resolve_ranges_;
|
||||
if (scaled) {
|
||||
++debug_frame_scaled_resolve_ranges_;
|
||||
}
|
||||
debug_frame_resolve_bytes_ += length;
|
||||
}
|
||||
|
||||
bool VulkanCommandProcessor::BeginSubmission(bool is_guest_command) {
|
||||
#if XE_GPU_FINE_GRAINED_DRAW_SCOPES
|
||||
SCOPE_profile_cpu_f("gpu");
|
||||
|
|
@ -5680,6 +6373,21 @@ bool VulkanCommandProcessor::BeginSubmission(bool is_guest_command) {
|
|||
if (is_opening_frame) {
|
||||
frame_open_ = true;
|
||||
frame_used_async_placeholder_pipeline_ = false;
|
||||
debug_frame_draws_ = 0;
|
||||
debug_frame_copy_resolves_ = 0;
|
||||
debug_frame_raster_draws_ = 0;
|
||||
debug_frame_color_draws_ = 0;
|
||||
debug_frame_depth_stencil_draws_ = 0;
|
||||
debug_frame_texture_draws_ = 0;
|
||||
debug_frame_placeholder_draws_ = 0;
|
||||
debug_frame_no_effect_draws_ = 0;
|
||||
debug_frame_memexport_draws_ = 0;
|
||||
debug_frame_resolve_ranges_ = 0;
|
||||
debug_frame_scaled_resolve_ranges_ = 0;
|
||||
debug_frame_resolve_bytes_ = 0;
|
||||
debug_frame_resolve_addresses_.fill(0);
|
||||
debug_frame_resolve_lengths_.fill(0);
|
||||
debug_frame_resolve_scaled_.fill(false);
|
||||
|
||||
// Reset bindings that depend on transient data.
|
||||
std::memset(current_float_constant_map_vertex_, 0, sizeof(current_float_constant_map_vertex_));
|
||||
|
|
|
|||
|
|
@ -1336,10 +1336,40 @@ void VulkanRenderTargetCache::RestoreEdramSnapshot(const void* snapshot) {
|
|||
}
|
||||
}
|
||||
|
||||
void VulkanRenderTargetCache::DebugLogLastUpdateRenderTargetsForTeamProfileBackgroundDraw(
|
||||
uint64_t frame) const {
|
||||
REXGPU_WARN(
|
||||
"Team profile BG Vulkan RT trace: frame={}, path={}, render_pass_key={:08X}, "
|
||||
"pitch_tiles_32bpp={}, framebuffer={}x{}",
|
||||
frame, uint32_t(GetPath()), last_update_render_pass_key_.key,
|
||||
last_update_framebuffer_pitch_tiles_at_32bpp_,
|
||||
last_update_framebuffer_ ? last_update_framebuffer_->host_extent.width : 0,
|
||||
last_update_framebuffer_ ? last_update_framebuffer_->host_extent.height : 0);
|
||||
for (uint32_t i = 0; i < 1 + xenos::kMaxColorRenderTargets; ++i) {
|
||||
const RenderTarget* rt =
|
||||
reinterpret_cast<const RenderTarget*>(last_update_framebuffer_attachments_[i]);
|
||||
if (rt == nullptr) {
|
||||
continue;
|
||||
}
|
||||
const RenderTargetKey key = rt->key();
|
||||
const VulkanRenderTarget* vulkan_rt = static_cast<const VulkanRenderTarget*>(rt);
|
||||
REXGPU_WARN(
|
||||
"Team profile BG Vulkan RT attachment: slot={}, kind={}, base_tiles={}, pitch_tiles={}, "
|
||||
"pitch_tiles_32bpp={}, width={}, format={}, msaa={}, image={:016X}, stage={:X}, "
|
||||
"access={:X}, layout={}",
|
||||
i, key.is_depth ? "depth" : "color", key.base_tiles, key.GetPitchTiles(),
|
||||
key.pitch_tiles_at_32bpp, key.GetWidth(), key.GetFormatName(), uint32_t(key.msaa_samples),
|
||||
uint64_t(uintptr_t(vulkan_rt->image())), uint32_t(vulkan_rt->current_stage_mask()),
|
||||
uint32_t(vulkan_rt->current_access_mask()), uint32_t(vulkan_rt->current_layout()));
|
||||
}
|
||||
}
|
||||
|
||||
bool VulkanRenderTargetCache::Resolve(const memory::Memory& memory,
|
||||
VulkanSharedMemory& shared_memory,
|
||||
VulkanTextureCache& texture_cache,
|
||||
uint32_t& written_address_out, uint32_t& written_length_out) {
|
||||
uint32_t& written_address_out,
|
||||
uint32_t& written_length_out,
|
||||
bool debug_log_team_profile_background_trace) {
|
||||
written_address_out = 0;
|
||||
written_length_out = 0;
|
||||
|
||||
|
|
@ -1422,6 +1452,21 @@ bool VulkanRenderTargetCache::Resolve(const memory::Memory& memory,
|
|||
}
|
||||
}
|
||||
|
||||
if (debug_log_team_profile_background_trace) {
|
||||
REXGPU_WARN(
|
||||
"Team profile BG Vulkan resolve trace: scaled={}, copy_shader={}, dest_base={:08X}, "
|
||||
"dest_extent={:08X}+{:X}, dest_range_unscaled={:X}, buffer_offset={:X}, "
|
||||
"buffer_range={:X}, use_range={:X}+{:X}, groups={}x{}, dest_bpe_log2={}",
|
||||
draw_resolution_scaled, uint32_t(copy_shader), resolve_info.copy_dest_base,
|
||||
resolve_info.copy_dest_extent_start, resolve_info.copy_dest_extent_length,
|
||||
copy_dest_range_unscaled, copy_dest_base, copy_dest_range_length,
|
||||
copy_dest_use_start, copy_dest_use_length, copy_group_count_x, copy_group_count_y,
|
||||
copy_shader_info.dest_bpe_log2);
|
||||
texture_cache.DebugRecordTeamProfileBackgroundResolveRange(
|
||||
resolve_info.copy_dest_base, copy_dest_range_unscaled, draw_resolution_scaled,
|
||||
copy_dest_base, copy_dest_range_length);
|
||||
}
|
||||
|
||||
// Make sure there is memory to write to.
|
||||
bool copy_dest_committed;
|
||||
if (draw_resolution_scaled) {
|
||||
|
|
@ -1465,7 +1510,7 @@ bool VulkanRenderTargetCache::Resolve(const memory::Memory& memory,
|
|||
|
||||
// Submit the resolve.
|
||||
if (draw_resolution_scaled) {
|
||||
texture_cache.UseScaledResolveBufferForWrite(copy_dest_use_start, copy_dest_use_length);
|
||||
texture_cache.UseScaledResolveBufferForWrite(copy_dest_base, copy_dest_range_length);
|
||||
} else {
|
||||
shared_memory.Use(VulkanSharedMemory::Usage::kComputeWrite,
|
||||
std::pair<uint32_t, uint32_t>(uint32_t(copy_dest_use_start),
|
||||
|
|
|
|||
|
|
@ -411,7 +411,7 @@ void VulkanSharedMemory::GetUsageMasks(Usage usage, VkPipelineStageFlags& stage_
|
|||
switch (usage) {
|
||||
case Usage::kComputeWrite:
|
||||
stage_mask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
|
||||
access_mask = VK_ACCESS_SHADER_READ_BIT;
|
||||
access_mask = VK_ACCESS_SHADER_WRITE_BIT;
|
||||
return;
|
||||
case Usage::kTransferDestination:
|
||||
stage_mask = VK_PIPELINE_STAGE_TRANSFER_BIT;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#include <rex/assert.h>
|
||||
|
|
@ -40,8 +41,27 @@ REXCVAR_DEFINE_BOOL(vulkan_scaled_resolve_write_barrier_overlap_only, false, "GP
|
|||
"not overlap")
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
|
||||
REXCVAR_DEFINE_INT32(vulkan_debug_log_3d_as_2d_remaining, 256, "GPU/Vulkan",
|
||||
"Log scaled 3D-as-2D texture wrapper usage")
|
||||
.range(0, 10000)
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload)
|
||||
.debug_only();
|
||||
|
||||
namespace rex::graphics::vulkan {
|
||||
|
||||
namespace {
|
||||
|
||||
bool Consume3DAs2DLogBudget() {
|
||||
const int32_t remaining = REXCVAR_GET(vulkan_debug_log_3d_as_2d_remaining);
|
||||
if (remaining <= 0) {
|
||||
return false;
|
||||
}
|
||||
REXCVAR_SET(vulkan_debug_log_3d_as_2d_remaining, remaining - 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Generated with `xb buildshaders`.
|
||||
namespace shaders {
|
||||
#include "../shaders/vulkan_spirv/texture_load_128bpb_cs.h"
|
||||
|
|
@ -649,6 +669,15 @@ VkImageView VulkanTextureCache::GetActiveBindingOrNullImageView(uint32_t fetch_c
|
|||
if (texture) {
|
||||
image_view = static_cast<VulkanTexture*>(texture)->GetOrCreate3DAs2DImageView(
|
||||
use_signed, binding->host_swizzle);
|
||||
if (Consume3DAs2DLogBudget()) {
|
||||
const TextureKey& key = texture->key();
|
||||
REXGPU_WARN(
|
||||
"Vulkan 3D-as-2D binding: fetch={}, signed={}, scaled={}, {}x{}x{}, format={}, "
|
||||
"view={:016X}",
|
||||
fetch_constant_index, use_signed, bool(key.scaled_resolve), key.GetWidth(),
|
||||
key.GetHeight(), key.GetDepthOrArraySize(), FormatInfo::Get(key.format)->name,
|
||||
uint64_t(uintptr_t(image_view)));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const VulkanTextureBinding& vulkan_binding = vulkan_texture_bindings_[fetch_constant_index];
|
||||
|
|
@ -1214,10 +1243,70 @@ bool VulkanTextureCache::EnsureScaledResolveMemoryCommitted(uint32_t start_unsca
|
|||
return EnsureScaledResolveBufferAllocated(start_scaled, length_scaled);
|
||||
}
|
||||
|
||||
bool VulkanTextureCache::MaterializeScaledResolveToSharedMemory(uint32_t start_unscaled,
|
||||
uint32_t length_unscaled,
|
||||
uint32_t pixel_size_log2) {
|
||||
return command_processor_.DownscaleScaledResolveToSharedMemory(start_unscaled, length_unscaled,
|
||||
pixel_size_log2);
|
||||
}
|
||||
|
||||
const char* VulkanTextureCache::DebugVulkanTextureUsageName(VulkanTexture::Usage usage) {
|
||||
switch (usage) {
|
||||
case VulkanTexture::Usage::kUndefined:
|
||||
return "undefined";
|
||||
case VulkanTexture::Usage::kTransferDestination:
|
||||
return "transfer-dst";
|
||||
case VulkanTexture::Usage::kGuestShaderSampled:
|
||||
return "guest-sampled";
|
||||
case VulkanTexture::Usage::kSwapSampled:
|
||||
return "swap-sampled";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
void VulkanTextureCache::DebugLogTeamProfileBackgroundGpuEvent(
|
||||
const char* action, uint32_t fetch_index, const Texture& texture, bool signed_view,
|
||||
uint32_t host_swizzle, VkImageView image_view) const {
|
||||
if (!DebugIsTeamProfileBackgroundTexture(texture)) {
|
||||
return;
|
||||
}
|
||||
const bool is_binding_event = std::string_view(action).starts_with("binding") ||
|
||||
std::string_view(action).starts_with("view");
|
||||
const int32_t remaining = is_binding_event
|
||||
? REXCVAR_GET(
|
||||
vulkan_debug_log_team_profile_background_bindings_remaining)
|
||||
: REXCVAR_GET(
|
||||
vulkan_debug_log_team_profile_background_candidates_remaining);
|
||||
if (remaining <= 0) {
|
||||
return;
|
||||
}
|
||||
if (is_binding_event) {
|
||||
REXCVAR_SET(vulkan_debug_log_team_profile_background_bindings_remaining, remaining - 1);
|
||||
} else {
|
||||
REXCVAR_SET(vulkan_debug_log_team_profile_background_candidates_remaining, remaining - 1);
|
||||
}
|
||||
|
||||
const VulkanTexture& vulkan_texture = static_cast<const VulkanTexture&>(texture);
|
||||
const TextureKey& key = texture.key();
|
||||
const HostFormatPair& host_format_pair = GetHostFormatPair(key);
|
||||
const VkFormat unsigned_format = host_format_pair.format_unsigned.format;
|
||||
const VkFormat signed_format = host_format_pair.format_signed.format;
|
||||
REXGPU_WARN(
|
||||
"Team profile BG Vulkan texture trace: action={}, fetch={}, signed_view={}, scaled={}, "
|
||||
"base={:08X}+{:X}, mips={:08X}+{:X}, image={:016X}, view={:016X}, usage={}, "
|
||||
"host_swizzle={:03X}, host_formats=({}, {}), host_swizzle_pair={:03X}",
|
||||
action, fetch_index, signed_view, bool(key.scaled_resolve), key.base_page << 12,
|
||||
texture.GetGuestBaseSize(), key.mip_page << 12, texture.GetGuestMipsSize(),
|
||||
uint64_t(uintptr_t(vulkan_texture.image())), uint64_t(uintptr_t(image_view)),
|
||||
DebugVulkanTextureUsageName(vulkan_texture.usage()), host_swizzle, uint32_t(unsigned_format),
|
||||
uint32_t(signed_format), host_format_pair.swizzle);
|
||||
}
|
||||
|
||||
bool VulkanTextureCache::LoadTextureDataFromResidentMemoryImpl(Texture& texture, bool load_base,
|
||||
bool load_mips) {
|
||||
VulkanTexture& vulkan_texture = static_cast<VulkanTexture&>(texture);
|
||||
TextureKey texture_key = vulkan_texture.key();
|
||||
DebugLogTeamProfileBackgroundGpuEvent("load-enter", UINT32_MAX, texture);
|
||||
|
||||
// Get the pipeline.
|
||||
const HostFormatPair& host_format_pair = GetHostFormatPair(texture_key);
|
||||
|
|
@ -1415,6 +1504,10 @@ bool VulkanTextureCache::LoadTextureDataFromResidentMemoryImpl(Texture& texture,
|
|||
VkDescriptorSet descriptor_set_source_mips = VK_NULL_HANDLE;
|
||||
VkDescriptorBufferInfo write_descriptor_set_source_base_buffer_info;
|
||||
VkDescriptorBufferInfo write_descriptor_set_source_mips_buffer_info;
|
||||
uint64_t debug_source_base_start = 0;
|
||||
uint64_t debug_source_base_range = 0;
|
||||
uint64_t debug_source_mips_start = 0;
|
||||
uint64_t debug_source_mips_range = 0;
|
||||
if (level_first == 0) {
|
||||
descriptor_set_source_base = command_processor_.AllocateSingleTransientDescriptor(
|
||||
VulkanCommandProcessor::SingleTransientDescriptorLayout ::kStorageBufferCompute);
|
||||
|
|
@ -1440,6 +1533,8 @@ bool VulkanTextureCache::LoadTextureDataFromResidentMemoryImpl(Texture& texture,
|
|||
}
|
||||
write_descriptor_set_source_base_buffer_info.buffer = vulkan_shared_memory.buffer();
|
||||
}
|
||||
debug_source_base_start = source_base_start;
|
||||
debug_source_base_range = source_base_range;
|
||||
write_descriptor_set_source_base_buffer_info.offset = source_base_start;
|
||||
write_descriptor_set_source_base_buffer_info.range = source_base_range;
|
||||
VkWriteDescriptorSet& write_descriptor_set_source_base =
|
||||
|
|
@ -1480,6 +1575,8 @@ bool VulkanTextureCache::LoadTextureDataFromResidentMemoryImpl(Texture& texture,
|
|||
}
|
||||
write_descriptor_set_source_mips_buffer_info.buffer = vulkan_shared_memory.buffer();
|
||||
}
|
||||
debug_source_mips_start = source_mips_start;
|
||||
debug_source_mips_range = source_mips_range;
|
||||
write_descriptor_set_source_mips_buffer_info.offset = source_mips_start;
|
||||
write_descriptor_set_source_mips_buffer_info.range = source_mips_range;
|
||||
VkWriteDescriptorSet& write_descriptor_set_source_mips =
|
||||
|
|
@ -1499,6 +1596,55 @@ bool VulkanTextureCache::LoadTextureDataFromResidentMemoryImpl(Texture& texture,
|
|||
dfn.vkUpdateDescriptorSets(device, write_descriptor_set_count, write_descriptor_sets.data(), 0,
|
||||
nullptr);
|
||||
}
|
||||
uint32_t debug_team_profile_base_serial = 0;
|
||||
uint32_t debug_team_profile_mips_serial = 0;
|
||||
bool debug_team_profile_base_scaled = false;
|
||||
bool debug_team_profile_mips_scaled = false;
|
||||
uint64_t debug_team_profile_base_scaled_start = 0;
|
||||
uint64_t debug_team_profile_base_scaled_length = 0;
|
||||
uint64_t debug_team_profile_mips_scaled_start = 0;
|
||||
uint64_t debug_team_profile_mips_scaled_length = 0;
|
||||
bool debug_team_profile_base_overlap =
|
||||
DebugTextureOverlapsRecentTeamProfileBackgroundResolve(
|
||||
texture, false, debug_team_profile_base_serial, debug_team_profile_base_scaled,
|
||||
debug_team_profile_base_scaled_start, debug_team_profile_base_scaled_length);
|
||||
bool debug_team_profile_mips_overlap =
|
||||
DebugTextureOverlapsRecentTeamProfileBackgroundResolve(
|
||||
texture, true, debug_team_profile_mips_serial, debug_team_profile_mips_scaled,
|
||||
debug_team_profile_mips_scaled_start, debug_team_profile_mips_scaled_length);
|
||||
if (DebugIsTeamProfileBackgroundTexture(texture)) {
|
||||
REXGPU_WARN(
|
||||
"Team profile BG Vulkan upload setup: scaled={}, load_base={}, load_mips={}, "
|
||||
"load_shader={}, host_buffer_size={:X}, source_buffer={}, source_base={:X}+{:X}, "
|
||||
"source_mips={:X}+{:X}, texture_scale={}x{}, levels={}..{}, packed_level={}",
|
||||
bool(texture_key.scaled_resolve), load_base, load_mips, uint32_t(load_shader),
|
||||
uint64_t(host_buffer_size), texture_key.scaled_resolve ? "scaled-resolve" : "shared-memory",
|
||||
debug_source_base_start, debug_source_base_range, debug_source_mips_start,
|
||||
debug_source_mips_range, texture_resolution_scale_x, texture_resolution_scale_y,
|
||||
level_first, level_last, level_packed);
|
||||
}
|
||||
if (debug_team_profile_base_overlap || debug_team_profile_mips_overlap) {
|
||||
REXGPU_WARN(
|
||||
"Team profile BG resolved texture upload: texture_scaled={}, load_base={}, load_mips={}, "
|
||||
"{} {}x{}x{} {}, base={:08X}+{:X}, mips={:08X}+{:X}, source_buffer={}, "
|
||||
"source_base={:X}+{:X}, source_mips={:X}+{:X}, texture_scale={}x{}, "
|
||||
"overlap_base=#{} {} {:X}+{:X}, overlap_mips=#{} {} {:X}+{:X}",
|
||||
bool(texture_key.scaled_resolve), load_base, load_mips,
|
||||
texture_key.tiled ? "tiled" : "linear", texture_key.GetWidth(), texture_key.GetHeight(),
|
||||
texture_key.GetDepthOrArraySize(), FormatInfo::Get(texture_key.format)->name,
|
||||
texture_key.base_page << 12, vulkan_texture.GetGuestBaseSize(),
|
||||
texture_key.mip_page << 12, vulkan_texture.GetGuestMipsSize(),
|
||||
texture_key.scaled_resolve ? "scaled-resolve" : "shared-memory", debug_source_base_start,
|
||||
debug_source_base_range, debug_source_mips_start, debug_source_mips_range,
|
||||
texture_resolution_scale_x, texture_resolution_scale_y, debug_team_profile_base_serial,
|
||||
debug_team_profile_base_overlap ? (debug_team_profile_base_scaled ? "scaled" : "1x")
|
||||
: "none",
|
||||
debug_team_profile_base_scaled_start, debug_team_profile_base_scaled_length,
|
||||
debug_team_profile_mips_serial,
|
||||
debug_team_profile_mips_overlap ? (debug_team_profile_mips_scaled ? "scaled" : "1x")
|
||||
: "none",
|
||||
debug_team_profile_mips_scaled_start, debug_team_profile_mips_scaled_length);
|
||||
}
|
||||
if (texture_key.scaled_resolve) {
|
||||
UseScaledResolveBufferForRead();
|
||||
} else {
|
||||
|
|
@ -1728,6 +1874,13 @@ bool VulkanTextureCache::LoadTextureDataFromResidentMemoryImpl(Texture& texture,
|
|||
vulkan_texture.MarkAsUsed();
|
||||
VulkanTexture::Usage texture_old_usage =
|
||||
vulkan_texture.SetUsage(VulkanTexture::Usage::kTransferDestination);
|
||||
if (DebugIsTeamProfileBackgroundTexture(texture)) {
|
||||
REXGPU_WARN(
|
||||
"Team profile BG Vulkan upload copy begin: old_usage={}, new_usage=transfer-dst, "
|
||||
"scratch_buffer={:016X}, scratch_size={:X}, image={:016X}",
|
||||
DebugVulkanTextureUsageName(texture_old_usage), uint64_t(uintptr_t(scratch_buffer)),
|
||||
uint64_t(host_buffer_size), uint64_t(uintptr_t(vulkan_texture.image())));
|
||||
}
|
||||
if (texture_old_usage != VulkanTexture::Usage::kTransferDestination) {
|
||||
VkPipelineStageFlags texture_src_stage_mask, texture_dst_stage_mask;
|
||||
VkAccessFlags texture_src_access_mask, texture_dst_access_mask;
|
||||
|
|
@ -1782,7 +1935,17 @@ bool VulkanTextureCache::LoadTextureDataFromResidentMemoryImpl(Texture& texture,
|
|||
copy_region.imageExtent.height =
|
||||
std::max((height * texture_resolution_scale_y) >> level, UINT32_C(1));
|
||||
copy_region.imageExtent.depth = std::max(depth >> level, UINT32_C(1));
|
||||
if (DebugIsTeamProfileBackgroundTexture(texture)) {
|
||||
REXGPU_WARN(
|
||||
"Team profile BG Vulkan upload copy region: level={}, buffer_offset={:X}, "
|
||||
"row_length={}, image_height={}, extent={}x{}x{}, layers={}",
|
||||
level, uint64_t(copy_region.bufferOffset), copy_region.bufferRowLength,
|
||||
copy_region.bufferImageHeight, copy_region.imageExtent.width,
|
||||
copy_region.imageExtent.height, copy_region.imageExtent.depth,
|
||||
copy_region.imageSubresource.layerCount);
|
||||
}
|
||||
}
|
||||
DebugLogTeamProfileBackgroundGpuEvent("load-exit", UINT32_MAX, texture);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1813,20 +1976,33 @@ void VulkanTextureCache::UpdateTextureBindingsImpl(uint32_t fetch_constant_mask)
|
|||
host_format_pair.format_unsigned.format != VK_FORMAT_UNDEFINED) {
|
||||
vulkan_binding.image_view_unsigned =
|
||||
static_cast<VulkanTexture*>(binding->texture)->GetView(false, binding->host_swizzle);
|
||||
DebugLogTeamProfileBackgroundGpuEvent("binding-unsigned", binding_index,
|
||||
*binding->texture, false, binding->host_swizzle,
|
||||
vulkan_binding.image_view_unsigned);
|
||||
}
|
||||
if (binding->texture_signed && uses_signed &&
|
||||
host_format_pair.format_signed.format != VK_FORMAT_UNDEFINED) {
|
||||
vulkan_binding.image_view_signed = static_cast<VulkanTexture*>(binding->texture_signed)
|
||||
->GetView(true, binding->host_swizzle);
|
||||
DebugLogTeamProfileBackgroundGpuEvent("binding-signed", binding_index,
|
||||
*binding->texture_signed, true,
|
||||
binding->host_swizzle,
|
||||
vulkan_binding.image_view_signed);
|
||||
}
|
||||
} else {
|
||||
VulkanTexture* texture = static_cast<VulkanTexture*>(binding->texture);
|
||||
if (texture) {
|
||||
if (uses_unsigned && host_format_pair.format_unsigned.format != VK_FORMAT_UNDEFINED) {
|
||||
vulkan_binding.image_view_unsigned = texture->GetView(false, binding->host_swizzle);
|
||||
DebugLogTeamProfileBackgroundGpuEvent("binding-unsigned", binding_index, *texture,
|
||||
false, binding->host_swizzle,
|
||||
vulkan_binding.image_view_unsigned);
|
||||
}
|
||||
if (uses_signed && host_format_pair.format_signed.format != VK_FORMAT_UNDEFINED) {
|
||||
vulkan_binding.image_view_signed = texture->GetView(true, binding->host_swizzle);
|
||||
DebugLogTeamProfileBackgroundGpuEvent("binding-signed", binding_index, *texture, true,
|
||||
binding->host_swizzle,
|
||||
vulkan_binding.image_view_signed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1900,6 +2076,8 @@ VkImageView VulkanTextureCache::VulkanTexture::GetView(bool is_signed, uint32_t
|
|||
// Try to find an existing view.
|
||||
auto it = views_.find(view_key);
|
||||
if (it != views_.end()) {
|
||||
vulkan_texture_cache.DebugLogTeamProfileBackgroundGpuEvent("view-reuse", UINT32_MAX, *this,
|
||||
is_signed, host_swizzle, it->second);
|
||||
return it->second;
|
||||
}
|
||||
|
||||
|
|
@ -1942,6 +2120,8 @@ VkImageView VulkanTextureCache::VulkanTexture::GetView(bool is_signed, uint32_t
|
|||
return VK_NULL_HANDLE;
|
||||
}
|
||||
views_.emplace(view_key, view);
|
||||
vulkan_texture_cache.DebugLogTeamProfileBackgroundGpuEvent("view-create", UINT32_MAX, *this,
|
||||
is_signed, host_swizzle, view);
|
||||
return view;
|
||||
}
|
||||
|
||||
|
|
@ -1977,6 +2157,17 @@ VkImageView VulkanTextureCache::VulkanTexture::GetOrCreate3DAs2DImageView(bool i
|
|||
image_create_info.format = wrapper_format;
|
||||
image_create_info.extent.width = key().GetWidth();
|
||||
image_create_info.extent.height = key().GetHeight();
|
||||
if (key().scaled_resolve) {
|
||||
image_create_info.extent.width *= vulkan_texture_cache.draw_resolution_scale_x();
|
||||
image_create_info.extent.height *= vulkan_texture_cache.draw_resolution_scale_y();
|
||||
}
|
||||
if (Consume3DAs2DLogBudget()) {
|
||||
REXGPU_WARN(
|
||||
"Vulkan 3D-as-2D create: scaled={}, source={}x{}x{}, wrapper={}x{}x1, format={}",
|
||||
bool(key().scaled_resolve), key().GetWidth(), key().GetHeight(),
|
||||
key().GetDepthOrArraySize(), image_create_info.extent.width,
|
||||
image_create_info.extent.height, uint32_t(wrapper_format));
|
||||
}
|
||||
image_create_info.extent.depth = 1;
|
||||
image_create_info.mipLevels = 1;
|
||||
image_create_info.arrayLayers = 1;
|
||||
|
|
@ -2011,6 +2202,11 @@ VkImageView VulkanTextureCache::VulkanTexture::GetOrCreate3DAs2DImageView(bool i
|
|||
texture_3d_as_2d_.reset();
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
if (Consume3DAs2DLogBudget()) {
|
||||
REXGPU_WARN("Vulkan 3D-as-2D load complete: scaled={}, wrapper_image={:016X}",
|
||||
bool(key().scaled_resolve),
|
||||
uint64_t(uintptr_t(texture_3d_as_2d_->image())));
|
||||
}
|
||||
|
||||
if (!vulkan_texture_cache.command_processor_.PushImageMemoryBarrier(
|
||||
texture_3d_as_2d_->image(), ui::vulkan::util::InitializeSubresourceRange(),
|
||||
|
|
@ -2056,6 +2252,10 @@ VkImageView VulkanTextureCache::VulkanTexture::GetOrCreate3DAs2DImageView(bool i
|
|||
REXGPU_ERROR("VulkanTextureCache: Failed to create 3D-as-2D wrapper image view");
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
if (Consume3DAs2DLogBudget()) {
|
||||
REXGPU_WARN("Vulkan 3D-as-2D view create: signed={}, scaled={}, view={:016X}", is_signed,
|
||||
bool(key().scaled_resolve), uint64_t(uintptr_t(cached_view)));
|
||||
}
|
||||
|
||||
return cached_view;
|
||||
}
|
||||
|
|
@ -2270,14 +2470,15 @@ void VulkanTextureCache::UseScaledResolveBufferForRead() {
|
|||
VkAccessFlags src_access_mask, dst_access_mask;
|
||||
GetScaledResolveUsageMasks(src_stage_mask, src_access_mask, scaled_resolve_last_usage_write_);
|
||||
GetScaledResolveUsageMasks(dst_stage_mask, dst_access_mask, false);
|
||||
VkDeviceSize offset;
|
||||
VkDeviceSize size;
|
||||
if (!scaled_resolve_last_usage_write_) {
|
||||
offset = VkDeviceSize(scaled_resolve_last_written_range_.first);
|
||||
size = VkDeviceSize(scaled_resolve_last_written_range_.second);
|
||||
} else {
|
||||
offset = 0;
|
||||
size = VK_WHOLE_SIZE;
|
||||
VkDeviceSize offset = VkDeviceSize(scaled_resolve_last_written_range_.first);
|
||||
VkDeviceSize size = VkDeviceSize(scaled_resolve_last_written_range_.second);
|
||||
if (scaled_resolve_last_usage_write_) {
|
||||
// The range tracked here is the union of scaled resolve writes since the
|
||||
// last read. Make exactly those writes visible to later texture uploads.
|
||||
if (!size) {
|
||||
offset = 0;
|
||||
size = VK_WHOLE_SIZE;
|
||||
}
|
||||
scaled_resolve_last_usage_write_ = false;
|
||||
}
|
||||
command_processor_.PushBufferMemoryBarrier(
|
||||
|
|
@ -2330,7 +2531,20 @@ void VulkanTextureCache::UseScaledResolveBufferForWrite(uint64_t written_start_s
|
|||
scaled_resolve_last_usage_write_ = true;
|
||||
}
|
||||
|
||||
scaled_resolve_last_written_range_ = std::make_pair(written_start_scaled, written_length_scaled);
|
||||
if (!written_length_scaled) {
|
||||
return;
|
||||
}
|
||||
if (!scaled_resolve_last_written_range_.second) {
|
||||
scaled_resolve_last_written_range_ = std::make_pair(written_start_scaled, written_length_scaled);
|
||||
return;
|
||||
}
|
||||
uint64_t written_end = written_start_scaled + written_length_scaled;
|
||||
uint64_t combined_start = std::min(scaled_resolve_last_written_range_.first, written_start_scaled);
|
||||
uint64_t combined_end =
|
||||
std::max(scaled_resolve_last_written_range_.first + scaled_resolve_last_written_range_.second,
|
||||
written_end);
|
||||
scaled_resolve_last_written_range_ =
|
||||
std::make_pair(combined_start, combined_end - combined_start);
|
||||
}
|
||||
|
||||
bool VulkanTextureCache::Initialize() {
|
||||
|
|
|
|||
|
|
@ -40,8 +40,15 @@ X_HRESULT XgiApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
|||
uint32_t user_index = memory::load_and_swap<uint32_t>(buffer + 0);
|
||||
uint32_t context_id = memory::load_and_swap<uint32_t>(buffer + 16);
|
||||
uint32_t context_value = memory::load_and_swap<uint32_t>(buffer + 20);
|
||||
REXKRNL_DEBUG("XGIUserSetContextEx({:08X}, {:08X}, {:08X})", user_index, context_id,
|
||||
context_value);
|
||||
auto old_value = kernel_state_->GetUserContext(user_index, context_id);
|
||||
kernel_state_->SetUserContext(user_index, context_id, context_value);
|
||||
if (!old_value || *old_value != context_value) {
|
||||
REXKRNL_INFO("XGIUserSetContextEx(user={}, context={:#x}, value={:#x})", user_index,
|
||||
context_id, context_value);
|
||||
} else {
|
||||
REXKRNL_DEBUG("XGIUserSetContextEx(user={}, context={:#x}, value={:#x})", user_index,
|
||||
context_id, context_value);
|
||||
}
|
||||
return X_E_SUCCESS;
|
||||
}
|
||||
case 0x000B0007: {
|
||||
|
|
@ -353,13 +360,18 @@ X_HRESULT XgiApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
|||
uint32_t context_ptr = memory::load_and_swap<uint32_t>(buffer + 16);
|
||||
auto context = context_ptr ? memory_->TranslateVirtual(context_ptr) : nullptr;
|
||||
uint32_t context_id = context ? memory::load_and_swap<uint32_t>(context + 0) : 0;
|
||||
REXKRNL_DEBUG("XGIUserGetContext({:08X}, {:08X}, {:08X}))", user_index, context_ptr,
|
||||
context_id);
|
||||
uint32_t value = 0;
|
||||
if (context) {
|
||||
memory::store_and_swap<uint32_t>(context + 4, value);
|
||||
auto stored_value = kernel_state_->GetUserContext(user_index, context_id);
|
||||
if (stored_value) {
|
||||
REXKRNL_DEBUG("XGIUserGetContext({:08X}, {:08X}, {:08X}) -> {:#x}", user_index,
|
||||
context_ptr, context_id, *stored_value);
|
||||
} else {
|
||||
REXKRNL_DEBUG("XGIUserGetContext({:08X}, {:08X}, {:08X}) -> unset", user_index,
|
||||
context_ptr, context_id);
|
||||
}
|
||||
return X_E_FAIL;
|
||||
if (context) {
|
||||
memory::store_and_swap<uint32_t>(context + 4, stored_value.value_or(0));
|
||||
}
|
||||
return stored_value ? X_E_SUCCESS : X_E_FAIL;
|
||||
}
|
||||
case 0x000B0060: {
|
||||
assert_true(!buffer_length || buffer_length == 32);
|
||||
|
|
|
|||
|
|
@ -363,17 +363,16 @@ class KeyboardInputDialog : public XamDialog {
|
|||
bool cancelled() const { return cancelled_; }
|
||||
|
||||
void OnDraw(ImGuiIO& io) override {
|
||||
bool first_draw = false;
|
||||
if (!has_opened_) {
|
||||
ImGui::OpenPopup(title_.c_str());
|
||||
has_opened_ = true;
|
||||
first_draw = true;
|
||||
focus_request_frames_ = 10;
|
||||
}
|
||||
if (ImGui::BeginPopupModal(title_.c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
if (description_.size()) {
|
||||
ImGui::TextWrapped("%s", description_.c_str());
|
||||
}
|
||||
if (first_draw) {
|
||||
if (focus_request_frames_) {
|
||||
ImGui::SetKeyboardFocusHere();
|
||||
}
|
||||
if (ImGui::InputText("##body", text_buffer_.data(), text_buffer_.size(),
|
||||
|
|
@ -383,6 +382,13 @@ class KeyboardInputDialog : public XamDialog {
|
|||
ImGui::CloseCurrentPopup();
|
||||
Close();
|
||||
}
|
||||
if (focus_request_frames_) {
|
||||
if (ImGui::IsItemActive()) {
|
||||
focus_request_frames_ = 0;
|
||||
} else {
|
||||
--focus_request_frames_;
|
||||
}
|
||||
}
|
||||
if (ImGui::Button("OK")) {
|
||||
text_ = std::string(text_buffer_.data(), text_buffer_.size());
|
||||
cancelled_ = false;
|
||||
|
|
@ -405,6 +411,7 @@ class KeyboardInputDialog : public XamDialog {
|
|||
|
||||
private:
|
||||
bool has_opened_ = false;
|
||||
uint32_t focus_request_frames_ = 0;
|
||||
std::string title_;
|
||||
std::string description_;
|
||||
std::string default_text_;
|
||||
|
|
|
|||
|
|
@ -995,6 +995,23 @@ void KernelState::BroadcastNotification(XNotificationID id, uint32_t data) {
|
|||
}
|
||||
}
|
||||
|
||||
void KernelState::SetUserContext(uint32_t user_index, uint32_t context_id, uint32_t value) {
|
||||
const uint64_t key = (uint64_t(user_index) << 32) | context_id;
|
||||
std::lock_guard lock(user_contexts_mutex_);
|
||||
user_contexts_[key] = value;
|
||||
}
|
||||
|
||||
std::optional<uint32_t> KernelState::GetUserContext(uint32_t user_index,
|
||||
uint32_t context_id) const {
|
||||
const uint64_t key = (uint64_t(user_index) << 32) | context_id;
|
||||
std::lock_guard lock(user_contexts_mutex_);
|
||||
auto it = user_contexts_.find(key);
|
||||
if (it == user_contexts_.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
void KernelState::CompleteOverlapped(uint32_t overlapped_ptr, X_RESULT result) {
|
||||
CompleteOverlappedEx(overlapped_ptr, result, result, 0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,14 +40,23 @@
|
|||
#include <rex/ui/keybinds.h>
|
||||
#include <rex/version.h>
|
||||
|
||||
#if REX_PLATFORM_LINUX
|
||||
#include <gnu/libc-version.h>
|
||||
#include <sys/utsname.h>
|
||||
#endif
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <imgui.h>
|
||||
#include <toml++/toml.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
namespace rex {
|
||||
|
|
@ -61,6 +70,139 @@ constexpr bool kBlockShaderStorageStartup =
|
|||
false;
|
||||
#endif
|
||||
|
||||
#if REX_PLATFORM_LINUX
|
||||
std::string Trim(std::string value) {
|
||||
const auto first = value.find_first_not_of(" \t\r\n");
|
||||
if (first == std::string::npos) {
|
||||
return "";
|
||||
}
|
||||
const auto last = value.find_last_not_of(" \t\r\n");
|
||||
return value.substr(first, last - first + 1);
|
||||
}
|
||||
|
||||
std::string UnquoteOsReleaseValue(std::string value) {
|
||||
value = Trim(std::move(value));
|
||||
if (value.size() >= 2 && value.front() == '"' && value.back() == '"') {
|
||||
std::string unquoted;
|
||||
unquoted.reserve(value.size() - 2);
|
||||
bool escaped = false;
|
||||
for (size_t i = 1; i + 1 < value.size(); ++i) {
|
||||
const char ch = value[i];
|
||||
if (escaped) {
|
||||
unquoted.push_back(ch);
|
||||
escaped = false;
|
||||
} else if (ch == '\\') {
|
||||
escaped = true;
|
||||
} else {
|
||||
unquoted.push_back(ch);
|
||||
}
|
||||
}
|
||||
return unquoted;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
std::optional<std::string> ReadOsReleaseValue(std::string_view key) {
|
||||
std::ifstream file("/etc/os-release");
|
||||
std::string line;
|
||||
while (std::getline(file, line)) {
|
||||
const auto equals = line.find('=');
|
||||
if (equals == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
if (std::string_view(line.data(), equals) == key) {
|
||||
return UnquoteOsReleaseValue(line.substr(equals + 1));
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::string TruncateForLog(std::string value) {
|
||||
constexpr size_t kMaxLength = 512;
|
||||
if (value.size() <= kMaxLength) {
|
||||
return value;
|
||||
}
|
||||
value.resize(kMaxLength);
|
||||
value += "...";
|
||||
return value;
|
||||
}
|
||||
|
||||
void LogEnvIfSet(const char* name) {
|
||||
const char* value = std::getenv(name);
|
||||
if (value && *value) {
|
||||
REXLOG_INFO(" {}={}", name, TruncateForLog(value));
|
||||
}
|
||||
}
|
||||
|
||||
void LogLinuxRuntimeDiagnostics() {
|
||||
REXLOG_INFO("Linux runtime diagnostics:");
|
||||
|
||||
const auto pretty_name = ReadOsReleaseValue("PRETTY_NAME");
|
||||
const auto id = ReadOsReleaseValue("ID");
|
||||
const auto version_id = ReadOsReleaseValue("VERSION_ID");
|
||||
if (pretty_name) {
|
||||
REXLOG_INFO(" OS: {}", *pretty_name);
|
||||
}
|
||||
if (id || version_id) {
|
||||
REXLOG_INFO(" OS ID: {} {}", id.value_or("unknown"), version_id.value_or(""));
|
||||
}
|
||||
|
||||
utsname uts = {};
|
||||
if (uname(&uts) == 0) {
|
||||
REXLOG_INFO(" Kernel: {} {} {}", uts.sysname, uts.release, uts.machine);
|
||||
}
|
||||
REXLOG_INFO(" glibc: {}", gnu_get_libc_version());
|
||||
|
||||
#if defined(__clang__)
|
||||
REXLOG_INFO(" Compiler: clang {}", __clang_version__);
|
||||
#elif defined(__GNUC__)
|
||||
REXLOG_INFO(" Compiler: GCC {}.{}.{}", __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__);
|
||||
#endif
|
||||
|
||||
REXLOG_INFO("Linux session environment:");
|
||||
LogEnvIfSet("XDG_SESSION_TYPE");
|
||||
LogEnvIfSet("XDG_CURRENT_DESKTOP");
|
||||
LogEnvIfSet("DESKTOP_SESSION");
|
||||
LogEnvIfSet("DISPLAY");
|
||||
LogEnvIfSet("WAYLAND_DISPLAY");
|
||||
LogEnvIfSet("GDK_BACKEND");
|
||||
LogEnvIfSet("SDL_VIDEODRIVER");
|
||||
LogEnvIfSet("LD_LIBRARY_PATH");
|
||||
|
||||
REXLOG_INFO("Steam runtime environment:");
|
||||
LogEnvIfSet("SteamAppId");
|
||||
LogEnvIfSet("SteamGameId");
|
||||
LogEnvIfSet("STEAM_COMPAT_APP_ID");
|
||||
LogEnvIfSet("STEAM_COMPAT_CLIENT_INSTALL_PATH");
|
||||
LogEnvIfSet("STEAM_COMPAT_DATA_PATH");
|
||||
LogEnvIfSet("STEAM_RUNTIME");
|
||||
LogEnvIfSet("STEAM_RUNTIME_LIBRARY_PATH");
|
||||
LogEnvIfSet("STEAM_RUNTIME_HEAVY");
|
||||
LogEnvIfSet("PRESSURE_VESSEL_RUNTIME");
|
||||
LogEnvIfSet("PRESSURE_VESSEL_APP_ID");
|
||||
LogEnvIfSet("container");
|
||||
|
||||
REXLOG_INFO("Steam Deck / gamescope environment:");
|
||||
LogEnvIfSet("SteamDeck");
|
||||
LogEnvIfSet("STEAMOS");
|
||||
LogEnvIfSet("GAMESCOPE_WAYLAND_DISPLAY");
|
||||
LogEnvIfSet("GAMESCOPE_EXTERNAL_OVERLAY");
|
||||
LogEnvIfSet("ENABLE_GAMESCOPE_WSI");
|
||||
LogEnvIfSet("MESA_VK_WSI_PRESENT_MODE");
|
||||
LogEnvIfSet("RADV_PERFTEST");
|
||||
LogEnvIfSet("VK_ICD_FILENAMES");
|
||||
LogEnvIfSet("VK_DRIVER_FILES");
|
||||
}
|
||||
|
||||
void StartForcedExitWatchdog(const char* reason) {
|
||||
std::thread([reason]() {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(10));
|
||||
REXLOG_WARN("{} watchdog exiting process after shutdown timeout", reason);
|
||||
std::_Exit(EXIT_SUCCESS);
|
||||
}).detach();
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
REXCVAR_DEFINE_BOOL(advanced_settings_overlay_enabled, true, "UI/Advanced",
|
||||
|
|
@ -239,6 +381,9 @@ bool ReXApp::SetupEnvironment() {
|
|||
REXLOG_INFO(" Update data: {}", update_data_root_.string());
|
||||
}
|
||||
REXLOG_INFO(" Cache root: {}", cache_root_.string());
|
||||
#if REX_PLATFORM_LINUX
|
||||
LogLinuxRuntimeDiagnostics();
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -300,8 +445,9 @@ bool ReXApp::ConstructRuntime(const PathConfig& paths) {
|
|||
auto* input_sys = static_cast<rex::input::InputSystem*>(runtime_->input_system());
|
||||
if (input_sys) {
|
||||
input_sys->SetActiveCallback([this]() {
|
||||
if (!debug_overlay_ && !console_overlay_ && !settings_overlay_)
|
||||
if (!imgui_drawer_->HasDialogs()) {
|
||||
return true;
|
||||
}
|
||||
const auto& io = imgui_drawer_->GetIO();
|
||||
return !io.WantCaptureMouse && !io.WantCaptureKeyboard;
|
||||
});
|
||||
|
|
@ -496,6 +642,9 @@ void ReXApp::OnKeyDown(ui::KeyEvent& e) {
|
|||
void ReXApp::OnClosing(ui::UIEvent& e) {
|
||||
(void)e;
|
||||
REXLOG_INFO("Window closing, shutting down...");
|
||||
#if REX_PLATFORM_LINUX
|
||||
StartForcedExitWatchdog("Linux window close");
|
||||
#endif
|
||||
shutting_down_.store(true, std::memory_order_release);
|
||||
#if REX_PLATFORM_MAC
|
||||
if (main_thread_ && main_thread_->is_running()) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue