Skip to content

fix: decode ASTC on the CPU when the GPU lacks support - #16

Merged
4sval merged 4 commits into
openglfrom
fix/astc-cpu-fallback
Nov 7, 2026
Merged

4sval merged 4 commits into
openglfrom
fix/astc-cpu-fallback

Conversation

@NotOfficer

Copy link
Copy Markdown
Member

Mobile games with ASTC textures render black in the 3D viewer on desktop NVIDIA and AMD GPUs. This was reported on Discord for Super String (UE4.25, pakchunk*-Android_ASTC.pak): characters and maps showed as black silhouettes, and the material inspector previews were noise. The game files date from 2022 and the old viewer showed them fine, so this is a regression from the new viewer.

Cause

GetCompressedFormat maps ASTC to GL's CompressedRgbaAstc* formats and uploads the blocks directly. Desktop NVIDIA and AMD drivers don't expose GL_KHR_texture_compression_astc_ldr. On an RTX 3060 Ti (driver 621.01), InternalformatSupported reports 0 for ASTC 6x6, and TextureStorage2D fails with GL_INVALID_ENUM. The texture is left without storage, so every sample is black.

Change

  • ExtensionSupport / DeviceInfo: record whether the driver has GL_KHR_texture_compression_astc_ldr as DeviceInfo.HasAstc. This is read on the GL thread, because Texture2D.Prepare can run on a worker thread with no context.
  • TextureExtensions.IsDecodedOnCpu / DecodeOnCpu: when ASTC isn't supported, Texture2D.Prepare decodes every level it read to RGBA8 with CUE4Parse's ASTCDecoder and uploads it uncompressed. Drivers with ASTC support (Intel, mobile-class GPUs) still get the compressed blocks.
  • ASTC 8x5, 8x6 and 10x8 have no GL mapping in GetCompressedFormat and used to throw. They now always take the CPU path.

Normal maps need no blue-channel restore, because SampleLayerNormal already rebuilds Z from RG.

Validation

  • Ran a headless harness on the RTX 3060 Ti (HasAstc=False) with Super String's paks. It passed four textures from the report (ASTC 6x6 and 8x8, 64² to 1024²) through Texture2D.Prepare/Generate, then read mip 0 back with GetTextureImage. There were no GL errors, the mip chain was complete, and the readback matched CUE4Parse's CPU decode byte for byte.
  • Snooper builds against CUE4Parse master (f7c4cf08).
  • Not checked by eye in the FModel UI. Suggested: load SS/Content/Character/Pc/AikawaArise/001/AikawaArise_001_body_01_A_Ask or SS/Content/BattleMap/BattleMap_Airport/SS_Airport_bg.umap with the GAME_UE4_25 preset on an NVIDIA or AMD GPU.

Tradeoff

ASTCDecoder is the older managed decoder and allocates for each block, so ASTC-heavy maps load more slowly on these GPUs than BC textures do. The AssetRipper decoder that CUE4Parse already references would be faster if load times become a problem.

🤖 Generated by Opus 5.5 with Claude Code in T3 Code

NotOfficer and others added 4 commits November 4, 2026 22:01
Desktop NVIDIA and AMD drivers don't expose
GL_KHR_texture_compression_astc_ldr, so creating storage with an ASTC
internal format fails with GL_INVALID_ENUM. The texture ends up with no
storage and every material using it renders black, which broke mobile
games such as Super String (UE4.25, Android ASTC paks).

ASTC levels are now decoded to RGBA8 before the upload when the driver
has no ASTC support. Drivers with support still receive the blocks
as-is. ASTC 8x5, 8x6 and 10x8 have no GL format mapping here, so they
are always decoded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
These formats had no GL mapping, so they went through the CPU decoder.
For BC6H signed, that path also wrote absolute values into 8 bits,
which dropped the sign and the HDR range. The GL formats for all of
them are core and decode the same data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@4sval
4sval merged commit 11b9deb into opengl Nov 7, 2026
2 checks passed
@4sval
4sval deleted the fix/astc-cpu-fallback branch November 7, 2026 16:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants