diff --git a/src/SharpEmu.Gpu.Vulkan.Native/src/backend.cpp b/src/SharpEmu.Gpu.Vulkan.Native/src/backend.cpp index f6dcb5fc..39871839 100644 --- a/src/SharpEmu.Gpu.Vulkan.Native/src/backend.cpp +++ b/src/SharpEmu.Gpu.Vulkan.Native/src/backend.cpp @@ -7,6 +7,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -43,6 +46,9 @@ struct se_gpu_backend { VkSemaphore image_available{}, render_finished{}; VkFence frame_fence{}; VkBuffer staging{}; VkDeviceMemory staging_memory{}; void* staging_map{}; VkDeviceSize staging_capacity{}; bool resized{}; + std::string base_title{"SharpEmu"}; + std::chrono::steady_clock::time_point fps_sample_started{std::chrono::steady_clock::now()}; + uint64_t fps_sample_frames{}; bool fps_counter_enabled{true}; std::unordered_map guest_images; std::unordered_map display_formats; std::vector buffer_pool; @@ -94,6 +100,20 @@ se_gpu_result abandon(se_gpu_backend* b, se_gpu_result result, std::string messa return fail(nullptr, result, std::move(message)); } +void record_presented_frame(se_gpu_backend* b) { + if (!b->fps_counter_enabled) return; + ++b->fps_sample_frames; + const auto now = std::chrono::steady_clock::now(); + const double seconds = std::chrono::duration(now - b->fps_sample_started).count(); + if (seconds < 0.5) return; + const double fps = static_cast(b->fps_sample_frames) / seconds; + char title[256]{}; + std::snprintf(title, sizeof(title), "FPS %.1f | %s", fps, b->base_title.c_str()); + SDL_SetWindowTitle(b->window, title); + b->fps_sample_frames = 0; + b->fps_sample_started = now; +} + uint32_t memory_type(se_gpu_backend* b, uint32_t bits, VkMemoryPropertyFlags required) { VkPhysicalDeviceMemoryProperties properties{}; vkGetPhysicalDeviceMemoryProperties(b->physical_device, &properties); @@ -411,10 +431,16 @@ se_gpu_result present_image(se_gpu_backend* b, guest_image* source) { present.waitSemaphoreCount = 1; present.pWaitSemaphores = &b->render_finished; present.swapchainCount = 1; present.pSwapchains = &b->swapchain; present.pImageIndices = &image_index; VkResult result = vkQueuePresentKHR(b->queue, &present); - if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { b->resized = true; return SE_GPU_OK; } + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { + b->resized = true; + if (result == VK_SUBOPTIMAL_KHR) record_presented_frame(b); + return SE_GPU_OK; + } if (result != VK_SUCCESS) return fail(b, SE_GPU_VULKAN_ERROR, "guest image present failed"); - return vkWaitForFences(b->device, 1, &b->frame_fence, VK_TRUE, UINT64_MAX) == VK_SUCCESS ? SE_GPU_OK : - fail(b, SE_GPU_VULKAN_ERROR, "guest image presentation completion failed"); + if (vkWaitForFences(b->device, 1, &b->frame_fence, VK_TRUE, UINT64_MAX) != VK_SUCCESS) + return fail(b, SE_GPU_VULKAN_ERROR, "guest image presentation completion failed"); + record_presented_frame(b); + return SE_GPU_OK; } se_gpu_result create_commands(se_gpu_backend* b) { @@ -673,6 +699,9 @@ se_gpu_result SE_GPU_CALL se_gpu_create(const se_gpu_create_info* info, se_gpu_b auto* b = new (std::nothrow) se_gpu_backend{}; if (!b) return fail(nullptr, SE_GPU_OUT_OF_MEMORY, "native GPU backend allocation failed"); b->log = info->log; b->log_user = info->log_user; + b->base_title = info->title_utf8 ? info->title_utf8 : "SharpEmu"; + const char* perf_hud = std::getenv("SHARPEMU_PERF_HUD"); + b->fps_counter_enabled = !perf_hud || std::strcmp(perf_hud, "0") != 0; // SharpEmu owns the process entry point (and is a WinExe on Windows), so // SDL never gets its usual SDL_main bootstrap. Mark the host entry point // ready before initializing video from the dedicated renderer thread. @@ -846,8 +875,14 @@ se_gpu_result SE_GPU_CALL se_gpu_present_bgra( info.waitSemaphoreCount = 1; info.pWaitSemaphores = &b->render_finished; info.swapchainCount = 1; info.pSwapchains = &b->swapchain; info.pImageIndices = &image_index; result = vkQueuePresentKHR(b->queue, &info); - if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { b->resized = true; return SE_GPU_OK; } - return result == VK_SUCCESS ? SE_GPU_OK : fail(b, SE_GPU_VULKAN_ERROR, "queue present failed"); + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { + b->resized = true; + if (result == VK_SUBOPTIMAL_KHR) record_presented_frame(b); + return SE_GPU_OK; + } + if (result != VK_SUCCESS) return fail(b, SE_GPU_VULKAN_ERROR, "queue present failed"); + record_presented_frame(b); + return SE_GPU_OK; } se_gpu_result SE_GPU_CALL se_gpu_submit_draw(se_gpu_backend* b, const se_gpu_draw* work) { if (!b || !work || work->struct_size < sizeof(*work) || !work->pixel_spirv.data ||