From 630d918b4a05d72d2a0f4361c9f7d24941104210 Mon Sep 17 00:00:00 2001 From: boykopovar Date: Wed, 23 Sep 2026 22:11:41 +0300 Subject: [PATCH] feat(agc): add timing logs for path after recompling --- .gitignore | 2 + .../Execution/include/PerformanceTimer.hpp | 54 +++++++++++++++++++ .../Execution/src/DisplayBuffer.cpp | 4 ++ .../libSceAgcDriver/Execution/src/Driver.cpp | 18 +++++++ .../Execution/src/VulkanDevice.cpp | 19 +++++++ .../Graphics/src/ColorTargetTransfer.cpp | 10 ++++ .../prx/libSceAgcDriver/Graphics/src/Draw.cpp | 15 ++++++ .../Graphics/src/Resources.cpp | 5 ++ .../libs/prx/libSceSaveData.native/Export.cpp | 29 ++++++---- core/libs/prx/libkernel/File/src/Open.cpp | 8 ++- 10 files changed, 153 insertions(+), 11 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp diff --git a/.gitignore b/.gitignore index 1c5a7db27..59f1ecc76 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,8 @@ *.app _SaveData/ +_sd/ + build/ b/ Build/ diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp new file mode 100644 index 000000000..443e18e13 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp @@ -0,0 +1,54 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_PERFORMANCETIMER_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_PERFORMANCETIMER_HPP + +#include "prx/libc/include/general/LogMacros.hpp" +#include +#include +#include +#include +#include +#include + +namespace AgcDriver { + +class PerformanceTimer { +public: + explicit PerformanceTimer(const char* scope) : scope(scope) {} + PerformanceTimer(const PerformanceTimer&) = delete; + PerformanceTimer& operator=(const PerformanceTimer&) = delete; + + ~PerformanceTimer() { + const auto end = Clock::now(); + APS5_LOG_OUT("[Timing] id=%llu scope=%s total_ms=%.3f tail_ms=%.3f unwinding=%u%s", id, scope, milliseconds(end - start), milliseconds(end - previous), std::uncaught_exceptions() > exceptions ? 1u : 0u, stages); + } + + void Mark(const char* stage) { + const auto now = Clock::now(); + const auto count = std::snprintf(stages + used, sizeof(stages) - used, " %s_ms=%.3f", stage, milliseconds(now - previous)); + if (count < 0 || static_cast(count) >= sizeof(stages) - used) { + throw std::runtime_error("PerformanceTimer: timing log capacity exceeded"); + } + used += static_cast(count); + previous = now; + } + +private: + using Clock = std::chrono::steady_clock; + + static double milliseconds(Clock::duration duration) { + return std::chrono::duration(duration).count(); + } + + inline static std::atomic nextId{0}; + const char* scope; + unsigned long long id = nextId.fetch_add(1, std::memory_order_relaxed); + Clock::time_point start = Clock::now(); + Clock::time_point previous = start; + int exceptions = std::uncaught_exceptions(); + char stages[2048]{}; + std::size_t used = 0; +}; + +} + +#endif diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/DisplayBuffer.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/DisplayBuffer.cpp index 2fa570a8b..7de87a438 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/DisplayBuffer.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/DisplayBuffer.cpp @@ -1,5 +1,6 @@ #include "prx/libSceAgcDriver/Execution/include/DisplayBuffer.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp" #include #include #include @@ -27,10 +28,12 @@ std::size_t DisplayBufferSize(const DisplayBuffer& buffer) { } std::vector DecodeDisplayBuffer(const DisplayBuffer& buffer, std::span source) { + PerformanceTimer timing("DisplayBuffer.Decode"); require(source.size() == DisplayBufferSize(buffer), "VideoOut: invalid tiled display buffer size"); std::vector pixels(static_cast(buffer.width) * buffer.height * 4); const auto blocksPerRow = (buffer.width + 127u) / 128u; const bool rgba = buffer.pixelFormat == 0x8000000022000000ull; + timing.Mark("validate_allocate"); for (std::uint32_t y = 0; y < buffer.height; ++y) { for (std::uint32_t x = 0; x < buffer.width; ++x) { const auto tiled = (static_cast(y / 128u) * blocksPerRow + x / 128u) * 65536u + tileOffset(x, y); @@ -41,6 +44,7 @@ std::vector DecodeDisplayBuffer(const DisplayBuffer& buffer, std::spa pixels[linear + 3] = source[tiled + 3]; } } + timing.Mark("detile_convert"); return pixels; } diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp index 2713526f4..a8f1c1fac 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp @@ -1,4 +1,5 @@ #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Execution/include/ShaderMemory.hpp" #include "prx/libSceAgcDriver/Execution/include/Pm4.hpp" @@ -184,35 +185,45 @@ public: } void Present(const PresentationWindow& window, const DisplayBuffer* buffer, bool opaque, void (*gpuReady)(void*), void* context) { + PerformanceTimer timing("Driver.Present"); CheckFailure(); require(gpuReady != nullptr && context != nullptr, "missing GPU completion callback"); require(window.getDrawableSize != nullptr, "missing window drawable size query"); std::shared_ptr presenting; + timing.Mark("validate"); try { { std::lock_guard lock(gpuMutex); + timing.Mark("gpu_mutex_wait"); if (device == nullptr || device->Window() == nullptr) { if (device) device->WaitIdle(); device = std::make_shared(&window); } require(device->Window() == window.context, "presentation window does not match device surface"); presenting = device; + timing.Mark("device_setup"); std::uint32_t drawableWidth = 0; std::uint32_t drawableHeight = 0; window.getDrawableSize(window.context, &drawableWidth, &drawableHeight); presenting->Resize(drawableWidth, drawableHeight); + timing.Mark("resize"); if (presenting->Presentable()) { if (buffer != nullptr) { require(buffer->width == window.width && buffer->height == window.height, "display buffer extent differs from output"); presenting->WaitIdle(); + timing.Mark("device_idle_wait"); const auto pixels = ReadDisplayBuffer(*buffer); + timing.Mark("display_read_detile"); presenting->PresentPixels(window.width, window.height, pixels); + timing.Mark("present_pixels"); } else { presenting->PresentClear(window.width, window.height, opaque); + timing.Mark("present_clear"); } } } gpuReady(context); + timing.Mark("release_and_callback"); CheckFailure(); } catch (...) { ReportFailure(std::current_exception()); @@ -329,6 +340,7 @@ private: } void draw(QueueState& queue, std::span packet, const Submission& submission) { + PerformanceTimer timing("Driver.Draw"); auto drawParameters = Pm4::ResolveDraw(packet, queue); if (!drawParameters.indexed && (drawParameters.indexCount == 0 || drawParameters.instanceCount == 0)) return; const auto graphics = Graphics::DecodeState(queue); @@ -416,8 +428,11 @@ private: memory.insert(memory.end(), program.memory.begin(), program.memory.end()); linked.push_back({roles[i], program.binary, program.userDataBase, program.firstUserSgpr, program.userData}); } + timing.Mark("prepare"); std::lock_guard gpuLock(gpuMutex); + timing.Mark("gpu_mutex_wait"); if (device == nullptr) device = std::make_shared(); + timing.Mark("device_setup"); ShaderMemory shaderMemory(memory); std::vector results; std::vector stages; @@ -454,6 +469,7 @@ private: stages.push_back({program.binary.stage, &result, result.pushConstants.empty() ? 0u : pushCursorBytes}); pushCursorBytes += static_cast(result.pushConstants.size()); } + timing.Mark("shader_compile_and_link"); if (graphics.rectList) { require(stages.size() == 2, "rect-list requires vertex and fragment programs"); auto rectangle = ShaderRecompiler::BuildRectListShaders(results[0], results[1], device->Target()); @@ -463,7 +479,9 @@ private: } std::vector snapshots; for (const auto& region : memory) snapshots.push_back({region.guestAddress, region.bytes}); + timing.Mark("post_compile_prepare"); device->Draw(graphics, drawParameters, stages, snapshots); + timing.Mark("draw_and_resource_release"); } void execute(const Submission& submission) { diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp index 76ace65cc..49b6a3a6c 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp @@ -1,5 +1,6 @@ #include "BdaAbi.hpp" #include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp" #include "prx/libSceAgcDriver/Execution/include/BdaFeatures.hpp" #include "prx/libSceAgcDriver/Execution/include/PresentationScaler.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp" @@ -534,10 +535,12 @@ void VulkanDevice::PresentPixels(std::uint32_t width, std::uint32_t height, std: } void VulkanDevice::present(std::uint32_t width, std::uint32_t height, bool opaque, std::span pixels) { + PerformanceTimer timing("Vulkan.Present"); APS5_LOG_OUT_DEBUG("present begin width=%u height=%u opaque=%u pixels=%zu", width, height, static_cast(opaque), pixels.size()); require(state->swapchain != VK_NULL_HANDLE, "device has no swapchain"); require(state->extent.width != 0 && state->extent.height != 0, "output window is minimized"); if (!pixels.empty()) state->Upload(pixels); + timing.Mark("pixel_upload"); APS5_LOG_OUT_DEBUG("present source=%s bytes=%zu", pixels.empty() ? "clear" : "pixels", pixels.size()); auto wait = state->DeviceFunction("vkWaitForFences"); auto reset = state->DeviceFunction("vkResetFences"); @@ -545,9 +548,12 @@ void VulkanDevice::present(std::uint32_t width, std::uint32_t height, bool opaqu check(reset(state->device, static_cast(fences.size()), fences.data()), "vkResetFences"); APS5_LOG_CHARS_OUT_DEBUG("present fences reset"); std::uint32_t index = 0; + timing.Mark("fence_reset"); check(state->DeviceFunction("vkAcquireNextImageKHR")(state->device, state->swapchain, 5'000'000'000ULL, VK_NULL_HANDLE, state->acquireFence, &index), "vkAcquireNextImageKHR"); + timing.Mark("acquire_image"); APS5_LOG_OUT_DEBUG("vkAcquireNextImageKHR index=%u imageCount=%zu", index, state->images.size()); check(wait(state->device, 1, &state->acquireFence, VK_TRUE, std::numeric_limits::max()), "vkWaitForFences acquire"); + timing.Mark("acquire_fence_wait"); APS5_LOG_OUT_DEBUG("Acquire fence complete index=%u", index); require(index < state->images.size() && index < state->rendered.size(), "acquired image index is out of range"); auto& rendered = state->rendered[index]; @@ -558,6 +564,7 @@ void VulkanDevice::present(std::uint32_t width, std::uint32_t height, bool opaqu check(state->DeviceFunction("vkCreateSemaphore")(state->device, &semaphore, nullptr, &rendered), "vkCreateSemaphore presentation"); } auto commands = state->clearCommands; + timing.Mark("retired_swapchains"); check(state->DeviceFunction("vkResetCommandBuffer")(commands, 0), "vkResetCommandBuffer"); VkCommandBufferBeginInfo begin{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; @@ -610,9 +617,12 @@ void VulkanDevice::present(std::uint32_t width, std::uint32_t height, bool opaqu submit.pCommandBuffers = &commands; submit.signalSemaphoreCount = 1; submit.pSignalSemaphores = &rendered; + timing.Mark("command_record_scale"); check(state->DeviceFunction("vkQueueSubmit")(state->queue, 1, &submit, state->renderFence), "vkQueueSubmit clear"); + timing.Mark("queue_submit"); APS5_LOG_CHARS_OUT_DEBUG("Presentation vkQueueSubmit OK"); check(wait(state->device, 1, &state->renderFence, VK_TRUE, std::numeric_limits::max()), "vkWaitForFences clear"); + timing.Mark("render_fence_wait"); APS5_LOG_CHARS_OUT_DEBUG("Presentation render fence complete"); VkPresentInfoKHR present{VK_STRUCTURE_TYPE_PRESENT_INFO_KHR}; present.waitSemaphoreCount = 1; @@ -621,6 +631,7 @@ void VulkanDevice::present(std::uint32_t width, std::uint32_t height, bool opaqu present.pSwapchains = &state->swapchain; present.pImageIndices = &index; check(state->DeviceFunction("vkQueuePresentKHR")(state->queue, &present), "vkQueuePresentKHR"); + timing.Mark("queue_present"); APS5_LOG_OUT_DEBUG("vkQueuePresentKHR queued imageIndex=%u", index); } @@ -668,6 +679,7 @@ void VulkanDevice::Draw(const Graphics::State& graphics, const Pm4::DrawParamete } void VulkanDevice::Dispatch(const ShaderRecompiler::RecompileResult& shader, std::uint32_t x, std::uint32_t y, std::uint32_t z, std::span snapshots) { + PerformanceTimer timing("Vulkan.Dispatch"); APS5_LOG_OUT_DEBUG("Dispatch groups=%ux%ux%u spirvWords=%zu bindings=%zu pushConstants=%zu", x, y, z, shader.spirv.size(), shader.bindings.size(), shader.pushConstants.size()); if (shader.spirv.size() < 5 || shader.spirv[0] != 0x07230203u) { throw std::runtime_error("Vulkan dispatch: invalid SPIR-V"); @@ -707,7 +719,9 @@ void VulkanDevice::Dispatch(const ShaderRecompiler::RecompileResult& shader, std moduleInfo.codeSize = shader.spirv.size() * sizeof(std::uint32_t); moduleInfo.pCode = shader.spirv.data(); check(state->DeviceFunction("vkCreateShaderModule")(state->device, &moduleInfo, nullptr, &module), "vkCreateShaderModule"); + timing.Mark("validate_shader_module"); Graphics::ShaderResources resources(context, shaders[0], snapshots); + timing.Mark("shader_resources"); const auto setLayout = resources.Layout(); const VkPushConstantRange push{VK_SHADER_STAGE_COMPUTE_BIT, 0, Graphics::PipelinePushConstantBytes}; VkPipelineLayoutCreateInfo layoutInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; @@ -723,6 +737,7 @@ void VulkanDevice::Dispatch(const ShaderRecompiler::RecompileResult& shader, std pipelineInfo.stage.pName = "main"; pipelineInfo.layout = layout; check(state->DeviceFunction("vkCreateComputePipelines")(state->device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &pipeline), "vkCreateComputePipelines"); + timing.Mark("pipeline_create"); VkCommandBufferAllocateInfo allocation{VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO}; allocation.commandPool = state->pool; allocation.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; @@ -751,15 +766,19 @@ void VulkanDevice::Dispatch(const ShaderRecompiler::RecompileResult& shader, std VkSubmitInfo submission{VK_STRUCTURE_TYPE_SUBMIT_INFO}; submission.commandBufferCount = 1; submission.pCommandBuffers = &commands; + timing.Mark("command_record"); check(submit(state->queue, 1, &submission, fence), "vkQueueSubmit"); + timing.Mark("queue_submit"); APS5_LOG_OUT_DEBUG("Dispatch submitted groups=%ux%ux%u", x, y, z); const auto result = wait(state->device, 1, &fence, VK_TRUE, std::numeric_limits::max()); + timing.Mark("fence_wait"); if (result != VK_SUCCESS && result != VK_ERROR_DEVICE_LOST) { const auto idle = state->DeviceFunction("vkDeviceWaitIdle")(state->device); check(idle, "vkDeviceWaitIdle after fence failure"); } check(result, "vkWaitForFences"); resources.WriteBack(); + timing.Mark("resources_writeback"); APS5_LOG_OUT_DEBUG("Dispatch complete groups=%ux%ux%u", x, y, z); } catch (...) { cleanup(); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ColorTargetTransfer.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ColorTargetTransfer.cpp index fbec232eb..0b3003e57 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ColorTargetTransfer.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ColorTargetTransfer.cpp @@ -1,10 +1,12 @@ #include "prx/libSceAgcDriver/Graphics/include/ColorTargetTransfer.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp" #include namespace AgcDriver::Graphics { void ReadColorTarget(const ColorTarget& target, std::span destination) { + PerformanceTimer timing("ColorTarget.Read"); const ColorTargetLayout layout(target.extent.width, target.extent.height, target.tileMode); Require(target.bytes == layout.Bytes() && destination.size() == layout.LinearBytes(), "color target transfer size mismatch"); if (target.tileMode == ColorTileMode::Linear) { @@ -12,11 +14,15 @@ void ReadColorTarget(const ColorTarget& target, std::span destination return; } std::vector tiled(layout.Bytes()); + timing.Mark("allocate"); GuestMemory::Read(target.address, tiled, layout.Alignment()); + timing.Mark("guest_read"); layout.Detile(tiled, destination); + timing.Mark("detile"); } void WriteColorTarget(const ColorTarget& target, std::span source) { + PerformanceTimer timing("ColorTarget.Write"); const ColorTargetLayout layout(target.extent.width, target.extent.height, target.tileMode); Require(target.bytes == layout.Bytes() && source.size() == layout.LinearBytes(), "color target transfer size mismatch"); if (target.tileMode == ColorTileMode::Linear) { @@ -24,9 +30,13 @@ void WriteColorTarget(const ColorTarget& target, std::span sour return; } std::vector tiled(layout.Bytes()); + timing.Mark("allocate"); GuestMemory::Read(target.address, tiled, layout.Alignment()); + timing.Mark("guest_read"); layout.Tile(source, tiled); + timing.Mark("tile"); GuestMemory::Write(target.address, tiled, layout.Alignment()); + timing.Mark("guest_write"); } } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index 5a6b6c98e..0ee1bc98d 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -2,6 +2,7 @@ #include "prx/libSceAgcDriver/Graphics/include/ColorTargetTransfer.hpp" #include "prx/libSceAgcDriver/Graphics/include/VertexInput.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp" #include "prx/libc/include/General.hpp" #include #include @@ -26,6 +27,7 @@ void imageBarrier(const Context& context, VkCommandBuffer commands, VkImage imag } void Draw(const Context& context, const State& state, const Pm4::DrawParameters& draw, std::span shaders, std::span snapshots) { + PerformanceTimer timing("Graphics.Draw"); APS5_LOG_OUT("Draw indices=%u instances=%u indexSize=%u flags=%u indexAddress=0x%llx", draw.indexCount, draw.instanceCount, draw.indexSize, draw.flags, static_cast(draw.indexAddress)); APS5_LOG_OUT_DEBUG("State colorTarget=%u render=%ux%u colorAddress=0x%llx colorBytes=%llu colorExtent=%ux%u", state.hasColorTarget ? 1u : 0u, state.renderExtent.width, state.renderExtent.height, static_cast(state.color.address), static_cast(state.color.bytes), state.color.extent.width, state.color.extent.height); APS5_LOG_OUT_DEBUG("Viewport x=%f y=%f w=%f h=%f minDepth=%f maxDepth=%f", state.viewport.x, state.viewport.y, state.viewport.width, state.viewport.height, state.viewport.minDepth, state.viewport.maxDepth); @@ -68,6 +70,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& Require(meshGroups <= context.meshLimits.maxMeshWorkGroupCount[0] && draw.instanceCount <= context.meshLimits.maxMeshWorkGroupCount[1] && static_cast(meshGroups) * draw.instanceCount <= context.meshLimits.maxMeshWorkGroupTotalCount, "mesh draw exceeds workgroup count limits"); } if (state.stages.tessellation) Require(draw.indexCount % state.stages.tessellation->inputControlPoints == 0, "incomplete tessellation patch"); + timing.Mark("validate"); std::unique_ptr indices; std::uint32_t maxIndex = draw.indexed ? 0u : draw.firstVertex + draw.indexCount - 1u; if (draw.indexed) { @@ -87,6 +90,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& } } APS5_LOG_CHARS_OUT_DEBUG("Index validation OK"); + timing.Mark("index_upload"); const auto& attributes = shaders.front().program->vertexAttributes; static_cast(BuildVertexInputLayout(context, attributes)); std::vector> vertexBuffers; @@ -103,6 +107,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& vertexHandles.push_back(buffer->Handle()); vertexBuffers.push_back(std::move(buffer)); } + timing.Mark("vertex_upload"); std::unique_ptr transfer; std::unique_ptr target; if (state.hasColorTarget) { @@ -110,17 +115,22 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& const ColorTargetLayout colorLayout(state.color.extent.width, state.color.extent.height, state.color.tileMode); transfer = std::make_unique(context, colorLayout.LinearBytes(), VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT); APS5_LOG_CHARS_OUT_DEBUG("Transfer buffer created"); + timing.Mark("color_transfer_allocate"); ReadColorTarget(state.color, transfer->Bytes()); + timing.Mark("color_read_detile"); APS5_LOG_CHARS_OUT_DEBUG("Color target read from guest memory"); target = std::make_unique(context, state.color, state.blend.blendEnable != 0); + timing.Mark("render_target_create"); APS5_LOG_CHARS_OUT_DEBUG("RenderTarget created"); } APS5_LOG_OUT_DEBUG("Color target object=%u", target ? 1u : 0u); APS5_LOG_CHARS_OUT_DEBUG("Creating ShaderResources"); ShaderResources resources(context, shaders, state.color, draw.indexAddress, static_cast(indexBytes), snapshots); + timing.Mark("shader_resources"); APS5_LOG_CHARS_OUT_DEBUG("ShaderResources created"); APS5_LOG_CHARS_OUT_DEBUG("Creating Pipeline"); Pipeline pipeline(context, state, target.get(), resources, shaders); + timing.Mark("pipeline_create"); APS5_LOG_CHARS_OUT_DEBUG("Pipeline created"); APS5_LOG_CHARS_OUT_DEBUG("Creating CommandBatch"); CommandBatch batch(context); @@ -183,14 +193,19 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& context.Function("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_TRANSFER_BIT | shaderStages, VK_PIPELINE_STAGE_HOST_BIT, 0, 1, &download, 0, nullptr, 0, nullptr); APS5_LOG_CHARS_OUT_DEBUG("Download barrier recorded"); APS5_LOG_CHARS_OUT_DEBUG("SubmitAndWait"); + timing.Mark("command_record"); batch.SubmitAndWait(); + timing.Mark("submit_wait"); APS5_LOG_CHARS_OUT_DEBUG("SubmitAndWait OK"); if (state.hasColorTarget) GuestMemory::CheckRange(reinterpret_cast(state.color.address), state.color.bytes, 256, true); if (state.hasColorTarget) APS5_LOG_CHARS_OUT_DEBUG("Color target guest range OK"); + timing.Mark("color_range_check"); APS5_LOG_CHARS_OUT_DEBUG("Shader resources WriteBack"); resources.WriteBack(); + timing.Mark("resources_writeback"); APS5_LOG_CHARS_OUT_DEBUG("Shader resources WriteBack OK"); if (state.hasColorTarget) WriteColorTarget(state.color, transfer->Bytes()); + timing.Mark("color_tile_writeback"); if (state.hasColorTarget) APS5_LOG_OUT_DEBUG("Color target written to guest address=0x%llx bytes=%llu", static_cast(state.color.address), static_cast(state.color.bytes)); APS5_LOG_CHARS_OUT("Draw finished"); } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Resources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Resources.cpp index 46a20456d..99d71b95b 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Resources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Resources.cpp @@ -1,4 +1,5 @@ #include "prx/libSceAgcDriver/Graphics/include/Resources.hpp" +#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp" #include namespace AgcDriver::Graphics { @@ -144,15 +145,19 @@ VkCommandBuffer CommandBatch::Handle() const { } void CommandBatch::SubmitAndWait() { + PerformanceTimer timing("Graphics.SubmitAndWait"); Require(!submitted, "command batch has already been submitted"); Check(context.Function("vkEndCommandBuffer")(commands), "vkEndCommandBuffer"); VkSubmitInfo submission{VK_STRUCTURE_TYPE_SUBMIT_INFO}; submission.commandBufferCount = 1; submission.pCommandBuffers = &commands; + timing.Mark("command_end"); Check(context.Function("vkQueueSubmit")(context.queue, 1, &submission, fence), "vkQueueSubmit graphics"); + timing.Mark("queue_submit"); pending = true; submitted = true; const auto result = context.Function("vkWaitForFences")(context.device, 1, &fence, VK_TRUE, 5'000'000'000ULL); + timing.Mark("fence_wait"); if (result == VK_SUCCESS || result == VK_ERROR_DEVICE_LOST) pending = false; Check(result, "vkWaitForFences graphics"); } diff --git a/core/libs/prx/libSceSaveData.native/Export.cpp b/core/libs/prx/libSceSaveData.native/Export.cpp index bd0fe2230..1672db2ef 100644 --- a/core/libs/prx/libSceSaveData.native/Export.cpp +++ b/core/libs/prx/libSceSaveData.native/Export.cpp @@ -9,7 +9,7 @@ #include "prx/libc/include/General.hpp" #include "SaveData.hpp" -static constexpr char SAVE_DIR[] = "_SaveData"; +static constexpr char SAVE_DIR[] = "_sd"; static std::atomic g_transaction_counter{1}; static bool g_initialized = false; @@ -18,10 +18,6 @@ static std::string save_root() { return std::string(SAVE_DIR); } -static std::string slot_mount_point(int slot) { - return std::string("/savedata") + std::to_string(slot); -} - static bool dir_name_match(const char* str, const char* pattern) { if (pattern == nullptr || pattern[0] == '\0') { return true; @@ -199,8 +195,22 @@ int APS5_VABI sceSaveDataMount3(const SaveDataMount3* mount, SaveDataMountResult if (!create && !create2 && !open) { throw std::runtime_error("sceSaveDataMount3: unknown mount_mode"); } - const std::string dir_name = mount->dir_name->data; - const std::string real_path = save_root() + "/" + dir_name; + const auto* nameEnd = static_cast(std::memchr(mount->dir_name->data, '\0', sizeof(mount->dir_name->data))); + if (nameEnd == nullptr) { + throw std::runtime_error("sceSaveDataMount3: unterminated directory name"); + } + const std::string dirName(mount->dir_name->data, static_cast(nameEnd - mount->dir_name->data)); + if (dirName.empty() || dirName == "." || dirName == ".." || dirName.find_first_of("/\\:") != std::string::npos) { + throw std::runtime_error("sceSaveDataMount3: invalid directory name"); + } + const std::string real_path = save_root() + "/" + dirName; + const std::string mountPoint = "/" + real_path; + if (mountPoint.size() >= sizeof(mount_result->mount_point.data)) { + throw std::runtime_error("sceSaveDataMount3: directory path exceeds mount point capacity"); + } + if (find_slot_by_mount_point(mountPoint.c_str()) != -1) { + return SAVE_DATA_ERROR_BUSY; + } const bool exists = std::filesystem::is_directory(real_path); if (create && exists) { return SAVE_DATA_ERROR_EXISTS; @@ -215,11 +225,10 @@ int APS5_VABI sceSaveDataMount3(const SaveDataMount3* mount, SaveDataMountResult if (create || create2) { std::filesystem::create_directories(real_path); } - const std::string mp = slot_mount_point(slot); g_slots[slot].used = true; - g_slots[slot].mount_point = mp; + g_slots[slot].mount_point = mountPoint; g_slots[slot].real_path = real_path; - std::snprintf(mount_result->mount_point.data, sizeof(mount_result->mount_point.data), "%s", mp.c_str()); + std::memcpy(mount_result->mount_point.data, mountPoint.c_str(), mountPoint.size() + 1); mount_result->required_blocks = 0; mount_result->mount_status = (create || create2) ? 1u : 0u; return SAVE_DATA_OK; diff --git a/core/libs/prx/libkernel/File/src/Open.cpp b/core/libs/prx/libkernel/File/src/Open.cpp index 7ec8cf8d2..d1f3a9cbe 100644 --- a/core/libs/prx/libkernel/File/src/Open.cpp +++ b/core/libs/prx/libkernel/File/src/Open.cpp @@ -9,6 +9,8 @@ #include #include +static constexpr int SCE_KERNEL_ERROR_ENOENT = -2147352574; + #ifdef _WIN32 #include #include @@ -92,7 +94,11 @@ int APS5_VABI sceKernelOpen(const char* path, int flags, std::uint16_t mode) { auto native = ResolvePath_nid_no_patch(path); int fd = NativeOpen(native, MapFlags(flags), mode); if (fd < 0) { - throw std::runtime_error(std::string(__func__) + ": failed to open " + native.string() + ", errno=" + std::to_string(errno)); + const int error = errno; + if (error == ENOENT) { + return SCE_KERNEL_ERROR_ENOENT; + } + throw std::runtime_error(std::string(__func__) + ": failed to open " + native.string() + ", errno=" + std::to_string(error)); } return fd; }