mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-21 19:36:55 +02:00
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:
@@ -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},
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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};
|
||||
}
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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, ®ion);
|
||||
const auto capture_tick = m_impl->present_scheduler.Submit();
|
||||
m_impl->present_scheduler.Wait(capture_tick);
|
||||
}
|
||||
|
||||
void* mapped = nullptr;
|
||||
graphics.MapMemory(staging.memory, mapped);
|
||||
if (mapped != nullptr) {
|
||||
auto* pixels = static_cast<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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user