cakepie

Textures in KSP/Unity, or how to not end up with a blurry mess in your UI

Recommended Posts

Disclaimer: I am not an expert on this, the following is just what I have discovered.
Some of this information is deduced based on empirical observation of KSP / Unity behavior.

There's only fragmented info out there re: how to set up UI textures. Hopefully by organizing this information together will help put an end to blurry fuzzy UI woes and nasty hacky workarounds.

Plugin authors:
This will hopefully explain why your buttons/icons/etc are a horrible mess and what you should do to avoid it.

Part modders:
This does not really affect textures you use in models, but may be informative.
(Side note: if you aren't already using DDS with mipmaps, you should be doing so.)

Players:
This is probably too technical. If you want to fix blurry UI in mods that you are using, see instructions here.


So I finally needed to make a mod with toolbar button and ran into the issue where the icon gets all blurry when graphics settings are at less than full resolution textures.

Within the modding community I found that:
- some modders not aware or haven't address the issue (e.g. low priority, haven't figured out what to do)
- blame placed on Unity and it's texture compression (loosely true but not strictly correct)
- some workarounds involving DIY reading texture directly from file, ignoring the version in GameDatabase --- file i/o overhead, yucks
- some workarounds using textures that are larger than they need to be

The crux of the problem isn't actually compression per se, it's mipmaps.
If you don't know what they are, you might want to google and read up more, but the short explanation is: mipmaps are just a bunch of smaller copies of the texture which can be used on-the-fly depending on the size required, rather than having to do expensive calculations to get a scaled version from the original at runtime. This is great for model textures, so if you're looking at a capsule in game at close range it would be textured using a high res (or fullres) version of the texture but if it is zoomed out and far away then a lower res mipmap can be used. (See explanation by HebaruSan below.)

Depending on what format texture files you're using and how they get loaded, they may already contain mipmaps in the file, or mipmaps may be generated for them during loading.

And the problem is, if you have small textures for UI purposes e.g. icons, buttons, you've probably already made it at appropriate size and want it to be used at crisp full res all the time. You do not want any of that fancy mipmap stuff.

When a texture has mipmaps, and the Texture Resolution option KSP's graphics settings are set to anything less than full resolution, then the following happens:
- At half res, only mipmap level 1 (halved width and height) and smaller is uploaded to GPU  <-- "factory default"
- At quarter res, only mipmap level 2 onwards is uploaded
- At eighth res, only level 3 onwards is available
This means that your appropriately-sized, original full res version of the texture (mipmap level 0) simply gets thrown away, so when your UI element is displayed it is forced to use a too-small version of the texture and scale it up.


What KSP / Unity does when loading textures

Textures are loaded from file into Unity Texture2D object.
All of the textures are kept in the GameDatabase along with some metadata in the form of GameDatabase.TextureInfo object.

TextureInfo attributes:
 

name:
This is the "url" used to lookup a texture when you call GameDatabase.Instance.GetTexture()
basically the path of the file relative to GameData folder, minus file extension

file:
Internal UrlDir.UrlFile format for storing path information

texture:
The Texture2D object with the texture in it

isNormal:
whether the texture is a normal map.
Note that this can change at runtime. So if you have a texture that isn't a normal map, and then call GetTexture() with asNormalMap true it will (try to) convert the existing texture to normal map, and isNormal flag will be changed to reflect this

isReadable:
a Texture2D can be set to be "no longer readable" which according to Unity documentation means "memory will be freed after uploading to GPU" and texture cannot be manipulated (e.g. edit the pixels) from CPU side.
this flag is supposed to reflect that.

isCompressed:
whether the texture has been compressed during the loading process.
Unity documentation: "Compressed textures use less graphics memory and are faster to render. After compression, texture will be in DXT1 format if the original texture had no alpha channel, and in DXT5 format if it had alpha channel."
This flag may be incorrect, it appears to be set as long as there was an attempt to compress the texture with Texture2D.Compress(). But that process can fail, and is usually seen in KSP.log when it complains such as "Texture resolution is not valid for compression: <filename> - consider changing the image's width and height to enable compression"

This gives us some insights into the texture loading process.

What KSP does when loading each texture depends on the file format, but the general steps include:
- read the texture data from file
- convert image format (if needed)
- (optional) try to compress to DXT
- (optional) generate mipmaps
- upload to GPU -- behavior depends on Texture Resolution setting, more on this later
- (optional) make texture no longer readable (discard from RAM)

We can learn more about how different texture file types are handled by observing what happens to them.
Below is a partial list of textures info dumped from a stock 1.7.0 install just after GameDatabase finished loading in LOADING scene. I've trimmed it down from the full set.
First three letters NRC reflect the three boolean flags. The fourth C shows whether the texture itself is actually DXT format.
This is followed by image dimensions, mipmapCount (1 if none) and TextureFormat, then the name of the texture.
The source code that dumped this info can be found here, it is part of the unBlur mod.

