M1: UI-fit instrumentation + headless ui_fit_test + first overflow fixes
Add panel overflow instrumentation (record_panel_fit reading ImGui ScrollMax), a forced layout-reference + debug-aware center split, and host harness commands (uisize/uifit). New headless ui_fit_test drives the real Controllers + Audio panels at reference resolutions with Debug details on and asserts no panel overflows its assigned size; the Audio panel gains a demo mode so its richest content renders without a live mirror. Fixes from the measured overflow: widen the center column (was too narrow -> horizontal overflow), merge the Controllers poll/round-trip tables and fold the trigger line into the slot line, and make the center height split Debug-details-aware (Video's height is mirroring-driven, not debug-driven, so a static split can't serve both modes). Controllers + Audio now fit at 1920x1080 with max info. Video/Injection/Log fit is finalized at the end via the live uifit harness (M1 stays open until then). 15/15 ctest. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -176,6 +176,28 @@ target_link_libraries(opengl_hook_test PRIVATE
|
||||
|
||||
add_test(NAME opengl_hook_test COMMAND opengl_hook_test)
|
||||
|
||||
# Headless UI-fit check (M1): drives the real Controllers + Audio panels at reference
|
||||
# resolutions with Debug details on and the richest content, and asserts no panel overflows
|
||||
# its assigned size. Pure ImGui layout (no GPU / window). The Audio panel renders in its
|
||||
# demo mode so the full content path shows without a live audio device.
|
||||
add_executable(ui_fit_test
|
||||
ui_fit_test.cpp
|
||||
${CMAKE_SOURCE_DIR}/host/src/audio_panel.cpp
|
||||
${CMAKE_SOURCE_DIR}/host/src/controllers_panel.cpp
|
||||
${CMAKE_SOURCE_DIR}/host/src/audio/audio_loopback.cpp
|
||||
${CMAKE_SOURCE_DIR}/host/src/audio/process_loopback_capture.cpp
|
||||
${CMAKE_SOURCE_DIR}/host/src/audio/audio_overrides.cpp
|
||||
${CMAKE_SOURCE_DIR}/host/src/ui/app_chrome.cpp)
|
||||
|
||||
target_include_directories(ui_fit_test PRIVATE ${CMAKE_SOURCE_DIR}/host/src)
|
||||
|
||||
# process_loopback_capture (pulled in via AudioMirror) needs the Win10 20H1 headers.
|
||||
target_compile_definitions(ui_fit_test PRIVATE NTDDI_VERSION=0x0A00000B)
|
||||
|
||||
target_link_libraries(ui_fit_test PRIVATE coop_common imgui ole32 mmdevapi)
|
||||
|
||||
add_test(NAME ui_fit_test COMMAND ui_fit_test)
|
||||
|
||||
# Keep the bin/<config>/ root deployable: stage every test exe under tests/. coop_tone
|
||||
# (the audio_loopback_test fixture) is staged there too, next to its consumer, so the
|
||||
# test's "coop_tone.exe alongside me" lookup keeps working.
|
||||
@@ -193,4 +215,5 @@ coop_output_subdir(tests
|
||||
present_hook_test
|
||||
dx12_present_hook_test
|
||||
opengl_hook_test
|
||||
mock_game_test)
|
||||
mock_game_test
|
||||
ui_fit_test)
|
||||
|
||||
149
tests/ui_fit_test.cpp
Normal file
149
tests/ui_fit_test.cpp
Normal file
@@ -0,0 +1,149 @@
|
||||
// Headless UI-fit check (M1).
|
||||
//
|
||||
// The overlay panels open at fixed sizes that scale with the monitor; with Debug details
|
||||
// on (maximum information) a panel's content can exceed its assigned size and get scrolled
|
||||
// out of view. This test drives the *real* Controllers and Audio panels (the tightest
|
||||
// center-column panels) at reference resolutions with debug details on and the richest
|
||||
// content, and asserts none overflow its assigned size. Pure ImGui layout -- no GPU, no
|
||||
// window -- so it runs anywhere. The Audio panel is driven in its demo mode (synthetic
|
||||
// mirror values) so the full content path renders without a live audio device.
|
||||
//
|
||||
// The Video/Injection/Log panels need a live D3D device / IPC channel and are verified via
|
||||
// the host's debug harness ("uisize"/"uifit") rather than headlessly here.
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
|
||||
#include <windows.h> // must precede mmreg.h (defines FAR/NEAR it relies on)
|
||||
|
||||
#include <mmreg.h> // WAVE_FORMAT_IEEE_FLOAT
|
||||
|
||||
#include "imgui.h"
|
||||
|
||||
#include "audio_panel.hpp"
|
||||
#include "controllers_panel.hpp"
|
||||
#include "input/input_source.hpp"
|
||||
#include "ipc/ipc_server.hpp"
|
||||
#include "ui/app_chrome.hpp"
|
||||
|
||||
using namespace coop;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// Populate the hook back-channel with the worst case for the panels: every pad busy, and
|
||||
// the maximum number of render streams, each low-confidence (the longest provenance label)
|
||||
// at 8ch / 32-bit / float (the widest format string).
|
||||
void fill_max_status(HookStatusView& st)
|
||||
{
|
||||
st.attached = true;
|
||||
st.focus_spoof = true;
|
||||
st.heartbeat = 123456;
|
||||
for (std::uint32_t i = 0; i < kMaxPads; ++i)
|
||||
{
|
||||
st.get_state[i] = 9876543;
|
||||
st.get_caps[i] = 4242;
|
||||
st.rumble_left[i] = 65535;
|
||||
st.rumble_right[i] = 65535;
|
||||
st.read_state[i].connected = 1;
|
||||
st.read_state[i].buttons = 0xFFFF;
|
||||
st.read_state[i].thumb_lx = -12345;
|
||||
st.read_state[i].thumb_ly = 23456;
|
||||
}
|
||||
st.game_pid = 4242;
|
||||
st.game_hwnd = 0x12345678;
|
||||
st.audio_streams_seen = kMaxAudioStreams + 5; // exercises the "(showing first N)" note
|
||||
for (std::uint32_t i = 0; i < kMaxAudioStreams; ++i)
|
||||
{
|
||||
AudioStreamInfo& s = st.audio_streams[i];
|
||||
s.is_primary = (i == 0) ? 1u : 0u;
|
||||
s.sample_rate = 192000;
|
||||
s.channels = 8;
|
||||
s.bits = 32;
|
||||
s.format_tag = WAVE_FORMAT_IEEE_FLOAT;
|
||||
s.frames_rendered = 1234567890123ull;
|
||||
s.format_state = AudioFormat_LowConfidence;
|
||||
}
|
||||
}
|
||||
|
||||
void fill_max_input(InputSnapshot& in)
|
||||
{
|
||||
for (std::uint32_t i = 0; i < kMaxPads; ++i)
|
||||
{
|
||||
in.pads[i].connected = true;
|
||||
in.pads[i].source = "XInput (RPT guest)";
|
||||
in.pads[i].state.connected = 1;
|
||||
in.pads[i].state.buttons = 0xFFFF;
|
||||
in.pads[i].state.thumb_lx = -32768;
|
||||
in.pads[i].state.thumb_ly = 32767;
|
||||
in.pads[i].state.left_trigger = 255;
|
||||
in.pads[i].state.right_trigger = 255;
|
||||
}
|
||||
in.backend = "XInput";
|
||||
}
|
||||
|
||||
// Drive the panels for a few frames at a reference size and report the worst overflow.
|
||||
// ImGui computes a window's ScrollMax in Begin() from the *previous* frame's content size,
|
||||
// so the reading is only stable from the second frame on -- run several.
|
||||
bool run_size(float w, float h, AudioPanel& audio, ControllersPanel& controllers, const HookStatusView& st,
|
||||
const InputSnapshot& in, bool required)
|
||||
{
|
||||
ImGui::GetIO().DisplaySize = ImVec2(w, h);
|
||||
set_layout_reference(w, h);
|
||||
set_layout_debug(true); // we drive the panels with Debug details on
|
||||
for (int frame = 0; frame < 4; ++frame)
|
||||
{
|
||||
ImGui::GetIO().DeltaTime = 1.0f / 60.0f;
|
||||
ImGui::NewFrame();
|
||||
reset_panel_fit();
|
||||
controllers.draw(in, st, /*debug_details=*/true);
|
||||
audio.draw_ui(st, /*debug_details=*/true);
|
||||
ImGui::Render();
|
||||
}
|
||||
float ox = 0.0f, oy = 0.0f;
|
||||
const bool over = panel_fit_overflow(&ox, &oy);
|
||||
char report[256];
|
||||
panel_fit_report(report, sizeof(report));
|
||||
std::printf(" %4.0fx%-4.0f %-9s %-28s (worst %.0f x, %.0f y)%s\n", w, h, over ? "OVERFLOW" : "fit",
|
||||
report, ox, oy, (over && !required) ? " [best-effort]" : "");
|
||||
return required ? !over : true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
{
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.IniFilename = nullptr; // never touch a .ini
|
||||
io.LogFilename = nullptr;
|
||||
unsigned char* px = nullptr;
|
||||
int tw = 0, th = 0;
|
||||
io.Fonts->GetTexDataAsRGBA32(&px, &tw, &th); // build the default font atlas
|
||||
io.Fonts->SetTexID((ImTextureID)1); // non-null so Render() is happy; no real backend
|
||||
|
||||
HookStatusView st{};
|
||||
fill_max_status(st);
|
||||
InputSnapshot in{};
|
||||
fill_max_input(in);
|
||||
|
||||
AudioPanel audio;
|
||||
audio.dev_set_demo(true); // render the richest content with synthetic mirror values
|
||||
ControllersPanel controllers;
|
||||
|
||||
std::printf("ui_fit_test: panels must fit their assigned size at max info (debug details on)\n");
|
||||
bool ok = true;
|
||||
ok &= run_size(1920, 1080, audio, controllers, st, in, /*required=*/true);
|
||||
// Smaller resolutions are reported but not required to pass (the host targets >=1080p).
|
||||
run_size(1600, 900, audio, controllers, st, in, /*required=*/false);
|
||||
run_size(1366, 768, audio, controllers, st, in, /*required=*/false);
|
||||
|
||||
ImGui::DestroyContext();
|
||||
if (!ok)
|
||||
{
|
||||
std::printf("FAIL: a panel overflows its assigned size at 1920x1080 with Debug details on.\n");
|
||||
return 1;
|
||||
}
|
||||
std::printf("PASS: Controllers + Audio fit at 1920x1080.\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user