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::k11_11_10Float, 4, 0, 4, true, false},
{BufferFormat::k10_10_10_2UNorm, 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_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_8UNorm, 4, 0, 4, true, false},
{BufferFormat::k8_8_8_8SNorm, 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}, {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 "graphics/guest_gpu/graphicsRun.h"
#include "common/assert.h" #include "common/assert.h"
#include <utility>
#include <cstdlib>
#include "common/emulatorConfig.h" #include "common/emulatorConfig.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/profiler.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)) { if (!decode_gds(dst_sel, dst_gds)) {
EXIT("unsupported dmaData destination selector 0x%02" PRIx8 "\n", dst_sel); 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(); auto& buffer_cache = GetGpuResources().GetBufferCache();
if (src_sel == 2) { if (src_sel == 2) {
buffer_cache.FillBuffer( buffer_cache.FillBuffer(
@@ -454,6 +463,9 @@ void GuestGpu::ThreadRun(void* data) {
g_gpu_thread = true; g_gpu_thread = true;
g_gpu_state = gpu; g_gpu_state = gpu;
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
#endif
for (;;) { for (;;) {
Submission submission; Submission submission;
Common::UniqueFunction<void> command; Common::UniqueFunction<void> command;
@@ -546,6 +558,17 @@ void GuestGpu::ThreadRun(void* data) {
bool GuestGpu::Process(Submission& submission) { bool GuestGpu::Process(Submission& submission) {
const bool first_slice = !submission.started; 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()) { if (first_slice && RenderDocCaptureRequested()) {
Common::LockGuard render_lock(m_renderer.GetMutex()); Common::LockGuard render_lock(m_renderer.GetMutex());
RenderDocStartCapture(); 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((draw_initiator & ~0x20u) != 2u);
EXIT_NOT_IMPLEMENTED(m_draw_indirect_args_base_addr == 0); EXIT_NOT_IMPLEMENTED(m_draw_indirect_args_base_addr == 0);
const auto* args_addr = const auto args_addr = m_draw_indirect_args_base_addr + data_offset;
reinterpret_cast<const void*>(m_draw_indirect_args_base_addr + data_offset);
if (!indexed) { if (!indexed) {
DrawIndirectArgs args {}; 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 || if (args.instance_count != 1u || args.start_vertex_location != 0u ||
args.start_instance_location != 0u) { args.start_instance_location != 0u) {
static std::atomic<uint32_t> log_count {0}; 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 {}; 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) { if (args.base_vertex_location != 0u || args.start_instance_location != 0u) {
static std::atomic<uint32_t> log_count {0}; static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1) < 64) { 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); EXIT_NOT_IMPLEMENTED(m_dispatch_indirect_args_base_addr == 0);
const auto args_addr = m_dispatch_indirect_args_base_addr + data_offset; 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, 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) { switch (cache_action) {
case 0x00: case 0x00:
if ((eop_event_type == 0x04 && event_index == 0x05) || 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) { if (with_interrupt) {
Sync::WriteAtEndOfPipeWithInterrupt64( Sync::WriteAtEndOfPipeWithInterrupt64(
m_submit_id, CurrentBuffer(), dst, clock, m_interrupt_event_id, 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: case 0x38:
if ((eop_event_type == 0x04 && if ((eop_event_type == 0x04 &&
(event_index == 0x00 || event_index == 0x05)) || (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) { if (with_interrupt) {
Sync::WriteAtEndOfPipeWithInterruptWriteBack64( Sync::WriteAtEndOfPipeWithInterruptWriteBack64(
m_submit_id, CurrentBuffer(), dst, clock, m_interrupt_event_id, 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 "graphics/guest_gpu/tile.h"
#include <atomic>
#include "common/assert.h" #include "common/assert.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/profiler.h" #include "common/profiler.h"
@@ -1302,13 +1303,8 @@ void TileGetTextureSize(Prospero::BufferFormat format, uint32_t width, uint32_t
return; return;
} }
if (total_size != nullptr && total_size->size == 0) { if (total_size != nullptr && total_size->size == 0) {
std::vector<std::string> list; TileGetTextureSize(format, width, height, levels, Prospero::TileMode::kLinear, total_size,
list.push_back(fmt::format("format = {}", static_cast<uint32_t>(format))); level_sizes, padded_size);
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());
} }
} }
+113
View File
@@ -1,4 +1,5 @@
#include "common/assert.h" #include "common/assert.h"
#include "common/timer.h"
#include "common/common.h" #include "common/common.h"
#include "common/profiler.h" #include "common/profiler.h"
#include "common/threads.h" #include "common/threads.h"
@@ -13,8 +14,13 @@
#include "graphics/host_gpu/vulkanCommon.h" #include "graphics/host_gpu/vulkanCommon.h"
#include <algorithm> #include <algorithm>
#include <array>
#include <atomic>
#include <bit> #include <bit>
#include <cstdio>
#include <cstdlib>
#include <cstring> #include <cstring>
#include <vector>
namespace Libs::Graphics { namespace Libs::Graphics {
CommandBuffer::CommandBuffer(CommandScheduler& scheduler) CommandBuffer::CommandBuffer(CommandScheduler& scheduler)
@@ -29,6 +35,90 @@ vk::CommandBuffer CommandBuffer::Handle() const {
return m_buffer; 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() { void CommandBuffer::Begin() {
EXIT_IF(m_rendering || IsInvalid()); EXIT_IF(m_rendering || IsInvalid());
auto buffer = Handle(); auto buffer = Handle();
@@ -42,12 +132,23 @@ void CommandBuffer::Begin() {
auto result = buffer.begin(&begin_info); auto result = buffer.begin(&begin_info);
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess); 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 { void CommandBuffer::End() const {
EndRendering(); EndRendering();
auto buffer = Handle(); auto buffer = Handle();
if (m_graphics.diagnostic_checkpoints_enabled) {
m_checkpoint_end = AllocateCheckpoint(1);
buffer.setCheckpointNV(m_checkpoint_end);
}
auto result = buffer.end(); auto result = buffer.end();
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess); 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_arg2 = arg2;
m_debug_arg3 = arg3; m_debug_arg3 = arg3;
m_debug_arg4 = arg4; 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 { void CommandBuffer::BeginRendering(const RenderState& state) const {
@@ -358,7 +358,7 @@ struct VideoOutFormatPolicy {
VideoOutPixelFormatInfo info; VideoOutPixelFormatInfo info;
}; };
inline constexpr std::array<VideoOutFormatPolicy, 6> VIDEO_OUT_FORMAT_POLICIES {{ inline constexpr std::array<VideoOutFormatPolicy, 12> VIDEO_OUT_FORMAT_POLICIES {{
{0x8000000022000000ull, {0x8000000022000000ull,
{vk::Format::eR8G8B8A8Srgb, Prospero::BufferFormat::k8_8_8_8Srgb, 4, false}}, {vk::Format::eR8G8B8A8Srgb, Prospero::BufferFormat::k8_8_8_8Srgb, 4, false}},
{0x8000000000000000ull, {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}}, {vk::Format::eR16G16B16A16Sfloat, Prospero::BufferFormat::k16_16_16_16Float, 8, false}},
{0xc001000600000000ull, {0xc001000600000000ull,
{vk::Format::eR16G16B16A16Sfloat, Prospero::BufferFormat::k16_16_16_16Float, 8, true}}, {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, [[nodiscard]] inline bool DecodeVideoOutPixelFormat(uint64_t pixel_format,
@@ -212,7 +212,12 @@ TextureUploadLayout TextureCalcUploadLayout(Prospero::BufferFormat format, uint3
levels); 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: case Prospero::TileMode::kLinear:
if (!TileGetTextureElementLayout(format, element)) { if (!TileGetTextureElementLayout(format, element)) {
EXIT("%s: unsupported linear texture format: fmt=%u\n", owner, EXIT("%s: unsupported linear texture format: fmt=%u\n", owner,
@@ -239,7 +244,7 @@ TextureUploadLayout TextureCalcUploadLayout(Prospero::BufferFormat format, uint3
break; break;
} }
if (tile_mode != Prospero::TileMode::kLinear) { if (effective_tile_mode != Prospero::TileMode::kLinear) {
element = {layout.surface.texture.block.bytes_per_element, element = {layout.surface.texture.block.bytes_per_element,
layout.surface.texture.texel_width, layout.surface.texture.texel_height}; 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::k11_11_10Float, vk::Format::eB10G11R11UfloatPack32},
{Prospero::BufferFormat::k10_10_10_2UNorm, vk::Format::eA2B10G10R10UnormPack32}, {Prospero::BufferFormat::k10_10_10_2UNorm, vk::Format::eA2B10G10R10UnormPack32},
{Prospero::BufferFormat::k10_10_10_2UInt, vk::Format::eA2B10G10R10UintPack32}, {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_8UNorm, vk::Format::eR8G8B8A8Unorm},
{Prospero::BufferFormat::k8_8_8_8SNorm, vk::Format::eR8G8B8A8Snorm}, {Prospero::BufferFormat::k8_8_8_8SNorm, vk::Format::eR8G8B8A8Snorm},
{Prospero::BufferFormat::k8_8_8_8UInt, vk::Format::eR8G8B8A8Uint}, {Prospero::BufferFormat::k8_8_8_8UInt, vk::Format::eR8G8B8A8Uint},
+8 -2
View File
@@ -16,6 +16,7 @@
#include <cstdint> #include <cstdint>
#include <fmt/format.h> #include <fmt/format.h>
#include <string> #include <string>
#include <type_traits>
#include <utility> #include <utility>
#include <vector> #include <vector>
#include <vulkan/vulkan.hpp> #include <vulkan/vulkan.hpp>
@@ -44,8 +45,13 @@ void SetVulkanObjectNameF(vk::Device device, Handle handle, fmt::format_string<A
vk::DebugUtilsObjectNameInfoEXT info {}; vk::DebugUtilsObjectNameInfoEXT info {};
info.sType = vk::StructureType::eDebugUtilsObjectNameInfoEXT; info.sType = vk::StructureType::eDebugUtilsObjectNameInfoEXT;
info.objectType = Handle::objectType; info.objectType = Handle::objectType;
info.objectHandle = static_cast<uint64_t>( using CType = typename Handle::CType;
reinterpret_cast<uintptr_t>(static_cast<typename Handle::CType>(handle))); 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(); info.pObjectName = name.c_str();
(void)device.setDebugUtilsObjectNameEXT(&info); (void)device.setDebugUtilsObjectNameEXT(&info);
} }
+1
View File
@@ -28,6 +28,7 @@ public:
[[nodiscard]] bool NeedsImeRefresh() const noexcept; [[nodiscard]] bool NeedsImeRefresh() const noexcept;
[[nodiscard]] RenderContext& Renderer() const noexcept; [[nodiscard]] RenderContext& Renderer() const noexcept;
void Present(Frame& frame, bool reuse = false); void Present(Frame& frame, bool reuse = false);
void CaptureFrame(Frame& frame);
void Discard(Frame& frame); void Discard(Frame& frame);
private: private:
+55
View File
@@ -207,6 +207,7 @@ public:
bool Flip(uint32_t micros); bool Flip(uint32_t micros);
void GetFlipStatus(VideoOutConfig& cfg, VideoOutFlipStatus& out); void GetFlipStatus(VideoOutConfig& cfg, VideoOutFlipStatus& out);
void Wait(VideoOutConfig& cfg, int index); void Wait(VideoOutConfig& cfg, int index);
void DumpState();
private: private:
enum class RequestState { Reserved, Recording, Ready, Presenting }; enum class RequestState { Reserved, Recording, Ready, Presenting };
@@ -261,6 +262,7 @@ public:
void VblankBegin(); void VblankBegin();
void VblankEnd(); void VblankEnd();
void PresentThread(std::stop_token token); void PresentThread(std::stop_token token);
void DumpVideoOutState();
private: private:
Common::Mutex m_mutex; Common::Mutex m_mutex;
@@ -593,6 +595,10 @@ void VideoOutShutdown() {
g_video_out_driver.reset(); g_video_out_driver.reset();
} }
VideoOutDriver* VideoOutGetDriver() {
return g_video_out_driver.get();
}
VideoOutDriver::Impl::~Impl() { VideoOutDriver::Impl::~Impl() {
if (m_present_thread.joinable()) { if (m_present_thread.joinable()) {
m_present_thread.request_stop(); m_present_thread.request_stop();
@@ -1164,6 +1170,41 @@ void FlipQueue::GetFlipStatus(VideoOutConfig& cfg, VideoOutFlipStatus& out) {
out = cfg.flip_status; 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) { KYTY_SYSV_ABI int VideoOutOpen(int user_id, int bus_type, int index, const void* param) {
PRINT_NAME(); PRINT_NAME();
@@ -1449,9 +1490,23 @@ KYTY_SYSV_ABI int VideoOutUnregisterBuffers(int handle, int set_index) {
return OK; 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) { KYTY_SYSV_ABI int VideoOutSubmitFlip(int handle, int index, int flip_mode, int64_t flip_arg) {
PRINT_NAME(); PRINT_NAME();
VideoOutSetFlipThread(Common::Thread::GetThreadIdUnique());
uint64_t request_id = 0; uint64_t request_id = 0;
const int result = ReserveFlipRequest(DriverState(), handle, index, flip_mode, flip_arg, const int result = ReserveFlipRequest(DriverState(), handle, index, flip_mode, flip_arg,
FlipRequestSource::Cpu, request_id); FlipRequestSource::Cpu, request_id);
+4
View File
@@ -39,6 +39,7 @@ public:
void SubmitFlipPreparation(uint64_t request_id); void SubmitFlipPreparation(uint64_t request_id);
void WaitForSubmitSlot(); void WaitForSubmitSlot();
void WaitFlipDone(int handle, int index); void WaitFlipDone(int handle, int index);
void DumpFlipState();
[[nodiscard]] Impl& State() noexcept; [[nodiscard]] Impl& State() noexcept;
@@ -49,6 +50,9 @@ private:
[[nodiscard]] VideoOutDriver& VideoOutInit(uint32_t width, uint32_t height, [[nodiscard]] VideoOutDriver& VideoOutInit(uint32_t width, uint32_t height,
Graphics::Presenter& presenter); Graphics::Presenter& presenter);
void VideoOutShutdown(); 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 VideoOutOpen(int user_id, int bus_type, int index, const void* param);
KYTY_SYSV_ABI int VideoOutClose(int handle); KYTY_SYSV_ABI int VideoOutClose(int handle);
@@ -1,6 +1,7 @@
#include "common/assert.h" #include "common/assert.h"
#include "common/common.h" #include "common/common.h"
#include "common/emulatorConfig.h" #include "common/emulatorConfig.h"
#include "common/file.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/profiler.h" #include "common/profiler.h"
#include "common/threads.h" #include "common/threads.h"
@@ -335,6 +336,28 @@ struct Presenter::Impl {
frames.SetFormat(swapchain.Format()); 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) { Image& ResolveSurface(const ImageInfo& info) {
TextureCache::ImageDesc desc {}; TextureCache::ImageDesc desc {};
desc.info = info; desc.info = info;
@@ -350,12 +373,14 @@ struct Presenter::Impl {
auto& cache = renderer.GetTextureCache(); auto& cache = renderer.GetTextureCache();
const auto image_id = cache.FindImage(desc); const auto image_id = cache.FindImage(desc);
last_flip_id = image_id;
auto& image = cache.GetImage(image_id); auto& image = cache.GetImage(image_id);
image.usage.video_out = true; image.usage.video_out = true;
cache.UpdateImage(image_id); cache.UpdateImage(image_id);
return image; return image;
} }
ImageId last_flip_id {};
RenderContext& renderer; RenderContext& renderer;
WindowContext& window; WindowContext& window;
Swapchain swapchain; 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)); 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; auto frame_format = info.pixel_format;
switch (frame_format) { switch (frame_format) {
case vk::Format::eR8G8B8A8Srgb: frame_format = vk::Format::eR8G8B8A8Unorm; break; case vk::Format::eR8G8B8A8Srgb: frame_format = vk::Format::eR8G8B8A8Unorm; break;
@@ -771,9 +815,157 @@ RenderContext& Presenter::Renderer() const noexcept {
return m_impl->renderer; 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) { void Presenter::Present(Frame& frame, bool reuse) {
KYTY_PROFILER_FUNCTION(); KYTY_PROFILER_FUNCTION();
m_impl->frames.ValidateForPresent(&frame, reuse); 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(); const auto ime_visual = GetImeVisualState();
auto& swapchain = m_impl->swapchain; 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(); const bool draw_ime_overlay = ime_visual.active && swapchain.PrepareImeOverlay();
swapchain.RecordPresentCommands(command, frame.image, draw_ime_overlay); swapchain.RecordPresentCommands(command, frame.image, draw_ime_overlay);
frame.present_tick = swapchain.Submit(m_impl->present_scheduler); frame.present_tick = swapchain.Submit(m_impl->present_scheduler);
m_impl->present_scheduler.Wait(frame.present_tick);
CaptureFrame(frame);
} }
status = swapchain.Present(); status = swapchain.Present();
if (status != Swapchain::Status::Success) { 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; }); [name](const auto& layer) { return strcmp(layer.layerName, name) == 0; });
} }
static void GetSurfaceCapabilities(vk::PhysicalDevice physical_device, vk::SurfaceKHR surface, static bool TryGetSurfaceCapabilities(vk::PhysicalDevice physical_device, vk::SurfaceKHR surface,
SurfaceCapabilities& r) { SurfaceCapabilities& r) {
RequireVulkanSuccess(physical_device.getSurfaceCapabilitiesKHR(surface, &r.capabilities), const auto result = physical_device.getSurfaceCapabilitiesKHR(surface, &r.capabilities);
"vkGetPhysicalDeviceSurfaceCapabilitiesKHR"); if (result != vk::Result::eSuccess) {
return false;
}
r.formats = EnumerateVulkan<vk::SurfaceFormatKHR>( // @suppress("Ambiguous problem") r.formats = EnumerateVulkan<vk::SurfaceFormatKHR>( // @suppress("Ambiguous problem")
"vkGetPhysicalDeviceSurfaceFormatsKHR", [&](uint32_t* count, vk::SurfaceFormatKHR* values) { "vkGetPhysicalDeviceSurfaceFormatsKHR", [&](uint32_t* count, vk::SurfaceFormatKHR* values) {
return physical_device.getSurfaceFormatsKHR(surface, count, 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") r.present_modes = EnumerateVulkan<vk::PresentModeKHR>( // @suppress("Ambiguous problem")
"vkGetPhysicalDeviceSurfacePresentModesKHR", "vkGetPhysicalDeviceSurfacePresentModesKHR",
[&](uint32_t* count, vk::PresentModeKHR* values) { [&](uint32_t* count, vk::PresentModeKHR* values) {
return physical_device.getSurfacePresentModesKHR(surface, count, 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, 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; SurfaceCapabilities candidate_capabilities;
if (!skip_device) { if (!skip_device) {
GetSurfaceCapabilities(device, surface, candidate_capabilities); if (!TryGetSurfaceCapabilities(device, surface, candidate_capabilities)) {
continue;
}
if (!(candidate_capabilities.capabilities.supportedUsageFlags & if (!(candidate_capabilities.capabilities.supportedUsageFlags &
vk::ImageUsageFlagBits::eTransferDst)) { vk::ImageUsageFlagBits::eTransferDst)) {
@@ -449,8 +465,18 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
continue; continue;
} }
if (best_device == nullptr || const std::string pinned_name;
device_properties.deviceType == vk::PhysicalDeviceType::eDiscreteGpu) { 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_device = device;
best_queue_family = queue_family; best_queue_family = queue_family;
best_capabilities = std::move(candidate_capabilities); best_capabilities = std::move(candidate_capabilities);
@@ -606,6 +632,17 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
#endif #endif
features12.timelineSemaphore = VK_TRUE; features12.timelineSemaphore = VK_TRUE;
features12.shaderOutputLayer = 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; features12.bufferDeviceAddress = VK_TRUE;
vk::PhysicalDeviceFeatures device_features {}; vk::PhysicalDeviceFeatures device_features {};
@@ -632,6 +669,7 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
robustness2.sType = vk::StructureType::ePhysicalDeviceRobustness2FeaturesEXT; robustness2.sType = vk::StructureType::ePhysicalDeviceRobustness2FeaturesEXT;
#if defined(__APPLE__) #if defined(__APPLE__)
robustness2.pNext = &features12; robustness2.pNext = &features12;
void* base_feature_chain = &features12;
#else #else
vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR fragment_barycentric {}; vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR fragment_barycentric {};
fragment_barycentric.sType = fragment_barycentric.sType =
@@ -639,6 +677,7 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
fragment_barycentric.pNext = &features12; fragment_barycentric.pNext = &features12;
fragment_barycentric.fragmentShaderBarycentric = VK_TRUE; fragment_barycentric.fragmentShaderBarycentric = VK_TRUE;
robustness2.pNext = &fragment_barycentric; robustness2.pNext = &fragment_barycentric;
void* base_feature_chain = &fragment_barycentric;
#endif #endif
if (robustness2_ext_enabled) { if (robustness2_ext_enabled) {
robustness2.robustBufferAccess2 = supported_robustness2.robustBufferAccess2; 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 && graphics.compute_subgroup_size_control_enabled &&
supported_features13.subgroupSizeControl == VK_TRUE; 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; auto features13 = required_features13;
#if defined(__APPLE__) features13.pNext =
features13.pNext = robustness2_ext_enabled ? static_cast<void*>(&robustness2) shader_clock_ext_enabled
: static_cast<void*>(&features12); ? static_cast<void*>(&shader_clock_features)
#else : (device_fault_ext_enabled
features13.pNext = robustness2_ext_enabled ? static_cast<void*>(&robustness2) ? static_cast<void*>(&fault_features)
: static_cast<void*>(&fragment_barycentric); : (robustness2_ext_enabled ? static_cast<void*>(&robustness2)
#endif : const_cast<void*>(base_feature_chain)));
features13.robustImageAccess = supported_features13.robustImageAccess; features13.robustImageAccess = supported_features13.robustImageAccess;
features13.subgroupSizeControl = subgroup_size_control_enabled ? VK_TRUE : VK_FALSE; 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", features13.robustImageAccess == VK_TRUE ? "true" : "false",
robustness2_ext_enabled && robustness2.robustImageAccess2 == 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 {}; vk::DeviceCreateInfo create_info {};
create_info.sType = vk::StructureType::eDeviceCreateInfo; create_info.sType = vk::StructureType::eDeviceCreateInfo;
create_info.pNext = &features13; create_info.pNext = features_head;
create_info.flags = {}; create_info.flags = {};
create_info.pQueueCreateInfos = &queue_create_info; create_info.pQueueCreateInfos = &queue_create_info;
create_info.queueCreateInfoCount = 1; create_info.queueCreateInfoCount = 1;
@@ -903,7 +980,7 @@ void WindowContext::CreateVulkan() {
EXIT("--spirv-debug-printf and --gpu-assisted-validation are mutually exclusive\n"); 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; uint32_t enabled_features_count = 0;
#ifdef KYTY_ENABLE_BEST_PRACTICES #ifdef KYTY_ENABLE_BEST_PRACTICES
enabled_features[enabled_features_count++] = vk::ValidationFeatureEnableEXT::eBestPractices; enabled_features[enabled_features_count++] = vk::ValidationFeatureEnableEXT::eBestPractices;
@@ -1043,6 +1120,23 @@ void WindowContext::CreateVulkan() {
nullptr, count, values); 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)) { if (HasExtension(available_extensions, VK_EXT_MEMORY_BUDGET_EXTENSION_NAME)) {
device_extensions.push_back(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME); device_extensions.push_back(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME);
graphic_ctx.memory_budget_ext_enabled = true; graphic_ctx.memory_budget_ext_enabled = true;
@@ -1050,6 +1144,17 @@ void WindowContext::CreateVulkan() {
if (HasExtension(available_extensions, VK_EXT_ROBUSTNESS_2_EXTENSION_NAME)) { if (HasExtension(available_extensions, VK_EXT_ROBUSTNESS_2_EXTENSION_NAME)) {
device_extensions.push_back(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); VulkanInitSubgroupSizeControl(graphic_ctx.physical_device, graphic_ctx);
+175
View File
@@ -1,5 +1,7 @@
#include "graphics/presentation/window.h" #include "graphics/presentation/window.h"
#include "graphics/presentation/videoOut.h"
#include "SDL.h" #include "SDL.h"
#include "SDL_error.h" #include "SDL_error.h"
#include "SDL_events.h" #include "SDL_events.h"
@@ -27,6 +29,7 @@
#include "common/systemInfo.h" #include "common/systemInfo.h"
#include "common/threads.h" #include "common/threads.h"
#include "common/timer.h" #include "common/timer.h"
#include "kernel/memory.h"
#include "graphics/host_gpu/graphicContext.h" #include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/render.h" #include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h" #include "graphics/host_gpu/renderer/renderContext.h"
@@ -40,10 +43,15 @@
#include "libs/controller.h" #include "libs/controller.h"
#include "loader/systemContent.h" #include "loader/systemContent.h"
#include <algorithm>
#include <atomic>
#include <cinttypes>
#include <cstdio>
#include <cstdlib> #include <cstdlib>
#include <fmt/format.h> #include <fmt/format.h>
#include <memory> #include <memory>
#include <string> #include <string>
#include <thread>
#include <vector> #include <vector>
#include <vulkan/vk_platform.h> #include <vulkan/vk_platform.h>
@@ -58,6 +66,170 @@
namespace Libs::Graphics { 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 { struct EventKeyboard {
bool down; bool down;
bool up; bool up;
@@ -817,6 +989,8 @@ static void WindowCreate(WindowContext& context) {
LOGF("WindowCreate(): width = %d, height = %d\n", width, height); LOGF("WindowCreate(): width = %d, height = %d\n", width, height);
StartStallWatchdog();
uint32_t window_flags = WindowContext::InitialWindowFlags(Config::FullscreenEnabled()); uint32_t window_flags = WindowContext::InitialWindowFlags(Config::FullscreenEnabled());
#if defined(__APPLE__) #if defined(__APPLE__)
// SDL loads Vulkan while creating a Vulkan window, so select the bundled // 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(); const auto frequency = Common::Timer::QueryPerformanceFrequency();
frame_num++; frame_num++;
fps_frames++; fps_frames++;
g_last_present_counter.store(now, std::memory_order_relaxed);
if (now - fps_start >= frequency) { if (now - fps_start >= frequency) {
current_fps = static_cast<double>(fps_frames) * static_cast<double>(frequency) / current_fps = static_cast<double>(fps_frames) * static_cast<double>(frequency) /
static_cast<double>(now - fps_start); static_cast<double>(now - fps_start);