Spoiler

  CC  256x160  1  DXT5            Squad/Agencies/C7AerospaceDivision
  --   64x40   1  ARGB32          Squad/Agencies/C7AerospaceDivision_scaled
  CC  256x160  1  DXT5            Squad/Agencies/DinkelsteinKermansConstructionEmporium
  --   64x40   1  ARGB32          Squad/Agencies/DinkelsteinKermansConstructionEmporium_scaled
  CC  256x160  1  DXT5            Squad/Agencies/ExperimentalEngineering
  --   64x40   1  ARGB32          Squad/Agencies/ExperimentalEngineering_scaled
  CC  256x160  1  DXT5            Squad/Agencies/FlooydResearchLab
  --   64x40   1  ARGB32          Squad/Agencies/FlooydResearchLab_scaled
  CC  256x160  1  DXT5            Squad/Agencies/GoliathNationalProducts
  --   64x40   1  ARGB32          Squad/Agencies/GoliathNationalProducts_scaled
  CC  256x160  1  DXT5            Squad/Agencies/IntegratedIntegrals
  --   64x40   1  ARGB32          Squad/Agencies/IntegratedIntegrals_scaled
  CC  256x160  1  DXT1            Squad/Agencies/IonicSymphonicProtonicElectronics
  --   64x40   1  ARGB32          Squad/Agencies/IonicSymphonicProtonicElectronics_scaled
  CC  256x160  1  DXT5            Squad/Agencies/JebsJunkyard
  --   64x40   1  ARGB32          Squad/Agencies/JebsJunkyard_scaled
  CC  256x160  1  DXT5            Squad/Agencies/KerbalMotion
  --   64x40   1  ARGB32          Squad/Agencies/KerbalMotion_scaled
  CC  256x160  1  DXT5            Squad/Agencies/KerbinWorldFirstRecordKeepingSociety
  --   64x40   1  ARGB32          Squad/Agencies/KerbinWorldFirstRecordKeepingSociety_scaled
  CC  256x160  1  DXT5            Squad/Agencies/Kerbodyne
  --   64x40   1  ARGB32          Squad/Agencies/Kerbodyne_scaled
  CC  256x160  1  DXT1            Squad/Agencies/Kerlington
  --   64x40   1  ARGB32          Squad/Agencies/Kerlington_scaled
  CC  256x160  1  DXT5            Squad/Agencies/MaxoConstructionToys
  --   64x40   1  ARGB32          Squad/Agencies/MaxoConstructionToys_scaled
  CC  256x160  1  DXT5            Squad/Agencies/MovingPartsExpertsGroup
  --   64x40   1  ARGB32          Squad/Agencies/MovingPartsExpertsGroup_scaled
  CC  256x160  1  DXT5            Squad/Agencies/OMBDemolition
  --   64x40   1  ARGB32          Squad/Agencies/OMBDemolition_scaled
  CC  256x160  1  DXT5            Squad/Agencies/PeriapsisCo
  --   64x40   1  ARGB32          Squad/Agencies/PeriapsisCo_scaled
  CC  256x160  1  DXT1            Squad/Agencies/Probodobodyne
  --   64x40   1  ARGB32          Squad/Agencies/Probodobodyne_scaled
  CC  256x160  1  DXT5            Squad/Agencies/R&D
  --   64x40   1  ARGB32          Squad/Agencies/R&D_scaled
  CC  256x160  1  DXT5            Squad/Agencies/ReactionSystemsLtd
  --   64x40   1  ARGB32          Squad/Agencies/ReactionSystemsLtd_scaled
  CC  256x160  1  DXT5            Squad/Agencies/Rockomax
  --   64x40   1  ARGB32          Squad/Agencies/Rockomax_scaled
  CC  256x160  1  DXT5            Squad/Agencies/Rokea
  --   64x40   1  ARGB32          Squad/Agencies/Rokea_scaled
  CC  256x160  1  DXT5            Squad/Agencies/SeansCannery
  --   64x40   1  ARGB32          Squad/Agencies/SeansCannery_scaled
  CC  256x160  1  DXT5            Squad/Agencies/SteadlerEngineeringCorps
  --   64x40   1  ARGB32          Squad/Agencies/SteadlerEngineeringCorps_scaled
  CC  256x160  1  DXT5            Squad/Agencies/StrutCo
  --   64x40   1  ARGB32          Squad/Agencies/StrutCo_scaled
  CC  256x160  1  DXT5            Squad/Agencies/Vac-Co
  --   64x40   1  ARGB32          Squad/Agencies/Vac-Co_scaled
  CC  256x160  1  DXT5            Squad/Agencies/WinterOwl
  --   64x40   1  ARGB32          Squad/Agencies/WinterOwl_scaled
  CC  256x160  1  DXT5            Squad/Agencies/ZaltonicElectronics
  --   64x40   1  ARGB32          Squad/Agencies/ZaltonicElectronics_scaled
  CC   32x32   1  DXT5            Squad/Contracts/Icons/balloon
  CC   32x32   1  DXT5            Squad/Contracts/Icons/custom
  CC   32x32   1  DXT5            Squad/Contracts/Icons/default
  CC   32x32   1  DXT5            Squad/Contracts/Icons/dish
  CC   32x32   1  DXT5            Squad/Contracts/Icons/eva
  CC   32x32   1  DXT5            Squad/Contracts/Icons/gravity
 RCC   32x32   1  DXT5            Squad/Contracts/Icons/ksc
  CC   32x32   6  DXT5            Squad/Contracts/Icons/launchsite
  CC   64x96   1  DXT5            Squad/Contracts/Icons/marker
  CC   32x32   1  DXT5            Squad/Contracts/Icons/pressure
  CC   32x32   1  DXT5            Squad/Contracts/Icons/report
  CC   32x32   6  DXT5            Squad/Contracts/Icons/runway
  CC   32x32   1  DXT5            Squad/Contracts/Icons/sample
  CC   32x32   1  DXT5            Squad/Contracts/Icons/seismic
  CC   32x32   1  DXT5            Squad/Contracts/Icons/thermometer
  CC   32x32   1  DXT5            Squad/Contracts/Icons/vessel
 RCC  256x160  1  DXT5            Squad/Flags/09
 RCC  512x320  1  DXT5            Squad/Flags/B612_Foundation_flag
 RCC  256x160  1  DXT5            Squad/Flags/blorbs
 RCC  256x160  1  DXT5            Squad/Flags/bullseye
 RCC  256x160  1  DXT5            Squad/Flags/capsule
 RCC  256x160  1  DXT5            Squad/Flags/circles
 RCC  256x160  1  DXT5            Squad/Flags/default
 RCC  256x160  1  DXT5            Squad/Flags/esa_dark_blue
 RCC  256x160  1  DXT5            Squad/Flags/hexagon
 RCC  256x160  1  DXT5            Squad/Flags/hexagonCircles
 RCC  256x160  1  DXT5            Squad/Flags/kerbal1
 RCC  256x160  1  DXT5            Squad/Flags/kerbal2
 RCC  256x160  1  DXT5            Squad/Flags/kerbin
 RCC  256x160  1  DXT5            Squad/Flags/kerbinmunflag
 RCC  256x160  1  DXT5            Squad/Flags/line
 RCC  256x160  1  DXT5            Squad/Flags/minimalistic
 RCC  256x160  1  DXT5            Squad/Flags/NASA
 RCC  256x160  1  DXT5            Squad/Flags/orbit
 RCC  256x160  1  DXT5            Squad/Flags/orbs
 RCC  256x160  1  DXT5            Squad/Flags/retro
 RCC  256x160  1  DXT5            Squad/Flags/rings
 RCC  256x160  1  DXT5            Squad/Flags/rocketScience
 RCC  256x160  1  DXT5            Squad/Flags/satellite
 RCC  512x320  1  DXT5            Squad/Flags/Sentinel_Flag
 RCC  256x160  1  DXT5            Squad/Flags/spheres
 RCC  256x160  1  DXT5            Squad/Flags/squadLogo
 RCC  256x160  1  DXT5            Squad/Flags/squadLogo2
 RCC  256x160  1  DXT5            Squad/Flags/stripes
 RCC  256x160  1  DXT5            Squad/Flags/trees
 RCC  256x160  1  DXT5            Squad/Flags/trippy
 RCC  256x160  1  DXT5            Squad/Flags/uk_space_agency
  CC   64x64   7  DXT1            Squad/FX/DiamondBlue
  CC   64x64   7  DXT1            Squad/FX/FlameBlueOrange
  CC   64x64   7  DXT1            Squad/FX/FlamePurple
  CC   32x32   6  DXT1            Squad/FX/FlameRed
  CC   64x64   7  DXT1            Squad/FX/FlameRedOrange
  CC   64x64   7  DXT1            Squad/FX/Monoprop
  CC   64x64   7  DXT1            Squad/FX/plasma2
  CC  256x256  9  DXT1            Squad/FX/rocketplume2
  CC  256x256  9  DXT1            Squad/FX/shockDiamond2
  CC   64x64   7  DXT1            Squad/FX/smokepuff1
  CC 2560x1600 12 DXT5            Squad/Interiors/Administration/AdminBuilding_Lvl1
  CC 2560x1600 12 DXT5            Squad/Interiors/Administration/AdminBuilding_Lvl2
  CC 2560x1600 12 DXT5            Squad/Interiors/Administration/AdminBuilding_Lvl3
  CC 1024x1024 11 DXT5            Squad/MenuProps/MunOrBust
 RCC   32x32   1  DXT5            Squad/PartList/SimpleIcons/cs_main
 RCC   32x32   1  DXT5            Squad/PartList/SimpleIcons/cs_mk2
 RCC   32x32   1  DXT5            Squad/PartList/SimpleIcons/cs_mk3
 RCC   32x32   1  DXT5            Squad/PartList/SimpleIcons/cs_size0
 RCC   32x32   1  DXT5            Squad/PartList/SimpleIcons/cs_size1
 RCC   32x32   1  DXT5            Squad/PartList/SimpleIcons/cs_size1p5
 RCC   32x32   1  DXT5            Squad/PartList/SimpleIcons/cs_size2
 RCC   32x32   1  DXT5            Squad/PartList/SimpleIcons/cs_size3
 RCC   32x32   1  DXT5            Squad/PartList/SimpleIcons/cs_size4
 RCC   32x32   1  DXT5            Squad/PartList/SimpleIcons/cs_surface
 RCC   32x32   1  DXT5            Squad/PartList/SimpleIcons/fuels_monopropellant
 RCC   32x32   1  DXT5            Squad/PartList/SimpleIcons/fuels_ore
 RCC   32x32   1  DXT5            Squad/PartList/SimpleIcons/fuels_oxidizer
 RCC   32x32   1  DXT5            Squad/PartList/SimpleIcons/fuels_solidfuel
 RCC   32x32   1  DXT5            Squad/PartList/SimpleIcons/fuels_xenongas
 RCC   32x32   1  DXT5            Squad/PartList/SimpleIcons/number1
 RCC   32x32   1  DXT5            Squad/PartList/SimpleIcons/number2
 RCC   32x32   1  DXT5            Squad/PartList/SimpleIcons/number3
 RCC   32x32   1  DXT5            Squad/PartList/SimpleIcons/number4
 RCC   32x32   1  DXT5            Squad/PartList/SimpleIcons/number5
 RCC   32x32   1  DXT5            Squad/PartList/SimpleIcons/number6
 RCC   32x32   1  DXT5            Squad/PartList/SimpleIcons/number7
 RCC   32x32   1  DXT5            Squad/PartList/SimpleIcons/number8
 RCC   32x32   1  DXT5            Squad/PartList/SimpleIcons/number9
