Skip to content

ImGui service and samples for SimCity 4 DLL plugins using the gzcom-dll SDK and the community ImGui backend for DirectX 7.

License

Notifications You must be signed in to change notification settings

caspervg/sc4-imgui-service

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

35 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

sc4-imgui-service

ImGui service and samples for SimCity 4 DLL plugins using the gzcom-dll SDK.

Screenshot

Build

Prereqs:

  • CMake 4.0+
  • Visual Studio (Win32 toolchain)

Steps (from repo root):

cmake -S . -B cmake-build-debug-visual-studio -G "Visual Studio 17 2022"
cmake --build cmake-build-debug-visual-studio --config Debug

Outputs

Required:

  • imgui.dll (deployed to ...\SimCity 4 Deluxe Edition\Apps)
  • SC4ImGuiService.dll (deployed to ...\Documents\SimCity 4\Plugins\)

Optional samples (deployed to ...\Documents\SimCity 4\Plugins\):

  • SC4ImGuiSample.dll (basic panel)
  • SC4ImGuiSampleCity.dll (city-view only)
  • SC4ImGuiSampleDemo.dll (ImGui demo window)
  • SC4ImGuiTextureSample.dll (texture management example)

Usage

  • Link your DLL against imgui.dll and include vendor/d3d7imgui/ImGui/imgui.h.
  • Query the service via cIGZFrameWork::GetSystemService with kImGuiServiceID and GZIID_cIGZImGuiService from src/public/ImGuiServiceIds.h.
  • Register a panel with ImGuiPanelDesc and render using ImGui::*.
  • The service owns the ImGui context; do not call ImGui::CreateContext() or ImGui::DestroyContext() in clients.

Public API

Client-facing headers live under src/public:

  • src/public/cIGZImGuiService.h (service interface + ImGuiPanelDesc)
  • src/public/ImGuiServiceIds.h (service IDs and API version)
  • src/public/ImGuiPanel.h and src/public/ImGuiPanelAdapter.h (optional class-style adapter)

Class-based usage (optional):

class MyPanel final : public ImGuiPanel {
public:
    void OnInit() override {}
    void OnRender() override {
        ImGui::Begin("Class Panel");
        ImGui::TextUnformatted("Hello from a class-based panel.");
        ImGui::End();
    }
};

void RegisterPanel(cIGZFrameWork* fw) {
    cIGZImGuiService* service = nullptr;
    if (!fw->GetSystemService(kImGuiServiceID, GZIID_cIGZImGuiService,
                              reinterpret_cast<void**>(&service))) {
        return;
    }

    auto* panel = new MyPanel();
    ImGuiPanelDesc desc = ImGuiPanelAdapter<MyPanel>::MakeDesc(panel, 0x12345678, 100, true);
    if (!service->RegisterPanel(desc)) {
        delete panel;
    }
    service->Release();
}

Example (minimal):

static void RenderPanel(void* data) {
    ImGui::Begin("My Panel");
    ImGui::TextUnformatted("Hello from a client DLL.");
    ImGui::End();
}

void RegisterPanel(cIGZFrameWork* fw) {
    cIGZImGuiService* service = nullptr;
    if (!fw->GetSystemService(kImGuiServiceID, GZIID_cIGZImGuiService,
                              reinterpret_cast<void**>(&service))) {
        return;
    }

    ImGuiPanelDesc desc{};
    desc.id = 0x12345678;
    desc.order = 100;
    desc.visible = true;
    desc.on_render = &RenderPanel;
    desc.on_shutdown = nullptr;
    desc.data = nullptr;
    service->RegisterPanel(desc);
    service->Release();
}

Safe Texture Management

The service provides a comprehensive texture management API that handles device loss, memory management, and automatic recreation. This is the recommended way to create textures for use with ImGui::Image().

Features

  • Automatic device loss handling: Textures survive Alt+Tab and resolution changes
  • Generation tracking: Old texture handles are automatically invalidated after device reset
  • Memory safety: Source pixel data is stored for automatic recreation
  • Fallback support: Automatically falls back to system memory if video memory is exhausted
  • RAII wrapper: Optional ImGuiTexture class for automatic lifetime management

Basic Usage (RAII Wrapper - Recommended)

#include "public/ImGuiTexture.h"

class MyPanel {
    cIGZImGuiService* service_;
    ImGuiTexture myTexture_;
    
    void OnInit() {
        // Generate RGBA32 pixel data (4 bytes per pixel)
        std::vector<uint8_t> pixels(128 * 128 * 4);
        // ... fill pixel data ...
        
        // Create texture - automatically manages lifetime
        if (!myTexture_.Create(service_, 128, 128, pixels.data())) {
            // Handle error
        }
    }
    
