openQ4 can use DDS texture-pack replacements without changing the original Quake 4 materials. This includes the stock game's progimg/ image-program results, dhewm3-style dds/ packs, and same-folder replacements used by some Quake 4 packs.
Put your .dds files under a dds/ folder inside baseoq4 (loose, or at the root of a PK4 you drop in baseoq4). Mirror the original asset path underneath it and change the extension:
Original asset: textures/stone/wall01.tga
Replacement: baseoq4/dds/textures/stone/wall01.dds
baseoq4/
dds/
textures/
stone/
wall01.dds
A .dds placed next to the original asset path instead — baseoq4/textures/stone/wall01.dds, the layout some Quake 4 packs use — also works. The dds/ tree is preferred and wins if both exist.
q4base/dds/... is on the search path too, but keep your files in baseoq4 so a Steam file verification does not delete them.
Quake 4 materials rarely name a bump texture directly. They usually write an image program:
bumpmap addnormals( textures/stone/wall01_local, heightmap( textures/stone/wall01_h, 4 ) )
You can replace either end of that:
-
Replace an input. Shipping
dds/textures/stone/wall01_local.ddsreplaces the source texture and openQ4 recomputes the program result on load. This is the simplest option and is what a Quake-4-shaped pack normally does. -
Replace the finished result. Name the file after the whole program using dhewm3's mangling:
heightmap(models/monsters/foo_local, 4) -> dds/heightmap/models/monsters/foo_local 4.ddsTexture-pack tools should preserve that mapping for composed programs such as
heightmap,addnormals,add, andscale.
If you are unsure what a given material image is called, turn on image_showPrecompressedTextures (below) — openQ4 prints every path it looked for.
For each image, openQ4 probes in this order and takes the first usable file:
dds/<image program name>.dds— the dhewm3 naming schemedds/<asset path>.dds— for plain image references<asset path>.dds— beside the original.tga/.jpg, the Quake 4 styleprogimg/...— the stock precomputed results shipped in the retail PK4s
The retail progimg/ tree is probed last, so a user replacement always wins. openQ4 recognizes those retail paths automatically, including nested image programs and plain gfx/env/ cube sides; you do not need to extract, rename, or regenerate the retail files.
Set image_usePrecompressedTextures before loading a map:
0disables automatic DDS replacements entirely, including your own packs.1(default) uses any supported DDS replacement — DXT1, DXT5, RXGB and BC7 — and also reuses the stockprogimg/files shipped in the Quake 4 PK4s. This is the right setting for a BC7 pack.2uses only BC7/BPTC replacements and ignores DXT/RXGB data, including the stockprogimg/tree. Use this only if you specifically want everything else to come from the uncompressed sources.
BC7 requires a renderer with BPTC support. Run gfxInfo and look for the BC7/BPTC capability line if a pack is not being selected.
DXT/RXGB files fall back to CPU decoding when direct texture-compression upload is unavailable. A BC7 file on a renderer without BPTC support is uploaded uncompressed rather than failing the load.
macOS has no BC7. BPTC entered OpenGL in 4.2, and Apple's OpenGL implementation stops at 4.1 — and at 2.1 on the compatibility profile openQ4 actually runs there.
gfxInforeportsBC7/BPTC=0on every Mac, Apple Silicon and Intel alike, and every BC7 file is uploaded uncompressed. This is a platform limit, not a configuration problem: no cvar, driver update orimage_usePrecompressedTexturesvalue changes it. Ship DXT/RXGB alongside BC7 if Mac users matter to your pack.
Supported BC7 files are single 2D textures using either:
- a DX10 header with
DXGI_FORMAT_BC7_UNORMorDXGI_FORMAT_BC7_UNORM_SRGB; or - a common
BC7FourCC variant.
Texture arrays, cube maps, volume textures, invalid dimensions, and truncated declared mip chains are rejected safely. Authored mip levels are uploaded directly. Texture downsizing skips leading authored levels, and images using an unmipped sampler upload only their base level.
Write BC7 normal maps the ordinary way: X, Y, Z in RGB, alpha unused. That is what texconv, NVTT, Compressonator and bc7enc all produce by default, and it is what openQ4 expects.
Changed: earlier openQ4 builds incorrectly forced BC7 bump maps into the DXT5nm/RXGB channel layout, which required encoding with
bc7enc -r2a. A pack authored that way will now render with flat lighting and must be re-encoded without-r2a. Only DXT5 and trueRXGBfiles use the alpha-swizzled layout; that convention exists because DXT5 gives alpha its own higher-precision interpolator, and BC7 has no such limitation.
Use these commands when checking a pack:
image_usePrecompressedTextures 1
image_showPrecompressedTextures 1
reloadImages all
The log then prints the DDS path selected for each replacement, and — for every image where nothing was selected — the full list of candidate paths it tried with the reason each one was rejected. That list is the fastest way to find out what a file should be named:
no DDS replacement for 'textures/stone/wall01':
dds/textures/stone/wall01.dds [dds/ (dhewm3 naming)] no such file
textures/stone/wall01.dds [alongside the original asset (Quake 4 style)] no such file
listImages identifies loaded BC7 images, while imageDDSSelfTest validates the built-in retail/dhewm3 naming conversions plus RXGB and BC7 header checks.
During development, an automatic DDS replacement older than its original source is ignored so stale generated data cannot hide a newer texture edit. The stock progimg/ archives receive a four-second timestamp-granularity allowance; a genuinely newer loose source still wins. Rebuild or retimestamp a custom DDS after changing its source image. Note that files inside a PK4 report no timestamp at all, so a packed replacement is never treated as stale — if you are authoring loose files alongside extracted .tga sources, the extracted sources will look newer and your .dds will be skipped.