Skip to content

Optimization ‐ Remove Expensive and Redundant `BuildColorMaps()` Function

Josh edited this page Jan 7, 2024 · 6 revisions

In src/field_weather.c, the BuildColorMaps() function does a lot of expensive computations to create color maps used to transform palette colors for weather effects. This function is called every time a new map is loaded. This would be useful, if it didn't generate the exact same arrays every time!

In this tutorial, we will save the wasted computation by precomputing these color maps and removing the function altogether.

First, open include/field_weather.h. We're in here to remove the color maps from struct Weather, since they will be const data in ROM now. As a bonus, this frees up 1216 bytes in EWRAM! That's 0.46%!

struct Weather
{
    //...
        struct
        {
            u8 filler0[0xA0];
            struct Sprite *fogHSprites[NUM_FOG_HORIZONTAL_SPRITES];
            struct Sprite *ashSprites[NUM_ASH_SPRITES];
            struct Sprite *fogDSprites[NUM_FOG_DIAGONAL_SPRITES];
            struct Sprite *sandstormSprites1[NUM_SANDSTORM_SPRITES];
            struct Sprite *sandstormSprites2[NUM_SWIRL_SANDSTORM_SPRITES];
        } s2;
    } sprites;
-    u8 darkenedContrastColorMaps[NUM_WEATHER_COLOR_MAPS][32];
-    u8 contrastColorMaps[NUM_WEATHER_COLOR_MAPS][32];
    s8 colorMapIndex;
    s8 targetColorMapIndex;
    u8 colorMapStepDelay;
    //...

Now go into src/field_weather.c. At the top, we're going to define our rather large arrays that take the place of these color maps. These values were ripped from an unmodified Emerald's memory right after BuildColorMaps() ran, so they are identical to the maps that the game normally builds.

// ...
EWRAM_DATA struct Weather gWeather = {0};
EWRAM_DATA static u8 ALIGNED(2) sFieldEffectPaletteColorMapTypes[32] = {0};

static const u8 *sPaletteColorMapTypes;
+
+static const u8 sDarkenedContrastColorMaps[NUM_WEATHER_COLOR_MAPS][32] = 
+{
+    {0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29},
+    {0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, 9, 10, 11, 12, 13, 14, 14, 15, 16, 17, 18, 19, 20, 21, 21, 22, 23, 24, 25, 26, 27},
+    {0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 8, 8, 9, 10, 11, 12, 13, 13, 14, 15, 16, 17, 17, 18, 19, 20, 21, 21, 22, 23, 24, 25},
+    {0, 1, 2, 3, 4, 4, 5, 6, 7, 8, 8, 9, 11, 11, 12, 13, 14, 14, 15, 16, 17, 17, 18, 19, 20, 20, 21, 22, 23, 24, 24, 25},
+    {1, 2, 3, 3, 4, 5, 6, 6, 7, 8, 9, 9, 12, 13, 13, 14, 15, 15, 16, 17, 18, 18, 19, 20, 20, 21, 22, 23, 23, 24, 25, 25},
+    {1, 2, 3, 4, 4, 5, 6, 7, 7, 8, 9, 10, 13, 14, 15, 15, 16, 17, 17, 18, 19, 19, 20, 20, 21, 22, 22, 23, 24, 24, 25, 26},
+    {1, 2, 3, 4, 4, 5, 6, 7, 7, 8, 9, 10, 15, 15, 16, 16, 17, 18, 18, 19, 19, 20, 21, 21, 22, 22, 23, 24, 24, 25, 26, 26},
+    {1, 2, 3, 4, 4, 5, 6, 7, 7, 8, 9, 10, 16, 16, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 23, 23, 24, 24, 25, 25, 26, 27},
+    {1, 2, 3, 4, 4, 5, 6, 7, 8, 8, 9, 10, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 26, 27},
+    {1, 2, 3, 4, 4, 5, 6, 7, 8, 8, 9, 10, 19, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 24, 25, 25, 26, 26, 27, 27},
+    {1, 2, 3, 4, 4, 5, 6, 7, 8, 8, 9, 10, 20, 20, 21, 21, 22, 22, 22, 23, 23, 24, 24, 24, 25, 25, 26, 26, 26, 27, 27, 28},
+    {1, 2, 3, 4, 4, 5, 6, 7, 8, 8, 9, 10, 21, 22, 22, 22, 23, 23, 23, 24, 24, 24, 25, 25, 25, 26, 26, 27, 27, 27, 28, 28},
+    {1, 2, 3, 4, 4, 5, 6, 7, 8, 8, 9, 10, 23, 23, 23, 23, 24, 24, 24, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 28, 28, 28},
+    {1, 2, 3, 4, 4, 5, 6, 7, 8, 8, 9, 10, 24, 24, 24, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27, 28, 28, 28, 28, 29},
+    {1, 2, 3, 4, 4, 5, 6, 7, 8, 8, 9, 10, 25, 25, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 29, 29, 29},
+    {1, 2, 3, 4, 4, 5, 6, 7, 8, 8, 9, 10, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29},
+    {1, 2, 3, 4, 4, 5, 6, 7, 8, 8, 9, 10, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 30, 30, 30},
+    {1, 2, 3, 4, 4, 5, 6, 7, 8, 8, 9, 10, 29, 29, 29, 29, 29, 29, 29, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30},
+    {1, 2, 3, 4, 4, 5, 6, 7, 8, 8, 9, 10, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31}
+};
+
+static const u8 sContrastColorMaps[NUM_WEATHER_COLOR_MAPS][32] =
+{
+    {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31},
+    {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31},
+    {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31},
+    {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31},
+    {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 11, 14, 15, 16, 17, 17, 18, 19, 20, 21, 22, 23, 24, 24, 25, 26, 27, 28, 29, 30, 31},
+    {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 15, 16, 17, 18, 18, 19, 20, 21, 22, 22, 23, 24, 25, 26, 26, 27, 28, 29, 30, 31},
+    {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 16, 17, 18, 19, 19, 20, 21, 22, 22, 23, 24, 25, 25, 26, 27, 28, 28, 29, 30, 31},
+    {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 17, 18, 19, 20, 20, 21, 22, 22, 23, 24, 24, 25, 26, 26, 27, 28, 28, 29, 30, 31},
+    {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 19, 19, 20, 21, 21, 22, 22, 23, 24, 24, 25, 26, 26, 27, 27, 28, 29, 29, 30, 31},
+    {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 20, 20, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 27, 27, 28, 28, 29, 29, 30, 31},
+    {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, 28, 28, 29, 29, 30, 30, 31},
+    {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 22, 23, 23, 24, 24, 24, 25, 25, 26, 26, 27, 27, 27, 28, 28, 29, 29, 30, 30, 31},
+    {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 23, 24, 24, 25, 25, 25, 26, 26, 26, 27, 27, 28, 28, 28, 29, 29, 29, 30, 30, 31},
+    {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 28, 28, 28, 29, 29, 29, 30, 30, 30, 31},
+    {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 26, 26, 26, 27, 27, 27, 27, 28, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, 30, 31},
+    {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 27, 27, 27, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 30, 30, 30, 30, 30, 31},
+    {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 30, 30, 30, 30, 30, 30, 30, 30, 31},
+    {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 29, 29, 29, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 31},
+    {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31}
+};