    void OnRender() {
        // Get texture ID - returns nullptr if device was lost
        void* texId = myTexture_.GetID();
        if (texId) {
            ImGui::Image(texId, ImVec2(128, 128));
        } else {
            ImGui::TextUnformatted("Texture unavailable");
        }
    }
    
    // Texture is automatically released in destructor
};

Manual API Usage

#include "public/cIGZImGuiService.h"

// Create texture descriptor
ImGuiTextureDesc desc{};
desc.width = 128;
desc.height = 128;
desc.pixels = myRGBA32Data;  // Must remain valid during call
desc.useSystemMemory = false;  // Prefer video memory

// Create texture - service stores pixel data internally
ImGuiTextureHandle handle = service->CreateTexture(desc);
if (handle.id == 0) {
    // Handle error
}

// In render loop:
void* texId = service->GetTextureID(handle);
if (texId) {
    ImGui::Image(texId, ImVec2(128, 128));
}

// When done:
service->ReleaseTexture(handle);

Device Generation Pattern

When the DirectX device is reset (Alt+Tab, resolution change), the device generation increments. Old texture handles become invalid:

void OnRender() {
    static uint32_t lastGen = 0;
    uint32_t currentGen = service->GetDeviceGeneration();
    
    if (currentGen != lastGen) {
        // Device was reset - recreate textures
        RecreateMyTextures();
        lastGen = currentGen;
    }
    
    // Use textures normally...
}

The ImGuiTexture RAII wrapper handles this automatically by returning nullptr from GetID() when generation changes.

Safety Notes

  • Always check return values: GetTextureID() can return nullptr if device is lost
  • Store source data: The service keeps a copy, but you may want to keep your own for modification
  • RGBA32 format: Pixel data must be in RGBA32 format (4 bytes per pixel: R, G, B, A)
  • Thread safety: Not thread-safe - call from render thread only
  • Lifetime: Release textures before unregistering panels or shutting down
  • Generation mismatch: Handles from old device generations return nullptr from GetTextureID()

Performance Considerations

  • Video memory preferred: Set useSystemMemory = false for better performance (default)
  • Automatic fallback: Service falls back to system memory if video memory is exhausted
  • On-demand recreation: Surfaces are recreated lazily when first accessed after device loss
  • Minimal overhead: Device loss detection uses existing cooperative level checks

Error Handling

The API is designed to fail gracefully:

// Creation can fail - check handle
ImGuiTextureHandle handle = service->CreateTexture(desc);
if (handle.id == 0) {
    LOG_ERROR("Texture creation failed");
    return;
}

// GetTextureID returns nullptr on failure
void* texId = service->GetTextureID(handle);
if (!texId) {
    // Device lost, generation mismatch, or surface recreation failed
    // Check IsTextureValid() to distinguish:
    if (!service->IsTextureValid(handle)) {
        // Handle is stale (generation mismatch)
        // Need to create new texture
    }
}

See src/sample/ImGuiTextureSample.cpp for a complete working example.

DX7 interface access

Note: For texture creation, prefer using the Safe Texture Management API instead of manually managing DirectDraw surfaces. The texture API handles device loss automatically.

If you need DirectX 7 interfaces for advanced use cases, use cIGZImGuiService::AcquireD3DInterfaces. The service AddRef()s both interfaces; callers must Release() them when done. Prefer acquiring per operation/frame rather than caching across frames. Use IsDeviceReady() and GetDeviceGeneration() to detect when cached resources must be recreated.

Example:

IDirect3DDevice7* d3d = nullptr;
IDirectDraw7* dd = nullptr;
if (service->AcquireD3DInterfaces(&d3d, &dd)) {
    // use d3d/dd
    d3d->Release();
    dd->Release();
}

Developer notes

  • The service is a cIGZSystemService registered on OnStart, and it is added to the tick list via cIGZFrameWork::AddToTick. Rendering is driven from the DX7 EndScene hook once the D3D interface is available. For more info on Services, see their gzcom-dll Wiki entry.
  • Panels are registered with a unique id and a simple on_render(void* data) callback. Use the data pointer for per-panel state instead of globals.
  • The ImGui context is owned by the service. Clients should not call ImGui::CreateContext() or ImGui::DestroyContext(). If you need to check readiness, call cIGZImGuiService::GetContext().
  • Use the visible flag in ImGuiPanelDesc to control initial visibility.

Notes

  • imgui.dll must live in the SC4 Apps folder to be loaded as a dependency.
  • The service owns the ImGui context and backend initialization. Clients do not need to call ImGui::CreateContext() or ImGui::DestroyContext() or worry about hooking into the DirectX7 etc.

About

ImGui service and samples for SimCity 4 DLL plugins using the gzcom-dll SDK and the community ImGui backend for DirectX 7.

Resources

License

Stars

Watchers

Forks

Packages

No packages published