From 13fac3d94b4d846ce8036bb5c85bc04a298ecfa2 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Sun, 16 Feb 2025 16:59:11 +0300 Subject: [PATCH 01/18] ogc: fix switching of render targets So far, our implementation of SDL_SetRenderTarget() was only correct in the case where we were switching between a texture target and the display (the NULL target), but was working incorrectly when switching from one texture target to another texture: in that case, we were not saving the EFB contents into the first texture, but into the display copy buffer. Change the code to always save the EFB contents into the texture target, if we have one. This issue was found testing the Angband game, where all the cached characters would be rendered as black boxes due to this bug. --- src/render/ogc/SDL_render_ogc.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/render/ogc/SDL_render_ogc.c b/src/render/ogc/SDL_render_ogc.c index ece76c7fcde78..b3edcc93c5210 100644 --- a/src/render/ogc/SDL_render_ogc.c +++ b/src/render/ogc/SDL_render_ogc.c @@ -274,24 +274,24 @@ static int OGC_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture) OGC_RenderData *data = renderer->driverdata; u8 desired_efb_pixel_format = GX_PF_RGB8_Z24; + if (data->render_target) { + save_efb_to_texture(data->render_target); + } else if (data->ops_after_present > 0) { + /* Save the current EFB contents if we already drew something onto + * it. We'll restore it later, when the rendering target is reset + * to NULL (the screen). */ + data->saved_efb_texture = create_efb_texture(data, texture); + save_efb_to_texture(data->saved_efb_texture); + } + if (texture) { if (texture->w > MAX_EFB_WIDTH || texture->h > MAX_EFB_HEIGHT) { - return SDL_SetError("Render target bigger than EFB"); - } - - if (data->ops_after_present > 0) { - /* Save the current EFB contents if we already drew something onto - * it. We'll restore it later, when the rendering target is reset - * to NULL (the screen). */ - data->saved_efb_texture = create_efb_texture(data, texture); - save_efb_to_texture(data->saved_efb_texture); + return SDL_SetError("Render target (%dx%d) bigger than EFB", texture->w, texture->h); } if (SDL_ISPIXELFORMAT_ALPHA(texture->format)) { desired_efb_pixel_format = GX_PF_RGBA6_Z24; } - } else if (data->render_target) { - save_efb_to_texture(data->render_target); } data->render_target = texture; From 60b652c5246ea1fb802f60bde77717bdcdbde1ef Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Sun, 16 Feb 2025 22:33:07 +0300 Subject: [PATCH 02/18] ogc: initialize EFB with texture contents when rendering into it When the render target is being set to a texture, we must load the texture's contents into the EFB. So far we haven't hit this issue because most applications do not reuse the same texture across different SDL_SetRenderTarget() calls, but this is a valid use case (seen with the Angband game). --- src/render/ogc/SDL_render_ogc.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/render/ogc/SDL_render_ogc.c b/src/render/ogc/SDL_render_ogc.c index b3edcc93c5210..1d3c8cce1a0f4 100644 --- a/src/render/ogc/SDL_render_ogc.c +++ b/src/render/ogc/SDL_render_ogc.c @@ -301,9 +301,11 @@ static int OGC_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture) GX_SetPixelFmt(data->efb_pixel_format, GX_ZC_LINEAR); } - /* Restore the EFB to how it was before the we started to render to a - * texture. */ - if (!texture && data->saved_efb_texture) { + if (texture) { + load_efb_from_texture(renderer, texture); + } else if (data->saved_efb_texture) { + /* Restore the EFB to how it was before the we started to render to a + * texture. */ load_efb_from_texture(renderer, data->saved_efb_texture); /* Flush the draw operation before destroying the texture */ GX_DrawDone(); From 4b6d6a22f8fb6c86e1f56c43c810df5b844ae5c2 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Sun, 16 Feb 2025 22:36:09 +0300 Subject: [PATCH 03/18] ogc: do not invoke opengx functions if GL is not in use Calling this function just generates an annoying warning every time: WARN: ogx_prepare_swap_buffers() called but opengx not used in build! This happens for all applications which do not use OpenGL. So, let's avoid calling any ogx_ function if the GL context has not been initialized (that is, if the SDL_WINDOW_OPENGL flag was not passed when creating a window). --- src/video/ogc/SDL_ogcvideo.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/video/ogc/SDL_ogcvideo.c b/src/video/ogc/SDL_ogcvideo.c index a1c32b96d4657..2f4d80b46e483 100644 --- a/src/video/ogc/SDL_ogcvideo.c +++ b/src/video/ogc/SDL_ogcvideo.c @@ -345,7 +345,8 @@ void OGC_video_flip(_THIS, bool vsync) SDL_VideoData *videodata = _this->driverdata; void *xfb = OGC_video_get_xfb(_this); - if (ogx_prepare_swap_buffers() < 0) return; + if (_this->gl_config.driver_loaded && + ogx_prepare_swap_buffers() < 0) return; #ifdef __wii__ OGC_draw_cursor(_this); From 073d528a46090800b4f09f21e7b3c9835d99f83c Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Mon, 17 Feb 2025 17:41:53 +0300 Subject: [PATCH 04/18] ogc: do not clear EFB after presenting it SDL documentation says that the framebuffer contents after presenting it are unspecified, and we need them to remain if we want to be able to animate the mouse cursor without the application's support (see next commit). But we need to remove the small optimization we had in OGC_RenderClear(), because now we always need to clear the EFB unconditionally. --- src/render/ogc/SDL_render_ogc.c | 7 ------- src/video/ogc/SDL_ogcvideo.c | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/render/ogc/SDL_render_ogc.c b/src/render/ogc/SDL_render_ogc.c index 1d3c8cce1a0f4..63d9348871573 100644 --- a/src/render/ogc/SDL_render_ogc.c +++ b/src/render/ogc/SDL_render_ogc.c @@ -460,13 +460,6 @@ static int OGC_RenderClear(SDL_Renderer *renderer, SDL_RenderCommand *cmd) cmd->data.color.a }; - /* If nothing has been drawn after Present, and if the clear color has not - * changed, there's no need to do anything here. */ - if (data->ops_after_present == 0 && - GX_COLOR_AS_U32(c) == GX_COLOR_AS_U32(data->clear_color)) { - return 0; - } - data->clear_color = c; GX_SetCopyClear(c, GX_MAX_Z24); if (renderer->target) { diff --git a/src/video/ogc/SDL_ogcvideo.c b/src/video/ogc/SDL_ogcvideo.c index 2f4d80b46e483..3aeecc6454013 100644 --- a/src/video/ogc/SDL_ogcvideo.c +++ b/src/video/ogc/SDL_ogcvideo.c @@ -351,7 +351,7 @@ void OGC_video_flip(_THIS, bool vsync) #ifdef __wii__ OGC_draw_cursor(_this); #endif - GX_CopyDisp(xfb, GX_TRUE); + GX_CopyDisp(xfb, GX_FALSE); GX_DrawDone(); GX_Flush(); From 62601ec1d002ca8a0631f44b79124c2438fc5600 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Mon, 17 Feb 2025 18:14:33 +0300 Subject: [PATCH 05/18] ogc: mock implementation of ShowMessageBox This is still a TODO, but for the time being print the message into the logs, so that it does not get completely lost. --- src/video/ogc/SDL_ogcvideo.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/video/ogc/SDL_ogcvideo.c b/src/video/ogc/SDL_ogcvideo.c index 3aeecc6454013..79513e74d0461 100644 --- a/src/video/ogc/SDL_ogcvideo.c +++ b/src/video/ogc/SDL_ogcvideo.c @@ -205,6 +205,15 @@ static void OGC_ShowWindow(_THIS, SDL_Window *window) SDL_SetKeyboardFocus(window); } +static int OGC_ShowMessageBox(_THIS, const SDL_MessageBoxData *messageboxdata, + int *buttonid) +{ + /* Unimplemented, but at least show the message in the log */ + SDL_SetError("ShowMessageBox unimplemented: \"%s\", \"%s\"", + messageboxdata->title, messageboxdata->message); + return 0; +} + /* OGC driver bootstrap functions */ static void OGC_DeleteDevice(SDL_VideoDevice *device) @@ -243,6 +252,7 @@ static SDL_VideoDevice *OGC_CreateDevice(void) device->CreateWindowFramebuffer = SDL_OGC_CreateWindowFramebuffer; device->UpdateWindowFramebuffer = SDL_OGC_UpdateWindowFramebuffer; device->DestroyWindowFramebuffer = SDL_OGC_DestroyWindowFramebuffer; + device->ShowMessageBox = OGC_ShowMessageBox; #ifdef SDL_VIDEO_OPENGL device->GL_LoadLibrary = SDL_OGC_GL_LoadLibrary; From 105b256ef09a33fca23e3d7aede3ff258664f57c Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Tue, 18 Feb 2025 22:49:54 +0300 Subject: [PATCH 06/18] ogc: fix implementation of clipping rectangle The GX_SetClipMode() function has very little to do with the clip rect, as it's about clipping vertices in the 3D space. What we want to use for 2D clipping is the scissor test. Therefore we must reset the scissor rectangle to the viewport rect when clipping is disabled. --- src/render/ogc/SDL_render_ogc.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/render/ogc/SDL_render_ogc.c b/src/render/ogc/SDL_render_ogc.c index 63d9348871573..f94cc8b6068e8 100644 --- a/src/render/ogc/SDL_render_ogc.c +++ b/src/render/ogc/SDL_render_ogc.c @@ -441,9 +441,11 @@ static int OGC_RenderSetClipRect(SDL_Renderer *renderer, SDL_RenderCommand *cmd) GX_SetScissor(renderer->viewport.x + rect->x, renderer->viewport.y + rect->y, rect->w, rect->h); - GX_SetClipMode(GX_CLIP_ENABLE); } else { - GX_SetClipMode(GX_CLIP_DISABLE); + GX_SetScissor(renderer->viewport.x, + renderer->viewport.y, + renderer->viewport.w, + renderer->viewport.h); } return 0; From d7096563a2e5a75d402591467cf0acaf32719d8f Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Mon, 17 Feb 2025 17:46:17 +0300 Subject: [PATCH 07/18] ogc: draw cursor if application is not refreshing the screen While most applications draw frames at regular intervals, some others only request the screen to be drawn (AKA swapped, flipped) only when its contents have changed. This poses a problem for us, because if the application is using a mouse (the Wiimote), the cursor movement will not be visible, since we draw it after each frame swap. Here we come up with a solution, where we compare how many times the mouse has been polled per each frame drawn: usually this ratio should be 1:1 (or close to that), so if we see that this is above 10:1, we assume that the application is not periodically redrawing and we activate our "draw cursor mode", where we issue a screen swap ourselves every time that the mouse is polled (with a time check, to avoid redrawing more than 30 times per second). The heuristics might be revisited in the future, if needed. The "draw cursor mode" works as following: 1. When the application explicitly draws a frame, we save the area behind the cursor into a texture; 2. We draw the cursor 3. When polling events, we do one of these: a. If 33 ms have not elapsed since last draw, we do nothing (back to point 1) b. if we do not have a saved cursor background, we do nothing (back to point 1) c. Otherwise, we repaint our cursor background (to erase the cursor from the image), then continue to next point: 4. We manually invoke a screen swap, that is we go back to point 1 This algorithm has been tested on the Angband game. --- src/video/ogc/SDL_ogcevents.c | 4 + src/video/ogc/SDL_ogcmouse.c | 216 ++++++++++++++++++++++++++++++---- src/video/ogc/SDL_ogcmouse.h | 2 + src/video/ogc/SDL_ogcvideo.c | 1 + 4 files changed, 198 insertions(+), 25 deletions(-) diff --git a/src/video/ogc/SDL_ogcevents.c b/src/video/ogc/SDL_ogcevents.c index 6fb8ac4557e34..4e4440d12dffe 100644 --- a/src/video/ogc/SDL_ogcevents.c +++ b/src/video/ogc/SDL_ogcevents.c @@ -84,6 +84,10 @@ static void pump_ir_events(_THIS) } } } + + if (OGC_prep_draw_cursor(_this)) { + OGC_video_flip(_this, false); + } } #endif diff --git a/src/video/ogc/SDL_ogcmouse.c b/src/video/ogc/SDL_ogcmouse.c index 7f750ec7e0fe7..6e3bda25650cc 100644 --- a/src/video/ogc/SDL_ogcmouse.c +++ b/src/video/ogc/SDL_ogcmouse.c @@ -33,9 +33,12 @@ #include "../SDL_sysvideo.h" #include "../../render/SDL_sysrender.h" +#include #include #include #include +#include +#include #include typedef struct _OGC_CursorData @@ -45,20 +48,69 @@ typedef struct _OGC_CursorData int w, h; } OGC_CursorData; -static void draw_cursor_rect(OGC_CursorData *curdata) +typedef struct +{ + void *texels; + int16_t x, y; + uint16_t w, h, maxside; +} OGC_CursorBackground; + +static OGC_CursorBackground s_cursor_background; +static int s_draw_counter = 0; +static bool s_extra_draw_enabled = false; +static bool s_2d_viewport_setup = false; + +static void draw_rect(s16 x, s16 y, u16 w, u16 h) { GX_Begin(GX_QUADS, GX_VTXFMT0, 4); - GX_Position2s16(-curdata->hot_x, -curdata->hot_y); + GX_Position2s16(x, y); GX_TexCoord2u8(0, 0); - GX_Position2s16(curdata->w - curdata->hot_x, -curdata->hot_y); + GX_Position2s16(x + w, y); GX_TexCoord2u8(1, 0); - GX_Position2s16(curdata->w - curdata->hot_x, curdata->h - curdata->hot_y); + GX_Position2s16(x + w, h + y); GX_TexCoord2u8(1, 1); - GX_Position2s16(-curdata->hot_x, curdata->h - curdata->hot_y); + GX_Position2s16(x, h + y); GX_TexCoord2u8(0, 1); GX_End(); } +static void draw_cursor_rect(OGC_CursorData *curdata) +{ + draw_rect(-curdata->hot_x, -curdata->hot_y, curdata->w, curdata->h); +} + +static void setup_2d_viewport(_THIS) +{ + int screen_w, screen_h; + + if (s_2d_viewport_setup) return; + + screen_w = _this->displays[0].current_mode.w; + screen_h = _this->displays[0].current_mode.h; + + OGC_set_viewport(0, 0, screen_w, screen_h); + + GX_ClearVtxDesc(); + GX_SetVtxDesc(GX_VA_POS, GX_DIRECT); + GX_SetVtxDesc(GX_VA_TEX0, GX_DIRECT); + GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XY, GX_S16, 0); + GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_U8, 0); + GX_SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY); + + GX_SetTevOp(GX_TEVSTAGE0, GX_REPLACE); + GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0); + GX_SetNumTevStages(1); + GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR); + GX_SetZMode(GX_DISABLE, GX_ALWAYS, GX_FALSE); + GX_SetCullMode(GX_CULL_NONE); + GX_SetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0); + + GX_SetNumTexGens(1); + GX_SetCurrentMtx(GX_PNMTX1); + + s_2d_viewport_setup = true; +} + /* Create a cursor from a surface */ static SDL_Cursor *OGC_CreateCursor(SDL_Surface *surface, int hot_x, int hot_y) { @@ -178,6 +230,11 @@ void OGC_draw_cursor(_THIS) int screen_w, screen_h; float angle = 0.0f; + s_draw_counter++; + + /* mark the texture as invalid */ + s_cursor_background.x = SHRT_MIN; + if (!mouse || !mouse->cursor_shown || !mouse->cur_cursor || !mouse->cur_cursor->driverdata) { return; @@ -195,6 +252,62 @@ void OGC_draw_cursor(_THIS) screen_h = _this->displays[0].current_mode.h; curdata = mouse->cur_cursor->driverdata; + + if (s_extra_draw_enabled) { + /* Save the are behind the cursor. We could use GX_ReadBoundingBox() to + * figure out which area to save, but that would require calling the + * drawing function once mode. So, let's just take a guess at the area, + * taking into account possible cursor rotation. */ + s16 x, y; + u16 w, h, radius, side; + u32 texture_size; + + /* +1 is for the rounding of x and y */ + radius = MAX(curdata->w, curdata->h) + 1; + x = mouse->x - radius; + y = mouse->y - radius; + /* x and y must be multiples of 2 */ + if (x % 2) x--; + if (y % 2) y--; + w = h = side = radius * 2; + if (x < 0) { + w += x; + x = 0; + } else if (x + w > screen_w) { + w = screen_w - x; + } + + if (y < 0) { + h += y; + y = 0; + } else if (y + h > screen_h) { + h = screen_h - y; + } + + /* Make sure all our variables are properly aligned */ + while (side % 4) side++; + while (w % 4) w++; + while (h % 4) h++; + + if (w > 0 && h > 0) { + texture_size = GX_GetTexBufferSize(side, side, GX_TF_RGBA8, + GX_FALSE, 0); + if (!s_cursor_background.texels || side > s_cursor_background.maxside) { + free(s_cursor_background.texels); + s_cursor_background.texels = memalign(32, texture_size); + s_cursor_background.maxside = side; + } + DCInvalidateRange(s_cursor_background.texels, texture_size); + GX_SetTexCopySrc(x, y, w, h); + GX_SetTexCopyDst(w, h, GX_TF_RGBA8, GX_FALSE); + GX_CopyTex(s_cursor_background.texels, GX_FALSE); + s_cursor_background.x = x; + s_cursor_background.y = y; + s_cursor_background.w = w; + s_cursor_background.h = h; + } + } + OGC_load_texture(curdata->texels, curdata->w, curdata->h, GX_TF_RGBA8, SDL_ScaleModeNearest); @@ -208,33 +321,19 @@ void OGC_draw_cursor(_THIS) guMtxTransApply(mv, mv, mouse->x, mouse->y, 0); GX_LoadPosMtxImm(mv, GX_PNMTX1); - OGC_set_viewport(0, 0, screen_w, screen_h); - - GX_ClearVtxDesc(); - GX_SetVtxDesc(GX_VA_POS, GX_DIRECT); - GX_SetVtxDesc(GX_VA_TEX0, GX_DIRECT); - GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XY, GX_S16, 0); - GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_U8, 0); - GX_SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY); - - GX_SetTevOp(GX_TEVSTAGE0, GX_REPLACE); - GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0); - GX_SetNumTevStages(1); - GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR); - GX_SetZMode(GX_DISABLE, GX_ALWAYS, GX_FALSE); - GX_SetCullMode(GX_CULL_NONE); - GX_SetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0); - + setup_2d_viewport(_this); - GX_SetNumTexGens(1); - GX_SetCurrentMtx(GX_PNMTX1); draw_cursor_rect(curdata); - GX_SetCurrentMtx(GX_PNMTX0); GX_DrawDone(); +} +void OGC_restore_viewport(_THIS) +{ /* Restore default state for SDL (opengx restores it at every frame, so we * don't care about it) */ + s_2d_viewport_setup = false; GX_SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE); + GX_SetCurrentMtx(GX_PNMTX0); if (_this->windows) { /* Restore previous viewport for the renderer */ SDL_Renderer *renderer = SDL_GetRenderer(_this->windows); @@ -245,6 +344,73 @@ void OGC_draw_cursor(_THIS) } } +bool OGC_prep_draw_cursor(_THIS) +{ + GXTexObj background; + Mtx mv; + static u32 last_draw_ms = 0; + u32 current_time_ms, elapsed_ms; + static int call_counter = 0; + static int last_draw_counter = 0; + + /* Ignore calls when a render target is set or OpenGL is not ready to swap + * the framebuffer */ + SDL_Renderer *renderer = SDL_GetRenderer(_this->windows); + if (renderer && renderer->target) return false; + + if (_this->gl_config.driver_loaded && + ogx_prepare_swap_buffers() < 0) return false; + + /* If this function is called repeatedly during the same frame, we assume + * that this is one of those applications that call SDL_OGC_GL_SwapWindow, + * SDL_UpdateWindowSurface or SDL_RenderPresent only if the video contents + * have actually changed. + * If that's the case, we toggle a flag that makes us redraw the screen + * when the mouse position has changed. + */ + if (!s_extra_draw_enabled) { + if (last_draw_counter != s_draw_counter) { + call_counter = 1; + last_draw_counter = s_draw_counter; + return false; + } + + if (call_counter++ > 10) { + s_extra_draw_enabled = true; + } else { + return false; + } + } + + /* Avoid drawing too often. 30 FPS should be enough */ + current_time_ms = gettime() / TB_TIMER_CLOCK; + elapsed_ms = current_time_ms - last_draw_ms; + if (elapsed_ms < 33) return false; + + /* If we have a texture for the cursor background, restore it; otherwise, + * we shouldn't draw the cursor. */ + if (!s_cursor_background.texels) return false; + + if (s_cursor_background.x != SHRT_MIN) { + setup_2d_viewport(_this); + + GX_PixModeSync(); + GX_InitTexObj(&background, s_cursor_background.texels, + s_cursor_background.w, s_cursor_background.h, + GX_TF_RGBA8, GX_CLAMP, GX_CLAMP, GX_FALSE); + GX_InitTexObjLOD(&background, GX_NEAR, GX_NEAR, + 0.0f, 0.0f, 0.0f, 0, 0, GX_ANISO_1); + GX_LoadTexObj(&background, GX_TEXMAP0); + GX_InvalidateTexAll(); + + guMtxIdentity(mv); + GX_LoadPosMtxImm(mv, GX_PNMTX1); + draw_rect(s_cursor_background.x, s_cursor_background.y, + s_cursor_background.w, s_cursor_background.h); + last_draw_ms = current_time_ms; + } + return true; +} #endif /* SDL_VIDEO_DRIVER_OGC */ /* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/video/ogc/SDL_ogcmouse.h b/src/video/ogc/SDL_ogcmouse.h index 5035b4745a299..aa9ff173feb5a 100644 --- a/src/video/ogc/SDL_ogcmouse.h +++ b/src/video/ogc/SDL_ogcmouse.h @@ -28,6 +28,8 @@ void OGC_InitMouse(_THIS); void OGC_QuitMouse(_THIS); void OGC_draw_cursor(_THIS); +void OGC_restore_viewport(_THIS); +bool OGC_prep_draw_cursor(_THIS); SDL_Cursor *OGC_CreateSystemCursor(SDL_SystemCursor id); #endif /* SDL_OGC_mouse_h_ */ diff --git a/src/video/ogc/SDL_ogcvideo.c b/src/video/ogc/SDL_ogcvideo.c index 79513e74d0461..5653700f738c3 100644 --- a/src/video/ogc/SDL_ogcvideo.c +++ b/src/video/ogc/SDL_ogcvideo.c @@ -360,6 +360,7 @@ void OGC_video_flip(_THIS, bool vsync) #ifdef __wii__ OGC_draw_cursor(_this); + OGC_restore_viewport(_this); #endif GX_CopyDisp(xfb, GX_FALSE); GX_DrawDone(); From 09c43b5abc717f917d24b4256df75c731fbf9a29 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Wed, 19 Feb 2025 18:47:58 +0300 Subject: [PATCH 08/18] ogc, renderer: count clearing the screen as an operation The clear operation changes the contents of the framebuffer, so it should count as a drawing operation. --- src/render/ogc/SDL_render_ogc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/render/ogc/SDL_render_ogc.c b/src/render/ogc/SDL_render_ogc.c index f94cc8b6068e8..123ff169cbeaf 100644 --- a/src/render/ogc/SDL_render_ogc.c +++ b/src/render/ogc/SDL_render_ogc.c @@ -469,6 +469,7 @@ static int OGC_RenderClear(SDL_Renderer *renderer, SDL_RenderCommand *cmd) } else { GX_CopyDisp(OGC_video_get_xfb(SDL_GetVideoDevice()), GX_TRUE); } + data->ops_after_present++; return 0; } From a0585c1b38c9465e529f670110b06aa54246f8a1 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Wed, 19 Feb 2025 23:00:41 +0300 Subject: [PATCH 09/18] ogc: set the number of stages when drawing solid geometry The number of stages was not set, so this was working out of pure luck. --- src/render/ogc/SDL_render_ogc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/render/ogc/SDL_render_ogc.c b/src/render/ogc/SDL_render_ogc.c index 123ff169cbeaf..9510b29997ee9 100644 --- a/src/render/ogc/SDL_render_ogc.c +++ b/src/render/ogc/SDL_render_ogc.c @@ -511,6 +511,7 @@ static int OGC_RenderGeometry(SDL_Renderer *renderer, void *vertices, GX_SetNumTevStages(stage - GX_TEVSTAGE0 + 1); } else { GX_SetTevOp(GX_TEVSTAGE0, GX_PASSCLR); + GX_SetNumTevStages(1); } GX_Begin(GX_TRIANGLES, GX_VTXFMT0, count); From c97f64b4bc0a615f57f838c8084e7a80f9dea37a Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Thu, 20 Feb 2025 17:45:10 +0300 Subject: [PATCH 10/18] ogc: fix format selection for EFB copy The old code was wrong, because the condition was never satisfied: we do not use the GX_PF_RGB565_Z16 format for the EFB, but always either GX_PF_RGB8_Z24 or GX_PF_RGBA6_Z24. Invert the logic of the condition to check for GX_PF_RGBA6_Z24, which is the only EFB format which has an alpha channel, and store the EFB backup copy in the GX_TF_RGBA8 in that case, otherwise use GX_TF_RGB565. The latter mode brings some precision loss, but hopefully that's OK for all applications. If not, we can change this code again and set the format to GX_TF_RGBA8 unconditionally, at the expense of increased RAM usage. --- src/render/ogc/SDL_render_ogc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/render/ogc/SDL_render_ogc.c b/src/render/ogc/SDL_render_ogc.c index 9510b29997ee9..7740a8f2cc64f 100644 --- a/src/render/ogc/SDL_render_ogc.c +++ b/src/render/ogc/SDL_render_ogc.c @@ -248,8 +248,8 @@ static SDL_Texture *create_efb_texture(OGC_RenderData *data, SDL_Texture *target ogc_tex = SDL_calloc(1, sizeof(OGC_TextureData)); if (!ogc_tex) goto fail_ogc_tex_alloc; - ogc_tex->format = data->efb_pixel_format == GX_PF_RGB565_Z16 ? - GX_TF_RGB565 : GX_TF_RGBA8; + ogc_tex->format = data->efb_pixel_format == GX_PF_RGBA6_Z24 ? + GX_TF_RGBA8 : GX_TF_RGB565; texture->w = target->w; texture->h = target->h; texture_size = GX_GetTexBufferSize(texture->w, texture->h, ogc_tex->format, From c4e332b6eecf2491861069936ee4d1a8aa478351 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Thu, 20 Feb 2025 17:54:28 +0300 Subject: [PATCH 11/18] ogc: fix EFB contents loss when switching rendering targets The previous code would fail in this scenario: 1. App has drawn something to the EFB 2. Set render target to a texture (the EFB gets copied into the backup) 3. Draw something again 4. Set the render target to NULL (the backup copy gets restored into the EFB, then the backup copy gets destroyed) 5. Set the render target to a texture (no copy of the EFB gets made, since we didn't modify it) 6. Draw something 7. Set the render target to NULL (the EFB does not get restored, since no copy was made) We fix this by not destroying the EFB copy in point 4, and instead by keeping it indefinitely: it might seem a waste of memory but, as a matter of fact, if an applications switches the render target they are likely to be doing it at every frame as part of their drawing routine, so this memory *is* going to be reused. And we avoid needless deallocations and reallocations. Note that in order to be able to reuse the same texture, we allocate if with the full size of the window, and we always save the full EFB. We might want to optimize it later, to only save the parts that we are going to modify. --- src/render/ogc/SDL_render_ogc.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/render/ogc/SDL_render_ogc.c b/src/render/ogc/SDL_render_ogc.c index 7740a8f2cc64f..c15c2f6068b2f 100644 --- a/src/render/ogc/SDL_render_ogc.c +++ b/src/render/ogc/SDL_render_ogc.c @@ -233,7 +233,7 @@ static void OGC_SetTextureScaleMode(SDL_Renderer *renderer, * loading it in OGC_load_texture(). */ } -static SDL_Texture *create_efb_texture(OGC_RenderData *data, SDL_Texture *target) +static SDL_Texture *create_efb_texture(OGC_RenderData *data, SDL_Window *window) { /* Note: we do return a SDL_Texture, but not via SDL's API, since that does * a bunch of other stuffs we don't care about. We create this texture for @@ -250,8 +250,8 @@ static SDL_Texture *create_efb_texture(OGC_RenderData *data, SDL_Texture *target ogc_tex->format = data->efb_pixel_format == GX_PF_RGBA6_Z24 ? GX_TF_RGBA8 : GX_TF_RGB565; - texture->w = target->w; - texture->h = target->h; + texture->w = window->w; + texture->h = window->h; texture_size = GX_GetTexBufferSize(texture->w, texture->h, ogc_tex->format, GX_FALSE, 0); ogc_tex->texels = memalign(32, texture_size); @@ -280,7 +280,8 @@ static int OGC_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture) /* Save the current EFB contents if we already drew something onto * it. We'll restore it later, when the rendering target is reset * to NULL (the screen). */ - data->saved_efb_texture = create_efb_texture(data, texture); + if (!data->saved_efb_texture) + data->saved_efb_texture = create_efb_texture(data, renderer->window); save_efb_to_texture(data->saved_efb_texture); } @@ -307,11 +308,7 @@ static int OGC_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture) /* Restore the EFB to how it was before the we started to render to a * texture. */ load_efb_from_texture(renderer, data->saved_efb_texture); - /* Flush the draw operation before destroying the texture */ - GX_DrawDone(); - OGC_DestroyTexture(renderer, data->saved_efb_texture); - SDL_free(data->saved_efb_texture); - data->saved_efb_texture = NULL; + /* We don't free data->saved_efb_texture, it will be reused */ } return 0; @@ -656,6 +653,12 @@ static void OGC_DestroyRenderer(SDL_Renderer *renderer) OGC_RenderData *data = renderer->driverdata; if (data) { + GX_DrawDone(); + if (data->saved_efb_texture) { + OGC_DestroyTexture(renderer, data->saved_efb_texture); + SDL_free(data->saved_efb_texture); + } + SDL_free(data); } From 5a6555870221641b616ee968901c717f41cb2f3e Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Thu, 20 Feb 2025 22:10:56 +0300 Subject: [PATCH 12/18] ogc: set viewport when loading the EFB Ensure that the viewport is large enough to load the texture into the EFB. --- src/render/ogc/SDL_render_ogc.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/render/ogc/SDL_render_ogc.c b/src/render/ogc/SDL_render_ogc.c index c15c2f6068b2f..c6ea2fcb4be6f 100644 --- a/src/render/ogc/SDL_render_ogc.c +++ b/src/render/ogc/SDL_render_ogc.c @@ -112,6 +112,9 @@ static void load_efb_from_texture(SDL_Renderer *renderer, SDL_Texture *texture) OGC_load_texture(ogc_tex->texels, texture->w, texture->h, ogc_tex->format, SDL_ScaleModeNearest); + /* The viewport is reset when OGC_SetRenderTarget() returns. */ + OGC_set_viewport(0, 0, texture->w, texture->h); + GX_ClearVtxDesc(); GX_SetVtxDesc(GX_VA_POS, GX_DIRECT); GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XY, GX_S16, 0); From 29cf76f8048b2de49172b5cc27aa980757db25a5 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Thu, 20 Feb 2025 22:14:19 +0300 Subject: [PATCH 13/18] ogc: always set the number of color channels when drawing This is one more thing that so far has worked by pure luck. --- src/render/ogc/SDL_render_ogc.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/render/ogc/SDL_render_ogc.c b/src/render/ogc/SDL_render_ogc.c index c6ea2fcb4be6f..52401c1f89437 100644 --- a/src/render/ogc/SDL_render_ogc.c +++ b/src/render/ogc/SDL_render_ogc.c @@ -122,9 +122,10 @@ static void load_efb_from_texture(SDL_Renderer *renderer, SDL_Texture *texture) GX_SetVtxDesc(GX_VA_TEX0, GX_DIRECT); GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_U8, 0); GX_SetNumTexGens(1); + GX_SetNumChans(0); GX_SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY); - GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0); + GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLORNULL); GX_SetTevOp(GX_TEVSTAGE0, GX_REPLACE); GX_SetNumTevStages(1); @@ -513,6 +514,7 @@ static int OGC_RenderGeometry(SDL_Renderer *renderer, void *vertices, GX_SetTevOp(GX_TEVSTAGE0, GX_PASSCLR); GX_SetNumTevStages(1); } + GX_SetNumChans(1); GX_Begin(GX_TRIANGLES, GX_VTXFMT0, count); for (int i = 0; i < count; i++) { From 0ecf11515ad318d0304cfffd81f183ee2ea17eea Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Fri, 21 Feb 2025 20:35:47 +0300 Subject: [PATCH 14/18] ogc: minor refactoring of blend mode handling It's more consistent if the function which checks the variable for the blending mode is the one which keeps it updated. Remove the check about the number of drawing operations, since in those places where we care about enforcing the blending mode we are calling set_blend_mode_real() directly. --- src/render/ogc/SDL_render_ogc.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/render/ogc/SDL_render_ogc.c b/src/render/ogc/SDL_render_ogc.c index 52401c1f89437..7f7fd00eb5b91 100644 --- a/src/render/ogc/SDL_render_ogc.c +++ b/src/render/ogc/SDL_render_ogc.c @@ -67,8 +67,6 @@ static void OGC_WindowEvent(SDL_Renderer *renderer, const SDL_WindowEvent *event static void set_blend_mode_real(SDL_Renderer *renderer, SDL_BlendMode blend_mode) { - OGC_RenderData *data = renderer->driverdata; - switch (blend_mode) { case SDL_BLENDMODE_NONE: GX_SetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_INVSRCALPHA, GX_LO_CLEAR); @@ -88,21 +86,19 @@ static void set_blend_mode_real(SDL_Renderer *renderer, SDL_BlendMode blend_mode default: return; } - - data->current_blend_mode = blend_mode; } static inline void OGC_SetBlendMode(SDL_Renderer *renderer, SDL_BlendMode blend_mode) { OGC_RenderData *data = renderer->driverdata; - if (data->ops_after_present > 0 && - blend_mode == data->current_blend_mode) { + if (blend_mode == data->current_blend_mode) { /* Nothing to do */ return; } set_blend_mode_real(renderer, blend_mode); + data->current_blend_mode = blend_mode; } static void load_efb_from_texture(SDL_Renderer *renderer, SDL_Texture *texture) From e08c236b8d8c8e827aa92b3190ef2ad112d26b09 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Fri, 21 Feb 2025 20:38:48 +0300 Subject: [PATCH 15/18] ogc: do not clear the EFB when saving it We don't need to clear the EFB, since we are going to immediately load a texture onto it. But at the same time we must make sure that there is no blending when we loat the new texture onto the EFB, because we want to completely replace its contents. --- src/render/ogc/SDL_render_ogc.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/render/ogc/SDL_render_ogc.c b/src/render/ogc/SDL_render_ogc.c index 7f7fd00eb5b91..488c2e1f7a004 100644 --- a/src/render/ogc/SDL_render_ogc.c +++ b/src/render/ogc/SDL_render_ogc.c @@ -107,6 +107,7 @@ static void load_efb_from_texture(SDL_Renderer *renderer, SDL_Texture *texture) OGC_load_texture(ogc_tex->texels, texture->w, texture->h, ogc_tex->format, SDL_ScaleModeNearest); + OGC_SetBlendMode(renderer, SDL_BLENDMODE_NONE); /* The viewport is reset when OGC_SetRenderTarget() returns. */ OGC_set_viewport(0, 0, texture->w, texture->h); @@ -137,7 +138,7 @@ static void load_efb_from_texture(SDL_Renderer *renderer, SDL_Texture *texture) GX_End(); } -static void save_efb_to_texture(SDL_Texture *texture) +static void save_efb_to_texture(SDL_Texture *texture, bool must_clear) { OGC_TextureData *ogc_tex = texture->driverdata; u32 texture_size; @@ -148,7 +149,7 @@ static void save_efb_to_texture(SDL_Texture *texture) GX_SetTexCopySrc(0, 0, texture->w, texture->h); GX_SetTexCopyDst(texture->w, texture->h, ogc_tex->format, GX_FALSE); - GX_CopyTex(ogc_tex->texels, GX_TRUE); + GX_CopyTex(ogc_tex->texels, must_clear ? GX_TRUE : GX_FALSE); GX_PixModeSync(); } @@ -275,14 +276,14 @@ static int OGC_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture) u8 desired_efb_pixel_format = GX_PF_RGB8_Z24; if (data->render_target) { - save_efb_to_texture(data->render_target); + save_efb_to_texture(data->render_target, false); } else if (data->ops_after_present > 0) { /* Save the current EFB contents if we already drew something onto * it. We'll restore it later, when the rendering target is reset * to NULL (the screen). */ if (!data->saved_efb_texture) data->saved_efb_texture = create_efb_texture(data, renderer->window); - save_efb_to_texture(data->saved_efb_texture); + save_efb_to_texture(data->saved_efb_texture, false); } if (texture) { @@ -462,7 +463,7 @@ static int OGC_RenderClear(SDL_Renderer *renderer, SDL_RenderCommand *cmd) data->clear_color = c; GX_SetCopyClear(c, GX_MAX_Z24); if (renderer->target) { - save_efb_to_texture(renderer->target); + save_efb_to_texture(renderer->target, true); } else { GX_CopyDisp(OGC_video_get_xfb(SDL_GetVideoDevice()), GX_TRUE); } From 58757e28b7db58009efdd07f066f803f08e7a282 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Wed, 19 Feb 2025 23:35:39 +0300 Subject: [PATCH 16/18] ogc: do not implement clearing by copying the EFB While copying the EFB is probably more efficient, it causes screen flashes (very noticeable on the console, and more rarely in Dolphin) due to the fact that we might be writing to the XFB while it's been read by the VI interface. So, let's implement clearing by drawing a rectangle over the window; this requires a little more code, but completely eliminates flashes. --- src/render/ogc/SDL_render_ogc.c | 34 +++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/src/render/ogc/SDL_render_ogc.c b/src/render/ogc/SDL_render_ogc.c index 488c2e1f7a004..e123a805c69c2 100644 --- a/src/render/ogc/SDL_render_ogc.c +++ b/src/render/ogc/SDL_render_ogc.c @@ -40,7 +40,6 @@ typedef struct { SDL_BlendMode current_blend_mode; - GXColor clear_color; int ops_after_present; bool vsync; u8 efb_pixel_format; @@ -459,16 +458,35 @@ static int OGC_RenderClear(SDL_Renderer *renderer, SDL_RenderCommand *cmd) cmd->data.color.b, cmd->data.color.a }; + int16_t x1 = 0; + int16_t y1 = 0; + int16_t x2 = renderer->window->w; + int16_t y2 = renderer->window->h; + OGC_set_viewport(0, 0, renderer->window->w, renderer->window->h); + OGC_SetBlendMode(renderer, SDL_BLENDMODE_NONE); - data->clear_color = c; - GX_SetCopyClear(c, GX_MAX_Z24); - if (renderer->target) { - save_efb_to_texture(renderer->target, true); - } else { - GX_CopyDisp(OGC_video_get_xfb(SDL_GetVideoDevice()), GX_TRUE); - } + GX_ClearVtxDesc(); + GX_SetVtxDesc(GX_VA_POS, GX_DIRECT); + GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XY, GX_S16, 0); + GX_SetTevColor(GX_TEVREG0, c); + GX_SetTevColorIn(GX_TEVSTAGE0, GX_CC_C0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO); + GX_SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_A0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO); + GX_SetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); + GX_SetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); + GX_SetNumTevStages(1); + GX_SetNumChans(0); + + GX_Begin(GX_QUADS, GX_VTXFMT0, 4); + GX_Position2s16(x1, y1); + GX_Position2s16(x2, y1); + GX_Position2s16(x2, y2); + GX_Position2s16(x1, y2); + GX_End(); data->ops_after_present++; + /* Restore the viewport */ + OGC_set_viewport(renderer->viewport.x, renderer->viewport.y, + renderer->viewport.w, renderer->viewport.h); return 0; } From da7240fa98cc9dc858c2ebc26091df2ef059a3d1 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Sun, 23 Feb 2025 23:25:56 +0300 Subject: [PATCH 17/18] ogc, renderer: workaround missing pixels in linestrips MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In addition to drawing the last point, we must draw all the other ones, too. The reason is that GX rendering of lines is a bit different than how SDL applications expect it, and there isn't a straightforward way to correct it in a way that is good for all cases. The problem is that GX draws a line (let's assume it's a horizontal one) between coordinates A and B like this: ┌────────────────────────────────────────────────────────┐ A B └────────────────────────────────────────────────────────┘ That is, the line is made as wide as needed, but the line caps are in "butt" style (using the HTML terminology), that is the line lenght is not increased by half width around the points A and B (which is what SDL seems to be doing on both the software and OpenGL renderer backends on the desktop). As a result, when drawing a linestrip as a rectangle with vertices A, B, C and D, the drawing that GX sets up is this: ┌────────────────────────────────────┐ ┌─A─┐ 1 ┌─B─┐ │ ├────────────────────────────────┤ │ │ 4 │ │ 2 │ │ │ │ │ │ ├────────────────────────────────┴─┐ │ └─D─┘ 3 C─┘ └────────────────────────────────────┘ If (like is the case with SDL) lines are only one pixel wide, the corner pixels will not be drawn if the positioning of the vertex is such that the GX engine decides the pixel is not covered enough. In practice, as can be tested with the sdl-subpixel program[1], it very often happens that at least one corner of the rectangle is not drawn. Since points are always drawn as a square around the given coordinates, adding squares at line vertices helps fixing this issue. [1]: https://github.com/mardy/sdl-subpixel --- src/render/ogc/SDL_render_ogc.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/render/ogc/SDL_render_ogc.c b/src/render/ogc/SDL_render_ogc.c index e123a805c69c2..e68f994771ce2 100644 --- a/src/render/ogc/SDL_render_ogc.c +++ b/src/render/ogc/SDL_render_ogc.c @@ -585,8 +585,10 @@ int OGC_RenderPrimitive(SDL_Renderer *renderer, u8 primitive, /* The last point is not drawn */ if (primitive == GX_LINESTRIP) { - GX_Begin(GX_POINTS, GX_VTXFMT0, 1); - GX_Position2f32(verts[count - 1].x, verts[count - 1].y); + GX_Begin(GX_POINTS, GX_VTXFMT0, count); + for (int i = 0; i < count; i++) { + GX_Position2f32(verts[i].x, verts[i].y); + } GX_End(); } From d243456c3b798ab608233be06936a768ebfcb0d3 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Mon, 24 Feb 2025 22:31:57 +0300 Subject: [PATCH 18/18] ogc: remove sub-pixel adjustment when drawing triangles and quads The adjustment is not needed, since the drawn geometries are exactly delimited by their vertices. The only adjustment that needs to be made is when drawing lines and point, due to the fact that in these cases the vertices do not really delimit the geometry, which is instead drawn around them. --- src/render/ogc/SDL_render_ogc.c | 15 +++++++++++++++ src/video/ogc/SDL_ogcgxcommon.c | 12 ------------ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/render/ogc/SDL_render_ogc.c b/src/render/ogc/SDL_render_ogc.c index e68f994771ce2..39996c8aececb 100644 --- a/src/render/ogc/SDL_render_ogc.c +++ b/src/render/ogc/SDL_render_ogc.c @@ -554,6 +554,8 @@ int OGC_RenderPrimitive(SDL_Renderer *renderer, u8 primitive, OGC_RenderData *data = renderer->driverdata; size_t count = cmd->data.draw.count; const SDL_FPoint *verts = (SDL_FPoint *)(vertices + cmd->data.draw.first); + Mtx mv; + bool did_change_matrix = false; GXColor c = { cmd->data.draw.r, cmd->data.draw.g, @@ -564,6 +566,14 @@ int OGC_RenderPrimitive(SDL_Renderer *renderer, u8 primitive, data->ops_after_present++; OGC_SetBlendMode(renderer, cmd->data.draw.blend); + if (primitive == GX_LINESTRIP || primitive == GX_POINTS) { + float adjustment = 0.5; + guMtxIdentity(mv); + guMtxTransApply(mv, mv, adjustment, adjustment, 0); + GX_LoadPosMtxImm(mv, GX_PNMTX0); + did_change_matrix = true; + } + /* TODO: optimize state changes. */ GX_SetTevColor(GX_TEVREG0, c); GX_SetTevColorIn(GX_TEVSTAGE0, GX_CC_C0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO); @@ -592,6 +602,11 @@ int OGC_RenderPrimitive(SDL_Renderer *renderer, u8 primitive, GX_End(); } + if (did_change_matrix) { + guMtxIdentity(mv); + GX_LoadPosMtxImm(mv, GX_PNMTX0); + } + return 0; } diff --git a/src/video/ogc/SDL_ogcgxcommon.c b/src/video/ogc/SDL_ogcgxcommon.c index 56c017f271763..ed7e096369c5a 100644 --- a/src/video/ogc/SDL_ogcgxcommon.c +++ b/src/video/ogc/SDL_ogcgxcommon.c @@ -54,20 +54,8 @@ void OGC_set_viewport(int x, int y, int w, int h) void OGC_draw_init(int w, int h) { - Mtx mv; - SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "OGC_draw_init called with %d, %d", w, h); - guMtxIdentity(mv); - /* Ideally we would use 0.5 to center the coordinates on the pixels, but - * this causes some visual artifacts due to rounding: in the VVVVVV game, - * all 8x8 pixel textures lose their rightmost column and bottom row, - * except when they are drawn on the bottom-right quadrant of the screen. - * Values from 0.1 to 0.4 fix this issue, while preserving pixel accuracy - * on drawing operations. */ - guMtxTransApply(mv, mv, 0.4, 0.4, 0); - GX_LoadPosMtxImm(mv, GX_PNMTX0); - GX_ClearVtxDesc(); GX_SetVtxDesc(GX_VA_POS, GX_DIRECT); GX_SetVtxDesc(GX_VA_TEX0, GX_INDEX8);