// The drought weather effect uses a precalculated color lookup table. Presumably this
// is because the underlying color shift calculation is slow.
// ...

Now it's time to remove the function, its declaration, and its call. The caller is StartWeather():

void StartWeather(void)
{
    if (!FuncIsActiveTask(Task_WeatherMain))
    {
        u8 index = AllocSpritePalette(PALTAG_WEATHER);
        CpuCopy32(gFogPalette, &gPlttBufferUnfaded[OBJ_PLTT_ID(index)], PLTT_SIZE_4BPP);
-        BuildColorMaps();
+        sPaletteColorMapTypes = sBasePaletteColorMapTypes;
        gWeatherPtr->contrastColorMapSpritePalIndex = index;
        gWeatherPtr->weatherPicSpritePalIndex = AllocSpritePalette(PALTAG_WEATHER_2);
        gWeatherPtr->rainSpriteCount = 0;
        gWeatherPtr->curRainSpriteIndex = 0;
        gWeatherPtr->cloudSpritesCreated = 0;
        gWeatherPtr->snowflakeSpriteCount = 0;
        gWeatherPtr->ashSpritesCreated = 0;
        gWeatherPtr->fogHSpritesCreated = 0;
        gWeatherPtr->fogDSpritesCreated = 0;
        gWeatherPtr->sandstormSpritesCreated = 0;
        gWeatherPtr->sandstormSwirlSpritesCreated = 0;
        gWeatherPtr->bubblesSpritesCreated = 0;
        gWeatherPtr->lightenedFogSpritePalsCount = 0;
        Weather_SetBlendCoeffs(16, 0);
        gWeatherPtr->currWeather = 0;
        gWeatherPtr->palProcessingState = WEATHER_PAL_STATE_IDLE;
        gWeatherPtr->readyForInit = FALSE;
        gWeatherPtr->weatherChangeComplete = TRUE;
        gWeatherPtr->taskId = CreateTask(Task_WeatherInit, 80);
    }
}

