xo-imgui wip

This commit is contained in:
Roland Conybeare 2025-09-24 23:24:35 -04:00
commit 7497751192
4 changed files with 233 additions and 26 deletions

View file

@ -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}

View file

@ -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<std::size_t>(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<std::chrono::milliseconds>(animate_dt).count()
/ static_cast<float>(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;

View file

@ -6,12 +6,14 @@
#include <imgui.h>
#include <vulkan/vulkan.h>
//#include <SDL_vulkan.h>
#include <chrono>
#include <vector>
#include <functional>
class VulkanApp {
public:
using ImguiDrawFn = std::function<ImDrawData * (ImGuiContext *)>;
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<VkFence> 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;

View file

@ -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<std::chrono::milliseconds>(now_tm_ - start_tm_).count();
return n_frame_ / elapsed_sec;
} else {
return 0.0;
}
}
void
VulkanApp::setup(std::function<void (ImGuiContext *)> 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<VkPresentModeKHR> 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<VkPresentModeKHR> 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<std::chrono::seconds>(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!");
}
}