Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SDL renderer: How to display the same texture with different scale modes? #7616

Closed
achabense opened this issue May 23, 2024 · 6 comments
Closed

Comments

@achabense
Copy link

Version/Branch of Dear ImGui:

Version 1.90.6

Back-ends:

imgui_impl_sdl2.cpp + imgui_impl_sdlrenderer2.cpp

Compiler, OS:

Windows 10 + MSVC 2022

Full config/build information:

No response

Details:

I want to display the same texture in different scale modes. The screenshot shows the effect of test_fn below. Can I achieve the same effect without creating multiple textures?

Screenshots/Video:

image

Minimal, Complete and Verifiable Example code:

static void test_fn(SDL_Renderer* renderer) {
    static SDL_Texture *nearest = nullptr, *linear = nullptr;
    const int w = 33, h = 25;
    const int zoom = 4;

    if (!nearest) {
        nearest = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STATIC, w, h);
        SDL_SetTextureScaleMode(nearest, SDL_ScaleModeNearest);
        linear = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STATIC, w, h);
        SDL_SetTextureScaleMode(linear, SDL_ScaleModeLinear);
        Uint32 pixels[h][w];
        bool b = false;
        for (auto& line : pixels) {
            for (Uint32& p : line) {
                p = b ? 0 : -1;
                b = !b;
            }
        }
        SDL_UpdateTexture(nearest, nullptr, pixels, w * sizeof(Uint32));
        SDL_UpdateTexture(linear, nullptr, pixels, w * sizeof(Uint32));
    }

    if (ImGui::Begin("Test", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse)) {
        ImGui::Image(nearest, ImVec2(w * zoom, h * zoom));
        ImGui::SameLine();
        ImGui::Image(linear, ImVec2(w * zoom, h * zoom));
    }
    ImGui::End();
}
@achabense
Copy link
Author

achabense commented May 26, 2024

Here is the background of this issue: I wanted to display a zoom window in nearest scale mode for a texture shown in linear mode. The problem was worked around by making a new texture with different scale mode, but I think the solution is wasteful.

@ocornut ocornut changed the title How to display the same texture with different scale modes? SDL renderer: How to display the same texture with different scale modes? Oct 9, 2024
@ocornut
Copy link
Owner

ocornut commented Oct 9, 2024

Hello,
Assuming that SDL Render allows switching texture scale mode between rendering,
you should be able to use ImGui::GetWindowDrawList->AddCallback() and pass a callback which will call SDL_SetTextureScaleMode().

This is analoguous to this example here changing the sampler for DX11 rendering:
https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples#modifying-render-state

@ocornut
Copy link
Owner

ocornut commented Oct 9, 2024

I've tried this:

void ImDrawCallback_ImplSDLRenderer_SetScaleModeNearest(const ImDrawList* parent_list, const ImDrawCmd* cmd)
{
    SDL_Texture* texture = (SDL_Texture*)cmd->UserCallbackData;
    SDL_SetTextureScaleMode(texture, SDL_ScaleModeNearest);
}

void ImDrawCallback_ImplSDLRenderer_SetScaleModeLinear(const ImDrawList* parent_list, const ImDrawCmd* cmd)
{
    SDL_Texture* texture = (SDL_Texture*)cmd->UserCallbackData;
    SDL_SetTextureScaleMode(texture, SDL_ScaleModeLinear);
}
ImGui::Image((ImTextureID)(intptr_t)my_texture, ImVec2((float)my_image_width * 4, (float)my_image_height * 4));
ImGui::GetWindowDrawList()->AddCallback(ImDrawCallback_ImplSDLRenderer_SetScaleModeNearest, (void*)my_texture);
ImGui::Image((ImTextureID)(intptr_t)my_texture, ImVec2((float)my_image_width * 4, (float)my_image_height * 4));
ImGui::GetWindowDrawList()->AddCallback(ImDrawCallback_ImplSDLRenderer_SetScaleModeLinear, (void*)my_texture);

But unfortunately SDL Renderer doesn't seem to allow that changes to happen: because all rendering is done at once and the texture scale mode at the time of calling SDL_RenderGeometryRaw() doesn't seem to be register.

This didn't work either, which is more puzzling:

void ImDrawCallback_ImplSDLRenderer_SetScaleModeNearest(const ImDrawList* parent_list, const ImDrawCmd* cmd)
{
    ImGui_ImplSDLRenderer2_RenderState* render_state = (ImGui_ImplSDLRenderer2_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState;
    SDL_RenderFlush(render_state->Renderer);
    SDL_Texture* texture = (SDL_Texture*)cmd->UserCallbackData;
    SDL_SetTextureScaleMode(texture, SDL_ScaleModeNearest);
}

void ImDrawCallback_ImplSDLRenderer_SetScaleModeLinear(const ImDrawList* parent_list, const ImDrawCmd* cmd)
{
    ImGui_ImplSDLRenderer2_RenderState* render_state = (ImGui_ImplSDLRenderer2_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState;
    SDL_RenderFlush(render_state->Renderer);
    SDL_Texture* texture = (SDL_Texture*)cmd->UserCallbackData;
    SDL_SetTextureScaleMode(texture, SDL_ScaleModeLinear);
}

I would expect this to work (even if it's perhaps not efficient).

ocornut added a commit that referenced this issue Oct 9, 2024
@ocornut
Copy link
Owner

ocornut commented Oct 9, 2024

Out of curiosity I tried with SDL3 and it worked (but that's with the SDL_FlushRenderer() call, which is not great).

void ImDrawCallback_ImplSDLRenderer3_SetScaleModeNearest(const ImDrawList* parent_list, const ImDrawCmd* cmd)
{
    ImGui_ImplSDLRenderer3_RenderState* render_state = (ImGui_ImplSDLRenderer3_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState;
    SDL_FlushRenderer(render_state->Renderer);
    SDL_Texture* texture = (SDL_Texture*)cmd->UserCallbackData;
    SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST);
}

void ImDrawCallback_ImplSDLRenderer3_SetScaleModeLinear(const ImDrawList* parent_list, const ImDrawCmd* cmd)
{
    ImGui_ImplSDLRenderer3_RenderState* render_state = (ImGui_ImplSDLRenderer3_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState;
    SDL_FlushRenderer(render_state->Renderer);
    SDL_Texture* texture = (SDL_Texture*)cmd->UserCallbackData;
    SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_LINEAR);
}

I don't think SDL_Renderer is designed for that, but you could ask the team if they would like texture state to be latched during draw calls, it may be a possible improvement for SDL3.

@ocornut
Copy link
Owner

ocornut commented Oct 9, 2024

I am going to have to close this as answered, unfortunately this is not supported by SDL, but feel free to ask on SDL3 repo if they want to make the change to make it work without flushing. I suspect SDL3 will nowadays focus their effort on SDL_GPU rather than SDL_Renderer, but if SDL_Renderer ends up using SDL_GPU then maybe this improvement will come automatically.

It's been interesting for me to dig because this topic aligns with recent work I've been doing toward making it easier to change backend render state. We might eventually settle on a mechanism with officially supported callbacks to do exactly what you are trying to do, since it's been a quite common request.
It also highlighted an old request that has been to make it easier to make more data to callbacks. I think I may extend ImDrawList to allow for user storage.

@ocornut ocornut closed this as completed Oct 9, 2024
@ocornut
Copy link
Owner

ocornut commented Oct 11, 2024

It also highlighted an old request that has been to make it easier to make more data to callbacks. I think I may extend ImDrawList to allow for user storage.

FYI, I just did that with 98d52b7
(but it doesn't solve your main issue here)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants