Page 1 of 1

Off-screen targets cannot be cleared with C2D_TargetClear

Posted: Thu May 28, 2020 11:24 am
by LiquidFenrir
Hello!

I made a game with citro2d that uses off screen targets/drawing to textures to be more efficient and easier to scale and tint, but there is a bug when clearing them with C2D_TargetClear.
I didn't encounter it before release because I got lazy and only tested with Citra, where it doesn't occur (where the expected behaviour of clearing does happen instead).

I tried a simple test case (see attachment) which displays the same issue.
If you don't want to have to download and compile this, here's a step by step explanation:
  • regular citro2d initialisation boilerplate
  • initialise a C3D_Tex with C3D_TexInit
  • in the main loop, if a target was created last frame, delete it with C3D_RenderTargetDelete
    • delete the C3D_Tex with C3D_TexDelete
    • regular citro2d exit
  • in the main loop, on an action:
    • initialize a C3D_RenderTarget* with C3D_RenderTargetCreateFromTex
    • clear the newly created target with C2D_TargetClear then begin rendering to it with C2D_SceneBegin, just like in the gpusprites example
    • draw a colored rectangle to it at the desired position
  • in the main loop, after checking input:
    • begin drawing with C3D_FrameBegin
    • clear the screen target with C2D_TargetClear then begin rendering to it with C2D_SceneBegin
    • create a C2D_Image that uses the C3D_Tex and draw it with C2D_DrawImageAt
  • after the main loop:
    • delete the C3D_Tex with C3D_TexDelete
    • regular citro2d exit
In Citra, this will work "correctly" and the previous squares will disappear whenever you add a new one.
On console, this will "not work" and the previous squares will remain, no matter what clear color you used.
That's about it, I hope this will get fixed or there is a workaround!
Thanks for the work, I love your libraries! :)

Re: Off-screen targets cannot be cleared with C2D_TargetClear

Posted: Thu May 28, 2020 12:53 pm
by fincs
Hi,

You're not supposed to dynamically create and destroy render targets inside the main game loop, since that will cause synchronization problems with the GPU - in other words, your code may end up accidentally deleting resources that are still being used by the GPU (which executes in parallel to the CPU). The recommended way to handle off-screen render targets is to create the ones you want ahead of rendering instead of inside the rendering loop. Note that dynamic memory allocations are also a really bad thing to be doing inside a game loop, as it's a pretty expensive operation with non-deterministic execution time (render target creation is such a type of allocation). It's likely the only reason your code works in Citra is because of the way they have to handle GPU resource synchronization, which on a PC involves a non-unified memory architecture, and they need to transfer resources from CPU accessible RAM into GPU accessible RAM (and such the CPU is free to corrupt the original memory all it wants without interfering with the GPU). It could also be that they simply do not emulate actual GPU/CPU parallel execution, but that's just speculation on my side.

Additionally, I suspect other things to be going wrong in your program (such as incorrectly calling FrameBegin multiple times per frame, which is unnecessary and causes issues), but unfortunately the pseudocode you provided isn't enough information for me to troubleshoot.

Re: Off-screen targets cannot be cleared with C2D_TargetClear

Posted: Thu May 28, 2020 2:44 pm
by LiquidFenrir
Oh I'm sorry, I actually forgot to add the zip with the example!

I added your changes, putting the rendertarget creation before the main loop and the destruction after the loop, but it still doesn't clear the target on console...
And don't worry, it goes

Code: Select all

C3D_FrameBegin(C3D_FRAME_SYNCDRAW);
// draw to target if needed
C2D_TargetClear(screen, clear_color_screen);
C2D_SceneBegin(screen);
// do the draw
C3D_FrameEnd(0);

Re: Off-screen targets cannot be cleared with C2D_TargetClear

Posted: Thu May 28, 2020 4:33 pm
by fincs
Okay, I've looked at your problem and I've found the following issues:
  • Off-screen render targets must be created on VRAM. It looks like hardware clearing of render targets only functions properly when it's located within VRAM.
  • The off-screen render target was used completely uninitialized. I added some logic to ensure it is cleared on the first frame.
  • The sequence of clears and binds/render operations was suboptimal and resulted in command buffer fragmentation. I've reorganized your code so that all clears are done first, then all rendering operations are done later. (I removed CLEAR_BEFORE_SCENE as a result - clearing should always be done before rendering...)
