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.
This commit is contained in:
Mac
2026-08-31 23:41:37 +05:30
parent e6ad3dc8a5
commit b56807b75d
14 changed files with 741 additions and 38 deletions
+1
View File
@@ -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},
+43 -8
View File
@@ -1,6 +1,8 @@
#include "graphics/guest_gpu/graphicsRun.h"
#include "common/assert.h"
#include <utility>
#include <cstdlib>
#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<uint64_t, uint64_t> 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<void> 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<uint32_t>(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<const void*>(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<uint32_t> unmapped_log {0};
return;
}
if (args.instance_count != 1u || args.start_vertex_location != 0u ||
args.start_instance_location != 0u) {
static std::atomic<uint32_t> 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<uint32_t> unmapped_log {0};
return;
}
if (args.base_vertex_location != 0u || args.start_instance_location != 0u) {
static std::atomic<uint32_t> 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<const DispatchIndirectArgs*>(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<uint32_t> 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,
+3 -7
View File
@@ -1,5 +1,6 @@
#include "graphics/guest_gpu/tile.h"
#include <atomic>
#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<std::string> list;
list.push_back(fmt::format("format = {}", static_cast<uint32_t>(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<uint32_t>(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);
}
}
+113
View File
@@ -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 <algorithm>
#include <array>
#include <atomic>
#include <bit>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
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<GpuCheckpoint, CHECKPOINT_RING_SIZE> g_checkpoints {};
static std::atomic<uint64_t> 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<vk::DeviceFaultAddressInfoEXT> addresses(counts.addressInfoCount);
std::vector<vk::DeviceFaultVendorInfoEXT> 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<const GpuCheckpoint*>(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<uint64_t>(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<const GpuCheckpoint*>(data.pCheckpointMarker);
if (entry < g_checkpoints.data() || entry >= g_checkpoints.data() + CHECKPOINT_RING_SIZE ||
(reinterpret_cast<uintptr_t>(entry) - reinterpret_cast<uintptr_t>(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 {
@@ -358,7 +358,7 @@ struct VideoOutFormatPolicy {
VideoOutPixelFormatInfo info;
};
inline constexpr std::array<VideoOutFormatPolicy, 6> VIDEO_OUT_FORMAT_POLICIES {{
inline constexpr std::array<VideoOutFormatPolicy, 12> 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<VideoOutFormatPolicy, 6> 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,
@@ -212,7 +212,12 @@ TextureUploadLayout TextureCalcUploadLayout(Prospero::BufferFormat format, uint3
levels);
}
switch (tile_mode) {
auto effective_tile_mode = tile_mode;
if (static_cast<uint32_t>(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};
}
+1
View File
@@ -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},
+8 -2
View File
@@ -16,6 +16,7 @@
#include <cstdint>
#include <fmt/format.h>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include <vulkan/vulkan.hpp>
@@ -44,8 +45,13 @@ void SetVulkanObjectNameF(vk::Device device, Handle handle, fmt::format_string<A
vk::DebugUtilsObjectNameInfoEXT info {};
info.sType = vk::StructureType::eDebugUtilsObjectNameInfoEXT;
info.objectType = Handle::objectType;
info.objectHandle = static_cast<uint64_t>(
reinterpret_cast<uintptr_t>(static_cast<typename Handle::CType>(handle)));
using CType = typename Handle::CType;
if constexpr (std::is_pointer_v<CType>) {
info.objectHandle =
static_cast<uint64_t>(reinterpret_cast<uintptr_t>(static_cast<CType>(handle)));
} else {
info.objectHandle = static_cast<uint64_t>(static_cast<CType>(handle));
}
info.pObjectName = name.c_str();
(void)device.setDebugUtilsObjectNameEXT(&info);
}
+1
View File
@@ -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:
+55
View File
@@ -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<int> 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);
+4
View File
@@ -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);
@@ -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<const void*>(&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<uint8_t>(v & 0xffu);
header[at + 1] = static_cast<uint8_t>((v >> 8u) & 0xffu);
};
auto put32 = [&header](size_t at, uint32_t v) {
header[at] = static_cast<uint8_t>(v & 0xffu);
header[at + 1] = static_cast<uint8_t>((v >> 8u) & 0xffu);
header[at + 2] = static_cast<uint8_t>((v >> 16u) & 0xffu);
header[at + 3] = static_cast<uint8_t>((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<uint32_t>(-static_cast<int32_t>(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<uint64_t>(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, &region);
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<uint8_t*>(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<uint8_t>((packed >> 2u) & 0xffu);
const auto mid = static_cast<uint8_t>((packed >> 12u) & 0xffu);
const auto high = static_cast<uint8_t>((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<uint32_t>(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) {
+123 -18
View File
@@ -86,23 +86,37 @@ static bool HasLayer(const std::vector<vk::LayerProperties>& 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<vk::SurfaceFormatKHR>( // @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<vk::PresentModeKHR>( // @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<void*>(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<void*>(&fault_features)
: (robustness2_ext_enabled ? static_cast<void*>(&robustness2)
: const_cast<void*>(base_feature_chain));
shader_clock_features.shaderDeviceClock = VK_TRUE;
auto features13 = required_features13;
#if defined(__APPLE__)
features13.pNext = robustness2_ext_enabled ? static_cast<void*>(&robustness2)
: static_cast<void*>(&features12);
#else
features13.pNext = robustness2_ext_enabled ? static_cast<void*>(&robustness2)
: static_cast<void*>(&fragment_barycentric);
#endif
features13.pNext =
shader_clock_ext_enabled
? static_cast<void*>(&shader_clock_features)
: (device_fault_ext_enabled
? static_cast<void*>(&fault_features)
: (robustness2_ext_enabled ? static_cast<void*>(&robustness2)
: const_cast<void*>(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<void*>(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);
+175
View File
@@ -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 <algorithm>
#include <atomic>
#include <cinttypes>
#include <cstdio>
#include <cstdlib>
#include <fmt/format.h>
#include <memory>
#include <string>
#include <thread>
#include <vector>
#include <vulkan/vk_platform.h>
@@ -58,6 +66,170 @@
namespace Libs::Graphics {
static std::atomic<uint64_t> g_last_present_counter {0};
static std::atomic<bool> 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<const void*>(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<uint64_t>(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<KnownTable> g_known_tables;
static void RecheckKnownTables() {
for (auto& e: g_known_tables) {
if (!StallProbeReadable(e.keys, static_cast<uint64_t>(e.cap) * 4u)) {
continue;
}
const auto* slots = reinterpret_cast<const uint32_t*>(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<uint64_t> reported;
for (uint64_t addr = region_start; addr + 0x18 <= region_end; addr += 8) {
if (!StallProbeReadable(addr, 0x18)) {
continue;
}
const auto keys = *reinterpret_cast<const uint64_t*>(addr);
const auto cnt = *reinterpret_cast<const uint32_t*>(addr + 0x10);
const auto cap = *reinterpret_cast<const uint32_t*>(addr + 0x14);
if (keys < GuestLow || keys >= GuestHigh || cap < kMinTableCapacity || cap > 0x40000u ||
(cap & (cap - 1u)) != 0 || cnt > cap) {
continue;
}
if (!StallProbeReadable(keys, static_cast<uint64_t>(cap) * 4u)) {
continue;
}
const auto* slots = reinterpret_cast<const uint32_t*>(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<const uint64_t*>(addr);
const auto cnt = *reinterpret_cast<const uint32_t*>(addr + 0x10);
const auto cap = *reinterpret_cast<const uint32_t*>(addr + 0x14);
if (keys < GuestLow || keys >= GuestHigh || cap < kMinTableCapacity || cap > 0x40000u ||
(cap & (cap - 1u)) != 0 || cnt > cap) {
continue;
}
if (!StallProbeReadable(keys, static_cast<uint64_t>(cap) * 4u)) {
continue;
}
const auto* slots = reinterpret_cast<const uint32_t*>(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<double>(fps_frames) * static_cast<double>(frequency) /
static_cast<double>(now - fps_start);