summaryrefslogtreecommitdiff
path: root/src/code/z_vismono.c
blob: 632d00a6e7d3a992f74bde9432812340a1fac940 (plain)
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
/**
 * @file z_vismono.c
 *
 * This file implements a full-screen framebuffer effect for desaturating the contents of the framebuffer image.
 *
 * Broadly, this effect is achieved by reinterpreting the contents of the RGBA16 color image as indices into an IA16
 * color palette that converts each color into the desaturated equivalent. More precise details can be found in inline
 * comments.
 */

#include "libc64/malloc.h"
#include "libu64/debug.h"
#include "attributes.h"
#include "gfx.h"
#include "gfxalloc.h"
#include "vis.h"

// Height of the fragments the color frame buffer (CFB) is split into.
// It is the maximum amount of lines such that all rgba16 SCREEN_WIDTH-long lines fit into
// the half of TMEM dedicated to color-indexed data.
#define VISMONO_CFBFRAG_HEIGHT ((TMEM_SIZE / 2) / (SCREEN_WIDTH * G_IM_SIZ_16b_BYTES))

// Maximum size of the dlist written by `VisMono_DesaturateDList`.
// `VisMono_DesaturateDList` consistently uses `VISMONO_DLSIZE - 2` double words, so this can be 2 less.
#define VISMONO_DLSIZE (3 + SCREEN_HEIGHT / VISMONO_CFBFRAG_HEIGHT * (7 + 2 + 2 + 3) + 2 + 2)

// How much each color component contributes to the desaturated result.
// These coefficients are close to what the YUV color space defines Y (luminance) as:
// https://en.wikipedia.org/wiki/YUV#Conversion_to/from_RGB
#define VISMONO_FAC_RED 2
#define VISMONO_FAC_GREEN 4
#define VISMONO_FAC_BLUE 1
#define VISMONO_FAC_NORM (0x1F * VISMONO_FAC_RED + 0x1F * VISMONO_FAC_GREEN + 0x1F * VISMONO_FAC_BLUE)

// color framebuffer
extern u16 D_0F000000[];

void VisMono_Init(VisMono* this) {
    bzero(this, sizeof(VisMono));
    this->vis.type = 0;
    this->vis.scissorType = VIS_NO_SETSCISSOR;
    this->vis.primColor.r = 255;
    this->vis.primColor.g = 255;
    this->vis.primColor.b = 255;
    this->vis.primColor.a = 255;
    this->vis.envColor.r = 0;
    this->vis.envColor.g = 0;
    this->vis.envColor.b = 0;
    this->vis.envColor.a = 0;
}

void VisMono_Destroy(VisMono* this) {
    SYSTEM_ARENA_FREE(this->dList, "../z_vismono.c", 137);
}

void VisMono_DesaturateTLUT(VisMono* this, u16* tlut) {
    s32 i;

    for (i = 0; i < 256; i++) {
        // `tlut[i]` is a IA16 color
        // `i` corresponds to either byte of a RGBA16 color RRRR_RGGG GGBB_BBBA from the color frame buffer

        // The high byte I (intensity) corresponds to `i` being interpreted as the high byte RRRR_RGGG
        // I = (RRRRR * FAC_RED + GGG00 * FAC_GREEN) * (255 / FAC_NORM)

        // The low byte A (alpha) corresponds to `i` being interpreted as the low byte GGBB_BBBA
        // A = (000GG * FAC_GREEN + BBBBB * FAC_BLUE) * (255 / FAC_NORM)

        // Note: I + A = (RRRRR * FAC_RED + GGGGG * FAC_GREEN + BBBBB * FAC_BLUE) * (255 / FAC_NORM)

        tlut[i] = GPACK_IA16(
            ((i >> 3 & 0x1F) * VISMONO_FAC_RED + (i << 2 & 0x1F) * VISMONO_FAC_GREEN) * 255 / VISMONO_FAC_NORM,
            ((i >> 6 & 0x1F) * VISMONO_FAC_GREEN + (i >> 1 & 0x1F) * VISMONO_FAC_BLUE) * 255 / VISMONO_FAC_NORM);
    }
}