...
 RCC   32x32   1  DXT5            Squad/PartList/SimpleIcons/RDicon_aerospaceTech2
 RCC   32x32   1  DXT5            Squad/PartList/SimpleIcons/RDicon_commandmodules
 RCC   32x32   1  DXT5            Squad/PartList/SimpleIcons/RDicon_fuelSystems-advanced
 RCC   32x32   1  DXT5            Squad/PartList/SimpleIcons/RDicon_fuelSystems-highPerformance
 RCC   32x32   1  DXT5            Squad/PartList/SimpleIcons/RDicon_largeVolumeContainment
 RCC   32x32   1  DXT5            Squad/PartList/SimpleIcons/RDicon_miniaturization
 RCC   32x32   1  DXT5            Squad/PartList/SimpleIcons/RDicon_propulsion-precision
 RCC   32x32   1  DXT5            Squad/PartList/SimpleIcons/RDicon_propulsionSystems
 RCC   32x32   1  DXT5            Squad/PartList/SimpleIcons/RDicon_telescope
  CC    4x4    1  DXT5            Squad/Parts/Aero/aerodynamicNoseCone/Rockomax_Adapters_diffuse
  CC    4x4    1  DXT5            Squad/Parts/Aero/aerodynamicNoseCone/Rockomax_Adapters_normal
  CC  256x512  10 DXT5            Squad/Parts/Aero/airbrake/Airbrake
  CC  512x512  10 DXT5            Squad/Parts/Aero/airIntakeRadialXM-G50/RadialIntake
  CC 2048x2048 12 DXT5            Squad/Parts/Aero/airlinerWings/AirlinerWings
  CC 1024x1024 11 DXT5            Squad/Parts/Aero/airplaneFins/AirplaneFins
  CC  128x256  9  DXT5            Squad/Parts/Aero/basicFin/BasicFin
  CC  256x256  9  DXT5            Squad/Parts/Aero/circularIntake/CircluarIntakes
  CC  256x256  9  DXT1            Squad/Parts/Aero/circularIntake/CircluarIntakes_Heat
