Search code examples
c++csdlsdl-2

Rendering single thing without having to clear entire screen in sdl


Are there layers to sdl or something?

by layers I mean like in photoshop we have multiple layer and can draw on one without effecting the other,

for example if I had a main_layer , a background_layer & an enemy_layer where the main player reandering (like moving the character by user), a static background rendering & enemies rendering can take place respectively?

instead of having to clear the entire screen then placing everything back again over and over? i.e. changing a single thing without effecting the other? can someone point me in the right direction?


Solution

  • You can implement your own layer system using render targets.

    1. Create a texture render target for each layer.
    2. Draw to a layer's render target to update it.
    3. Every frame, draw each layer to the screen. You still need to clear the final frame beforehand.

    It's worth noting that there is a point of diminishing return here. If a layer only contains a few sprites, it's probably cheaper to draw each sprite directly to the screen every frame even if they don't move.

    Example:

    // Given a renderer
    SDL_Renderer *renderer = ...;
    
    
    // *** Creating the layer ***
    SDL_Texture *my_layer = SDL_CreateTexture(
        renderer, 
        SDL_PIXELFORMAT_RGBA8888,
        SDL_TEXTUREACCESS_TARGET, 
        screen_width, screen_height);
    
    // To make transparency work (for non-base layers):
    SDL_SetTextureBlendMode(my_layer, SDL_BLENDMODE_BLEND);
    
    
    // *** Drawing TO the layer ***
    SDL_SetRenderTarget(renderer, my_layer);
    
    // For non-base layers, you want to make sure you clear to *transparent* pixels.
    SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
    SDL_RenderClear(renderer);
    
    // ... Draw to the layer ...
    
    
    // *** Drawing the layer to the screen / window ***
    SDL_SetRenderTarget(renderer, NULL);
    SDL_RenderCopy(renderer, my_layer, NULL, NULL);
    

    You can take this a bit further by creating layers that are larger than screen_width x screen_height and use the srcrect parameter of SDL_RenderCopy() to scroll the layer. With a few background layers, that can be used to get efficient and neat-looking old-school parallax effects.

    You will also probably want to encapsulate the notion of a layer into some Layer class in C++. Here's a rough starting point:

    class Layer {
      public:
        Layer(SDL_Renderer *renderer, int w, int h)
          : texture_(SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, w, h),
          , display_{0, 0, 0, 0}
        {
            SDL_GetRendererOutputSize(renderer, &display_.w, &display_.h);
            w = std::min(w, display_.w);
            h = std::min(h, display_.h);
    
            max_scroll_x = w - display_.w;
            max_scroll_y = h - display_.h;
    
            SDL_SetTextureBlendMode(texture_, SDL_BLENDMODE_BLEND);
        }
    
        Layer(Layer&& rhs) 
          : texture_(rhs.texture_)
          , display_(rhs.display)
          , max_scroll_x(rhs.max_scroll_x)
          , max_scroll_y(rhs.max_scroll_y) {
            rhs.texture_ = nullptr;
        }
    
        Layer& operator=(const Layer& rhs) {
          if(texture_) {SDL_DestroyTexture(texture_);}
    
          texture_ = rhs.texture_;
          display_ = rhs.display_;
          max_scroll_x = rhs.max_scroll_x;
          max_scroll_y = rhs.max_scroll_y;
    
          rhs.texture_ = nullptr;
        )
    
        Layer(const Layer& rhs) = delete;
        Layer& operator=(const Layer& rhs) = delete;
    
        ~Layer() {
          if(texture_) {SDL_DestroyTexture(texture_);}
        }
    
        // Subsequent draw calls will target this layer
        void makeCurrent(SDL_Renderer* renderer) {
            SDL_SetRenderTarget(renderer, texture_);
        }
    
        // Draws the layer to the currently active render target
        void commit(SDL_Renderer* renderer, const SDL_Rect * dstrect=nullptr) {
          SDL_RenderCopy(renderer, texture_, &display_, dstrect);
        }
    
        // Changes the offset of the layer
        void scrollTo(int x, int y) {
          display_.x = std::clamp(x, 0, max_scroll_x);
          display_.y = std::clamp(y, 0, max_scroll_y);
        }
    private:
        SDL_Texture* texture_;
        SDL_Rect display_;
        int max_scroll_x;
        int max_scroll_y;
    };