If using dynamic palettes, you will need to make the above changes but with an added for loop like so:

void StartWeather(void)
{
    if (!FuncIsActiveTask(Task_WeatherMain))
    {
        u8 index = 15;
        CpuCopy32(gFogPalette, &gPlttBufferUnfaded[OBJ_PLTT_ID(index)], PLTT_SIZE_4BPP);
-        BuildColorMaps();
+        for (i = 0; i < NUM_PALS_TOTAL; i++)
+            sBasePaletteColorMapTypes[i] = COLOR_MAP_DARK_CONTRAST;
+        sPaletteColorMapTypes = sBasePaletteColorMapTypes;
        gWeatherPtr->contrastColorMapSpritePalIndex = index;
        gWeatherPtr->rainSpriteCount = 0;
        gWeatherPtr->curRainSpriteIndex = 0;
        gWeatherPtr->cloudSpritesCreated = 0;
        gWeatherPtr->snowflakeSpriteCount = 0;
        gWeatherPtr->ashSpritesCreated = 0;
        gWeatherPtr->fogHSpritesCreated = 0;
        gWeatherPtr->fogDSpritesCreated = 0;
        gWeatherPtr->sandstormSpritesCreated = 0;
        gWeatherPtr->sandstormSwirlSpritesCreated = 0;
        gWeatherPtr->bubblesSpritesCreated = 0;
        gWeatherPtr->lightenedFogSpritePalsCount = 0;
        Weather_SetBlendCoeffs(16, 0);
        gWeatherPtr->currWeather = 0;
        gWeatherPtr->palProcessingState = WEATHER_PAL_STATE_IDLE;
        gWeatherPtr->readyForInit = FALSE;
        gWeatherPtr->weatherChangeComplete = TRUE;
        gWeatherPtr->taskId = CreateTask(Task_WeatherInit, 80);
    }
}

And remove the BuildColorMaps() function itself:

//...
static u8 None_Finish(void)
{
    return 0;
}

-// Builds two tables that contain color maps, used for directly transforming
-// palette colors in weather effects. The colors maps are a spectrum of
-// brightness + contrast mappings. By transitioning between the maps, weather
-// effects like lightning are created.
-// It's unclear why the two tables aren't declared as const arrays, since
-// this function always builds the same two tables.
-static void BuildColorMaps(void)
-{
-    u16 i;
-    u8 (*colorMaps)[32];
-    u16 colorVal;
-    u16 curBrightness;
-    u16 brightnessDelta;
-    u16 colorMapIndex;
-    u16 baseBrightness;
-    u32 remainingBrightness;
-    s16 diff;
-    sPaletteColorMapTypes = sBasePaletteColorMapTypes;
-    for (i = 0; i < 2; i++)
-    {
-        if (i == 0)
-            colorMaps = gWeatherPtr->darkenedContrastColorMaps;
-        else
-            colorMaps = gWeatherPtr->contrastColorMaps;
-        for (colorVal = 0; colorVal < 32; colorVal++)
-        {
-            curBrightness = colorVal << 8;
-            if (i == 0)
-                brightnessDelta = (colorVal << 8) / 16;
-            else
-                brightnessDelta = 0;
-            // First three color mappings are simple brightness modifiers which are
-            // progressively darker, according to brightnessDelta.
-            for (colorMapIndex = 0; colorMapIndex < 3; colorMapIndex++)
-            {
-                curBrightness -= brightnessDelta;
-                colorMaps[colorMapIndex][colorVal] = curBrightness >> 8;
-            }
-            baseBrightness = curBrightness;
-            remainingBrightness = 0x1f00 - curBrightness;
-            if ((0x1f00 - curBrightness) < 0)
-                remainingBrightness += 0xf;
-            brightnessDelta = remainingBrightness / (NUM_WEATHER_COLOR_MAPS - 3);
-            if (colorVal < 12)
-            {
-                // For shadows (color values < 12), the remaining color mappings are
-                // brightness modifiers, which are increased at a significantly lower rate
-                // than the midtones and highlights (color values >= 12). This creates a
-                // high contrast effect, used in the thunderstorm weather.
-                for (; colorMapIndex < NUM_WEATHER_COLOR_MAPS; colorMapIndex++)
-                {
-                    curBrightness += brightnessDelta;
-                    diff = curBrightness - baseBrightness;
-                    if (diff > 0)
-                        curBrightness -= diff / 2;
-                    colorMaps[colorMapIndex][colorVal] = curBrightness >> 8;
-                    if (colorMaps[colorMapIndex][colorVal] > 31)
-                        colorMaps[colorMapIndex][colorVal] = 31;
-                }
-            }
-            else
-            {
-                // For midtones and highlights (color values >= 12), the remaining
-                // color mappings are simple brightness modifiers which are
-                // progressively brighter, hitting exactly 31 at the last mapping.
-                for (; colorMapIndex < NUM_WEATHER_COLOR_MAPS; colorMapIndex++)
-                {
-                    curBrightness += brightnessDelta;
-                    colorMaps[colorMapIndex][colorVal] = curBrightness >> 8;
-                    if (colorMaps[colorMapIndex][colorVal] > 31)
-                        colorMaps[colorMapIndex][colorVal] = 31;
-                }
-            }
-        }
-    }
-}
-
// When the weather is changing, it gradually updates the palettes
// towards the desired color map.
static void UpdateWeatherColorMap(void)
//...

