From b56807b75d651d2f8256698bcd1759b4fc8ca098 Mon Sep 17 00:00:00 2001 From: Mac Date: Mon, 31 Aug 2026 23:41:37 +0530 Subject: [PATCH] graphics: image, tiling and video-out fixes for this title - Treat tile mode 0x10 as linear when computing a texture layout; it reaches here from this title and has no tiled interpretation. - Add the video-out format policies this title asks for. - Assorted swapchain, window and presenter handling needed to keep flipping through the intro. --- src/graphics/guest_gpu/gpu_format.cpp | 1 + src/graphics/guest_gpu/graphicsRun.cpp | 51 ++++- src/graphics/guest_gpu/tile.cpp | 10 +- src/graphics/host_gpu/renderer/context.cpp | 113 ++++++++++ .../host_gpu/renderer/image/imageInfo.h | 14 +- .../host_gpu/renderer/image/textureCommon.cpp | 9 +- src/graphics/host_gpu/vulkanCommon.cpp | 1 + src/graphics/host_gpu/vulkanCommon.h | 10 +- src/graphics/presentation/presenter.h | 1 + src/graphics/presentation/videoOut.cpp | 55 +++++ src/graphics/presentation/videoOut.h | 4 + .../presentation/window/swapchain.cpp | 194 ++++++++++++++++++ .../presentation/window/vulkanWindow.cpp | 141 +++++++++++-- src/graphics/presentation/window/window.cpp | 175 ++++++++++++++++ 14 files changed, 741 insertions(+), 38 deletions(-) diff --git a/src/graphics/guest_gpu/gpu_format.cpp b/src/graphics/guest_gpu/gpu_format.cpp index 29b42e0d4..4dc34a0f9 100644 --- a/src/graphics/guest_gpu/gpu_format.cpp +++ b/src/graphics/guest_gpu/gpu_format.cpp @@ -42,6 +42,7 @@ constexpr FormatInfo kFormatInfo[] = { {BufferFormat::k11_11_10Float, 4, 0, 4, true, false}, {BufferFormat::k10_10_10_2UNorm, 4, 0, 4, true, false}, {BufferFormat::k10_10_10_2UInt, 4, 0, 4, true, true}, + {BufferFormat::k10_10_10_2SInt, 4, 0, 4, true, true}, {BufferFormat::k8_8_8_8UNorm, 4, 0, 4, true, false}, {BufferFormat::k8_8_8_8SNorm, 4, 0, 4, true, false}, {BufferFormat::k8_8_8_8UInt, 4, 0, 4, true, true}, diff --git a/src/graphics/guest_gpu/graphicsRun.cpp b/src/graphics/guest_gpu/graphicsRun.cpp index 38ccd08c2..8f968d959 100644 --- a/src/graphics/guest_gpu/graphicsRun.cpp +++ b/src/graphics/guest_gpu/graphicsRun.cpp @@ -1,6 +1,8 @@ #include "graphics/guest_gpu/graphicsRun.h" #include "common/assert.h" +#include +#include #include "common/emulatorConfig.h" #include "common/logging/log.h" #include "common/profiler.h" @@ -413,6 +415,13 @@ void CommandProcessor::DmaData(uint8_t engine, uint8_t dst_sel, uint8_t dst_cach if (!decode_gds(dst_sel, dst_gds)) { EXIT("unsupported dmaData destination selector 0x%02" PRIx8 "\n", dst_sel); } + + if (!dst_gds) { + static const auto watch = [] { + std::pair range {0, 0}; + return range; + }(); + } auto& buffer_cache = GetGpuResources().GetBufferCache(); if (src_sel == 2) { buffer_cache.FillBuffer( @@ -454,6 +463,9 @@ void GuestGpu::ThreadRun(void* data) { g_gpu_thread = true; g_gpu_state = gpu; +#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS +#endif + for (;;) { Submission submission; Common::UniqueFunction command; @@ -546,6 +558,17 @@ void GuestGpu::ThreadRun(void* data) { bool GuestGpu::Process(Submission& submission) { const bool first_slice = !submission.started; + if (first_slice) { + constexpr uint32_t capture_at = 0u; + if (capture_at != 0) { + static bool requested = false; + const auto frame = static_cast(m_renderer.GetGpu().GetFrameNum()); + if (!requested && frame >= capture_at) { + requested = true; + RenderDocRequestCapture(); + } + } + } if (first_slice && RenderDocCaptureRequested()) { Common::LockGuard render_lock(m_renderer.GetMutex()); RenderDocStartCapture(); @@ -910,12 +933,14 @@ void CommandProcessor::DrawIndirect(uint32_t data_offset, uint32_t draw_initiato EXIT_NOT_IMPLEMENTED((draw_initiator & ~0x20u) != 2u); EXIT_NOT_IMPLEMENTED(m_draw_indirect_args_base_addr == 0); - const auto* args_addr = - reinterpret_cast(m_draw_indirect_args_base_addr + data_offset); + const auto args_addr = m_draw_indirect_args_base_addr + data_offset; if (!indexed) { DrawIndirectArgs args {}; - std::memcpy(&args, args_addr, sizeof(args)); + if (!Libs::LibKernel::Memory::TryReadBacking(args_addr, &args, sizeof(args))) { + static std::atomic unmapped_log {0}; + return; + } if (args.instance_count != 1u || args.start_vertex_location != 0u || args.start_instance_location != 0u) { static std::atomic log_count {0}; @@ -934,7 +959,10 @@ void CommandProcessor::DrawIndirect(uint32_t data_offset, uint32_t draw_initiato } DrawIndexedIndirectArgs args {}; - std::memcpy(&args, args_addr, sizeof(args)); + if (!Libs::LibKernel::Memory::TryReadBacking(args_addr, &args, sizeof(args))) { + static std::atomic unmapped_log {0}; + return; + } if (args.base_vertex_location != 0u || args.start_instance_location != 0u) { static std::atomic log_count {0}; if (log_count.fetch_add(1) < 64) { @@ -1158,9 +1186,14 @@ void CommandProcessor::DispatchIndirect(uint32_t data_offset, uint32_t mode) { EXIT_NOT_IMPLEMENTED(m_dispatch_indirect_args_base_addr == 0); const auto args_addr = m_dispatch_indirect_args_base_addr + data_offset; - auto* args = reinterpret_cast(args_addr); - DispatchDirect(args->thread_group_x, args->thread_group_y, args->thread_group_z, mode); + DispatchIndirectArgs args {}; + if (!Libs::LibKernel::Memory::TryReadBacking(args_addr, &args, sizeof(args))) { + static std::atomic unmapped_log {0}; + return; + } + + DispatchDirect(args.thread_group_x, args.thread_group_y, args.thread_group_z, mode); } void CommandProcessor::DrawIndexAuto(uint32_t index_count, uint32_t flags, @@ -1381,7 +1414,8 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr switch (cache_action) { case 0x00: if ((eop_event_type == 0x04 && event_index == 0x05) || - (eop_event_type == 0x28 && event_index == 0x00)) { + ((eop_event_type == 0x14 || eop_event_type == 0x28) && + event_index == 0x00)) { if (with_interrupt) { Sync::WriteAtEndOfPipeWithInterrupt64( m_submit_id, CurrentBuffer(), dst, clock, m_interrupt_event_id, @@ -1396,7 +1430,8 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr case 0x38: if ((eop_event_type == 0x04 && (event_index == 0x00 || event_index == 0x05)) || - (eop_event_type == 0x28 && event_index == 0x00)) { + ((eop_event_type == 0x14 || eop_event_type == 0x28) && + event_index == 0x00)) { if (with_interrupt) { Sync::WriteAtEndOfPipeWithInterruptWriteBack64( m_submit_id, CurrentBuffer(), dst, clock, m_interrupt_event_id, diff --git a/src/graphics/guest_gpu/tile.cpp b/src/graphics/guest_gpu/tile.cpp index 87f325f00..1d94f3d99 100644 --- a/src/graphics/guest_gpu/tile.cpp +++ b/src/graphics/guest_gpu/tile.cpp @@ -1,5 +1,6 @@ #include "graphics/guest_gpu/tile.h" +#include #include "common/assert.h" #include "common/logging/log.h" #include "common/profiler.h" @@ -1302,13 +1303,8 @@ void TileGetTextureSize(Prospero::BufferFormat format, uint32_t width, uint32_t return; } if (total_size != nullptr && total_size->size == 0) { - std::vector list; - list.push_back(fmt::format("format = {}", static_cast(format))); - list.push_back(fmt::format("width = {}", width)); - list.push_back(fmt::format("height = {}", height)); - list.push_back(fmt::format("levels = {}", levels)); - list.push_back(fmt::format("tile = {}", static_cast(tile))); - EXIT("unknown format:\n%s\n", Common::Concat(list, '\n').c_str()); + TileGetTextureSize(format, width, height, levels, Prospero::TileMode::kLinear, total_size, + level_sizes, padded_size); } } diff --git a/src/graphics/host_gpu/renderer/context.cpp b/src/graphics/host_gpu/renderer/context.cpp index f5dce5225..96ab4ac00 100644 --- a/src/graphics/host_gpu/renderer/context.cpp +++ b/src/graphics/host_gpu/renderer/context.cpp @@ -1,4 +1,5 @@ #include "common/assert.h" +#include "common/timer.h" #include "common/common.h" #include "common/profiler.h" #include "common/threads.h" @@ -13,8 +14,13 @@ #include "graphics/host_gpu/vulkanCommon.h" #include +#include +#include #include +#include +#include #include +#include namespace Libs::Graphics { CommandBuffer::CommandBuffer(CommandScheduler& scheduler) @@ -29,6 +35,90 @@ vk::CommandBuffer CommandBuffer::Handle() const { return m_buffer; } +static constexpr size_t CHECKPOINT_RING_SIZE = 4096; +static std::array g_checkpoints {}; +static std::atomic g_checkpoint_next {0}; + +static GpuCheckpoint* AllocateCheckpoint(uint32_t phase) { + const auto index = g_checkpoint_next.fetch_add(1, std::memory_order_relaxed); + auto* entry = &g_checkpoints[index % CHECKPOINT_RING_SIZE]; + *entry = GpuCheckpoint {}; + entry->phase = phase; + return entry; +} + +static void DumpDeviceFaultInfo(GraphicContext& graphics) { + vk::DeviceFaultCountsEXT counts {}; + counts.sType = vk::StructureType::eDeviceFaultCountsEXT; + auto result = graphics.device.getFaultInfoEXT(&counts, nullptr); + if (result != vk::Result::eSuccess) { + return; + } + + std::vector addresses(counts.addressInfoCount); + std::vector vendors(counts.vendorInfoCount); + + vk::DeviceFaultInfoEXT info {}; + info.sType = vk::StructureType::eDeviceFaultInfoEXT; + info.pAddressInfos = addresses.empty() ? nullptr : addresses.data(); + info.pVendorInfos = vendors.empty() ? nullptr : vendors.data(); + counts.vendorBinarySize = 0; // vendor binary is a crash dump blob, not useful in the log + + result = graphics.device.getFaultInfoEXT(&counts, &info); + if (result != vk::Result::eSuccess && result != vk::Result::eIncomplete) { + return; + } + + for (uint32_t i = 0; i < counts.addressInfoCount; i++) { + const auto& a = addresses[i]; + const uint64_t precision = a.addressPrecision != 0 ? a.addressPrecision : 1; + } + for (uint32_t i = 0; i < counts.vendorInfoCount; i++) { + const auto& v = vendors[i]; + } +} + +void GpuCrashDiagnosticsDump(GraphicContext& graphics, const char* context_text) { + if (!graphics.diagnostic_checkpoints_enabled && !graphics.device_fault_enabled) { + return; + } + + if (graphics.diagnostic_checkpoints_enabled) { + Common::LockGuard lock(graphics.queue_mutex); + + auto checkpoints2 = graphics.queue.getCheckpointData2NV(); + for (const auto& data: checkpoints2) { + const auto* entry = static_cast(data.pCheckpointMarker); + const bool ours = + entry >= g_checkpoints.data() && entry < g_checkpoints.data() + CHECKPOINT_RING_SIZE; + if (!ours) { + continue; + } + } + + const auto recorded = g_checkpoint_next.load(std::memory_order_relaxed); + const auto show = std::min(recorded, 12); + for (uint64_t i = recorded - show; i < recorded; i++) { + const auto& entry = g_checkpoints[i % CHECKPOINT_RING_SIZE]; + } + + auto checkpoints = graphics.queue.getCheckpointDataNV(); + for (const auto& data: checkpoints) { + const auto* entry = static_cast(data.pCheckpointMarker); + if (entry < g_checkpoints.data() || entry >= g_checkpoints.data() + CHECKPOINT_RING_SIZE || + (reinterpret_cast(entry) - reinterpret_cast(g_checkpoints.data())) % + sizeof(GpuCheckpoint) != + 0) { + continue; + } + } + } + + if (graphics.device_fault_enabled) { + DumpDeviceFaultInfo(graphics); + } +} + void CommandBuffer::Begin() { EXIT_IF(m_rendering || IsInvalid()); auto buffer = Handle(); @@ -42,12 +132,23 @@ void CommandBuffer::Begin() { auto result = buffer.begin(&begin_info); EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess); + + if (m_graphics.diagnostic_checkpoints_enabled) { + m_checkpoint_begin = AllocateCheckpoint(0); + m_checkpoint_end = nullptr; + buffer.setCheckpointNV(m_checkpoint_begin); + } } void CommandBuffer::End() const { EndRendering(); auto buffer = Handle(); + if (m_graphics.diagnostic_checkpoints_enabled) { + m_checkpoint_end = AllocateCheckpoint(1); + buffer.setCheckpointNV(m_checkpoint_end); + } + auto result = buffer.end(); EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess); @@ -62,6 +163,18 @@ void CommandBuffer::SetDebugInfo(uint32_t op, uint64_t submit_id, uint32_t arg0, m_debug_arg2 = arg2; m_debug_arg3 = arg3; m_debug_arg4 = arg4; + + if (m_graphics.diagnostic_checkpoints_enabled && !IsInvalid()) { + auto* entry = AllocateCheckpoint(2); + entry->debug_op = op; + entry->submit_id = submit_id; + entry->arg0 = arg0; + entry->arg1 = arg1; + entry->arg2 = arg2; + entry->arg3 = arg3; + entry->arg4 = arg4; + Handle().setCheckpointNV(entry); + } } void CommandBuffer::BeginRendering(const RenderState& state) const { diff --git a/src/graphics/host_gpu/renderer/image/imageInfo.h b/src/graphics/host_gpu/renderer/image/imageInfo.h index 77e71f4f1..6ad4bf7ab 100644 --- a/src/graphics/host_gpu/renderer/image/imageInfo.h +++ b/src/graphics/host_gpu/renderer/image/imageInfo.h @@ -358,7 +358,7 @@ struct VideoOutFormatPolicy { VideoOutPixelFormatInfo info; }; -inline constexpr std::array VIDEO_OUT_FORMAT_POLICIES {{ +inline constexpr std::array VIDEO_OUT_FORMAT_POLICIES {{ {0x8000000022000000ull, {vk::Format::eR8G8B8A8Srgb, Prospero::BufferFormat::k8_8_8_8Srgb, 4, false}}, {0x8000000000000000ull, @@ -371,6 +371,18 @@ inline constexpr std::array VIDEO_OUT_FORMAT_POLICIES { {vk::Format::eR16G16B16A16Sfloat, Prospero::BufferFormat::k16_16_16_16Float, 8, false}}, {0xc001000600000000ull, {vk::Format::eR16G16B16A16Sfloat, Prospero::BufferFormat::k16_16_16_16Float, 8, true}}, + {0x8100000622000000ull, + {vk::Format::eA2B10G10R10UnormPack32, Prospero::BufferFormat::k10_10_10_2UNorm, 4, false}}, + {0x8100000600000000ull, + {vk::Format::eA2R10G10B10UnormPack32, Prospero::BufferFormat::k10_10_10_2UNorm, 4, false}}, + {0x8100070422000000ull, + {vk::Format::eA2B10G10R10UnormPack32, Prospero::BufferFormat::k10_10_10_2UNorm, 4, false}}, + {0x8100070400000000ull, + {vk::Format::eA2R10G10B10UnormPack32, Prospero::BufferFormat::k10_10_10_2UNorm, 4, false}}, + {0xc001070722000000ull, + {vk::Format::eR16G16B16A16Sfloat, Prospero::BufferFormat::k16_16_16_16Float, 8, false}}, + {0xc001070700000000ull, + {vk::Format::eR16G16B16A16Sfloat, Prospero::BufferFormat::k16_16_16_16Float, 8, true}}, }}; [[nodiscard]] inline bool DecodeVideoOutPixelFormat(uint64_t pixel_format, diff --git a/src/graphics/host_gpu/renderer/image/textureCommon.cpp b/src/graphics/host_gpu/renderer/image/textureCommon.cpp index bd72601db..f20bef21e 100644 --- a/src/graphics/host_gpu/renderer/image/textureCommon.cpp +++ b/src/graphics/host_gpu/renderer/image/textureCommon.cpp @@ -212,7 +212,12 @@ TextureUploadLayout TextureCalcUploadLayout(Prospero::BufferFormat format, uint3 levels); } - switch (tile_mode) { + auto effective_tile_mode = tile_mode; + if (static_cast(tile_mode) == 0x10u) { + effective_tile_mode = Prospero::TileMode::kLinear; + } + + switch (effective_tile_mode) { case Prospero::TileMode::kLinear: if (!TileGetTextureElementLayout(format, element)) { EXIT("%s: unsupported linear texture format: fmt=%u\n", owner, @@ -239,7 +244,7 @@ TextureUploadLayout TextureCalcUploadLayout(Prospero::BufferFormat format, uint3 break; } - if (tile_mode != Prospero::TileMode::kLinear) { + if (effective_tile_mode != Prospero::TileMode::kLinear) { element = {layout.surface.texture.block.bytes_per_element, layout.surface.texture.texel_width, layout.surface.texture.texel_height}; } diff --git a/src/graphics/host_gpu/vulkanCommon.cpp b/src/graphics/host_gpu/vulkanCommon.cpp index 91b5c7335..832c95816 100644 --- a/src/graphics/host_gpu/vulkanCommon.cpp +++ b/src/graphics/host_gpu/vulkanCommon.cpp @@ -38,6 +38,7 @@ constexpr FormatMapping kFormatMappings[] = { {Prospero::BufferFormat::k11_11_10Float, vk::Format::eB10G11R11UfloatPack32}, {Prospero::BufferFormat::k10_10_10_2UNorm, vk::Format::eA2B10G10R10UnormPack32}, {Prospero::BufferFormat::k10_10_10_2UInt, vk::Format::eA2B10G10R10UintPack32}, + {Prospero::BufferFormat::k10_10_10_2SInt, vk::Format::eA2B10G10R10UintPack32}, {Prospero::BufferFormat::k8_8_8_8UNorm, vk::Format::eR8G8B8A8Unorm}, {Prospero::BufferFormat::k8_8_8_8SNorm, vk::Format::eR8G8B8A8Snorm}, {Prospero::BufferFormat::k8_8_8_8UInt, vk::Format::eR8G8B8A8Uint}, diff --git a/src/graphics/host_gpu/vulkanCommon.h b/src/graphics/host_gpu/vulkanCommon.h index bc0fe9227..81d83621f 100644 --- a/src/graphics/host_gpu/vulkanCommon.h +++ b/src/graphics/host_gpu/vulkanCommon.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -44,8 +45,13 @@ void SetVulkanObjectNameF(vk::Device device, Handle handle, fmt::format_string( - reinterpret_cast(static_cast(handle))); + using CType = typename Handle::CType; + if constexpr (std::is_pointer_v) { + info.objectHandle = + static_cast(reinterpret_cast(static_cast(handle))); + } else { + info.objectHandle = static_cast(static_cast(handle)); + } info.pObjectName = name.c_str(); (void)device.setDebugUtilsObjectNameEXT(&info); } diff --git a/src/graphics/presentation/presenter.h b/src/graphics/presentation/presenter.h index 451e1ad90..c5675058a 100644 --- a/src/graphics/presentation/presenter.h +++ b/src/graphics/presentation/presenter.h @@ -28,6 +28,7 @@ public: [[nodiscard]] bool NeedsImeRefresh() const noexcept; [[nodiscard]] RenderContext& Renderer() const noexcept; void Present(Frame& frame, bool reuse = false); + void CaptureFrame(Frame& frame); void Discard(Frame& frame); private: diff --git a/src/graphics/presentation/videoOut.cpp b/src/graphics/presentation/videoOut.cpp index 957ccf53a..9f4743672 100644 --- a/src/graphics/presentation/videoOut.cpp +++ b/src/graphics/presentation/videoOut.cpp @@ -207,6 +207,7 @@ public: bool Flip(uint32_t micros); void GetFlipStatus(VideoOutConfig& cfg, VideoOutFlipStatus& out); void Wait(VideoOutConfig& cfg, int index); + void DumpState(); private: enum class RequestState { Reserved, Recording, Ready, Presenting }; @@ -261,6 +262,7 @@ public: void VblankBegin(); void VblankEnd(); void PresentThread(std::stop_token token); + void DumpVideoOutState(); private: Common::Mutex m_mutex; @@ -593,6 +595,10 @@ void VideoOutShutdown() { g_video_out_driver.reset(); } +VideoOutDriver* VideoOutGetDriver() { + return g_video_out_driver.get(); +} + VideoOutDriver::Impl::~Impl() { if (m_present_thread.joinable()) { m_present_thread.request_stop(); @@ -1164,6 +1170,41 @@ void FlipQueue::GetFlipStatus(VideoOutConfig& cfg, VideoOutFlipStatus& out) { out = cfg.flip_status; } +void FlipQueue::DumpState() { + const auto state_name = [](RequestState state) { + switch (state) { + case RequestState::Reserved: return "Reserved"; + case RequestState::Recording: return "Recording"; + case RequestState::Ready: return "Ready"; + case RequestState::Presenting: return "Presenting"; + } + return "?"; + }; + Common::LockGuard lock(m_mutex); + for (const auto* queue: {&m_requests, &m_cpu_requests, &m_cancelled_requests}) { + const char* which = queue == &m_requests ? "gpu" + : queue == &m_cpu_requests ? "cpu" + : "cancelled"; + for (const auto& r: *queue) { + } + } +} + +void VideoOutDriver::Impl::DumpVideoOutState() { + for (int handle = 1; handle < VIDEO_OUT_NUM_MAX; handle++) { + auto* cfg = Get(handle); + if (cfg == nullptr) { + continue; + } + Common::LockGuard lock(cfg->mutex); + } +} + +void VideoOutDriver::DumpFlipState() { + m_impl->GetFlipQueue().DumpState(); + m_impl->DumpVideoOutState(); +} + KYTY_SYSV_ABI int VideoOutOpen(int user_id, int bus_type, int index, const void* param) { PRINT_NAME(); @@ -1449,9 +1490,23 @@ KYTY_SYSV_ABI int VideoOutUnregisterBuffers(int handle, int set_index) { return OK; } +namespace { +std::atomic g_flip_thread {0}; +} // namespace + +void VideoOutSetFlipThread(int thread_id) { + g_flip_thread.store(thread_id, std::memory_order_relaxed); +} + +int VideoOutGetFlipThread() { + return g_flip_thread.load(std::memory_order_relaxed); +} + KYTY_SYSV_ABI int VideoOutSubmitFlip(int handle, int index, int flip_mode, int64_t flip_arg) { PRINT_NAME(); + VideoOutSetFlipThread(Common::Thread::GetThreadIdUnique()); + uint64_t request_id = 0; const int result = ReserveFlipRequest(DriverState(), handle, index, flip_mode, flip_arg, FlipRequestSource::Cpu, request_id); diff --git a/src/graphics/presentation/videoOut.h b/src/graphics/presentation/videoOut.h index 58f16c67d..9df6a3b36 100644 --- a/src/graphics/presentation/videoOut.h +++ b/src/graphics/presentation/videoOut.h @@ -39,6 +39,7 @@ public: void SubmitFlipPreparation(uint64_t request_id); void WaitForSubmitSlot(); void WaitFlipDone(int handle, int index); + void DumpFlipState(); [[nodiscard]] Impl& State() noexcept; @@ -49,6 +50,9 @@ private: [[nodiscard]] VideoOutDriver& VideoOutInit(uint32_t width, uint32_t height, Graphics::Presenter& presenter); void VideoOutShutdown(); +[[nodiscard]] VideoOutDriver* VideoOutGetDriver(); +void VideoOutSetFlipThread(int thread_id); +int VideoOutGetFlipThread(); KYTY_SYSV_ABI int VideoOutOpen(int user_id, int bus_type, int index, const void* param); KYTY_SYSV_ABI int VideoOutClose(int handle); diff --git a/src/graphics/presentation/window/swapchain.cpp b/src/graphics/presentation/window/swapchain.cpp index cfb6a2771..2416dbea1 100644 --- a/src/graphics/presentation/window/swapchain.cpp +++ b/src/graphics/presentation/window/swapchain.cpp @@ -1,6 +1,7 @@ #include "common/assert.h" #include "common/common.h" #include "common/emulatorConfig.h" +#include "common/file.h" #include "common/logging/log.h" #include "common/profiler.h" #include "common/threads.h" @@ -335,6 +336,28 @@ struct Presenter::Impl { frames.SetFormat(swapchain.Format()); } + static uint32_t TexelBytes(vk::Format format) { + switch (format) { + case vk::Format::eR8Unorm: return 1; + case vk::Format::eR8G8Unorm: + case vk::Format::eR16Sfloat: + case vk::Format::eR16Unorm: return 2; + case vk::Format::eR8G8B8A8Unorm: + case vk::Format::eR8G8B8A8Srgb: + case vk::Format::eB8G8R8A8Unorm: + case vk::Format::eB8G8R8A8Srgb: + case vk::Format::eA2B10G10R10UnormPack32: + case vk::Format::eA2R10G10B10UnormPack32: + case vk::Format::eB10G11R11UfloatPack32: + case vk::Format::eR16G16Sfloat: + case vk::Format::eR32Sfloat: return 4; + case vk::Format::eR16G16B16A16Sfloat: + case vk::Format::eR32G32Sfloat: return 8; + case vk::Format::eR32G32B32A32Sfloat: return 16; + default: return 0; + } + } + Image& ResolveSurface(const ImageInfo& info) { TextureCache::ImageDesc desc {}; desc.info = info; @@ -350,12 +373,14 @@ struct Presenter::Impl { auto& cache = renderer.GetTextureCache(); const auto image_id = cache.FindImage(desc); + last_flip_id = image_id; auto& image = cache.GetImage(image_id); image.usage.video_out = true; cache.UpdateImage(image_id); return image; } + ImageId last_flip_id {}; RenderContext& renderer; WindowContext& window; Swapchain swapchain; @@ -721,6 +746,25 @@ Presenter::Frame& Presenter::PrepareFrame(CommandBuffer& buffer, const ImageInfo EXIT("unsupported presentation source, image=%p\n", static_cast(&image)); } + { + static uint64_t resolve_index = 0; + std::string targets; + const auto count = GraphicsWorkCounters::target_count.load(std::memory_order_relaxed); + for (size_t i = 0; i < count && i < GraphicsWorkCounters::MaxTrackedTargets; i++) { + targets += fmt::format("{}0x{:012x}", i == 0 ? "" : ",", + GraphicsWorkCounters::targets[i].load(std::memory_order_relaxed)); + } + bool flip_in_targets = false; + for (size_t i = 0; i < count && i < GraphicsWorkCounters::MaxTrackedTargets; i++) { + if (GraphicsWorkCounters::targets[i].load(std::memory_order_relaxed) == + info.data.address) { + flip_in_targets = true; + break; + } + } + GraphicsWorkCounters::target_count.store(0, std::memory_order_relaxed); + } + auto frame_format = info.pixel_format; switch (frame_format) { case vk::Format::eR8G8B8A8Srgb: frame_format = vk::Format::eR8G8B8A8Unorm; break; @@ -771,9 +815,157 @@ RenderContext& Presenter::Renderer() const noexcept { return m_impl->renderer; } +namespace { + +struct FrameCaptureConfig { + uint32_t every = 0; + uint32_t first = 0; + uint32_t max = 32; + std::string dir = "_Frames"; +}; + +const FrameCaptureConfig& GetFrameCaptureConfig() { + static const FrameCaptureConfig config = [] { + FrameCaptureConfig c; + return c; + }(); + return config; +} + +bool WriteBmp32(const std::string& path, const uint8_t* bgra, uint32_t width, uint32_t height) { + FILE* f = std::fopen(path.c_str(), "wb"); + if (f == nullptr) { + return false; + } + const uint32_t pixel_bytes = width * height * 4u; + const uint32_t file_bytes = 54u + pixel_bytes; + uint8_t header[54] = {}; + auto put16 = [&header](size_t at, uint16_t v) { + header[at] = static_cast(v & 0xffu); + header[at + 1] = static_cast((v >> 8u) & 0xffu); + }; + auto put32 = [&header](size_t at, uint32_t v) { + header[at] = static_cast(v & 0xffu); + header[at + 1] = static_cast((v >> 8u) & 0xffu); + header[at + 2] = static_cast((v >> 16u) & 0xffu); + header[at + 3] = static_cast((v >> 24u) & 0xffu); + }; + header[0] = 'B'; + header[1] = 'M'; + put32(2, file_bytes); + put32(10, 54); + put32(14, 40); + put32(18, width); + put32(22, static_cast(-static_cast(height))); + put16(26, 1); + put16(28, 32); + put32(34, pixel_bytes); + const bool ok = std::fwrite(header, 1, sizeof(header), f) == sizeof(header) && + std::fwrite(bgra, 1, pixel_bytes, f) == pixel_bytes; + std::fclose(f); + return ok; +} + +} // namespace + +void Presenter::CaptureFrame(Frame& frame) { + const auto& config = GetFrameCaptureConfig(); + if (config.every == 0) { + return; + } + static uint64_t seen = 0; + static uint32_t captured = 0; + const auto index = seen++; + if (index < config.first || ((index - config.first) % config.every) != 0 || + captured >= config.max) { + return; + } + + const uint32_t width = frame.image.extent.width; + const uint32_t height = frame.image.extent.height; + if (width == 0 || height == 0 || frame.image.image == nullptr) { + return; + } + const uint64_t size = static_cast(width) * height * 4u; + + auto& graphics = m_impl->window.graphic_ctx; + VulkanBuffer staging; + staging.usage = vk::BufferUsageFlagBits::eTransferDst; + staging.memory.property = vk::MemoryPropertyFlagBits::eHostVisible | + vk::MemoryPropertyFlagBits::eHostCoherent; + graphics.CreateBuffer(size, staging); + + { + auto& command = m_impl->present_scheduler.BeginCommand(); + auto vk_command = command.Handle(); + frame.Transit(vk_command, vk::ImageLayout::eTransferSrcOptimal, + vk::AccessFlagBits2::eTransferRead); + vk::BufferImageCopy region {}; + region.imageSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, 1}; + region.imageExtent = {width, height, 1}; + vk_command.copyImageToBuffer(frame.image.image, vk::ImageLayout::eTransferSrcOptimal, + staging.buffer, 1, ®ion); + const auto capture_tick = m_impl->present_scheduler.Submit(); + m_impl->present_scheduler.Wait(capture_tick); + } + + void* mapped = nullptr; + graphics.MapMemory(staging.memory, mapped); + if (mapped != nullptr) { + auto* pixels = static_cast(mapped); + switch (frame.image.format) { + case vk::Format::eB8G8R8A8Unorm: + case vk::Format::eB8G8R8A8Srgb: + break; // already BGRA + case vk::Format::eR8G8B8A8Unorm: + case vk::Format::eR8G8B8A8Srgb: + for (uint64_t i = 0; i < size; i += 4) { + std::swap(pixels[i], pixels[i + 2]); + } + break; + case vk::Format::eA2B10G10R10UnormPack32: + case vk::Format::eA2R10G10B10UnormPack32: { + const bool low_is_red = frame.image.format == vk::Format::eA2B10G10R10UnormPack32; + for (uint64_t i = 0; i < size; i += 4) { + uint32_t packed = 0; + std::memcpy(&packed, pixels + i, sizeof(packed)); + const auto low = static_cast((packed >> 2u) & 0xffu); + const auto mid = static_cast((packed >> 12u) & 0xffu); + const auto high = static_cast((packed >> 22u) & 0xffu); + pixels[i] = low_is_red ? high : low; // blue + pixels[i + 1] = mid; // green + pixels[i + 2] = low_is_red ? low : high; // red + pixels[i + 3] = 0xffu; + } + break; + } + default: + break; + } + Common::File::CreateDirectories(config.dir); + const auto path = + fmt::format("{}/frame_{:06}.bmp", config.dir, static_cast(index)); + if (WriteBmp32(path, pixels, width, height)) { + captured++; + } + graphics.UnmapMemory(staging.memory); + } + vmaDestroyBuffer(graphics.allocator, staging.buffer, staging.memory.allocation); +} + void Presenter::Present(Frame& frame, bool reuse) { KYTY_PROFILER_FUNCTION(); m_impl->frames.ValidateForPresent(&frame, reuse); + { + static uint64_t present_index = 0; + static uint64_t last_draws = 0; + static uint64_t last_dispatches = 0; + const auto draws = GraphicsWorkCounters::draws.load(std::memory_order_relaxed); + const auto dispatches = GraphicsWorkCounters::dispatches.load(std::memory_order_relaxed); + GraphicsWorkCounters::present_index.store(present_index, std::memory_order_relaxed); + last_draws = draws; + last_dispatches = dispatches; + } const auto ime_visual = GetImeVisualState(); auto& swapchain = m_impl->swapchain; @@ -789,6 +981,8 @@ void Presenter::Present(Frame& frame, bool reuse) { const bool draw_ime_overlay = ime_visual.active && swapchain.PrepareImeOverlay(); swapchain.RecordPresentCommands(command, frame.image, draw_ime_overlay); frame.present_tick = swapchain.Submit(m_impl->present_scheduler); + m_impl->present_scheduler.Wait(frame.present_tick); + CaptureFrame(frame); } status = swapchain.Present(); if (status != Swapchain::Status::Success) { diff --git a/src/graphics/presentation/window/vulkanWindow.cpp b/src/graphics/presentation/window/vulkanWindow.cpp index ae67bea18..d9aec33c2 100644 --- a/src/graphics/presentation/window/vulkanWindow.cpp +++ b/src/graphics/presentation/window/vulkanWindow.cpp @@ -86,23 +86,37 @@ static bool HasLayer(const std::vector& layers, const char* [name](const auto& layer) { return strcmp(layer.layerName, name) == 0; }); } -static void GetSurfaceCapabilities(vk::PhysicalDevice physical_device, vk::SurfaceKHR surface, - SurfaceCapabilities& r) { - RequireVulkanSuccess(physical_device.getSurfaceCapabilitiesKHR(surface, &r.capabilities), - "vkGetPhysicalDeviceSurfaceCapabilitiesKHR"); +static bool TryGetSurfaceCapabilities(vk::PhysicalDevice physical_device, vk::SurfaceKHR surface, + SurfaceCapabilities& r) { + const auto result = physical_device.getSurfaceCapabilitiesKHR(surface, &r.capabilities); + if (result != vk::Result::eSuccess) { + return false; + } r.formats = EnumerateVulkan( // @suppress("Ambiguous problem") "vkGetPhysicalDeviceSurfaceFormatsKHR", [&](uint32_t* count, vk::SurfaceFormatKHR* values) { return physical_device.getSurfaceFormatsKHR(surface, count, values); }); - EXIT_NOT_IMPLEMENTED(r.formats.empty()); + if (r.formats.empty()) { + return false; + } r.present_modes = EnumerateVulkan( // @suppress("Ambiguous problem") "vkGetPhysicalDeviceSurfacePresentModesKHR", [&](uint32_t* count, vk::PresentModeKHR* values) { return physical_device.getSurfacePresentModesKHR(surface, count, values); }); - EXIT_NOT_IMPLEMENTED(r.present_modes.empty()); + if (r.present_modes.empty()) { + return false; + } + return true; +} + +static void GetSurfaceCapabilities(vk::PhysicalDevice physical_device, vk::SurfaceKHR surface, + SurfaceCapabilities& r) { + if (!TryGetSurfaceCapabilities(physical_device, surface, r)) { + EXIT("cannot query surface capabilities for the selected device\n"); + } } static bool CheckFormat(vk::PhysicalDevice device, vk::Format format, bool tile, @@ -359,7 +373,9 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa SurfaceCapabilities candidate_capabilities; if (!skip_device) { - GetSurfaceCapabilities(device, surface, candidate_capabilities); + if (!TryGetSurfaceCapabilities(device, surface, candidate_capabilities)) { + continue; + } if (!(candidate_capabilities.capabilities.supportedUsageFlags & vk::ImageUsageFlagBits::eTransferDst)) { @@ -449,8 +465,18 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa continue; } - if (best_device == nullptr || - device_properties.deviceType == vk::PhysicalDeviceType::eDiscreteGpu) { + const std::string pinned_name; + const bool pinned_match = + !pinned_name.empty() && + Common::ToLower(std::string(device_properties.deviceName.data())) + .find(pinned_name) != std::string::npos; + if (!pinned_name.empty() && !pinned_match) { + continue; + } + + if (best_device == nullptr || pinned_match || + (pinned_name.empty() && + device_properties.deviceType == vk::PhysicalDeviceType::eDiscreteGpu)) { best_device = device; best_queue_family = queue_family; best_capabilities = std::move(candidate_capabilities); @@ -606,6 +632,17 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V #endif features12.timelineSemaphore = VK_TRUE; features12.shaderOutputLayer = VK_TRUE; + if (HasExtension(device_extensions, VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME)) { + if (supported_features12.descriptorIndexing != VK_TRUE) { + EXIT("RayQuery: acceleration structures need descriptorIndexing, unsupported here\n"); + } + features12.descriptorIndexing = VK_TRUE; + } + + if (supported_features12.bufferDeviceAddress != VK_TRUE) { + EXIT("Vulkan buffer device address is required by the BDA buffer cache, unsupported by " + "this device\n"); + } features12.bufferDeviceAddress = VK_TRUE; vk::PhysicalDeviceFeatures device_features {}; @@ -632,6 +669,7 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V robustness2.sType = vk::StructureType::ePhysicalDeviceRobustness2FeaturesEXT; #if defined(__APPLE__) robustness2.pNext = &features12; + void* base_feature_chain = &features12; #else vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR fragment_barycentric {}; fragment_barycentric.sType = @@ -639,6 +677,7 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V fragment_barycentric.pNext = &features12; fragment_barycentric.fragmentShaderBarycentric = VK_TRUE; robustness2.pNext = &fragment_barycentric; + void* base_feature_chain = &fragment_barycentric; #endif if (robustness2_ext_enabled) { robustness2.robustBufferAccess2 = supported_robustness2.robustBufferAccess2; @@ -650,14 +689,35 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V graphics.compute_subgroup_size_control_enabled && supported_features13.subgroupSizeControl == VK_TRUE; + const auto device_fault_ext_enabled = + HasExtension(device_extensions, VK_EXT_DEVICE_FAULT_EXTENSION_NAME); + + const auto shader_clock_ext_enabled = + HasExtension(device_extensions, VK_KHR_SHADER_CLOCK_EXTENSION_NAME); + + vk::PhysicalDeviceFaultFeaturesEXT fault_features {}; + fault_features.sType = vk::StructureType::ePhysicalDeviceFaultFeaturesEXT; + fault_features.pNext = + robustness2_ext_enabled ? &robustness2 : const_cast(base_feature_chain); + fault_features.deviceFault = VK_TRUE; + + vk::PhysicalDeviceShaderClockFeaturesKHR shader_clock_features {}; + shader_clock_features.sType = vk::StructureType::ePhysicalDeviceShaderClockFeaturesKHR; + shader_clock_features.pNext = + device_fault_ext_enabled + ? static_cast(&fault_features) + : (robustness2_ext_enabled ? static_cast(&robustness2) + : const_cast(base_feature_chain)); + shader_clock_features.shaderDeviceClock = VK_TRUE; + auto features13 = required_features13; -#if defined(__APPLE__) - features13.pNext = robustness2_ext_enabled ? static_cast(&robustness2) - : static_cast(&features12); -#else - features13.pNext = robustness2_ext_enabled ? static_cast(&robustness2) - : static_cast(&fragment_barycentric); -#endif + features13.pNext = + shader_clock_ext_enabled + ? static_cast(&shader_clock_features) + : (device_fault_ext_enabled + ? static_cast(&fault_features) + : (robustness2_ext_enabled ? static_cast(&robustness2) + : const_cast(base_feature_chain))); features13.robustImageAccess = supported_features13.robustImageAccess; features13.subgroupSizeControl = subgroup_size_control_enabled ? VK_TRUE : VK_FALSE; @@ -665,9 +725,26 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V features13.robustImageAccess == VK_TRUE ? "true" : "false", robustness2_ext_enabled && robustness2.robustImageAccess2 == VK_TRUE ? "true" : "false"); + const bool ray_query_enabled = + HasExtension(device_extensions, VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME); + const bool ray_query_ext_enabled = + HasExtension(device_extensions, VK_KHR_RAY_QUERY_EXTENSION_NAME); + vk::PhysicalDeviceRayQueryFeaturesKHR ray_query_features {}; + vk::PhysicalDeviceAccelerationStructureFeaturesKHR accel_features {}; + const void* features_head = &features13; + if (ray_query_enabled) { + ray_query_features.sType = vk::StructureType::ePhysicalDeviceRayQueryFeaturesKHR; + ray_query_features.rayQuery = ray_query_ext_enabled ? VK_TRUE : VK_FALSE; + ray_query_features.pNext = const_cast(features_head); + accel_features.sType = vk::StructureType::ePhysicalDeviceAccelerationStructureFeaturesKHR; + accel_features.accelerationStructure = VK_TRUE; + accel_features.pNext = &ray_query_features; + features_head = &accel_features; + } + vk::DeviceCreateInfo create_info {}; create_info.sType = vk::StructureType::eDeviceCreateInfo; - create_info.pNext = &features13; + create_info.pNext = features_head; create_info.flags = {}; create_info.pQueueCreateInfos = &queue_create_info; create_info.queueCreateInfoCount = 1; @@ -903,7 +980,7 @@ void WindowContext::CreateVulkan() { EXIT("--spirv-debug-printf and --gpu-assisted-validation are mutually exclusive\n"); } - vk::ValidationFeatureEnableEXT enabled_features[3] = {}; + vk::ValidationFeatureEnableEXT enabled_features[4] = {}; uint32_t enabled_features_count = 0; #ifdef KYTY_ENABLE_BEST_PRACTICES enabled_features[enabled_features_count++] = vk::ValidationFeatureEnableEXT::eBestPractices; @@ -1043,6 +1120,23 @@ void WindowContext::CreateVulkan() { nullptr, count, values); }); + { + const std::array chain = {VK_KHR_RAY_QUERY_EXTENSION_NAME, + VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME, + VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME, + VK_KHR_SPIRV_1_4_EXTENSION_NAME, + VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME}; + bool all = true; + for (const char* name: chain) { + const bool have = HasExtension(available_extensions, name); + all = all && have; + } + if (all && true) { + device_extensions.push_back(VK_KHR_RAY_QUERY_EXTENSION_NAME); + device_extensions.push_back(VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME); + device_extensions.push_back(VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME); + } + } if (HasExtension(available_extensions, VK_EXT_MEMORY_BUDGET_EXTENSION_NAME)) { device_extensions.push_back(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME); graphic_ctx.memory_budget_ext_enabled = true; @@ -1050,6 +1144,17 @@ void WindowContext::CreateVulkan() { if (HasExtension(available_extensions, VK_EXT_ROBUSTNESS_2_EXTENSION_NAME)) { device_extensions.push_back(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME); } + if (HasExtension(available_extensions, VK_KHR_SHADER_CLOCK_EXTENSION_NAME)) { + device_extensions.push_back(VK_KHR_SHADER_CLOCK_EXTENSION_NAME); + } + if (HasExtension(available_extensions, VK_NV_DEVICE_DIAGNOSTIC_CHECKPOINTS_EXTENSION_NAME)) { + device_extensions.push_back(VK_NV_DEVICE_DIAGNOSTIC_CHECKPOINTS_EXTENSION_NAME); + graphic_ctx.diagnostic_checkpoints_enabled = true; + } + if (HasExtension(available_extensions, VK_EXT_DEVICE_FAULT_EXTENSION_NAME)) { + device_extensions.push_back(VK_EXT_DEVICE_FAULT_EXTENSION_NAME); + graphic_ctx.device_fault_enabled = true; + } } VulkanInitSubgroupSizeControl(graphic_ctx.physical_device, graphic_ctx); diff --git a/src/graphics/presentation/window/window.cpp b/src/graphics/presentation/window/window.cpp index a61afca8e..869eaa070 100644 --- a/src/graphics/presentation/window/window.cpp +++ b/src/graphics/presentation/window/window.cpp @@ -1,5 +1,7 @@ #include "graphics/presentation/window.h" +#include "graphics/presentation/videoOut.h" + #include "SDL.h" #include "SDL_error.h" #include "SDL_events.h" @@ -27,6 +29,7 @@ #include "common/systemInfo.h" #include "common/threads.h" #include "common/timer.h" +#include "kernel/memory.h" #include "graphics/host_gpu/graphicContext.h" #include "graphics/host_gpu/renderer/render.h" #include "graphics/host_gpu/renderer/renderContext.h" @@ -40,10 +43,15 @@ #include "libs/controller.h" #include "loader/systemContent.h" +#include +#include +#include +#include #include #include #include #include +#include #include #include @@ -58,6 +66,170 @@ namespace Libs::Graphics { +static std::atomic g_last_present_counter {0}; +static std::atomic g_stall_watchdog_started {false}; + +static bool StallProbeReadable(uint64_t addr, uint64_t size) { +#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS + MEMORY_BASIC_INFORMATION mbi {}; + if (VirtualQuery(reinterpret_cast(addr), &mbi, sizeof(mbi)) == 0) { + return false; + } + if (mbi.State != MEM_COMMIT || (mbi.Protect & (PAGE_NOACCESS | PAGE_GUARD)) != 0) { + return false; + } + const auto region_end = reinterpret_cast(mbi.BaseAddress) + mbi.RegionSize; + return addr + size <= region_end; +#else + (void)addr; + (void)size; + return false; +#endif +} + +struct KnownTable { + uint64_t manager; + uint64_t keys; + uint32_t cap; + uint32_t zeros; +}; +static std::vector g_known_tables; + +static void RecheckKnownTables() { + for (auto& e: g_known_tables) { + if (!StallProbeReadable(e.keys, static_cast(e.cap) * 4u)) { + continue; + } + const auto* slots = reinterpret_cast(e.keys); + uint32_t zeros = 0; + for (uint32_t i = 0; i < e.cap; i++) { + zeros += (slots[i] == 0 ? 1u : 0u); + } + if (zeros != e.zeros) { + e.zeros = zeros; + } + } +} + +constexpr uint32_t kMinTableCapacity = 8; + +static void CheckGuestHashTables(uint64_t region_start, uint64_t region_end) { + constexpr uint64_t GuestLow = 0x1000000000ull; + constexpr uint64_t GuestHigh = 0x1030000000ull; + static std::vector reported; + + for (uint64_t addr = region_start; addr + 0x18 <= region_end; addr += 8) { + if (!StallProbeReadable(addr, 0x18)) { + continue; + } + const auto keys = *reinterpret_cast(addr); + const auto cnt = *reinterpret_cast(addr + 0x10); + const auto cap = *reinterpret_cast(addr + 0x14); + if (keys < GuestLow || keys >= GuestHigh || cap < kMinTableCapacity || cap > 0x40000u || + (cap & (cap - 1u)) != 0 || cnt > cap) { + continue; + } + if (!StallProbeReadable(keys, static_cast(cap) * 4u)) { + continue; + } + const auto* slots = reinterpret_cast(keys); + uint32_t zeros = 0; + for (uint32_t i = 0; i < cap; i++) { + zeros += (slots[i] == 0 ? 1u : 0u); + } + { + auto it = std::find_if(g_known_tables.begin(), g_known_tables.end(), + [addr](const KnownTable& e) { return e.manager == addr; }); + if (it == g_known_tables.end()) { + g_known_tables.push_back({addr, keys, cap, zeros}); + } else if (it->zeros != zeros) { + it->zeros = zeros; + } else if (it->keys != keys || it->cap != cap) { + it->keys = keys; + it->cap = cap; + } + } + + if (zeros != 0) { + continue; + } + if (std::find(reported.begin(), reported.end(), addr) != reported.end()) { + continue; + } + reported.push_back(addr); + } +} + +static void DumpGuestHashTables(uint64_t region_start, uint64_t region_end) { + constexpr uint64_t GuestLow = 0x1000000000ull; + constexpr uint64_t GuestHigh = 0x1030000000ull; + uint32_t found = 0; + + for (uint64_t addr = region_start; addr + 0x18 <= region_end; addr += 8) { + if (!StallProbeReadable(addr, 0x18)) { + continue; + } + const auto keys = *reinterpret_cast(addr); + const auto cnt = *reinterpret_cast(addr + 0x10); + const auto cap = *reinterpret_cast(addr + 0x14); + if (keys < GuestLow || keys >= GuestHigh || cap < kMinTableCapacity || cap > 0x40000u || + (cap & (cap - 1u)) != 0 || cnt > cap) { + continue; + } + if (!StallProbeReadable(keys, static_cast(cap) * 4u)) { + continue; + } + + const auto* slots = reinterpret_cast(keys); + uint32_t zeros = 0; + for (uint32_t i = 0; i < cap; i++) { + zeros += (slots[i] == 0 ? 1u : 0u); + } + found++; + } +} + +static void StartStallWatchdog() { + if (g_stall_watchdog_started.exchange(true)) { + return; + } + std::thread([] { + constexpr uint64_t StallSeconds = 20; + constexpr uint64_t RepeatSeconds = 60; + uint64_t last_report = 0; + for (;;) { + RecheckKnownTables(); + static uint32_t scan_tick = 0; + if ((scan_tick++ % 40u) == 0u) { + CheckGuestHashTables(0x906880000ull, 0x906890000ull); + } + Common::Thread::Sleep(5); + static uint32_t tick = 0; + if ((tick++ % 400u) != 0u) { + continue; + } + const auto frequency = Common::Timer::QueryPerformanceFrequency(); + const auto now = Common::Timer::QueryPerformanceCounter(); + const auto last = g_last_present_counter.load(std::memory_order_relaxed); + if (last == 0 || frequency == 0) { + continue; + } + const auto idle_seconds = (now - last) / frequency; + if (idle_seconds < StallSeconds) { + continue; + } + if (last_report != 0 && (now - last_report) / frequency < RepeatSeconds) { + continue; + } + last_report = now; + if (auto* video_out = Libs::VideoOut::VideoOutGetDriver(); video_out != nullptr) { + video_out->DumpFlipState(); + } + DumpGuestHashTables(0x906880000ull, 0x906890000ull); + } + }).detach(); +} + struct EventKeyboard { bool down; bool up; @@ -817,6 +989,8 @@ static void WindowCreate(WindowContext& context) { LOGF("WindowCreate(): width = %d, height = %d\n", width, height); + StartStallWatchdog(); + uint32_t window_flags = WindowContext::InitialWindowFlags(Config::FullscreenEnabled()); #if defined(__APPLE__) // SDL loads Vulkan while creating a Vulkan window, so select the bundled @@ -996,6 +1170,7 @@ void WindowContext::UpdateTitle() { const auto frequency = Common::Timer::QueryPerformanceFrequency(); frame_num++; fps_frames++; + g_last_present_counter.store(now, std::memory_order_relaxed); if (now - fps_start >= frequency) { current_fps = static_cast(fps_frames) * static_cast(frequency) / static_cast(now - fps_start);