mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-02 06:40:42 -07:00
cfg: d3d9 standalone configurator (#720)
## Description of change **Configurator (spicecfg.exe)** - Attempts to create a D3D9 device on startup and uses the hardware ImGui DX9 path when successful; falls back to the existing software rasterizer automatically if D3D9 init fails. - Rewrites the configurator window loop: refresh-rate-aware render timer, minimize/resize/`WM_DISPLAYCHANGE` handling, direct Win32 -> ImGui input (keyboard, mouse, wheel), and optimized software painting via `SetDIBitsToDevice` instead of per-frame GDI `HBITMAP` allocation. **Overlay / input (scoped to standalone configurator)** - Skips expensive per-frame rawinput polling in `ImGui_ImplSpice_NewFrame` when `CONFIGURATOR_STANDALONE` is set (Win32 messages drive input instead). - Adds software-renderer idle-frame detection (`sw_pixels_dirty` + draw-data hash) so spicecfg only repaints when pixels actually change. - Adds `ImGuiWindowFlags_NoScrollWithMouse` on the configurator root window to prevent scroll jolt on non-scrollable child widgets.
This commit is contained in:
@@ -327,8 +327,9 @@ void overlay::SpiceOverlay::init() {
|
||||
// disable config
|
||||
io.IniFilename = nullptr;
|
||||
|
||||
// allow CTRL+WHEEL scaling
|
||||
io.FontAllowUserScaling = true;
|
||||
// allow CTRL+WHEEL scaling in the in-game overlay; disable for the standalone
|
||||
// configurator so the mouse wheel always scrolls the configuration window.
|
||||
io.FontAllowUserScaling = !cfg::CONFIGURATOR_STANDALONE;
|
||||
|
||||
// add default font
|
||||
io.Fonts->AddFontDefaultBitmap();
|
||||
@@ -590,6 +591,57 @@ void overlay::SpiceOverlay::new_frame() {
|
||||
ImGui::EndFrame();
|
||||
}
|
||||
|
||||
// FNV-1a 64-bit hash helper used by the software renderer to detect idle frames.
|
||||
static inline uint64_t overlay_fnv1a64(uint64_t h, const void *data, size_t len) {
|
||||
const auto *p = static_cast<const uint8_t *>(data);
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
h ^= p[i];
|
||||
h *= 0x100000001B3ULL;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
// Compute a hash over the visual content of ImDrawData. Only the geometry the
|
||||
// software rasterizer actually consumes (vertex/index buffers + display size)
|
||||
// feeds into the hash, so frames where ImGui produces identical draw data
|
||||
// (i.e. nothing animated this tick) can skip the full pixel rasterization.
|
||||
static uint64_t overlay_hash_draw_data(const ImDrawData *draw_data) {
|
||||
uint64_t h = 0xcbf29ce484222325ULL;
|
||||
if (draw_data == nullptr) {
|
||||
return h;
|
||||
}
|
||||
const float display[4] = {
|
||||
draw_data->DisplayPos.x,
|
||||
draw_data->DisplayPos.y,
|
||||
draw_data->DisplaySize.x,
|
||||
draw_data->DisplaySize.y,
|
||||
};
|
||||
h = overlay_fnv1a64(h, display, sizeof(display));
|
||||
|
||||
const int cmd_lists = draw_data->CmdListsCount;
|
||||
h = overlay_fnv1a64(h, &cmd_lists, sizeof(cmd_lists));
|
||||
|
||||
for (int i = 0; i < draw_data->CmdListsCount; i++) {
|
||||
const ImDrawList *cmd_list = draw_data->CmdLists[i];
|
||||
if (cmd_list == nullptr) {
|
||||
continue;
|
||||
}
|
||||
const int vtx_count = cmd_list->VtxBuffer.Size;
|
||||
const int idx_count = cmd_list->IdxBuffer.Size;
|
||||
h = overlay_fnv1a64(h, &vtx_count, sizeof(vtx_count));
|
||||
h = overlay_fnv1a64(h, &idx_count, sizeof(idx_count));
|
||||
if (vtx_count > 0) {
|
||||
h = overlay_fnv1a64(h, cmd_list->VtxBuffer.Data,
|
||||
static_cast<size_t>(vtx_count) * sizeof(ImDrawVert));
|
||||
}
|
||||
if (idx_count > 0) {
|
||||
h = overlay_fnv1a64(h, cmd_list->IdxBuffer.Data,
|
||||
static_cast<size_t>(idx_count) * sizeof(ImDrawIdx));
|
||||
}
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
void overlay::SpiceOverlay::render() {
|
||||
|
||||
// skip if new_frame() didn't begin a frame this tick
|
||||
@@ -603,7 +655,28 @@ void overlay::SpiceOverlay::render() {
|
||||
// implementation render
|
||||
switch (this->renderer) {
|
||||
case OverlayRenderer::D3D9:
|
||||
ImGui_ImplDX9_RenderDrawData(ImGui::GetDrawData());
|
||||
if (cfg::CONFIGURATOR_STANDALONE) {
|
||||
const auto *draw_data = ImGui::GetDrawData();
|
||||
const uint64_t draw_hash = overlay_hash_draw_data(draw_data);
|
||||
const auto &io = ImGui::GetIO();
|
||||
const int display_w = static_cast<int>(std::ceil(io.DisplaySize.x));
|
||||
const int display_h = static_cast<int>(std::ceil(io.DisplaySize.y));
|
||||
const bool size_matches = (this->d3d9_last_display_w == display_w
|
||||
&& this->d3d9_last_display_h == display_h);
|
||||
if (this->d3d9_has_last_draw_hash
|
||||
&& draw_hash == this->d3d9_last_draw_hash
|
||||
&& size_matches) {
|
||||
this->d3d9_frame_dirty = false;
|
||||
break;
|
||||
}
|
||||
this->d3d9_last_draw_hash = draw_hash;
|
||||
this->d3d9_has_last_draw_hash = true;
|
||||
this->d3d9_last_display_w = display_w;
|
||||
this->d3d9_last_display_h = display_h;
|
||||
this->d3d9_frame_dirty = true;
|
||||
} else {
|
||||
ImGui_ImplDX9_RenderDrawData(ImGui::GetDrawData());
|
||||
}
|
||||
break;
|
||||
#ifdef SPICE_D3D11
|
||||
case OverlayRenderer::D3D11:
|
||||
@@ -619,6 +692,23 @@ void overlay::SpiceOverlay::render() {
|
||||
auto height = static_cast<size_t>(std::ceil(io.DisplaySize.y));
|
||||
auto pixels = width * height;
|
||||
|
||||
// skip the (expensive) full software rasterization when the draw data
|
||||
// is byte-identical to the previous frame and the existing pixel
|
||||
// buffer still matches the current display size.
|
||||
const auto *draw_data = ImGui::GetDrawData();
|
||||
const uint64_t draw_hash = overlay_hash_draw_data(draw_data);
|
||||
const bool size_matches = (this->pixel_data_width == width
|
||||
&& this->pixel_data_height == height
|
||||
&& this->pixel_data.size() >= pixels);
|
||||
if (this->sw_has_last_draw_hash
|
||||
&& draw_hash == this->sw_last_draw_hash
|
||||
&& size_matches) {
|
||||
this->sw_pixels_dirty = false;
|
||||
break;
|
||||
}
|
||||
this->sw_last_draw_hash = draw_hash;
|
||||
this->sw_has_last_draw_hash = true;
|
||||
|
||||
// make sure buffer is big enough
|
||||
if (this->pixel_data.size() < pixels) {
|
||||
this->pixel_data.resize(pixels, 0);
|
||||
@@ -635,6 +725,7 @@ void overlay::SpiceOverlay::render() {
|
||||
imgui_sw::paint_imgui(&this->pixel_data[0], width, height, options);
|
||||
pixel_data_width = width;
|
||||
pixel_data_height = height;
|
||||
this->sw_pixels_dirty = true;
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -647,6 +738,16 @@ void overlay::SpiceOverlay::render() {
|
||||
this->has_pending_frame = false;
|
||||
}
|
||||
|
||||
void overlay::SpiceOverlay::d3d9_render_draw(const bool force_submit) {
|
||||
if (this->renderer != OverlayRenderer::D3D9) {
|
||||
return;
|
||||
}
|
||||
if (!force_submit && !this->d3d9_frame_dirty) {
|
||||
return;
|
||||
}
|
||||
ImGui_ImplDX9_RenderDrawData(ImGui::GetDrawData());
|
||||
}
|
||||
|
||||
void overlay::SpiceOverlay::update() {
|
||||
|
||||
// check overlay toggle
|
||||
@@ -795,6 +896,10 @@ void overlay::SpiceOverlay::reset_invalidate() {
|
||||
}
|
||||
switch (overlay::OVERLAY->renderer) {
|
||||
case OverlayRenderer::D3D9:
|
||||
if (cfg::CONFIGURATOR_STANDALONE) {
|
||||
overlay::OVERLAY->d3d9_has_last_draw_hash = false;
|
||||
overlay::OVERLAY->d3d9_frame_dirty = true;
|
||||
}
|
||||
ImGui_ImplDX9_InvalidateDeviceObjects();
|
||||
break;
|
||||
#ifdef SPICE_D3D11
|
||||
|
||||
Reference in New Issue
Block a user