...
  CC  512x512  10 DXT1            Squad/Parts/Aero/fairings/AutoTruss
  CC  256x256  9  DXT5            Squad/Parts/Aero/fairings/FairingBase
  CC  512x512  1  DXT5            Squad/Parts/Aero/fairings/FairingBaseNormals
  CC  512x512  1  DXT5            Squad/Parts/Aero/fairings/fairingsCap
  CC 1024x1024 11 DXT1            Squad/Parts/Aero/fairings/fairings_diff
  CC 1024x1024 11 DXT5            Squad/Parts/Aero/fairings/fairings_diff_grey
  CC 1024x1024 1  DXT5            Squad/Parts/Aero/fairings/fairings_diff_orange
  CC 1024x1024 1  DXT5            Squad/Parts/Aero/fairings/fairings_grey_normals
N CC 1024x1024 11 DXT5            Squad/Parts/Aero/fairings/fairings_normals
  CC 1024x1024 1  DXT5            Squad/Parts/Aero/fairings/fairings_orange_normals
  CC  256x256  9  DXT5            Squad/Parts/Aero/HeatShield/Fairing
  CC  512x512  10 DXT5            Squad/Parts/Aero/HeatShield/heatshield
  CC 1024x1024 11 DXT5            Squad/Parts/Aero/InflatableHeatShield/HeatShield
  CC  512x512  10 DXT1            Squad/Parts/Aero/InflatableHeatShield/HeatShieldFairing
  CC 1024x1024 11 DXT5            Squad/Parts/Aero/InflatableHeatShield/HeatShield_NRM
...
  CC 1024x1024 11 DXT5            Squad/Parts/Command/probeCoreCube/QBE_New_diffuse
N C- 1024x1024 11 RGBA32          Squad/Parts/Command/probeCoreCube/QBE_New_NRM
  CC 1024x1024 11 DXT5            Squad/Parts/Command/probeCoreCube/QBE_New_specular
  CC 1024x1024 11 DXT5            Squad/Parts/Command/probeCoreHex/ksp_m_hexProbe_diff
  CC  512x512  10 DXT5            Squad/Parts/Command/probeCoreHex/ksp_m_hexProbe_normal
  CC  512x512  10 DXT5            Squad/Parts/Command/probeCoreHex_v2/hecsDiffuse
  CC  512x512  10 DXT5            Squad/Parts/Command/probeCoreHex_v2/hecsNormal
  CC  512x256  10 DXT5            Squad/Parts/Command/probeCoreOcto/model000
  CC  512x256  10 DXT5            Squad/Parts/Command/probeCoreOcto/model001
  CC  512x256  10 DXT1            Squad/Parts/Command/probeCoreOcto2/model000
  C-   16x16   1  11              Squad/Parts/Command/probeCoreOcto2_v2/octoDiffuse
  C-   16x16   1  11              Squad/Parts/Command/probeCoreOcto2_v2/octoNormal
  C-   16x16   1  11              Squad/Parts/Command/probeCoreOcto2_v2/octoSpecular
  CC  512x512  10 DXT5            Squad/Parts/Command/probeCoreOcto_v2/octoDiffuse
  CC  512x512  10 DXT5            Squad/Parts/Command/probeCoreOcto_v2/octoNormal
  CC  512x512  10 DXT5            Squad/Parts/Command/probeCoreOcto_v2/octoSpecular
  CC  512x512  10 DXT1            Squad/Parts/Command/probeRoverBody/model000
  CC  512x512  10 DXT5            Squad/Parts/Command/probeRoverBody/model001
  CC 1024x1024 11 DXT5            Squad/Parts/Command/probeRoverBody_v2/probeRoverBody_v2_gold_diffuse
N C- 1024x1024 11 RGBA32          Squad/Parts/Command/probeRoverBody_v2/probeRoverBody_v2_gold_NRM
  CC 1024x1024 11 DXT5            Squad/Parts/Command/probeRoverBody_v2/probeRoverBody_v2_gold_specular
  CC 1024x1024 11 DXT5            Squad/Parts/Command/probeRoverBody_v2/probeRoverBody_v2_silver_diffuse
N C- 1024x1024 11 RGBA32          Squad/Parts/Command/probeRoverBody_v2/probeRoverBody_v2_silver_NRM
  CC 1024x1024 11 DXT5            Squad/Parts/Command/probeRoverBody_v2/probeRoverBody_v2_silver_specular
  CC    4x4    1  DXT5            Squad/Parts/Command/probeRoverBody_v2/QBE_New_diffuse
  CC    4x4    1  DXT5            Squad/Parts/Command/probeRoverBody_v2/QBE_New_NRM
  CC    4x4    1  DXT5            Squad/Parts/Command/probeRoverBody_v2/QBE_New_specular
