diff --git a/default.nix b/default.nix index cce83eb2..6b6db244 100644 --- a/default.nix +++ b/default.nix @@ -541,7 +541,10 @@ in # TODO: consider a nix project to generate this directory. nixwsl }; - # like shell4 but drop etc/hostwsl2 + # like shell4 but drop etc/hostwsl2 symlink dir. + # looks like nixpkgs mesa not built for wsl2 dxg + # works with opengl (llvmpipe) and vulkan + # shell5 = pkgs.mkShell { buildInputs = docutils ++ xodeps ++ devutils ++ ideutils ++ x11utils ++ gldeps ++ vkdeps ++ imguideps; @@ -598,18 +601,18 @@ in ${pkgs.fontconfig}/bin/fc-cache -f # works but only get glx using llvmpipe. - # vkcube uses d3d12 however! + # vkcube uses dzn (see VK_ICD_FILENAMES) however! export LD_LIBRARY_PATH=/usr/lib/wsl/lib:${glpath}:$LD_LIBRARY_PATH # need this on OSX, + claude wants it for wsl2. but looks sketchy to me export VK_LAYER_PATH=${pkgs.vulkan-validation-layers}/share/vulkan/explicit_layer.d export VK_ICD_FILENAMES="${pkgs.mesa}/share/vulkan/icd.d/dzn_icd.x86_64.json" - # hardware acceleration (not sure if we need this) + # hardware acceleration (so far ineffective w/ nixpkgs mesa) export LIBGL_ALWAYS_SOFTWARE=0 export MESA_LOADER_DRIVER_OVERRIDE="d3d12" - # wsl2-specific gpu setup (doubt this is effective) + # wsl2-specific gpu setup (so far ineffective w/ nixpkgs mesa) export MESA_D3D12_DEFAULT_ADAPTER_NAME=DX echo "using d3d12 vulkan driver: $VK_ICD_FILENAMES" @@ -620,6 +623,9 @@ in echo "nix_libgl=${pkgs.libGL}" nix_libgl=${pkgs.libGL} + echo "nix_vk_validation: VK_LAYER_PATH=${pkgs.vulkan-validation-layers}/share/vulkan/explicit_layer.d" + nix_vk_validation=${pkgs.vulkan-validation-layers}/share/vulkan/explicit_layer.d + # don't seem to need this -- doesn't invoke mesa hardware accel #echo "nix_nixgl=${pkgs.nixgl.nixGLMesa}" #nix_nixgl=${pkgs.nixgl.nixGLMesa} diff --git a/xo-imgui/example/ex4/imgui_ex4.cpp b/xo-imgui/example/ex4/imgui_ex4.cpp index a12342b3..3aef0fa9 100644 --- a/xo-imgui/example/ex4/imgui_ex4.cpp +++ b/xo-imgui/example/ex4/imgui_ex4.cpp @@ -24,7 +24,7 @@ namespace { app_duty_cycle_top(AppState * p_app_state, DrawState * p_draw_state) { - scope log(XO_DEBUG(true)); + scope log(XO_DEBUG(false)); log && log(xtag("imgui_cx", (void*)ImGui::GetCurrentContext())); @@ -54,6 +54,8 @@ namespace { * that callback captures copy details (per object!) in AppState */ if (p_app_state->gc_->enable_gc_once()) { + scope log(XO_DEBUG(true)); + log && log(xtag("gc-type", (p_app_state->upto_ == generation::tenured) ? "full" : "incremental")); p_draw_state->state_type_ = draw_state_type::animate_gc; @@ -83,9 +85,11 @@ namespace { return [p_app_state, p_draw_state, p_f, p_counter](ImGuiContext * imgui_cx) { - scope log(XO_DEBUG(true)); + scope log(XO_DEBUG(false)); +#ifdef TEMPORARILY_REMOVE app_duty_cycle_top(p_app_state, p_draw_state); +#endif log && log(xtag("imgui_cx", (void*)ImGui::GetCurrentContext())); @@ -94,11 +98,12 @@ namespace { ImGui_ImplSDL2_NewFrame(); ImGui::NewFrame(); +#ifdef TEMPORARILY_REMOVE log && log("after NewFrame", xtag("imgui_cx", (void*)ImGui::GetCurrentContext())); -#ifdef NOT_WORKING ImGuiIO & io = ImGui::GetIO(); (void)io; +# ifdef NOT_WORKING // background ImGui::SetNextWindowPos(ImVec2(0, 0)); ImGui::SetNextWindowSize(io.DisplaySize); @@ -107,24 +112,115 @@ namespace { | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoDecoration); ImGui::End(); +# endif #endif - // Create a simple ImGui window + // 1. create a simple ImGui window ImGui::Begin("Hello, Vulkan + SDL2!"); ImGui::Text("This is a minimal ImGui + Vulkan + SDL2 example!"); +#ifdef TEMPORARILY_REMOVE + ImGui::Text("appl average %.3f ms/frame (%.1f fps)", + 1000.0f / io.Framerate, io.Framerate); + ImGui::Checkbox("demo window", &p_draw_state->show_demo_window_); - ImGui::SliderFloat("float", p_f, 0.0f, 1.0f); - if (ImGui::Button("Button")) - ++(*p_counter); - ImGui::SameLine(); - ImGui::Text("counter = %d", *p_counter); - ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", - 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate); + + ImGui::SliderInt("alloc/cycle", &p_app_state->alloc_per_cycle_, 1, 100); + ImGui::SliderInt("copy animation budget", &p_draw_state->animate_copy_budget_ms_, 10, 10000); + ImGui::NewLine(); + + /* N\u2080 = N0, N\u2081 = N1 */ + ImGui::Text("alloc [%lu] avail [%lu] ", + p_draw_state->gcstate_.gc_allocated_, + p_draw_state->gcstate_.gc_available_); + + ImGui::Text("promoted [%lu] copy animation [%lu / %lu]", + p_draw_state->gcstate_.total_promoted_, + static_cast(p_draw_state->animate_copy_hi_pct_ + * p_app_state->copy_detail_v_.size() / 100), + p_app_state->copy_detail_v_.size()); + + ImGui::Text("mutation [%lu] mlog [%lu]", + p_draw_state->gcstate_.total_n_mutation_, + p_draw_state->gcstate_.gc_mlog_size_); + + ImGui::Text("appl average %.3f ms/frame (%.1f fps)", + 1000.0f / io.Framerate, io.Framerate); + + ImGui::Text("layout:" + " nursery-src alloc rect [%.1f %.1f %.1f %.1f]" + " nursery-dest alloc rect [%.1f %.1f %.1f %.1f]" + " history rect [%.1f %.1f %.1f %.1f]", + p_draw_state->gcw_nursery_layout_.to_alloc_rect().x_lo(), + p_draw_state->gcw_nursery_layout_.to_alloc_rect().y_lo(), + p_draw_state->gcw_nursery_layout_.to_alloc_rect().x_hi(), + p_draw_state->gcw_nursery_layout_.to_alloc_rect().y_hi(), + p_draw_state->gcw_nursery_layout_.from_alloc_rect().x_lo(), + p_draw_state->gcw_nursery_layout_.from_alloc_rect().y_lo(), + p_draw_state->gcw_nursery_layout_.from_alloc_rect().x_hi(), + p_draw_state->gcw_nursery_layout_.from_alloc_rect().y_hi(), + p_draw_state->gcw_history_rect_.x_lo(), + p_draw_state->gcw_history_rect_.y_lo(), + p_draw_state->gcw_history_rect_.x_hi(), + p_draw_state->gcw_history_rect_.y_hi()); + ImGui::Text("nursery-dest copy offset [%lu] / size [%lu]" + " tenured-dest copy offset [%lu] / size [%lu]", + p_app_state->copy_detail_max_nursery_dest_offset_, + p_app_state->copy_detail_nursery_dest_size_, + p_app_state->copy_detail_max_tenured_dest_offset_, + p_app_state->copy_detail_tenured_dest_size_); + + ImDrawList * draw_list = ImGui::GetWindowDrawList(); + + ImVec2 canvas_p0 = ImGui::GetCursorScreenPos(); + ImVec2 canvas_sz = ImGui::GetContentRegionAvail(); + ImVec2 canvas_p1 = ImVec2(canvas_p0.x + canvas_sz.x, canvas_p0.y + canvas_sz.y); + + /* stash so GC copy animation can find it */ + p_draw_state->gcw_draw_list_ = draw_list; + p_draw_state->gcw_canvas_p0_ = canvas_p0; + p_draw_state->gcw_canvas_p1_ = canvas_p1; + + DrawState::draw_gc_state(*p_app_state, + p_draw_state->gcstate_, + ImRect(canvas_p0, canvas_p1), + draw_list, + &p_draw_state->gcw_nursery_layout_, + &p_draw_state->gcw_tenured_layout_, + &p_draw_state->gcw_history_rect_); + + if (p_draw_state->state_type_ == draw_state_type::animate_gc) { + auto animate_copy_t1 = std::chrono::steady_clock::now(); + auto animate_dt = animate_copy_t1 - p_draw_state->animate_copy_t0_; + float animate_fraction_spent + = (std::chrono::duration_cast(animate_dt).count() + / static_cast(p_draw_state->animate_copy_budget_ms_)); + + p_draw_state->animate_copy_hi_pct_ = 100.0 * animate_fraction_spent; + + DrawState::animate_gc_copy(*p_app_state, + *p_draw_state, + draw_list); + + /* see 25.0 constant in animate_gc_copy() */ + if (p_draw_state->animate_copy_hi_pct_ >= 114) { + p_draw_state->state_type_ = draw_state_type::alloc; + p_draw_state->animate_copy_hi_pct_ = 0; + p_app_state->copy_detail_v_.clear(); + p_app_state->copy_detail_max_nursery_dest_offset_ = 0; + p_app_state->copy_detail_nursery_dest_size_ = 0; + p_app_state->copy_detail_max_tenured_dest_offset_ = 0; + p_app_state->copy_detail_tenured_dest_size_ = 0; + } + } +#endif + ImGui::End(); - // 1. big demo window +#ifdef TEMPORARILY_REMOVE + // 2. big demo window if (p_draw_state->show_demo_window_) ImGui::ShowDemoWindow(&p_draw_state->show_demo_window_); +#endif // Rendering ImGui::Render(); @@ -134,7 +230,7 @@ namespace { void app_imgui_load_fonts(ImGuiContext * imgui_cx) { - scope log(XO_DEBUG(true)); + scope log(XO_DEBUG(false)); log && log(xtag("imgui_cx", (void*)ImGui::GetCurrentContext())); ImGuiIO & io = ImGui::GetIO(); (void)io; diff --git a/xo-imgui/include/xo/imgui/VulkanApp.hpp b/xo-imgui/include/xo/imgui/VulkanApp.hpp index c2289c8f..e8dfe2f0 100644 --- a/xo-imgui/include/xo/imgui/VulkanApp.hpp +++ b/xo-imgui/include/xo/imgui/VulkanApp.hpp @@ -6,12 +6,14 @@ #include #include //#include +#include #include #include class VulkanApp { public: using ImguiDrawFn = std::function; + using time_point = std::chrono::steady_clock::time_point; public: VulkanApp(ImguiDrawFn fn); @@ -21,6 +23,9 @@ public: void assign_imgui_draw_frame(ImguiDrawFn fn); #endif + /** frames per second since inception **/ + float lifetime_fps() const; + /** equivalent to sequence setup(), main_loop(), cleanup() **/ void run(); @@ -89,6 +94,15 @@ private: std::vector in_flight_fences_; VkDescriptorPool descriptor_pool_; + /** frame counter, monotonic **/ + uint32_t n_frame_ = 0; + /** VulkanApp start time. Captured in 1st render loop, see @ref record_command_buffer **/ + time_point start_tm_; + /** time asof most recent render loop **/ + time_point now_tm_; + /** time of last console report of fps **/ + time_point last_log_fps_tm_; + /** image index of current frame **/ uint32_t current_frame_ = 0; uint32_t graphics_queue_family_ = 0; diff --git a/xo-imgui/src/imgui/VulkanApp.cpp b/xo-imgui/src/imgui/VulkanApp.cpp index 79e0f8c5..f33d45df 100644 --- a/xo-imgui/src/imgui/VulkanApp.cpp +++ b/xo-imgui/src/imgui/VulkanApp.cpp @@ -23,6 +23,17 @@ VulkanApp::VulkanApp(ImguiDrawFn draw_fn) { } +float +VulkanApp::lifetime_fps() const { + if (n_frame_ > 0) { + float elapsed_sec = 1.0e-3f * std::chrono::duration_cast(now_tm_ - start_tm_).count(); + + return n_frame_ / elapsed_sec; + } else { + return 0.0; + } +} + void VulkanApp::setup(std::function load_fonts) { if (!setup_done_) { @@ -55,7 +66,8 @@ VulkanApp::init_window() { this->window_ = SDL_CreateWindow("Xo ImGui Vulkan SDL2 Frame", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, - 800, 600, + 600, 600, + //1250, 1200, SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE); if (!window_) { @@ -83,6 +95,8 @@ VulkanApp::init_vulkan() void VulkanApp::create_instance() { + scope log(XO_DEBUG(true)); + VkApplicationInfo appInfo{}; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pApplicationName = "ImGui Vulkan App"; @@ -128,6 +142,11 @@ VulkanApp::create_instance() // CRITICAL: Enable portability enumeration flag for MoltenVK createInfo.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; + log && log(xtag("vkCreateInstance.layers", createInfo.enabledLayerCount)); + for (uint32_t i = 0; i < createInfo.enabledLayerCount; ++i) { + log && log(xtag("i", i), xtag("layer[i]", createInfo.ppEnabledLayerNames[i])); + } + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("Failed to create instance!"); } @@ -205,9 +224,27 @@ VulkanApp::create_logical_device() vkGetDeviceQueue(device_, graphics_queue_family_, 0, &graphics_queue_); } +const char * +present_mode_descr(VkPresentModeKHR m) { + switch(m) { + case VK_PRESENT_MODE_IMMEDIATE_KHR: return "immediate"; + case VK_PRESENT_MODE_MAILBOX_KHR: return "mailbox"; + case VK_PRESENT_MODE_FIFO_KHR: return "fifo"; + case VK_PRESENT_MODE_FIFO_RELAXED_KHR: return "fifo-relaxed"; + case VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR: return "shared-demand-refresh"; + case VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR: return "shared-continuous-refresh"; + case VK_PRESENT_MODE_FIFO_LATEST_READY_EXT: return "fifo-latest-ready"; + case VK_PRESENT_MODE_MAX_ENUM_KHR: break; + } + + return "?present-mode"; +} + void VulkanApp::create_swapchain() { + scope log(XO_DEBUG(true)); + VkSurfaceCapabilitiesKHR capabilities; vkGetPhysicalDeviceSurfaceCapabilitiesKHR (physical_device_, surface_, &capabilities); @@ -234,6 +271,40 @@ VulkanApp::create_swapchain() imageCount = capabilities.maxImageCount; } + VkPresentModeKHR present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR; + { + uint32_t n_avail_mode = 0; + vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device_, surface_, &n_avail_mode, nullptr); + + std::vector avail_mode_v(n_avail_mode); + vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device_, surface_, &n_avail_mode, avail_mode_v.data()); + + if (log) { + log(xtag("n_avail_present_mode", n_avail_mode)); + for (uint32_t i = 0; i < n_avail_mode; ++i) + log(xtag("i", i), xtag("avail_mode_v[i]", present_mode_descr(avail_mode_v[i]))); + } + + std::vector prefer_mode_v = { + VK_PRESENT_MODE_IMMEDIATE_KHR, + VK_PRESENT_MODE_MAILBOX_KHR, + VK_PRESENT_MODE_FIFO_KHR // forces vsync, v.slow on wsl + }; + + bool found_flag = false; + for (uint32_t i = 0; i < prefer_mode_v.size() && !found_flag; ++i) { + for (uint32_t j = 0; j < avail_mode_v.size() && !found_flag; ++j) { + if (prefer_mode_v[i] == avail_mode_v[j]) { + present_mode = prefer_mode_v[i]; + found_flag = true; + + } + } + } + + log && log(xtag("present_mode", present_mode_descr(present_mode))); + } + VkSwapchainCreateInfoKHR createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; createInfo.surface = surface_; @@ -246,7 +317,7 @@ VulkanApp::create_swapchain() createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; createInfo.preTransform = capabilities.currentTransform; createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; - createInfo.presentMode = VK_PRESENT_MODE_FIFO_KHR; + createInfo.presentMode = present_mode; createInfo.clipped = VK_TRUE; if (vkCreateSwapchainKHR @@ -710,15 +781,18 @@ VulkanApp::draw_frame() } /*draw_frame*/ void -VulkanApp::record_command_buffer(VkCommandBuffer commandBuffer, +VulkanApp::record_command_buffer(VkCommandBuffer command_buffer, uint32_t imageIndex) { // as long as we do this per-frame, independent of swapchain + if (n_frame_ == 0) { + this->start_tm_ = std::chrono::steady_clock::now(); + } VkCommandBufferBeginInfo begin_info{}; begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - if (vkBeginCommandBuffer(commandBuffer, &begin_info) != VK_SUCCESS) { + if (vkBeginCommandBuffer(command_buffer, &begin_info) != VK_SUCCESS) { throw std::runtime_error("Failed to begin recording command buffer!"); } @@ -733,17 +807,34 @@ VulkanApp::record_command_buffer(VkCommandBuffer commandBuffer, render_pass_info.clearValueCount = 1; render_pass_info.pClearValues = &clear_color; - vkCmdBeginRenderPass(commandBuffer, + vkCmdBeginRenderPass(command_buffer, &render_pass_info, VK_SUBPASS_CONTENTS_INLINE); - ImDrawData * draw_data = this->imgui_draw_frame_(imgui_cx_); + ImDrawData * draw_data = this->imgui_draw_frame_(imgui_cx_); (void)draw_data; - ImGui_ImplVulkan_RenderDrawData(draw_data, commandBuffer); +#ifdef TEMPORARILY_REMOVE + ImGui_ImplVulkan_RenderDrawData(draw_data, command_buffer); +#endif - vkCmdEndRenderPass(commandBuffer); + vkCmdEndRenderPass(command_buffer); - if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + { + ++(this->n_frame_); + this->now_tm_ = std::chrono::steady_clock::now(); + + if ((this->n_frame_ == 1) + || (std::chrono::duration_cast(now_tm_ - last_log_fps_tm_).count() > 1)) + { + this->last_log_fps_tm_ = now_tm_; + + scope log(XO_DEBUG(true)); + + log && log(xtag("lifetime_fps", this->lifetime_fps())); + } + } + + if (vkEndCommandBuffer(command_buffer) != VK_SUCCESS) { throw std::runtime_error("Failed to record command buffer!"); } }