summaryrefslogtreecommitdiff
path: root/src/racing/framebuffer_effects.c
blob: ccb3460e35743ead9db6eb721a2f2ec9ae1217dc (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
#include <libultraship.h>
#include "framebuffer_effects.h"
#include "mk64.h"
#include <assets/common_data.h>
#include "port/Engine.h"
#include "math_util.h"
#include <stdio.h>

int gfx_create_framebuffer(uint32_t width, uint32_t height, uint32_t native_width, uint32_t native_height,
                           uint8_t resize);

// A framebuffer that should only be used for drawing in the same frame that it is copied too
// i.e. the VisMono and VisFbuf effects
s32 gReusableFrameBuffer = -1;

// N64 resolution sized buffer (320x240), used by picto box and deku bubble
s32 gN64ResFrameBuffer = -1;

void FB_CreateFramebuffers(void) {
    if (gReusableFrameBuffer == -1) {
        gReusableFrameBuffer = gfx_create_framebuffer(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT, true);
    }

    if (gN64ResFrameBuffer == -1) {
        gN64ResFrameBuffer = gfx_create_framebuffer(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT, false);
    }
}

/**
 * Copies the current texture data from the source frame buffer to the destination frame buffer
 * Setting oncePerFrame ensures that the copy will only happen once every game frame. This
 * is important for effects that could be affected by increased frame interpolation (like motion blur).
 * A pointer to a boolean is passed to the render for the render to set once the copy has been performed.
 * This function uses opcodes from f3dex2 but may be called when s2dex is loaded, such as during shrink window. Make
 * sure f3dex2 is loaded before this function is called.
 */
void FB_CopyToFramebuffer(Gfx** gfxP, s32 fb_src, s32 fb_dest, u8 oncePerFrame, u8* hasCopied) {
    Gfx* gfx = *gfxP;

    // gSPMatrix(gfx++, &gIdentityMatrix, G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);

    gDPSetOtherMode(gfx++,
                    G_AD_DISABLE | G_CD_DISABLE | G_CK_NONE | G_TC_FILT | G_TF_POINT | G_TT_NONE | G_TL_TILE |
                        G_TD_CLAMP | G_TP_NONE | G_CYC_1CYCLE | G_PM_NPRIMITIVE,
                    G_AC_NONE | G_ZS_PRIM | G_RM_OPA_SURF | G_RM_OPA_SURF2);

    gSPClearGeometryMode(gfx++, G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR);
    gSPSetGeometryMode(gfx++, G_ZBUFFER | G_SHADE | G_SHADING_SMOOTH);

    gDPSetBlendColor(gfx++, 255, 255, 255, 8);
    gDPSetPrimDepth(gfx++, 0xFFFF, 0xFFFF);

    gDPSetEnvColor(gfx++, 255, 255, 255, 255);
    gDPSetCombineLERP(gfx++, TEXEL0, 0, ENVIRONMENT, 0, 0, 0, 0, ENVIRONMENT, TEXEL0, 0, ENVIRONMENT, 0, 0, 0, 0,
                      ENVIRONMENT);

    gDPSetScissor(gfx++, G_SC_NON_INTERLACE, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);

    gDPCopyFB(gfx++, fb_dest, fb_src, oncePerFrame, hasCopied);

    *gfxP = gfx;
}

/**
 * Copies a 4:3 slice of the current framebuffer scaled down to 320x240 to a CPU buffer address.
 * The buffer output will be in RGBA16 format.
 * Specify the byteswap flag to force the buffer data to be written as BigEndian, which is
 * required if the buffer is being used as a texture in F3D.
 */
void FB_WriteFramebufferSliceToCPU(Gfx** gfxP, void* buffer, u8 byteSwap) {
    Gfx* gfx = *gfxP;
    FB_CopyToFramebuffer(&gfx, 0, gReusableFrameBuffer, false, NULL);
    
    // Set the N64 resolution framebuffer as the draw target (320x240)
    gsSPSetFB(gfx++, gN64ResFrameBuffer);
    // Reset scissor for new framebuffer
    gDPSetScissor(gfx++, G_SC_NON_INTERLACE, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);

    int16_t s0 = 0, t0 = 0;
    int16_t s1 = OTRGetGameRenderWidth();
    int16_t t1 = OTRGetGameRenderHeight();
    float aspectRatio = OTRGetAspectRatio();
    float fourByThree = 4.0f / 3.0f;

    // Adjust the texture coordinates so that only a 4:3 region from the center is drawn
    // to the N64 resolution buffer. Currently ratios smaller than 4:3 will just stretch to fill.
    if (aspectRatio > fourByThree) {
        int16_t adjustedWidth = OTRGetGameRenderWidth() / (aspectRatio / fourByThree);
        s0 = (OTRGetGameRenderWidth() - adjustedWidth) / 2;
        s1 -= s0;
    }

    gDPSetTextureImageFB(gfx++, 0, 0, 0, gReusableFrameBuffer);
    gDPImageRectangle(gfx++, 0 << 2, 0 << 2, s0, t0, SCREEN_WIDTH << 2, SCREEN_HEIGHT << 2, s1, t1, G_TX_RENDERTILE,
                      OTRGetGameRenderWidth(), OTRGetGameRenderHeight());

    // Read the final N64 framebuffer back as rgba16 into the CPU-side buffer
    gDPReadFB(gfx++, gN64ResFrameBuffer, buffer, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, byteSwap);

    gsSPResetFB(gfx++);
    // Reset scissor for original framebuffer
    gDPSetScissor(gfx++, G_SC_NON_INTERLACE, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);

    *gfxP = gfx;
}

/**
 * Draws the texture data from the specified frame buffer as a full screen image
 */
void FB_DrawFromFramebuffer(Gfx** gfxP, s32 fb, u8 alpha) {
    Gfx* gfx = *gfxP;

    // gSPMatrix(gfx++, &gIdentityMatrix, G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);

    gDPSetEnvColor(gfx++, 255, 255, 255, alpha);

    gDPSetOtherMode(gfx++,
                    G_AD_NOISE | G_CD_NOISE | G_CK_NONE | G_TC_FILT | G_TF_POINT | G_TT_NONE | G_TL_TILE | G_TD_CLAMP |
                        G_TP_NONE | G_CYC_1CYCLE | G_PM_NPRIMITIVE,
                    G_AC_NONE | G_ZS_PRIM | G_RM_CLD_SURF | G_RM_CLD_SURF2);

    gSPClearGeometryMode(gfx++, G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR);
    gSPSetGeometryMode(gfx++, G_ZBUFFER | G_SHADE | G_SHADING_SMOOTH);

    gDPSetCombineLERP(gfx++, TEXEL0, 0, ENVIRONMENT, 0, 0, 0, 0, ENVIRONMENT, TEXEL0, 0, ENVIRONMENT, 0, 0, 0, 0,
                      ENVIRONMENT);

    gDPSetScissor(gfx++, G_SC_NON_INTERLACE, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);

    gDPSetTextureImageFB(gfx++, 0, 0, 0, fb);
    gDPImageRectangle(gfx++, OTRGetRectDimensionFromLeftEdge(0) << 2, 0 << 2, 0, 0,
                      OTRGetRectDimensionFromRightEdge(SCREEN_WIDTH) << 2, SCREEN_HEIGHT << 2, OTRGetGameRenderWidth(),
                      OTRGetGameRenderHeight(), G_TX_RENDERTILE, OTRGetGameRenderWidth(), OTRGetGameRenderHeight());

    *gfxP = gfx;
}

/**
 * Similar to FB_DrawFromFramebuffer, but scales the image relative to the center of the screen.
 * This function uses opcodes from f3dex2 but may be called when s2dex is loaded, such as during shrink window. Make
 * sure f3dex2 is loaded before this function is called.
 */
void FB_DrawFromFramebufferScaled(Gfx** gfxP, s32 fb, u8 alpha, float scaleX, float scaleY) {
    Gfx* gfx = *gfxP;

    gDPSetEnvColor(gfx++, 255, 255, 255, alpha);

    gDPSetOtherMode(gfx++,
                    G_AD_NOISE | G_CD_NOISE | G_CK_NONE | G_TC_FILT | G_TF_POINT | G_TT_NONE | G_TL_TILE | G_TD_CLAMP |
                        G_TP_NONE | G_CYC_1CYCLE | G_PM_NPRIMITIVE,
                    G_AC_NONE | G_ZS_PRIM | G_RM_CLD_SURF | G_RM_CLD_SURF2);

    gDPSetCombineLERP(gfx++, TEXEL0, 0, ENVIRONMENT, 0, 0, 0, 0, ENVIRONMENT, TEXEL0, 0, ENVIRONMENT, 0, 0, 0, 0,
                      ENVIRONMENT);

    gDPSetScissor(gfx++, G_SC_NON_INTERLACE, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);

    gDPSetTextureImageFB(gfx++, 0, 0, 0, fb);

    float x0 = SCREEN_WIDTH * 0.5f * scaleX;
    float y0 = SCREEN_HEIGHT * 0.5f * scaleY;

    gDPImageRectangle(gfx++, OTRGetRectDimensionFromLeftEdge(x0) << 2, (int) (y0) << 2, 0, 0,
                      OTRGetRectDimensionFromRightEdge((float) (SCREEN_WIDTH - x0)) << 2,
                      (int) ((float) (SCREEN_HEIGHT - y0)) << 2, OTRGetGameRenderWidth(), OTRGetGameRenderHeight(),
                      G_TX_RENDERTILE, OTRGetGameRenderWidth(), OTRGetGameRenderHeight());

    *gfxP = gfx;
}