...
  CC 2048x1024 12 DXT5            Squad/Parts/FuelTank/adapterTanks/Mk3Adapters
  CC  512x512  10 DXT5            Squad/Parts/FuelTank/FoilTanks/RadialTanks
  CC  512x512  10 DXT5            Squad/Parts/FuelTank/FoilTanks/RadialTanks_N
  CC  128x64   8  DXT1            Squad/Parts/FuelTank/fuelTankOscarB/model000
  CC  256x128  9  DXT5            Squad/Parts/FuelTank/fuelTankOscarB/model001
  CC  128x64   8  DXT1            Squad/Parts/FuelTank/fuelTankOscarB/tank
N CC  256x128  9  DXT5            Squad/Parts/FuelTank/fuelTankOscarB/tank_n
  CC  256x256  9  DXT5            Squad/Parts/FuelTank/miniFuselage/Fuselage
  CC 1024x1024 11 DXT5            Squad/Parts/FuelTank/mk2Adapters/mk2adapters1m
  CC 1024x1024 11 DXT5            Squad/Parts/FuelTank/mk2FuselageLong/mk2Fuselage
  CC 1024x1024 11 DXT5            Squad/Parts/FuelTank/mk2FuselageShort/mk2FuselageShort
  CC 2048x1024 12 DXT5            Squad/Parts/FuelTank/mk3Fuselage/Mk3Fuselage
  CC  256x128  9  DXT1            Squad/Parts/FuelTank/mk3Fuselage/Mk3Fuselage_LUM
...
  CC  512x256  10 DXT5            Squad/Parts/Structural/structuralPanel1x1/model000
  CC  512x256  10 DXT5            Squad/Parts/Structural/structuralPanel1x1/model001
  CC  512x256  10 DXT5            Squad/Parts/Structural/structuralPanel2x2/model000
  CC  512x256  10 DXT5            Squad/Parts/Structural/structuralPanel2x2/model001
  CC  512x512  10 DXT5            Squad/Parts/Structural/structuralPylons/Pylons
  CC   64x64   7  DXT1            Squad/Parts/Structural/strutCubicOcto/cubestrut
  CC  128x128  8  DXT5            Squad/Parts/Structural/strutOcto/model000
  CC  256x256  9  DXT1            Squad/Parts/Structural/trussGirderAdapter/model000
  CC  512x512  10 DXT1            Squad/Parts/Structural/trussGirderAdapter/model001
  CC 1024x1024 11 DXT1            Squad/Parts/Structural/trussGirderL/model000
  CC 1024x1024 11 DXT1            Squad/Parts/Structural/trussGirderXL/model000
  CC 1024x1024 11 DXT1            Squad/Parts/Thermal/FoldingRadiators/radiator
N CC 1024x1024 11 DXT5            Squad/Parts/Thermal/FoldingRadiators/radiator_N_NRM
  CC 1024x1024 11 DXT1            Squad/Parts/Thermal/RadiatorPanels/radPanel
N CC 1024x1024 11 DXT5            Squad/Parts/Thermal/RadiatorPanels/radPanel_N_NRM
  CC  512x512  10 DXT5            Squad/Parts/Utility/commDish88-88/comm_dish_array
  CC  512x512  10 DXT5            Squad/Parts/Utility/commDish88-88/comm_dish_v2_diff
  CC  256x256  9  DXT5            Squad/Parts/Utility/commDish88-88/model000
  CC 1024x1024 11 DXT5            Squad/Parts/Utility/commsAntennaDTS-M1/mediumDishAntenna
  CC  256x256  9  DXT1            Squad/Parts/Utility/commsAntennaDTS-M1/mediumDishAntenna_Emit
...
  CC  512x512  10 DXT1            Squad/Parts/Wheel/roverWheelM1/model000
  CC  512x512  10 DXT1            Squad/Parts/Wheel/roverWheelM1/roverwheel1
  CC  256x256  9  DXT5            Squad/Parts/Wheel/roverWheelS2/model000
  CC  512x512  10 DXT5            Squad/Parts/Wheel/roverWheelS2/model001
  CC  256x256  9  DXT5            Squad/Parts/Wheel/roverWheelS2/roverwheel2
N CC  512x512  10 DXT5            Squad/Parts/Wheel/roverWheelS2/roverwheel2_n
  CC 1024x1024 11 DXT5            Squad/Parts/Wheel/roverWheelTR-2L/ksp_r_medWheel_diff
  CC 1024x1024 11 DXT5            Squad/Parts/Wheel/roverWheelTR-2L/ksp_r_medWheel_normal
  CC 1024x1024 11 DXT5            Squad/Parts/Wheel/roverWheelTR-2L/ksp_r_medWheel_wheel_diff
  CC 1024x1024 11 DXT5            Squad/Parts/Wheel/roverWheelTR-2L/ksp_r_medWheel_wheel_normal
...
  CC  128x128  8  DXT5            Squad/Props/IVANavBall/Arrows8dir
  CC  512x256  10 DXT1            Squad/Props/IVANavBall/IVANavBall
  CC  512x256  10 DXT1            Squad/Props/IVANavBall/IVANavBall_Glow
  CC  128x128  8  DXT5            Squad/Props/IVANavBall/ManeuverNode_vectors
  CC  256x256  9  DXT5            Squad/Props/IVANavBall/navball2
  CC  256x256  9  DXT5            Squad/Props/IVANavBall/navBall_DV_IVA
  CC  256x256  9  DXT5            Squad/Props/IVANavBall/navBall_vectors_IVA
 RCC  128x128  8  DXT5            Squad/Props/IVANavBallNoBase/Arrows8dir
 RCC  512x256  10 DXT5            Squad/Props/IVANavBallNoBase/IVANavBall
 RCC  128x128  8  DXT5            Squad/Props/IVANavBallNoBase/ManeuverNode_vectors
 RCC  256x256  9  DXT5            Squad/Props/IVANavBallNoBase/navball2
 RCC  256x256  9  DXT5            Squad/Props/IVANavBallNoBase/navBall_DV_IVA
 RCC  256x256  9  DXT5            Squad/Props/IVANavBallNoBase/navBall_vectors_IVA
  CC  256x256  9  DXT1            Squad/Props/ledPanelSpeed/circularButton
  CC  256x256  9  DXT1            Squad/Props/ledPanelSpeed/ledPanel
  CC  256x256  9  DXT5            Squad/Props/Monitor/Emissives
  CC  256x256  9  DXT1            Squad/Props/Monitor/Emissives_glow
  CC  512x512  10 DXT5            Squad/Props/Monitor/Monitor
