From 64aa534c3b5169cc125c3d24e403ef831dfc78f8 Mon Sep 17 00:00:00 2001 From: Andrey Kryuchenko Date: Sat, 18 Apr 2026 21:11:04 +0200 Subject: [PATCH] refactor: split RenderFrame into per-section helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Step 4 (final) of the cli_args_debugger.cpp decomposition (steps 1-3 were #3, #4, #6). RenderFrame was the last function above the static analysis threshold at CCN 21 / 203 NLOC / 289 lines and touching 37 other class members. Split into an orchestrator plus ten private section methods: UpdateFrameTiming - tick + FPS + rotation RenderCube - D3D11: clear, viewport, MVP, draw RenderTextHud - description, cli-args header, args, status RenderLoadedDataPanel - logs/saved-data panel (show_logs_) RenderPathsPanel - path items (show_paths_) RenderInputPrompt - exit prompt + user_input_ RenderQrBitmap - QR code blit RenderVolumeMeter - mic level bars + device name (or no-mic text) EndOverlay - D2D EndDraw + device-lost recreate PresentFrame - FPS log + Present + device-removed recreate RenderFrame itself is now ~20 lines of sequencing. No behaviour change intended — each helper receives the same D2D1_SIZE_F and draws into the same render target, in the same order as before. EndOverlay's bool return lets the orchestrator skip Present if the D2D device was lost, mirroring the original early `return`. Numbers: - Warnings (CCN > 15 or length > 1000) in cli_args_debugger.cpp: was 2 (PollMicrophone=50 already extracted, RenderFrame=21) → now 0. - cli_args_debugger.cpp stays within a line of the previous total — structural split, not a net-line reduction. - Highest-CCN function in the file drops to WindowProc at CCN 7. --- cli_args_debugger.cpp | 440 +++++++++++++++++++++--------------------- 1 file changed, 219 insertions(+), 221 deletions(-) diff --git a/cli_args_debugger.cpp b/cli_args_debugger.cpp index 78ae8fe..bb3a195 100644 --- a/cli_args_debugger.cpp +++ b/cli_args_debugger.cpp @@ -41,7 +41,9 @@ #include // For setw/setfill #include #include -#include // For _wfsopen share flags +#include // For PlaySound +#include // For GetProcessMemoryInfo +#include // For _wfsopen share flags #include #include #include @@ -49,9 +51,7 @@ #include #include #include -#include // For PlaySound #include -#include // For GetProcessMemoryInfo // Debug memory leak detection #ifdef _DEBUG @@ -136,7 +136,8 @@ constexpr const wchar_t* kWindowCaption = L"Cloud Streaming Args Debugger"; // Description text for the window const std::vector kDescriptionLines = { - L"Cloud Streaming Args Debugger", L"This utility displays all command-line arguments for cloud streaming applications.", + L"Cloud Streaming Args Debugger", + L"This utility displays all command-line arguments for cloud streaming applications.", L"Type 'exit', 'save', 'read', 'logs', 'path', 'sound' or 'memory' and press Enter to execute commands."}; constexpr float kMargin = 20.0f; @@ -196,6 +197,21 @@ class ArgumentDebuggerWindow void Cleanup(); void UpdateRotation(float delta_time); + // RenderFrame is split into small section methods. RenderFrame itself just + // orchestrates the sequence; each helper owns one visible region of the UI. + void UpdateFrameTiming(); + void RenderCube(const D3D11_VIEWPORT& vp); + void RenderTextHud(const D2D1_SIZE_F& size, float& y_pos); + void RenderLoadedDataPanel(const D2D1_SIZE_F& size); + void RenderPathsPanel(const D2D1_SIZE_F& size); + void RenderInputPrompt(const D2D1_SIZE_F& size); + void RenderQrBitmap(const D2D1_SIZE_F& size); + void RenderVolumeMeter(const D2D1_SIZE_F& size); + // Returns false if the D2D device was lost and has been recreated; in that + // case the caller should skip Present and move on to the next frame. + bool EndOverlay(); + void PresentFrame(); + private: // Update QR code – here we add the FPS synchronization logic. void UpdateQrCode(ULONGLONG current_time); @@ -206,13 +222,13 @@ class ArgumentDebuggerWindow // Helper to load entire log file into loaded_data_ void ShowLogs(); - + // Helper to calculate and cache path information once void CalculatePathInfo(); - + // Play telephone-like beeps for 1 minute void PlayTelephoneBeeps(); - + // Show memory usage statistics void ShowMemoryStats(); @@ -227,9 +243,9 @@ class ArgumentDebuggerWindow std::wstring command_status_; std::wstring loaded_data_; std::wstring loaded_data_title_; // Title for the loaded data section - bool show_paths_ = false; // Flag to control file paths display - bool show_logs_ = false; // Flag to control logs display - + bool show_paths_ = false; // Flag to control file paths display + bool show_logs_ = false; // Flag to control logs display + // Cached path information to avoid expensive system calls every frame std::vector> cached_path_items_; @@ -248,8 +264,8 @@ class ArgumentDebuggerWindow ComPtr dwrite_factory_; ComPtr text_format_; ComPtr small_text_format_; // Smaller font for logs - ComPtr data_text_format_; // Medium font for loaded data - + ComPtr data_text_format_; // Medium font for loaded data + // D2D Brushes - created once and reused ComPtr white_brush_; ComPtr green_brush_; @@ -381,7 +397,7 @@ int ArgumentDebuggerWindow::RunMessageLoop() static ULONGLONG lastFrameTime = 0; ULONGLONG currentFrameTime = GetTickCount64(); const ULONGLONG targetFrameTime = 16; // ~60 FPS (1000ms / 60 = 16.67ms) - + if (currentFrameTime - lastFrameTime < targetFrameTime) { // Sleep to maintain target frame rate @@ -400,7 +416,7 @@ int ArgumentDebuggerWindow::RunMessageLoop() catch (const std::exception& ex) { // Safe string conversion with bounds checking - size_t msgLen = strnlen_s(ex.what(), 1024); // Limit to 1KB + size_t msgLen = strnlen_s(ex.what(), 1024); // Limit to 1KB std::wstring wideMsg; if (msgLen > 0) { @@ -637,7 +653,7 @@ void ArgumentDebuggerWindow::CreateD2DResources() small_text_format_->SetWordWrapping(DWRITE_WORD_WRAPPING_WRAP); // Use trailing alignment for device name text to align to the right small_text_format_->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_TRAILING); - + // Create a medium text format for loaded data (double the size of small font) DX_CALL(dwrite_factory_->CreateTextFormat(L"Consolas", nullptr, // Using monospaced font for data DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL, @@ -648,7 +664,7 @@ void ArgumentDebuggerWindow::CreateD2DResources() data_text_format_->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_NEAR); data_text_format_->SetWordWrapping(DWRITE_WORD_WRAPPING_WRAP); data_text_format_->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING); - + // Create brushes once during initialization DX_CALL(d2d_render_target_->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White), white_brush_.GetAddressOf()), "Failed to create white brush."); @@ -807,30 +823,49 @@ void ArgumentDebuggerWindow::UpdateQrCode(ULONGLONG current_time) } void ArgumentDebuggerWindow::RenderFrame() +{ + UpdateFrameTiming(); + + RECT rc; + GetClientRect(window_handle_, &rc); + D3D11_VIEWPORT vp{}; + vp.Width = static_cast(rc.right - rc.left); + vp.Height = static_cast(rc.bottom - rc.top); + vp.MaxDepth = 1.0f; + RenderCube(vp); + + d2d_render_target_->BeginDraw(); + UpdateQrCode(GetTickCount64()); + + const D2D1_SIZE_F size = d2d_render_target_->GetSize(); + float y_pos = kMargin; + RenderTextHud(size, y_pos); + RenderLoadedDataPanel(size); + RenderPathsPanel(size); + RenderInputPrompt(size); + RenderQrBitmap(size); + RenderVolumeMeter(size); + + if (!EndOverlay()) // device lost → D2D resources already recreated + return; + + PresentFrame(); +} + +void ArgumentDebuggerWindow::UpdateFrameTiming() { ULONGLONG current_time = GetTickCount64(); float delta_time = (current_time - last_time_) / 1000.0f; last_time_ = current_time; - - // Update the instantaneous FPS current_fps_ = (delta_time > 0.0f) ? (1.0f / delta_time) : 0.0f; - UpdateRotation(delta_time); +} - // Clear the D3D render target +void ArgumentDebuggerWindow::RenderCube(const D3D11_VIEWPORT& vp) +{ FLOAT clear_color[4] = {0.0f, 0.0f, 0.0f, 1.0f}; immediate_context_->ClearRenderTargetView(d3d_render_target_view_.Get(), clear_color); immediate_context_->OMSetRenderTargets(1, d3d_render_target_view_.GetAddressOf(), nullptr); - - RECT rc; - GetClientRect(window_handle_, &rc); - D3D11_VIEWPORT vp; - vp.Width = static_cast(rc.right - rc.left); - vp.Height = static_cast(rc.bottom - rc.top); - vp.MinDepth = 0.0f; - vp.MaxDepth = 1.0f; - vp.TopLeftX = 0.0f; - vp.TopLeftY = 0.0f; immediate_context_->RSSetViewports(1, &vp); UINT stride = sizeof(SimpleVertex); @@ -844,7 +879,6 @@ void ArgumentDebuggerWindow::RenderFrame() XMVECTOR eye = XMVectorSet(0.0f, 2.0f, -5.0f, 0.0f); XMVECTOR at = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f); XMVECTOR up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f); - XMMATRIX view = XMMatrixLookAtLH(eye, at, up); XMMATRIX proj = XMMatrixPerspectiveFovLH(XM_PIDIV4, vp.Width / vp.Height, 0.01f, 100.0f); @@ -855,19 +889,11 @@ void ArgumentDebuggerWindow::RenderFrame() immediate_context_->VSSetShader(vertex_shader_.Get(), nullptr, 0); immediate_context_->VSSetConstantBuffers(0, 1, constant_buffer_.GetAddressOf()); immediate_context_->PSSetShader(pixel_shader_.Get(), nullptr, 0); - immediate_context_->DrawIndexed(36, 0, 0); +} - // Begin Direct2D drawing. - d2d_render_target_->BeginDraw(); - - // Update the QR code (no more than once every 5 seconds). - UpdateQrCode(current_time); - - D2D1_SIZE_F size = d2d_render_target_->GetSize(); - float y_pos = kMargin; - - // Draw description lines. +void ArgumentDebuggerWindow::RenderTextHud(const D2D1_SIZE_F& size, float& y_pos) +{ for (const auto& line : kDescriptionLines) { D2D1_RECT_F rect = D2D1::RectF(kMargin, y_pos, size.width - kMargin, y_pos + kLineHeight); @@ -877,16 +903,12 @@ void ArgumentDebuggerWindow::RenderFrame() } y_pos += kLineHeight; - // Draw command-line argument header. std::wstring cli_text = BuildCliHeaderText(args_); - { - D2D1_RECT_F rect = D2D1::RectF(kMargin, y_pos, size.width - kMargin, y_pos + kLineHeight); - d2d_render_target_->DrawText(cli_text.c_str(), static_cast(cli_text.size()), text_format_.Get(), rect, - green_brush_.Get()); - } + D2D1_RECT_F header_rect = D2D1::RectF(kMargin, y_pos, size.width - kMargin, y_pos + kLineHeight); + d2d_render_target_->DrawText(cli_text.c_str(), static_cast(cli_text.size()), text_format_.Get(), + header_rect, green_brush_.Get()); y_pos += kLineHeight; - // Draw the actual command-line arguments (if any). if (!args_.empty()) { std::wstring formatted_args = BuildCliArgsText(args_); @@ -897,57 +919,53 @@ void ArgumentDebuggerWindow::RenderFrame() } y_pos += 10.0f; - // Additional drawing: display command execution status. - { - D2D1_RECT_F status_rect = - D2D1::RectF(kMargin, size.height - 220.0f, size.width - kMargin, size.height - 190.0f); - d2d_render_target_->DrawText(command_status_.c_str(), static_cast(command_status_.size()), - text_format_.Get(), status_rect, white_brush_.Get()); - } + D2D1_RECT_F status_rect = D2D1::RectF(kMargin, size.height - 220.0f, size.width - kMargin, size.height - 190.0f); + d2d_render_target_->DrawText(command_status_.c_str(), static_cast(command_status_.size()), + text_format_.Get(), status_rect, white_brush_.Get()); +} + +void ArgumentDebuggerWindow::RenderLoadedDataPanel(const D2D1_SIZE_F& size) +{ + if (!show_logs_ || loaded_data_.empty()) + return; + + D2D1_RECT_F data_rect = D2D1::RectF(size.width - 750.0f, kMargin, size.width - kMargin, kMargin + 380.0f); + d2d_render_target_->DrawText(loaded_data_.c_str(), static_cast(loaded_data_.size()), + data_text_format_.Get(), data_rect, green_brush_.Get()); - // Draw loaded data (if any) in the top-right corner. - if (show_logs_ && !loaded_data_.empty()) + if (!loaded_data_title_.empty()) { - // Use a smaller font for logs and expand the display area - // Added padding at the bottom (reduced height by 20px) - D2D1_RECT_F data_rect = D2D1::RectF(size.width - 750.0f, kMargin, size.width - kMargin, kMargin + 380.0f); - d2d_render_target_->DrawText(loaded_data_.c_str(), static_cast(loaded_data_.size()), - data_text_format_.Get(), data_rect, green_brush_.Get()); - - // Add a title for the loaded data section - if (!loaded_data_title_.empty()) - { - D2D1_RECT_F title_rect = D2D1::RectF(size.width - 750.0f, kMargin - 30.0f, size.width - kMargin, kMargin); - d2d_render_target_->DrawText(loaded_data_title_.c_str(), static_cast(loaded_data_title_.size()), - text_format_.Get(), title_rect, yellow_brush_.Get()); - } + D2D1_RECT_F title_rect = D2D1::RectF(size.width - 750.0f, kMargin - 30.0f, size.width - kMargin, kMargin); + d2d_render_target_->DrawText(loaded_data_title_.c_str(), static_cast(loaded_data_title_.size()), + text_format_.Get(), title_rect, yellow_brush_.Get()); } +} + +void ArgumentDebuggerWindow::RenderPathsPanel(const D2D1_SIZE_F& size) +{ + if (!show_paths_ || cached_path_items_.empty()) + return; + + constexpr float pathWidth = 400.0f; + constexpr float pathLineHeight = 25.0f; + const float pathEndX = size.width - kMargin; + const float pathStartX = pathEndX - pathWidth; + float currentY = size.height * 0.3f; - // Display file paths only if enabled and cached data is available - if (show_paths_ && !cached_path_items_.empty()) + for (const auto& item : cached_path_items_) { - // Use smaller font for path information - // Position at the right edge, aligned with mic indicators - float pathWidth = 400.0f; // Width of the path info block - float pathEndX = size.width - kMargin; // Same margin as mic indicators - float pathStartX = pathEndX - pathWidth; // Left edge of the path block - float pathStartY = size.height * 0.3f; // Move up a bit to fit more items - float pathLineHeight = 25.0f; - - // Draw cached path information with smaller font - float currentY = pathStartY; - for (const auto& item : cached_path_items_) - { - std::wstring fullLine = item.first + item.second; - D2D1_RECT_F rect = D2D1::RectF(pathStartX, currentY, pathEndX, currentY + pathLineHeight); - d2d_render_target_->DrawText(fullLine.c_str(), static_cast(fullLine.size()), small_text_format_.Get(), - rect, white_brush_.Get()); - currentY += pathLineHeight; - } - } // End of if (show_paths_) + std::wstring fullLine = item.first + item.second; + D2D1_RECT_F rect = D2D1::RectF(pathStartX, currentY, pathEndX, currentY + pathLineHeight); + d2d_render_target_->DrawText(fullLine.c_str(), static_cast(fullLine.size()), small_text_format_.Get(), + rect, white_brush_.Get()); + currentY += pathLineHeight; + } +} - // Input field and prompt. - std::wstring exit_prompt = L"Type 'exit', 'save', 'read', 'logs', 'path', 'sound' or 'memory' and press Enter:"; +void ArgumentDebuggerWindow::RenderInputPrompt(const D2D1_SIZE_F& size) +{ + const std::wstring exit_prompt = + L"Type 'exit', 'save', 'read', 'logs', 'path', 'sound' or 'memory' and press Enter:"; D2D1_RECT_F exit_prompt_rect = D2D1::RectF(kMargin, size.height - 100.0f, size.width - kMargin, size.height - 70.0f); d2d_render_target_->DrawText(exit_prompt.c_str(), static_cast(exit_prompt.size()), text_format_.Get(), @@ -956,119 +974,96 @@ void ArgumentDebuggerWindow::RenderFrame() D2D1_RECT_F user_input_rect = D2D1::RectF(kMargin, size.height - 60.0f, size.width - kMargin, size.height - 30.0f); d2d_render_target_->DrawText(user_input_.c_str(), static_cast(user_input_.size()), text_format_.Get(), user_input_rect, green_brush_.Get()); +} - // Draw the QR code in the bottom-left corner. - if (qr_bitmap_) - { - constexpr int qr_size = 375; - constexpr float qr_margin = 60.0f; - float qr_x = qr_margin; - float qr_y = size.height - qr_size - qr_margin - 100.0f - (size.height * 0.2f); // Raised by 20% of screen height - d2d_render_target_->DrawBitmap(qr_bitmap_.Get(), D2D1::RectF(qr_x, qr_y, qr_x + qr_size, qr_y + qr_size)); - } - - // --- Volume Meter (L/R) --- - if (audio_capture_.IsAvailable()) - { - float level = audio_capture_.Level(); // 0..1 - - // Draw two volume bars - one for left, one for right - float bar_w = 30.0f, bar_h = 150.0f; - float spacing = 15.0f; - float total_width = bar_w * 2 + spacing; - float x0 = size.width - kMargin - total_width; - float y0 = size.height - kMargin - bar_h; - - // Device name title with improved layout - const std::wstring& dev_name = audio_capture_.Name(); - std::wstring dev_title = L"Mic: " + (dev_name.empty() ? L"" : dev_name); - - // Position device name at the right edge of the screen - // parameters for the text area - float devAreaWidth = 200.0f; // width of the area - float devAreaHeight = 2 * kLineHeight; // two lines high - float marginRight = kMargin; // margin from right edge of screen - float marginBottom = 5.0f; // margin above bars - - // right edge of the area - just after the screen margin - float devRight = size.width - marginRight; - // left - right minus area width - float devLeft = devRight - devAreaWidth; - // bottom of area - y0 (top of the bars) - // y0 is already defined: y0 = size.height - kMargin - bar_h - // top - bottom minus area height and small margin - float devBottom = y0; - float devTop = devBottom - devAreaHeight - marginBottom; - - D2D1_RECT_F dev_rect = D2D1::RectF(devLeft, devTop, devRight, devBottom); - - // Draw multiline text - d2d_render_target_->DrawText(dev_title.c_str(), (UINT32)dev_title.size(), small_text_format_.Get(), dev_rect, - white_brush_.Get()); - - // Left channel (using the same level for both channels in this demo) - // In a real stereo implementation, you'd capture separate L/R levels - float left_level = level; - float left_filled = bar_h * left_level; - - // Left bar outline - d2d_render_target_->DrawRectangle(D2D1::RectF(x0, y0, x0 + bar_w, y0 + bar_h), white_brush_.Get(), 2.0f); - - // Left bar fill - d2d_render_target_->FillRectangle(D2D1::RectF(x0, y0 + (bar_h - left_filled), x0 + bar_w, y0 + bar_h), - green_brush_.Get()); - - // Right channel (using same level for demo, but with slight variation) - float right_level = level * 0.9f; // Slight variation for demo - float right_filled = bar_h * right_level; - float right_x = x0 + bar_w + spacing; - - // Right bar outline - d2d_render_target_->DrawRectangle(D2D1::RectF(right_x, y0, right_x + bar_w, y0 + bar_h), white_brush_.Get(), - 2.0f); - - // Right bar fill - d2d_render_target_->FillRectangle( - D2D1::RectF(right_x, y0 + (bar_h - right_filled), right_x + bar_w, y0 + bar_h), green_brush_.Get()); - - // Draw L/R labels - std::wstring left_label = L"L"; - std::wstring right_label = L"R"; - - D2D1_RECT_F left_label_rect = D2D1::RectF(x0, y0 - 30.0f, x0 + bar_w, y0); - D2D1_RECT_F right_label_rect = D2D1::RectF(right_x, y0 - 30.0f, right_x + bar_w, y0); +void ArgumentDebuggerWindow::RenderQrBitmap(const D2D1_SIZE_F& size) +{ + if (!qr_bitmap_) + return; + constexpr int qr_size = 375; + constexpr float qr_margin = 60.0f; + const float qr_x = qr_margin; + const float qr_y = size.height - qr_size - qr_margin - 100.0f - (size.height * 0.2f); + d2d_render_target_->DrawBitmap(qr_bitmap_.Get(), D2D1::RectF(qr_x, qr_y, qr_x + qr_size, qr_y + qr_size)); +} - d2d_render_target_->DrawText(left_label.c_str(), (UINT32)left_label.size(), text_format_.Get(), left_label_rect, - yellow_brush_.Get()); - d2d_render_target_->DrawText(right_label.c_str(), (UINT32)right_label.size(), text_format_.Get(), - right_label_rect, yellow_brush_.Get()); - } - else +void ArgumentDebuggerWindow::RenderVolumeMeter(const D2D1_SIZE_F& size) +{ + if (!audio_capture_.IsAvailable()) { std::wstring no_mic = L"No microphone detected"; D2D1_RECT_F r = D2D1::RectF(size.width - 300.f, size.height - 50.f, size.width - kMargin, size.height - kMargin); - d2d_render_target_->DrawText(no_mic.c_str(), (UINT32)no_mic.size(), text_format_.Get(), r, yellow_brush_.Get()); + d2d_render_target_->DrawText(no_mic.c_str(), static_cast(no_mic.size()), text_format_.Get(), r, + yellow_brush_.Get()); + return; } - // End Direct2D drawing with proper error handling - HRESULT hrEnd = d2d_render_target_->EndDraw(); - if (hrEnd == D2DERR_RECREATE_TARGET) + const float level = audio_capture_.Level(); + + constexpr float bar_w = 30.0f; + constexpr float bar_h = 150.0f; + constexpr float spacing = 15.0f; + const float total_width = bar_w * 2 + spacing; + const float x0 = size.width - kMargin - total_width; + const float y0 = size.height - kMargin - bar_h; + + const std::wstring& dev_name = audio_capture_.Name(); + const std::wstring dev_title = L"Mic: " + (dev_name.empty() ? L"" : dev_name); + + constexpr float devAreaWidth = 200.0f; + constexpr float devAreaHeight = 2 * kLineHeight; + constexpr float marginBottom = 5.0f; + const float devRight = size.width - kMargin; + const float devLeft = devRight - devAreaWidth; + const float devBottom = y0; + const float devTop = devBottom - devAreaHeight - marginBottom; + + d2d_render_target_->DrawText(dev_title.c_str(), static_cast(dev_title.size()), small_text_format_.Get(), + D2D1::RectF(devLeft, devTop, devRight, devBottom), white_brush_.Get()); + + // Left channel bar. + const float left_filled = bar_h * level; + d2d_render_target_->DrawRectangle(D2D1::RectF(x0, y0, x0 + bar_w, y0 + bar_h), white_brush_.Get(), 2.0f); + d2d_render_target_->FillRectangle(D2D1::RectF(x0, y0 + (bar_h - left_filled), x0 + bar_w, y0 + bar_h), + green_brush_.Get()); + + // Right channel bar — slight variation for visual distinction from mono. + const float right_level = level * 0.9f; + const float right_filled = bar_h * right_level; + const float right_x = x0 + bar_w + spacing; + d2d_render_target_->DrawRectangle(D2D1::RectF(right_x, y0, right_x + bar_w, y0 + bar_h), white_brush_.Get(), 2.0f); + d2d_render_target_->FillRectangle(D2D1::RectF(right_x, y0 + (bar_h - right_filled), right_x + bar_w, y0 + bar_h), + green_brush_.Get()); + + const std::wstring left_label = L"L"; + const std::wstring right_label = L"R"; + d2d_render_target_->DrawText(left_label.c_str(), static_cast(left_label.size()), text_format_.Get(), + D2D1::RectF(x0, y0 - 30.0f, x0 + bar_w, y0), yellow_brush_.Get()); + d2d_render_target_->DrawText(right_label.c_str(), static_cast(right_label.size()), text_format_.Get(), + D2D1::RectF(right_x, y0 - 30.0f, right_x + bar_w, y0), yellow_brush_.Get()); +} + +bool ArgumentDebuggerWindow::EndOverlay() +{ + HRESULT hr = d2d_render_target_->EndDraw(); + if (hr == D2DERR_RECREATE_TARGET) { - // Device lost, resources need to be recreated Log(L"Device lost detected, recreating D2D resources"); - // Reset brushes and text formats before recreating white_brush_.Reset(); green_brush_.Reset(); yellow_brush_.Reset(); data_text_format_.Reset(); CreateD2DResources(); - return; + return false; } - if (FAILED(hrEnd)) + if (FAILED(hr)) throw std::runtime_error("Failed to end Direct2D draw."); + return true; +} - // Log FPS no more than once every 5 seconds to avoid cluttering the log +void ArgumentDebuggerWindow::PresentFrame() +{ static ULONGLONG lastFpsLogTime = 0; ULONGLONG currentTime = GetTickCount64(); if (currentTime - lastFpsLogTime > 5000) @@ -1077,14 +1072,15 @@ void ArgumentDebuggerWindow::RenderFrame() lastFpsLogTime = currentTime; } - // Present the frame on screen with error checking - // Use no VSync on Wine/Proton for better performance - bool isWine = GetModuleHandleW(L"ntdll.dll") && GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "wine_get_version"); - UINT syncInterval = isWine ? 0 : 1; // 0 = no VSync, 1 = VSync - HRESULT hrPresent = swap_chain_->Present(syncInterval, 0); - if (hrPresent == DXGI_ERROR_DEVICE_REMOVED || hrPresent == DXGI_ERROR_DEVICE_RESET) + // Skip VSync under Wine/Proton — blocking Present on Wine can starve the + // whole message loop, producing reported FPS in the single digits. + HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll"); + const bool isWine = hNtdll && GetProcAddress(hNtdll, "wine_get_version"); + const UINT syncInterval = isWine ? 0 : 1; + + HRESULT hr = swap_chain_->Present(syncInterval, 0); + if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) { - // Device was removed or reset, recreate resources Log(L"Device removed/reset detected, recreating all graphics resources"); CreateDeviceAndSwapChain(static_cast(d2d_render_target_->GetSize().width), static_cast(d2d_render_target_->GetSize().height)); @@ -1093,7 +1089,7 @@ void ArgumentDebuggerWindow::RenderFrame() CreateShadersAndGeometry(); return; } - if (FAILED(hrPresent)) + if (FAILED(hr)) throw std::runtime_error("Failed to present frame."); } @@ -1124,7 +1120,7 @@ void ArgumentDebuggerWindow::Cleanup() d3d_device_.Reset(); text_format_.Reset(); small_text_format_.Reset(); // Release the small text format - data_text_format_.Reset(); // Release the data text format + data_text_format_.Reset(); // Release the data text format dwrite_factory_.Reset(); d2d_render_target_.Reset(); d2d_factory_.Reset(); @@ -1280,28 +1276,30 @@ void ArgumentDebuggerWindow::CalculatePathInfo() cached_path_items_ = path_info::Collect(); } -void ArgumentDebuggerWindow::PlayTelephoneBeeps() // Name kept for compatibility +void ArgumentDebuggerWindow::PlayTelephoneBeeps() // Name kept for compatibility { // Create a separate thread to play beeps so UI doesn't freeze - std::thread beepThread([]() { - // Low-frequency continuous beep pattern - const int beepFrequency = 300; // 300Hz - lower tone - const int beepDuration = 2000; // 2 seconds beep - const int pauseDuration = 2000; // 2 seconds pause - const int totalDuration = 60000; // 1 minute total - - DWORD startTime = GetTickCount(); - - while (GetTickCount() - startTime < totalDuration) + std::thread beepThread( + []() { - // Single long beep - Beep(beepFrequency, beepDuration); - - // Pause - Sleep(pauseDuration); - } - }); - + // Low-frequency continuous beep pattern + const int beepFrequency = 300; // 300Hz - lower tone + const int beepDuration = 2000; // 2 seconds beep + const int pauseDuration = 2000; // 2 seconds pause + const int totalDuration = 60000; // 1 minute total + + DWORD startTime = GetTickCount(); + + while (GetTickCount() - startTime < totalDuration) + { + // Single long beep + Beep(beepFrequency, beepDuration); + + // Pause + Sleep(pauseDuration); + } + }); + // Detach thread so it runs independently beepThread.detach(); } @@ -1321,7 +1319,7 @@ void ArgumentDebuggerWindow::ShowMemoryStats() stats += L"Private Bytes: " + std::to_wstring(pmc.PrivateUsage / 1024 / 1024) + L" MB\n"; stats += L"Virtual Memory: " + std::to_wstring(pmc.PagefileUsage / 1024 / 1024) + L" MB\n"; stats += L"Peak Virtual: " + std::to_wstring(pmc.PeakPagefileUsage / 1024 / 1024) + L" MB\n"; - + // Get system memory info MEMORYSTATUSEX memInfo; memInfo.dwLength = sizeof(MEMORYSTATUSEX); @@ -1332,17 +1330,17 @@ void ArgumentDebuggerWindow::ShowMemoryStats() stats += L"Available RAM: " + std::to_wstring(memInfo.ullAvailPhys / 1024 / 1024) + L" MB\n"; stats += L"Memory Load: " + std::to_wstring(memInfo.dwMemoryLoad) + L"%\n"; } - + // Add runtime information ULONGLONG uptime = GetTickCount64(); stats += L"\n=== RUNTIME ===\n"; stats += L"Uptime: " + std::to_wstring(uptime / 1000) + L" seconds\n"; - + // Store in loaded_data_ to display on screen loaded_data_ = stats; loaded_data_title_ = L"Memory Statistics:"; show_logs_ = true; - + command_status_ = L"Memory statistics displayed."; Log(L"Memory stats: WorkingSet=" + std::to_wstring(pmc.WorkingSetSize / 1024 / 1024) + L"MB"); }