TIP 754: Cache Composed Pixmaps for Ttk Image Elements

Login
Author:         Mason McParlane <[email protected]>
State:          Withdrawn
Type:           Project
Vote:           Pending
Created:        2026-05-05
Tcl-Version:    9.0.5
Tk-branch:      tip-754-ttk-composed-pixmaps-cache
Forum:          https://core.tcl-lang.org/tips/forumpost/07974cca59

Abstract

Custom Ttk scrollbar elements created with ttk::style element create ... image exhibit severe performance degradation on Windows due to per-tile GDI object creation and destruction in the image rendering pipeline. This TIP adds a per-element rendered-pixmap cache to ImageElementDraw so that repeated draws at the same size and state blit a single pre-composed bitmap instead of tiling via hundreds of individual Tk_RedrawImage calls. It also fixes an off-by-one error in the Ttk_Fill tiling loop.

All changes are confined to generic/ttk/ttkImage.c.

Rationale

The Ttk_Fill function tiles a source image region into a destination region by calling Tk_RedrawImage once per tile. For a scrollbar trough element (e.g., 14px wide, 400px tall) using a small source image with -border 9-slice settings, the center fill region may produce 200+ individual Tk_RedrawImage calls for a single element draw.

On Windows, each Tk_RedrawImage call reaches TkPutImage (win/tkWinDraw.c) which creates a memory DC and a DIB bitmap, performs a BitBlt, then destroys both objects. For images with semi-transparent pixels (the COMPLEX_ALPHA path), there is an additional XGetImage read-back and software alpha blend per tile.

During scrolling or window resizing with a custom image-based theme, this produces 6,000-12,000 GDI create/destroy cycles per second, causing visible UI lag and high CPU usage. The same elements render without issue on macOS, which uses hardware-accelerated Core Graphics compositing and avoids per-tile object allocation.

Built-in themes like clam are unaffected because they draw scrollbar elements with direct GDI primitives (a single XFillRectangle for the trough), totaling roughly 15-20 lightweight calls per frame versus hundreds of expensive ones.

Specification

Per-element pixmap cache

Each ImageData instance (one per registered image element) gains a single-entry cache holding a pre-composed Pixmap of the fully-tiled element. The cache is keyed by (width, height, x, y, state).

Position (x, y) is included in the key because the cache pixmap is seeded with the destination background content before tiling. This ensures correct alpha compositing for images with semi-transparent pixels — without the background seed, transparent areas would composite against an uninitialized (black) pixmap. If the element's position changes, the underlying background may differ, so the cache must be invalidated.

On each ImageElementDraw call:

  1. If the requested dimensions, position, and state match the cache, XCopyArea the cached pixmap to the destination drawable in one call and return.

  2. Otherwise, allocate a new pixmap and copy the current destination background into it via XCopyArea. Then render the tiled image over this background copy via Ttk_Tile. Store the result as the cache entry (freeing any prior cached pixmap), then XCopyArea the composited result to the destination.

The cache is invalidated (pixmap freed, dimensions zeroed) when:

If the window is not yet realized (Tk_WindowId(tkwin) == None), caching is skipped and the element is drawn directly via Ttk_Tile (preserving existing behavior).

Off-by-one fix in Ttk_Fill

The tiling loop at line 231 uses y <= db where db = dst.y + dst.height (one past the last row). This causes one extra iteration per column where the computed tile height is zero, resulting in a wasted zero-height Tk_RedrawImage call. The fix changes <= to <, consistent with the x loop on the preceding line.

Properties

Reference Implementation

Implementation is in branch tip-754-ttk-composed-pixmaps-cache. See the performance test script for reproducing the issue.

Tested with Tcl/Tk 9.0.3 on Windows using custom image-based scrollbar themes. Performance improvement is on the order of 10-50x reduction in GDI object operations during scrolling and resize.

Copyright

This document has been placed in the public domain.