Then search static void BuildColorMaps(void); and remove its declaration at the top of the file:

//...
// This file's functions.
static bool8 LightenSpritePaletteInFog(u8);
-static void BuildColorMaps(void);
static void UpdateWeatherColorMap(void);
//...

Almost done! Now we just have to fix two functions that use the old color maps from struct Weather. First, CTRL + F to the ApplyColorMap() function:

static void ApplyColorMap(u8 startPalIndex, u8 numPalettes, s8 colorMapIndex)
{
    u16 curPalIndex;
    u16 palOffset;
-    u8 *colorMap;
-    u16 i;
+    const u8 *colorMap;
+    u32 i;

    if (colorMapIndex > 0)
    {
        colorMapIndex--;
        palOffset = PLTT_ID(startPalIndex);
        numPalettes += startPalIndex;
        curPalIndex = startPalIndex;

        // Loop through the specified palette range and apply necessary color maps.
        while (curPalIndex < numPalettes)
        {
            if (sPaletteColorMapTypes[curPalIndex] == COLOR_MAP_NONE)
            {
                // No palette change.
                CpuFastCopy(&gPlttBufferUnfaded[palOffset], &gPlttBufferFaded[palOffset], PLTT_SIZE_4BPP);
                palOffset += 16;
            }
            else
            {
                u8 r, g, b;

                if (sPaletteColorMapTypes[curPalIndex] == COLOR_MAP_CONTRAST || curPalIndex - 16 == gWeatherPtr->contrastColorMapSpritePalIndex)
-                    colorMap = gWeatherPtr->contrastColorMaps[colorMapIndex];
+                    colorMap = sContrastColorMaps[colorMapIndex];
                else
-                    colorMap = gWeatherPtr->darkenedContrastColorMaps[colorMapIndex];
+                    colorMap = sDarkenedContrastColorMaps[colorMapIndex];

                for (i = 0; i < 16; i++)
                {
                    // Apply color map to the original color.
                    struct RGBColor baseColor = *(struct RGBColor *)&gPlttBufferUnfaded[palOffset];
                    r = colorMap[baseColor.r];
                    g = colorMap[baseColor.g];
                    b = colorMap[baseColor.b];
                    gPlttBufferFaded[palOffset++] = RGB2(r, g, b);
                }
            }

            curPalIndex++;
        }
    }
//...

Then, CTRL + F to find the ApplyColorMapWithBlend() function:

static void ApplyColorMapWithBlend(u8 startPalIndex, u8 numPalettes, s8 colorMapIndex, u8 blendCoeff, u16 blendColor)
{
    u16 palOffset;
    u16 curPalIndex;
-    u16 i;
+    u32 i;
    struct RGBColor color = *(struct RGBColor *)&blendColor;
    u8 rBlend = color.r;
    u8 gBlend = color.g;
    u8 bBlend = color.b;

    palOffset = PLTT_ID(startPalIndex);
    numPalettes += startPalIndex;
    colorMapIndex--;
    curPalIndex = startPalIndex;

    while (curPalIndex < numPalettes)
    {
        if (sPaletteColorMapTypes[curPalIndex] == COLOR_MAP_NONE)
        {
            // No color map. Simply blend the colors.
            BlendPalette(palOffset, 16, blendCoeff, blendColor);
            palOffset += 16;
        }
        else
        {
-            u8 *colorMap;
+            const u8 *colorMap;

            if (sPaletteColorMapTypes[curPalIndex] == COLOR_MAP_DARK_CONTRAST)
-                colorMap = gWeatherPtr->darkenedContrastColorMaps[colorMapIndex];
+                colorMap = sDarkenedContrastColorMaps[colorMapIndex];
            else
-                colorMap = gWeatherPtr->contrastColorMaps[colorMapIndex];
+                colorMap = sContrastColorMaps[colorMapIndex];
//...

And now we're done. Compile to make sure you haven't missed anything. Your map loading is now much more efficient!

Clone this wiki locally