0
votes

My application has multiple views which utilize their own SDL_Window and SDL_Renderer, using the renderer to draw all of my tile layers into the main view (the main view contains a composited result of rendering several layers of tiles, and the other views are meant to show each individual layer in a photoshop-style 'layer panel'). The problem here is trying to take a texture (or surface, renderer, etc) and rendering it to another self-contained view. If I use SetRenderTarget and SDL_RenderCopy, it fails because the target texture belongs to a different renderer...

I've also tried locking the pixels of each view's texture, in an attempt to manually copy over the pixels, but locking the pixels fails unless the texture was created as a streaming texture... Also, the views are of different sizes, so the target texture would need to be stretched down to fit, causing more work than should be necessary.

This led me to the use of SDL_SoftStretch... Which works, but of course is painfully slow since it can't be used with hardware accelerated textures.

Is there any plausible, reasonably efficient way of taking the rendered output of one SDL_Window, and rendering a scaled-down version version of it into another?


EDIT:
Besides the cramped use of SDL_SoftStretch, this is the closest I came to getting things to work out... It fails because I hit another brick wall when looking for a way to grab the pixel data from the renderer:

SDLView.cpp

//...
BOOL SDLView::Init()
{    
    m_sdlWindow = SDL_CreateWindowFrom( (PVOID)m_hWndParent );
    if(m_sdlWindow == NULL)
        return false;

    m_sdlRenderer = SDL_CreateRenderer(m_sdlWindow, -1, SDL_RendererFlags::SDL_RENDERER_ACCELERATED);
    if(m_sdlRenderer == NULL)
        return false;

    memset(&m_rect, 0, sizeof(SDL_Rect));
    SDL_GetRendererOutputSize(m_sdlRenderer, &m_rect.w, &m_rect.h);
    m_pixelFormat = SDL_GetWindowPixelFormat(m_sdlWindow);
    m_sdlTexture = SDL_CreateTexture(m_sdlRenderer, m_pixelFormat, SDL_TEXTUREACCESS_STREAMING, m_rect.w, m_rect.h);

    SDL_SetRenderDrawColor(m_sdlRenderer, 0x64, 0x95, 0xED, 0xFF);

    return true;
}

BOOL SDLView::Clear()
{
    memset(&m_rect, 0, sizeof(SDL_Rect));
    SDL_GetRendererOutputSize(m_sdlRenderer, &m_rect.w, &m_rect.h);
    return SDL_RenderClear(m_sdlRenderer) == 0;
}

VOID SDLView::Present()
{
    // Doesn't work because we still have no way of grabbing the rendered output!!
    SDL_RenderPresent(m_sdlRenderer);
    int result = SDL_RenderCopy(m_sdlRenderer, m_sdlTexture, NULL, NULL);
    if(result != 0) {
        std::string err(SDL_GetError());
        DebugBreak();
    }
}


LayerListItem.cpp

void CLayerListItem::OnPaint()
{
    CWnd::OnPaint();
    CRect rc;
    GetClientRect(&rc);

    if(m_destView != nullptr)
    {
        m_destView->Clear();
        if(m_srcView != nullptr)
        {
            int srcPitch = 0, destPitch = 0;
            void *srcPixels = nullptr, *destPixels = nullptr;

            // Lock the source pixels
            int ret = SDL_LockTexture(m_srcView->GetTexture(), &m_srcView->GetRect(), &srcPixels, &srcPitch);
            if(ret != 0) {
                std::string err(SDL_GetError());
                DebugBreak();
            }
            // Lock the destination pixels
            ret = SDL_LockTexture(m_destView->GetTexture(), &m_destView->GetRect(), &destPixels, &destPitch);
            if(ret != 0) {
                std::string err(SDL_GetError());
                DebugBreak();
            }

            // Unlock all pixels
            SDL_UnlockTexture(m_destView->GetTexture());
            SDL_UnlockTexture(m_srcView->GetTexture());

            // Update our destinaition texture
            // (I have tried every possible combination for this line, still no dice!)
            int result = SDL_UpdateTexture(
                m_destView->GetTexture(),
                &m_destView->GetRect(),
                srcPixels, srcPitch
            );
            if(ret != 0) {
                std::string err(SDL_GetError());
                DebugBreak();
            }
            SDL_RenderCopy(m_destView->GetRenderer(), m_destView->GetTexture(), NULL, NULL);

            OutputDebugStringA("[RENDERED!]...\n");
        }
        m_destView->Present();
    }
}



An OnPaint event is sent to fire off every few seconds, which in turn attempts to grab the rendered output and store it in m_destView (which, like m_srcView, is an SDLView). At best, this appears to result in a properly rendered main view (the 'source view'), but a pitch black destination view. I feel like I've tried everything at this point, unless there's something beyond what I've already attempted... any ideas?

1

1 Answers

1
votes

I've sort of solved this using the somewhat slow -- (yet, still better than SDL_SoftRect) -- function SDL_RenderReadPixels:

VOID SDLView::CopyTo(SDLView *targetView)
{
    int result = SDL_RenderReadPixels(m_sdlRenderer, nullptr, m_pixelFormat, m_sdlSurface->pixels, m_sdlSurface->pitch);
    if(result != 0) {
        std::string err(SDL_GetError());
        OutputDebugStringA(err.c_str());
        return;
    }

    SDL_Texture *destTexture = targetView->GetTexture();
    result = SDL_UpdateTexture(destTexture, nullptr, m_sdlSurface->pixels, m_sdlSurface->pitch);
    if(result != 0) {
        std::string err(SDL_GetError());
        OutputDebugStringA(err.c_str());
        return;
    }
}


Works well when paired with m_sdlSurface = SDL_GetWindowSurface(m_sdlWindow) as well as m_pixelFormat = SDL_GetWindowPixelFormat(m_sdlWindow)...

Note however that this does DOES NOT help with scaling the image down, likely meaning that I would still need to use a RenderCopyEx or equivalent at some point before providing the final image to the destination. But for now, I'll mark this as the answer until someone can provide a better solution.