...
  CC 2048x2048 12 DXT5            Squad/Spaces/landerCabinSmallInternal/ksp_s_landerCan_internal_diff
  CC 2048x2048 12 DXT5            Squad/Spaces/landerCabinSmallInternal/ksp_s_landerCan_internal_normal
  CC 1024x1024 11 DXT5            Squad/Spaces/landerCabinSmallInternal/ksp_s_landerCan_internal_window_alpha
  CC 1024x1024 11 DXT5            Squad/Spaces/landerCabinSmallInternal/pilot Seat
  CC  512x512  10 DXT5            Squad/Spaces/LargeCrewedLabInternals/Glass
  CC 2048x2048 12 DXT5            Squad/Spaces/LargeCrewedLabInternals/MPL_Int
N CC  512x512  10 DXT5            Squad/Spaces/LargeCrewedLabInternals/MPL_Int_n_NRM
  CC 2048x2048 1  DXT5            Squad/Spaces/Mk1-3/Mk1_3_IvaProps_Diffuse
  CC 2048x2048 1  DXT5            Squad/Spaces/Mk1-3/Mk1_3_IvaProps_Normal
  CC 2048x2048 1  DXT5            Squad/Spaces/Mk1-3/Mk1_3_IvaShell_Diffuse
  CC 2048x2048 1  DXT5            Squad/Spaces/Mk1-3/Mk1_3_IvaShell_Emissive
  CC 2048x2048 1  DXT5            Squad/Spaces/Mk1-3/Mk1_3_IvaShell_Normal
  CC 1024x1024 11 DXT5            Squad/Spaces/Mk1-3/pilot Seat
...
  CC 2048x2048 12 DXT5            Squad/Spaces/mk1pod_IVA/mk1pod_iva_props_diffuse
  CC  512x512  10 DXT5            Squad/Spaces/mk1pod_IVA/mk1pod_iva_props_emissive
  CC 2048x2048 12 DXT5            Squad/Spaces/mk1pod_IVA/mk1pod_iva_props_normal
  CC 2048x2048 12 DXT5            Squad/Spaces/mk1pod_IVA/mk1pod_iva_shell_diffuse
  CC 2048x2048 12 DXT5            Squad/Spaces/mk1pod_IVA/mk1pod_iva_shell_normal
 RCC 1024x1024 11 DXT5            Squad/Spaces/mk1pod_IVA/pilot Seat
  CC  256x128  9  DXT5            Squad/Spaces/mk2CockpitStandardInternal/CargoBagA
  CC 1024x1024 11 DXT5            Squad/Spaces/mk2CockpitStandardInternal/Mk2StandardIVA
  CC  512x512  10 DXT5            Squad/Spaces/mk2CockpitStandardInternal/Pilotseat
  CC  512x256  10 DXT5            Squad/Spaces/mk2CockpitStandardInternal/Windows
...
  CC 2048x2048 12 DXT5            Squad/Spaces/mk2LanderCanInternal/mk2LanderCan_props_diffuse
  CC 2048x2048 12 DXT5            Squad/Spaces/mk2LanderCanInternal/mk2LanderCan_props_normal
  CC 2048x2048 12 DXT5            Squad/Spaces/mk2LanderCanInternal/mk2LanderCan_shell_diffuse
  CC 2048x2048 12 DXT5            Squad/Spaces/mk2LanderCanInternal/mk2LanderCan_shell_normal
 RCC 1024x1024 11 DXT5            Squad/Spaces/mk2LanderCanInternal/pilot Seat
  CC  512x512  10 DXT5            Squad/Spaces/MK3CockpitInternal/Glass
  CC 1024x1024 11 DXT1            Squad/Spaces/MK3CockpitInternal/Mk2StandardIVA
...
  CC 1024x1024 11 DXT5            Squad/Spaces/sharedAssets/CockpitGeneric
  CC 1024x1024 11 DXT5            Squad/Spaces/sharedAssets/CockpitGeneric_NRM
 RCC   48x48   1  DXT5            Squad/Strategies/Icons/AggressiveNegotiations
 RCC   48x48   1  DXT5            Squad/Strategies/Icons/AppreciationCampaign
 RCC   48x48   1  DXT5            Squad/Strategies/Icons/BailOutGrant
 RCC   48x48   1  DXT5            Squad/Strategies/Icons/FundraisingCampaign
 RCC   48x48   1  DXT5            Squad/Strategies/Icons/LeadershipInitiative
 RCC   48x48   1  DXT5            Squad/Strategies/Icons/OpenSourceTechProgram
 RCC   48x48   1  DXT5            Squad/Strategies/Icons/OutsourcedResearch
 RCC   48x48   1  DXT5            Squad/Strategies/Icons/PatentsLicensing
 RCC   48x48   1  DXT5            Squad/Strategies/Icons/RecoveryTransponderFitting
 RCC   48x48   1  DXT5            Squad/Strategies/Icons/ResearchRightsSellOut
 RCC   48x48   1  DXT5            Squad/Strategies/Icons/UnpaidResearchProgram
 RCC  192x32   1  DXT5            Squad/Tutorials/ChuteColors
  CC   92x32   1  DXT5            Squad/Tutorials/EditorCoM
 RCC  128x128  1  DXT5            Squad/Tutorials/EditorSnap
 RCC  128x128  1  DXT5            Squad/Tutorials/EditorSnap4x
 RCC  128x128  1  DXT5            Squad/Tutorials/EditorSymm
 RCC   64x128  1  DXT5            Squad/Tutorials/StagingStack
 RC-  250x213  1  ARGB32          Squad/Tutorials/YPRDiagram

 

