mirror of
https://github.com/par274/sharpemu.git
synced 2026-09-01 22:31:17 +08:00
Add FPS counter to native renderer title
This commit is contained in:
@@ -7,6 +7,9 @@
|
|||||||
#include <SDL3/SDL_vulkan.h>
|
#include <SDL3/SDL_vulkan.h>
|
||||||
#include <vulkan/vulkan.h>
|
#include <vulkan/vulkan.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <new>
|
#include <new>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -43,6 +46,9 @@ struct se_gpu_backend {
|
|||||||
VkSemaphore image_available{}, render_finished{}; VkFence frame_fence{};
|
VkSemaphore image_available{}, render_finished{}; VkFence frame_fence{};
|
||||||
VkBuffer staging{}; VkDeviceMemory staging_memory{}; void* staging_map{};
|
VkBuffer staging{}; VkDeviceMemory staging_memory{}; void* staging_map{};
|
||||||
VkDeviceSize staging_capacity{}; bool resized{};
|
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<uint64_t, guest_image> guest_images;
|
std::unordered_map<uint64_t, guest_image> guest_images;
|
||||||
std::unordered_map<uint64_t, uint32_t> display_formats;
|
std::unordered_map<uint64_t, uint32_t> display_formats;
|
||||||
std::vector<host_buffer> buffer_pool;
|
std::vector<host_buffer> 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));
|
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<double>(now - b->fps_sample_started).count();
|
||||||
|
if (seconds < 0.5) return;
|
||||||
|
const double fps = static_cast<double>(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) {
|
uint32_t memory_type(se_gpu_backend* b, uint32_t bits, VkMemoryPropertyFlags required) {
|
||||||
VkPhysicalDeviceMemoryProperties properties{};
|
VkPhysicalDeviceMemoryProperties properties{};
|
||||||
vkGetPhysicalDeviceMemoryProperties(b->physical_device, &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.waitSemaphoreCount = 1; present.pWaitSemaphores = &b->render_finished;
|
||||||
present.swapchainCount = 1; present.pSwapchains = &b->swapchain; present.pImageIndices = &image_index;
|
present.swapchainCount = 1; present.pSwapchains = &b->swapchain; present.pImageIndices = &image_index;
|
||||||
VkResult result = vkQueuePresentKHR(b->queue, &present);
|
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");
|
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 :
|
if (vkWaitForFences(b->device, 1, &b->frame_fence, VK_TRUE, UINT64_MAX) != VK_SUCCESS)
|
||||||
fail(b, SE_GPU_VULKAN_ERROR, "guest image presentation completion failed");
|
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) {
|
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{};
|
auto* b = new (std::nothrow) se_gpu_backend{};
|
||||||
if (!b) return fail(nullptr, SE_GPU_OUT_OF_MEMORY, "native GPU backend allocation failed");
|
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->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
|
// 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
|
// SDL never gets its usual SDL_main bootstrap. Mark the host entry point
|
||||||
// ready before initializing video from the dedicated renderer thread.
|
// 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.waitSemaphoreCount = 1; info.pWaitSemaphores = &b->render_finished;
|
||||||
info.swapchainCount = 1; info.pSwapchains = &b->swapchain; info.pImageIndices = &image_index;
|
info.swapchainCount = 1; info.pSwapchains = &b->swapchain; info.pImageIndices = &image_index;
|
||||||
result = vkQueuePresentKHR(b->queue, &info);
|
result = vkQueuePresentKHR(b->queue, &info);
|
||||||
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) {
|
||||||
return result == VK_SUCCESS ? SE_GPU_OK : fail(b, SE_GPU_VULKAN_ERROR, "queue present failed");
|
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) {
|
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 ||
|
if (!b || !work || work->struct_size < sizeof(*work) || !work->pixel_spirv.data ||
|
||||||
|
|||||||
Reference in New Issue
Block a user