Here's the fixed code, which I've verified as working on my own 3DS:

Code: Select all

#include <3ds.h>
#include <citro3d.h>
#include <citro2d.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define CLEAR_TARGET_OPAQUE

int main(int argc, char** argv)
{
    gfxInitDefault();
    C3D_Init(C3D_DEFAULT_CMDBUF_SIZE);
    C2D_Init(C2D_DEFAULT_MAX_OBJECTS);
    C2D_Prepare();

    consoleInit(GFX_TOP, NULL);
    C3D_RenderTarget* screen = C2D_CreateScreenTarget(GFX_BOTTOM, GFX_LEFT);

    const Tex3DS_SubTexture subtex = {
        512, 256,
        0.0f, 1.0f, 1.0f, 0.0f
    };
    C3D_Tex tex;
    C3D_TexInitVRAM(&tex, 512, 256, GPU_RGBA8);
    C3D_RenderTarget* target = C3D_RenderTargetCreateFromTex(&tex, GPU_TEXFACE_2D, 0, -1);
    C2D_Image img = {&tex, &subtex};

    const u32 clear_color_screen = C2D_Color32f(0,0,0,1);
    const u32 gray_color = C2D_Color32f(0.125f,0.125f,0.125f,1);
    #ifdef CLEAR_TARGET_OPAQUE
    const u32 clear_color_target = C2D_Color32f(0,0,0,1);
    #else
    const u32 clear_color_target = C2D_Color32f(0,0,0,0);
    #endif
    
    const u32 color_a = C2D_Color32f(1,0,0,0.5f);
    const u32 color_b = C2D_Color32f(0,1,0,0.5f);
    const u32 color_x = C2D_Color32f(0,0,1,0.5f);
    const u32 color_y = C2D_Color32f(1,1,1,0.5f);

    const u32* color_selected = &color_a;
    touchPosition old_touch;
    bool touching = false;
    bool released = false;
    bool shouldClear = true;

    printf("Offscreen rendertarget test\n");
    printf("Touch the bottom screen to place a colored square centered on the touch point\n");
    printf("Press ABXY to change the color of the square\n");

    printf("\nPress START to quit.\n");

    while(aptMainLoop())
    {
        hidScanInput();
        u32 kDown = hidKeysDown();
        u32 kHeld = hidKeysHeld();

        // Respond to user input
        if(kDown & KEY_START) break;

        if(kDown & KEY_A) color_selected = &color_a;
        else if(kDown & KEY_B) color_selected = &color_b;
        else if(kDown & KEY_X) color_selected = &color_x;
        else if(kDown & KEY_Y) color_selected = &color_y;

        touchPosition touch;
        hidTouchRead(&touch);

        if(kDown & KEY_TOUCH)
        {
            touching = true;
        }
        else if(kHeld & KEY_TOUCH)
        {
            // we're holding
        }
        else if(touching)
        {
            released = true;
            shouldClear = true;
            touching = false;
        }

        // Render the scene
        C3D_FrameBegin(C3D_FRAME_SYNCDRAW);

        C2D_TargetClear(screen, clear_color_screen);

        if(shouldClear)
        {
            C2D_TargetClear(target, clear_color_target);
            shouldClear = false;
        }

        if(released)
        {
            C2D_SceneBegin(target);

            C2D_DrawRectSolid(old_touch.px - 12, old_touch.py - 12, 0.5f, 24, 24, *color_selected);

            released = false;
        }

        C2D_SceneBegin(screen);

        C2D_DrawRectSolid(32, 32, 0.0f, 256, 176, gray_color);
        C2D_DrawImageAt(img, 0, 0, 0.5f, NULL, 1.0f, 1.0f);

        if(touching)
        {
            C2D_DrawRectSolid(touch.px - 12, touch.py - 12, 1.0f, 24, 24, *color_selected);
        }

        C3D_FrameEnd(0);

        old_touch = touch;
    }

    C3D_RenderTargetDelete(target);
    C3D_TexDelete(&tex);

    C2D_Fini();
    C3D_Fini();
    gfxExit();
    return 0;
}

Re: Off-screen targets cannot be cleared with C2D_TargetClear

Posted: Thu May 28, 2020 4:39 pm
by LiquidFenrir
Thank you so much! I never knew this, it will work nicely, I hope.
and thanks also for the clear information, I'll fix my game :)

Have a good day