If you install unBlur you can use it replicate the above as well as investigate what KSP is doing to your textures.

It provides access to its functionality via a console command in the Alt+F12 debug console so you can inspect individual texture info, disable mipmaps for textures, and dump the full list of textures from GameDatabase.
It is also possible to have unBlur dump the state of textures from GameDatabase immediately after loading, while in the loading screen, by turning on verbose debug mode.

For details, consult the unBlur forum thread.


How the texture resolution setting affects texture loading

The texture resolution option in KSP's graphics settings actually control a Unity setting called QualitySettings.masterTextureLimit.
The setting is stored in settings.cfg as TEXTURE_QUALITY with 0 = full res, 1 = half res, ... 3 = eighth res.

As described earlier, masterTextureLimit prevents the n highest resolution mipmap(s) from being uploaded to the GPU.
Per Unity docs, "This can be used to decrease video memory requirements on low-end computers."

However, if a texture does not have mipmaps, then the full texture must of course be used.

Once the texture has been upload to the GPU, that's the copy we have to work with. If the texture resolution setting was at eighth res when starting the game, that's the quality that you are stuck with -- changing the setting at run time does not appear to have any effect -- because only a lower res version is available in the GPU, and in general because the texture was made no longer readable by CPU side after loading, even if you turn the quality back up to full res, the full res texture data is not available anymore without actually reloading from file again.

In cases where the texture is still readable and in RAM, plugins can access the full res version of the texture from there. (This is how unBlur fixes blurry png textures.)


How various file formats are handled

Based on observations from GameDatabase TextureInfo dumps, including both stock and modded.

*.dds

These files are already in the compressed format preferred internally.
Loading them is fast, because the data is loaded as-is and no conversion is needed.
Being compressed, they use less graphics memory and are faster to render.
- Loaded format: as per file, i.e. DXT1 (no alpha) or DXT5 (with alpha)
- already compressed
- mipmaps: as per file
- not kept readable

*.mbm

Old KSP propietary format, very few instances left.
- Loaded format: DXT
- compressed
- mipmaps: yes
- kept readable

*.png

Loading them is slow, because they have to be converted from RGBA32 and additional processing is done.
They usually get compressed to DXT5 for upload to GPU, but are kept CPU-readable so also consume RAM.
- Loaded format: usually DXT5
- will usually be compressed
- mipmaps: may be generated <-- blame your blurry UI on this
- kept readable