Gfx* VisMono_DesaturateDList(VisMono* this, Gfx* gfx) {
    s32 y;
    s32 height = VISMONO_CFBFRAG_HEIGHT;
    u16* cfbFrag = D_0F000000;

    gDPPipeSync(gfx++);
    // `G_TT_IA16`: use color-indexed images, and IA16 palettes
    gDPSetOtherMode(gfx++,
                    G_AD_DISABLE | G_CD_DISABLE | G_CK_NONE | G_TC_FILT | G_TF_POINT | G_TT_IA16 | G_TL_TILE |
                        G_TD_CLAMP | G_TP_NONE | G_CYC_2CYCLE | G_PM_1PRIMITIVE,
                    G_AC_NONE | G_ZS_PRIM | G_RM_PASS | G_RM_CLD_SURF2);
    // First color cycle sums texel 1 alpha and texel 0 color
    // By using IA16 palettes, this means summing A (from the IA16 color texel 1 maps to)
    // with I (from the IA16 color texel 0 maps to)
    gDPSetCombineLERP(gfx++, 1, 0, TEXEL1_ALPHA, TEXEL0, 0, 0, 0, 1, PRIMITIVE, ENVIRONMENT, COMBINED, ENVIRONMENT, 0,
                      0, 0, PRIMITIVE);

    for (y = 0; y <= SCREEN_HEIGHT - height; y += height) {
        // Load a few lines of the color frame buffer
        gDPLoadTextureBlock(gfx++, cfbFrag, G_IM_FMT_CI, G_IM_SIZ_8b, SCREEN_WIDTH * 2, height, 0,
                            G_TX_NOMIRROR | G_TX_CLAMP, G_TX_NOMIRROR | G_TX_CLAMP, G_TX_NOMASK, G_TX_NOMASK,
                            G_TX_NOLOD, G_TX_NOLOD);

        // Set texel 0 to be a CI8 image with width `SCREEN_WIDTH * 2` and height `VISMONO_CFBFRAG_HEIGHT`
        // Its position in texture image space is shifted along +S by 2
        gDPSetTile(gfx++, G_IM_FMT_CI, G_IM_SIZ_8b, SCREEN_WIDTH * 2 * G_IM_SIZ_8b_LINE_BYTES / 8, 0x0, G_TX_RENDERTILE,
                   0, G_TX_NOMIRROR | G_TX_CLAMP, 0, 0, G_TX_NOMIRROR | G_TX_CLAMP, 0, 0);
        gDPSetTileSize(gfx++, G_TX_RENDERTILE, 2 << 2, 0, ((SCREEN_WIDTH * 2) + 1) << 2, (height - 1) << 2);

        // Set texel 1 to be a CI8 image with width `SCREEN_WIDTH * 2` and height `VISMONO_CFBFRAG_HEIGHT`
        // Its position in texture image space is shifted along +S by 1
        // Note the palette index for this tile has also been incremented from 0 to 1, however the palette index is
        // ignored for CI8 texture sampling.
        gDPSetTile(gfx++, G_IM_FMT_CI, G_IM_SIZ_8b, SCREEN_WIDTH * 2 * G_IM_SIZ_8b_LINE_BYTES / 8, 0x0, 1, 1,
                   G_TX_NOMIRROR | G_TX_CLAMP, 0, 0, G_TX_NOMIRROR | G_TX_CLAMP, 0, 0);
        gDPSetTileSize(gfx++, 1, 1 << 2, 0, (SCREEN_WIDTH * 2) << 2, (height - 1) << 2);

        // Draw a `SCREEN_WIDTH` wide, `height` high rectangle.
        // Texture coordinate T (vertical) starts at 0 and changes by one each line (dtdy = 1)
        // Texture coordinate S (horizontal) starts at 2 and changes by two each column (dsdx = 2)

        // Because texel 0 is shifted by 2 and texel 1 only by 1 along +S,
        // a pixel at S coordinates s = 2+2*n will look at the 2*n-th byte of texel 0 and the 2*n+1-th byte of texel 1.
        // (in "s = 2+2*n" the first "2" is the starting S coordinate and the second "2" is the dsdx value)

        // The 2*n-th byte of texel 0 is the high byte of the n-th RGBA16 color of the color frame buffer.
        // The 2*n+1-th byte of texel 1 is the low byte of the n-th RGBA16 color of the color frame buffer.

        // With the TLUT computed by `VisMono_DesaturateTLUT`:
        // The 2*n-th byte of texel 0 maps to a IA16 color where the high byte I (intensity) corresponds to
        // the high byte of the n-th RGBA16 color of the color frame buffer.
        // The 2*n+1-th byte of texel 1 maps to a IA16 color where the low byte A (alpha) corresponds to
        // the low byte of the n-th RGBA16 color of the color frame buffer.

        // Since the combiner is in part set up to sum texel 0 color (I, intensity) with texel 1 alpha (A, alpha),
        // the resulting color in the drawn rectangle is a desaturated color as defined by the `VISMONO_FAC_*` values.

        gSPTextureRectangle(gfx++, 0, y << 2, SCREEN_WIDTH << 2, (y + height) << 2, G_TX_RENDERTILE, 2 << 5, 0, 2 << 10,
                            1 << 10);
        cfbFrag += SCREEN_WIDTH * height;
    }

    gDPPipeSync(gfx++);
    gSPEndDisplayList(gfx++);
    return gfx;
}

void VisMono_Draw(VisMono* this, Gfx** gfxP) {
    Gfx* gfx = *gfxP;
    u16* tlut;
    Gfx* dList;
    Gfx* dListEnd;

    if (this->tlut) {
        tlut = this->tlut;
    } else {
        tlut = Gfx_Alloc(&gfx, 256 * G_IM_SIZ_16b_BYTES);
        VisMono_DesaturateTLUT(this, tlut);
    }

    if (this->dList) {
        dList = this->dList;
    } else {
        dList = Gfx_Alloc(&gfx, VISMONO_DLSIZE * sizeof(Gfx));
        dListEnd = VisMono_DesaturateDList(this, dList);

        if (!(dListEnd <= dList + VISMONO_DLSIZE)) {
            LOG_ADDRESS("glistp_end", dListEnd, "../z_vismono.c", 257);
            LOG_ADDRESS("mono_dl", dList, "../z_vismono.c", 258);
            LOG_ADDRESS("mono_dl + (1+3+1+1+80*(7+2+2+3)+1)", dList + VISMONO_DLSIZE, "../z_vismono.c", 259);
            LOG_ADDRESS("(1+3+1+1+80*(7+2+2+3)+1)", VISMONO_DLSIZE, "../z_vismono.c", 260);
        }
        ASSERT(dListEnd <= dList + VISMONO_DLSIZE, "glistp_end <= mono_dl + DLSIZE", "../z_vismono.c", 262);
    }

    gDPPipeSync(gfx++);

    if (this->vis.scissorType == VIS_SETSCISSOR) {
        gDPSetScissor(gfx++, G_SC_NON_INTERLACE, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
    }

    gDPSetColor(gfx++, G_SETPRIMCOLOR, this->vis.primColor.rgba);
    gDPSetColor(gfx++, G_SETENVCOLOR, this->vis.envColor.rgba);

    gDPLoadTLUT_pal256(gfx++, tlut);

    gSPDisplayList(gfx++, dList);

    gDPPipeSync(gfx++);

    *gfxP = gfx;
}

void VisMono_DrawOld(VisMono* this) {
    UNUSED_NDEBUG Gfx* dListEnd;

    if (this->tlut == NULL) {
        this->tlut = SYSTEM_ARENA_MALLOC(256 * G_IM_SIZ_16b_BYTES, "../z_vismono.c", 283);
        VisMono_DesaturateTLUT(this, this->tlut);
    }

    if (this->dList == NULL) {
        this->dList = SYSTEM_ARENA_MALLOC(VISMONO_DLSIZE * sizeof(Gfx), "../z_vismono.c", 289);
        dListEnd = VisMono_DesaturateDList(this, this->dList);
        ASSERT(dListEnd <= this->dList + VISMONO_DLSIZE, "glistp_end <= this->mono_dl + DLSIZE", "../z_vismono.c", 292);
    }
}