Skip to content

Commit f2c9a67

Browse files
mardyWinterMute
authored andcommitted
ogc: save and restore EFB when rendering to a texture starts and stops
Indeed, the situation where a rendering to texture operation begins after the EFB has already been painted can happen. In order not to lose the existing EFB contents, make a copy of them into a texture (using the same size of the render-to-texture target) and then restore them by redrawing the saved texture over the EFB, once the render-to-texture operation has completed. Note that it's important that any calls to GX_SetPixelFmt() happen in the appropriate order: 1. Save the current EFB contents 2. Change the EFB format (by calling GX_SetPixelFmt()) 3. Perform all the render-to-texture drawing operations 4. Save the EFB into the target texture 5. Restore the EFB format 6. Restore the EFB contents by drawing the texture saved in step 1 Swapping the 5th and 6th steps does not cause any visible issues in Dolphin, but causes the EFB to be painted back with wrong colors on a real Wii.
1 parent b0e70e1 commit f2c9a67

1 file changed

Lines changed: 87 additions & 10 deletions

File tree

src/render/ogc/SDL_render_ogc.c

Lines changed: 87 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ typedef struct
4545
bool vsync;
4646
u8 efb_pixel_format;
4747
SDL_Texture *render_target;
48+
SDL_Texture *saved_efb_texture;
4849
} OGC_RenderData;
4950

5051
typedef struct
@@ -58,6 +59,8 @@ typedef struct
5859
u8 needed_stages; // Normally 1, set to 2 for palettized formats
5960
} OGC_TextureData;
6061

62+
static void OGC_DestroyTexture(SDL_Renderer *renderer, SDL_Texture *texture);
63+
6164
static void OGC_WindowEvent(SDL_Renderer *renderer, const SDL_WindowEvent *event)
6265
{
6366
}
@@ -102,6 +105,38 @@ static inline void OGC_SetBlendMode(SDL_Renderer *renderer, SDL_BlendMode blend_
102105
set_blend_mode_real(renderer, blend_mode);
103106
}
104107

108+
static void load_efb_from_texture(SDL_Renderer *renderer, SDL_Texture *texture)
109+
{
110+
OGC_TextureData *ogc_tex = texture->driverdata;
111+
112+
OGC_load_texture(ogc_tex->texels, texture->w, texture->h,
113+
ogc_tex->format, SDL_ScaleModeNearest);
114+
115+
GX_ClearVtxDesc();
116+
GX_SetVtxDesc(GX_VA_POS, GX_DIRECT);
117+
GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XY, GX_S16, 0);
118+
119+
GX_SetVtxDesc(GX_VA_TEX0, GX_DIRECT);
120+
GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_U8, 0);
121+
GX_SetNumTexGens(1);
122+
123+
GX_SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY);
124+
GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);
125+
GX_SetTevOp(GX_TEVSTAGE0, GX_REPLACE);
126+
GX_SetNumTevStages(1);
127+
128+
GX_Begin(GX_QUADS, GX_VTXFMT0, 4);
129+
GX_Position2s16(0, 0);
130+
GX_TexCoord2u8(0, 0);
131+
GX_Position2s16(texture->w, 0);
132+
GX_TexCoord2u8(1, 0);
133+
GX_Position2s16(texture->w, texture->h);
134+
GX_TexCoord2u8(1, 1);
135+
GX_Position2s16(0, texture->h);
136+
GX_TexCoord2u8(0, 1);
137+
GX_End();
138+
}
139+
105140
static void save_efb_to_texture(SDL_Texture *texture)
106141
{
107142
OGC_TextureData *ogc_tex = texture->driverdata;
@@ -192,6 +227,42 @@ static void OGC_SetTextureScaleMode(SDL_Renderer *renderer,
192227
* loading it in OGC_load_texture(). */
193228
}
194229

230+
static SDL_Texture *create_efb_texture(OGC_RenderData *data, SDL_Texture *target)
231+
{
232+
/* Note: we do return a SDL_Texture, but not via SDL's API, since that does
233+
* a bunch of other stuffs we don't care about. We create this texture for
234+
* our internal use, so we initialize only those fields we care about. */
235+
SDL_Texture *texture;
236+
OGC_TextureData *ogc_tex;
237+
u32 texture_size;
238+
239+
texture = SDL_calloc(1, sizeof(*texture));
240+
if (!texture) goto fail_texture_alloc;
241+
242+
ogc_tex = SDL_calloc(1, sizeof(OGC_TextureData));
243+
if (!ogc_tex) goto fail_ogc_tex_alloc;
244+
245+
ogc_tex->format = data->efb_pixel_format == GX_PF_RGB565_Z16 ?
246+
GX_TF_RGB565 : GX_TF_RGBA8;
247+
texture->w = target->w;
248+
texture->h = target->h;
249+
texture_size = GX_GetTexBufferSize(texture->w, texture->h, ogc_tex->format,
250+
GX_FALSE, 0);
251+
ogc_tex->texels = memalign(32, texture_size);
252+
if (!ogc_tex->texels) goto fail_texels_alloc;
253+
254+
texture->driverdata = ogc_tex;
255+
return texture;
256+
257+
fail_texels_alloc:
258+
SDL_free(ogc_tex->texels);
259+
fail_ogc_tex_alloc:
260+
SDL_free(texture);
261+
fail_texture_alloc:
262+
SDL_OutOfMemory();
263+
return NULL;
264+
}
265+
195266
static int OGC_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture)
196267
{
197268
OGC_RenderData *data = renderer->driverdata;
@@ -203,16 +274,11 @@ static int OGC_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture)
203274
}
204275

205276
if (data->ops_after_present > 0) {
206-
/* We should save the current EFB contents into the window data.
207-
* However, it's unclear whether this is a possible scenario, since
208-
* all actual drawing happens in RunCommandQueue() and this method
209-
* will not be called in between of the drawing operations; but
210-
* just to be on the safe side, log a warning. We can come back to
211-
* this later and implement the EFB saving if we see that this
212-
* happens in real life.
213-
*/
214-
SDL_LogWarn(SDL_LOG_CATEGORY_RENDER,
215-
"Render target set after drawing!");
277+
/* Save the current EFB contents if we already drew something onto
278+
* it. We'll restore it later, when the rendering target is reset
279+
* to NULL (the screen). */
280+
data->saved_efb_texture = create_efb_texture(data, texture);
281+
save_efb_to_texture(data->saved_efb_texture);
216282
}
217283

218284
if (SDL_ISPIXELFORMAT_ALPHA(texture->format)) {
@@ -229,6 +295,17 @@ static int OGC_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture)
229295
GX_SetPixelFmt(data->efb_pixel_format, GX_ZC_LINEAR);
230296
}
231297

298+
/* Restore the EFB to how it was before the we started to render to a
299+
* texture. */
300+
if (!texture && data->saved_efb_texture) {
301+
load_efb_from_texture(renderer, data->saved_efb_texture);
302+
/* Flush the draw operation before destroying the texture */
303+
GX_DrawDone();
304+
OGC_DestroyTexture(renderer, data->saved_efb_texture);
305+
SDL_free(data->saved_efb_texture);
306+
data->saved_efb_texture = NULL;
307+
}
308+
232309
return 0;
233310
}
234311

0 commit comments

Comments
 (0)