Notes

  1. Some stock pngs avoid having mipmaps generated (e.g. Squad/Flags/*, Squad/PartList/SimpleIcons/*, Squad/Strategies/Icons/*) but others do (e.g. Squad/Props/IVANavBallNoBase/*)
    mechanism not well understood, perhaps hardcoded to identify certain directories (*/Flags/* maybe?) but not sure we can rely on this behavior
  2. A very small number of new normal maps (_NRM) for redesigned parts are *.png that store in GameDatabase as RGBA32, unreadable, uncompressed (despite isCompressed true), with mipmaps generated.
  3. Squad/Tutorials/YPRDiagram fails to compress, which reveals that pngs are loaded as ARGB32 before getting compressed to DXT
  4. mipmaps may still be generated even if compression fails, I have positively observed this (38x38 ARGB32 CommNetConstellation/Textures/cnclauncherbutton.png with mipmapCount of 6)

*.truecolor

Notably used for small (_scaled) versions of agency logos. Explanation here.
Actually renamed *.png files, so loading needs to convert format from RGBA32.
- Loaded format: ARGB32
- will not be compressed
- mipmaps: will not be generated
- not kept readable

*.jpg

Like png, these are comparatively slower to load.
They are compressed to DXT1 since they don't have transparency, and kept CPU-readable after loading to GPU
- Loaded format: DXT1
- compressed
- mipmaps: will be generated
- kept readable


Why does it behave like that?

If you provide a texture in DDS format, it is already in the format that is used by the GPU, so KSP/Unity takes the file as-is and treats it as what you intended -- so you can provide exactly what you want.
If you are using a texture for models, you'd include mipmaps, and things would work great (because that's exactly the use-case they were designed for.)
If you are using a texture for UI, you wouldn't generate mipmaps when saving the file, and KSP/Unity will just always use it at full-res (exactly as intended)
The texture file is already in the correct format, already compressed, and has mipmaps if you want them. KSP/Unity presumes that everything is as you want it to be, and the texture will no longer be modified by code once loaded, and so discards its data from main memory after it has been loaded into graphics memory.

For other formats, however, they need to be converted for use. Because the API doesn't provide a mechanism for us to attach any metadata to png/jpg/etc files to indicate to KSP/Unity what our intentions are and what the texture is for, I think the texture loader simply makes the assumption that whatever textures it loads are for models. So, in general, it will generate mipmaps from the full-res image, and after that it will attempt to compress the texture to DXT for better performance. But it keeps the texture's pixel-by-pixel data available in RAM, in case you might want to write some code that accesses/manipulates that.

This is actually a reasonable default assumption, after all, most textures are going to be model textures. As for UI textures, pretty much most if not all of the UI in the stock game are built in Unity, prefabbed, and saved into assets, so that takes care of loading UI textures for the stock game. (Mods could do the same thing, but most of the time it's far too much trouble if all we need is some simple UI.)

Anyway, this is the behavior in general for formats like png and jpg. It seems like KSP might have some code that handles special cases in the stock game's data like flag textures (Squad/Flags/*) and icons (Squad/PartList/SimpleIcons/*, Squad/Strategies/Icons/*) so that they don't get treated like model textures. Those cases can be hardcoded because they know about it in advance, but for modders I don't recommend we rely on that behavior.

*.truecolor files are the special case, which seems to be added to handle agency logos. Agency logos in general are stored as DDS and are displayed in game at various medium-ish sizes, e.g. contracts window.
However, for the part-picker in editor when sorted by manufacturer, it needs to be a small icon. Scaling down from the fullsize logo at runtime didn't work well visually, so smaller "*_scaled" version of the logos were specifically made.
These were png files, but as above the texture loader would have generated mipmaps for them and cause blurriness. To deal with that, the files were named as *.truecolor instead and the texture loader instructed specifically to treat them differently, i.e don't generate mipmaps.


How to proceed from here

UI textures in your own mod

Including toolbar button icons for stock AppLauncher or blizzy toolbar.

If you are currently using *.png for icon/button graphics, the quick hotfix is to simply rename to *.truecolor. Besides not having mipmaps generated, the other benefit is that it will not be kept in RAM after uploading to GPU. The one downside compared to png is that it will no longer be compressed. Also, like png, it is still slower to load.

For long term, if you can convert your files to *.dds without mipmaps would be most ideal: fast to load, compressed, not kept in RAM.

If you are using the workaround of reading textures directly from file yourself

Specifically the workaround offered here.
Stop doing this if it is for textures in your own mod that you have control over. Rename/convert them instead, see above. By reading the file yourself and creating another texture, you incur file IO overheads and consume additional resources for the texture that you create, while the copy in GameDatabase continues to occupy both RAM and GPU. And if not implemented properly, you may be leaking memory.

If you have to use the workaround because you have no control over the textures, i.e. they are passed to you by other mods. You can use unBlur, call unBlur to tell it to strip mipmaps from the texture in GameDatabase before you fetch and use it.

If you are using the workaround of making textures larger than they need to be

This workaround costs a disproportionate amount of disk space and loading time, don't use it.

The way that this worked is as follows:
Suppose you make a 64x64 image when you actually only need 32x32, this will work at half res settings because the 64x64 copy is thrown away but mipmap level 1 at 32x32 is still available.
But at eighth res setting, only mipmap level 3 at 8x8 is available so it doesn't fully solve to problem. You would need to make full res at 256x256 in order for eighth res setting to still have a 32x32 copy.

If you have programmatically created Texture2Ds in UI for other reasons

Here are tips for best performance and results

  • Make sure you use the constructor that lets you specify mimmap false.
  • Use Compress() if possible. Works more consistently if you do this before making unreadable using Apply(). (ref)
  • Once finished making modifications to the pixel data, call Apply(false, true) to upload to GPU and discard from RAM
  • Make sure you dispose of the texture when done with it. For most monobehaviors (i.e. destroyed/recreated when changing scenes) you should do it in OnDestroy even if it is something you "hang on to", otherwise the texture will not be GC'd and you end up making another copy. Other ideas: you can have a singleton monobehavior with DontDestroyOnLoad that is responsible for hanging on to one copy of textures only. Or init once into a static field.

UI textures that require mipmapping

If you have UI textures that are larger, such as banners or backgrounds that need to be displayed at different sizes, then you might actually want mipmapping.
But then, you'll need some way of getting around the texture quality settings causing the n highest-res mipmaps being discarded.

If you find yourself in this situation, my suggestion is to use png format so that mipmaps will be automatically generated for you, but the texture is also kept readable in RAM.
After the texture has been loaded into GameDatabase, you will need to fetch the texture -- which is missing first few mipmaps on GPU, but still has full-res data preserved in RAM -- and upload to GPU again, forcing it to include all mipmaps this time.
This should be achievable by temporarily forcing QualitySettings.masterTextureLimit to 0, calling Apply(false, false) on the texture, and then restoring the masterTextureLimit when done.
(unBlur will also provide such functionality in the future.)

Non-UI, i.e. model textures

You've probably heard this before, but for fastest loading and best performance you should really encode in dds with appropriate mipmap level.

Edited by cakepie
additions / corrections / updates

Share this post


Link to post
Share on other sites
48 minutes ago, cakepie said:

it is computationally cheap to slap a lower res mipmap on it

I thought the point of mipmaps was to make distant objects look better, not performance. Looking up N texture coordinates and texels for N pixels is the same cost regardless of the size of the texture, just with different scale factors. But if you use a much higher res texture, those pixels would be sourced from a much smaller area than they represent visually, making the image look scrambled and change jarringly as the object moves.

(I guess it improves performance if you compare it to trying to blend the right texels on the fly at runtime. But hopefully no one is that crazy.)

Share this post


Link to post
Share on other sites
8 minutes ago, HebaruSan said:

I thought the point of mipmaps was to make distant objects look better[...] if you use a much higher res texture, those pixels would be sourced from a much smaller area than they represent visually, making the image look scrambled and change jarringly as the object moves.

(I guess it improves performance if you compare it to trying to blend the right texels on the fly at runtime. But hopefully no one is that crazy.)

Ah, as in using too high res texture actually ends up sampling pixels that are quite far apart on the texture and rendering them adjacent to one another?

Share this post


Link to post
Share on other sites
2 minutes ago, cakepie said:

Ah, as in using too high res texture actually ends up sampling pixels that are quite far apart on the texture and rendering them adjacent to one another?

Yes, exactly. And for small features like a border between two sections, pieces of it would be drawn disconnectedly and pop in and out of existence as you moved around.

Share this post


Link to post
Share on other sites
This thread is quite old. Please consider starting a new thread rather than reviving this one.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.