Search code examples
c++sdlsdl-2

SDL2 drawing to texture different than drawing to screen directly


I am using SDL2 version 2.28.3 on windows 11, the issue happens on all backends (DirectX, opengl, software) , i am getting different results when i draw to the screen directly compared to drawing to a texture first.

I have written the following anti-aliased circle algorithm, the algorithm itself is not important except that it uses alpha blending, so i suspect there is an issue with that, the issue also goes away when i disable alpha blending and just color the background white, but alpha blending is needed for practical use.

enter image description here

enter image description here

calling SDL_SetTextureBlendMode(stored_arc_texture, SDL_BLENDMODE_BLEND) and SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND) didn't help.

#include <SDL2/SDL.h>
#include <array>

void DrawFilledArcAA3(SDL_Renderer* renderer, SDL_Point midpoint, int radius, const SDL_Color color)
{
    SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
    int y = 0;
    int max_y = radius;
    int raidus_squared = radius * radius;
    while (y <= max_y)
    {
        double x = sqrt(raidus_squared - y * y);
        {
            int max_x = static_cast<int>(floor(x));
            if (floor(x) + 2 < y)
            {
                break;
            }
            SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, 255);
            std::array<SDL_Point, 4> points{
            SDL_Point{midpoint.x, midpoint.y + y}, SDL_Point{midpoint.x + max_x, midpoint.y + y},
            SDL_Point{midpoint.x + y, midpoint.y}, SDL_Point{midpoint.x + y, midpoint.y + max_x},
            };
            SDL_RenderDrawLines(renderer, points.data(), points.size());
        }

        {
            double overflow = x - floor(x);
            int color_value = 255 * overflow;
            SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color_value);
            int max_x = static_cast<int>(floor(x + 1));
            std::array<SDL_Point, 2> points{
                SDL_Point{midpoint.x + max_x, midpoint.y + y},
                SDL_Point{midpoint.x + y, midpoint.y + max_x},
            };

            SDL_RenderDrawPoints(renderer, points.data(), points.size());
        }

        y++;
    }
}

int main(int argc, char** argv) 
{

    SDL_Window* pWindow = NULL;
    pWindow = SDL_CreateWindow("test AA circle", SDL_WINDOWPOS_UNDEFINED,
        SDL_WINDOWPOS_UNDEFINED,
        640,
        480,
        SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);

    SDL_Renderer* renderer = SDL_CreateRenderer(pWindow, -1, 
        SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_TARGETTEXTURE);

    bool is_running = true;
    while (is_running)
    {

        SDL_Event e;
        while (SDL_PollEvent(&e))
        {
            if (e.type == SDL_QUIT)
            {
                is_running = false;
                break;
            }
        }

        SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);

        SDL_RenderClear(renderer);
        
        {
            // render directly to screen
            DrawFilledArcAA3(renderer, SDL_Point{ 10,4 }, 100, SDL_Color{ 180,180,180,255 });

        }

        // render to texture first
        {
            int stored_radius = 100;
            SDL_Texture* old_texture = SDL_GetRenderTarget(renderer);
            SDL_Texture* stored_arc_texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_TARGET, stored_radius, stored_radius);
            SDL_SetRenderTarget(renderer, stored_arc_texture);
            SDL_SetTextureBlendMode(stored_arc_texture, SDL_BLENDMODE_BLEND);
            DrawFilledArcAA3(renderer, { 0,0 }, stored_radius, SDL_Color{ 180,180,180,255 });
            SDL_SetRenderTarget(renderer, old_texture);
            SDL_Rect dst{ 120 + 10, 4, stored_radius, stored_radius};
            SDL_RenderCopy(renderer, stored_arc_texture, 0, &dst);
            SDL_DestroyTexture(stored_arc_texture);
        }

        SDL_RenderPresent(renderer);

    }

    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(pWindow);
    SDL_Quit();
    return 0;
}

How to fix this so that both will look the same ?


Solution

  • The issue was solved, i mainly used Renderdoc for debugging this and found the problem.

    when drawing directly to the screen with alpha blending SDL_BLENDMODE_BLEND i go from a pixel being (1.0, 1.0, 1.0, 1.0) being blended with (0.7, 0.7, 0.7, 0.5) the result is a pixel being (0.85, 0.85, 0.85, 1) which is correct for the case where i draw directly on the screen.

    but when i draw to a texture that starts with (0.0, 0.0, 0.0, 0.0) and blend it with (0.7, 0.7, 0.7, 0.5) the result is a pixel with (0.35, 0.35, 0.35, 0.5) .... which again gets blended with (1.0, 1.0, 1.0, 1.0) resulting in something close to (0.7, 0.7, 0.7, 1) which is incorrect.

    the fix is simple, Disable Alpha blending when drawing to the texture. only let alpha blending happen when drawing the texture to the screen in the final blending, ie: alpha blending only happens once, not twice. so removing the SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND) from the version that draws to the texture fixes this issue, so in short, you cannot use the exact same code for both versions and expect them to look the same, you need to plan when the blending will happen when you draw to the texture.