Созданные текстуры ломаются в SDL2 при изменении размера окна

Я пытаюсь создать и визуализировать большое количество текстур в окне с флагом SDL_WINDOW_RESIZABLE, но при изменении его размера некоторые текстуры иногда ломаются. Я не заметил этой проблемы, если создавал текстуру каким-то другим способом, например, визуализируя глиф и преобразуя его в текстуру. Вот полный код:

#include <iostream>
#include <string>
#include <SDL.h>
#include <vector>

SDL_Texture* generateColoredTexture(SDL_Renderer *renderer_, int w_, int h_, const SDL_Color &col_)
{
    auto *tex = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, w_, h_);
    SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND);

    auto oldTar = SDL_GetRenderTarget(renderer_);
    SDL_SetRenderTarget(renderer_, tex);

    SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 0);
    SDL_RenderClear(renderer_);

    SDL_SetRenderDrawColor(renderer_, col_.r, col_.g, col_.b, col_.a);
    SDL_Rect dst = {4, 4, w_ - 8, h_ - 8};
    SDL_RenderFillRect(renderer_, &dst);

    SDL_SetRenderTarget(renderer_, oldTar);
    return tex;
}

int main(int argc, char* args[])
{    
    if (SDL_Init(SDL_INIT_EVERYTHING) < 0)
    {
        std::cout << "SDL initialization error: " << SDL_GetError() << std::endl;
        return -1;
    }
    
    auto window = SDL_CreateWindow("TestWin", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1280, 720, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);
    if (window == nullptr)
    {
        std::cout << "Window creation error: " << SDL_GetError() << std::endl;
        return -3;
    }

    auto renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE);
    if (renderer == nullptr) {
        std::cout << "Renderer creation error: " << SDL_GetError() << std::endl;
        return -4;
    }

    std::vector<SDL_Texture*> texs;
    for (int i = 0; i < 53; ++i)
    {
        texs.push_back(generateColoredTexture(renderer, 50, 50, {uint8_t(rand() % 255), uint8_t(rand() % 255), uint8_t(rand() % 255), 255}));
    }
    
    std::vector<std::vector<int>> cells;
    for (int y = 0; y < 10; ++y)
    {
        cells.push_back({});
        for (int x = 0; x < 20; ++x)
        {
            cells.back().push_back(rand() % texs.size());
        }
    }

    bool running = true;
    SDL_Event e;

    while (running)
    {
        while( SDL_PollEvent( &e ) != 0 )
        {
            if ( e.type == SDL_QUIT )
            {
                running = false;
            }
        }

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

        for (int y = 0; y < cells.size(); ++y)
        {
            for (int x = 0; x < cells[y].size(); ++x)
            {
                SDL_Rect dstrect{x * 50, y * 50, 50, 50};
                SDL_RenderCopy(renderer, texs[cells[y][x]], NULL, &dstrect);
            }
        }

        SDL_RenderPresent(renderer);
    }

    for (auto *tex : texs)
    {
        SDL_DestroyTexture(tex);
    }
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();
    return 0;
}

Видео этого выпуска. Я не нашел никаких заметок о созданных текстурах или изменении размера окна в вики sdl. Я также пытался поймать событие SDL_WINDOWEVENT_EXPOSED, но его не было. Что вызывает эту проблему и есть ли какое-либо решение?

РЕДАКТИРОВАТЬ. Я пытался собрать этот код в Linux (EndeavourOS, 6.9.3-arch1-1, Plasma 6.0.5) и не заметил там никаких проблем, так что, думаю, это зависит от Windows.

Обычно это называется «потерей устройства». Найдите события SDL_RENDER_TARGETS_RESET и SDL_RENDER_DEVICE_RESET. Это означает, что все текстуры, предназначенные для рендеринга, больше не действительны и их необходимо повторно отрисовать. Есть несколько способов обойти это, например. использование средства рендеринга OpenGL или копирование ваших данных в текстуры без рендеринга, если содержимое не меняется.

keltar 27.07.2024 18:28

@keltar, ты имеешь в виду текстуры без SDL_TEXTUREACCESS_TARGET под «текстурами без рендеринга»? Как можно удалить это свойство или скопировать в текстуру без него? Я не нашел другого способа скопировать текстуру, кроме как установить ее в качестве цели рендеринга и отрендерить напрямую. Кроме того, разве это не проблема OpenGL?

SavedowW 27.07.2024 18:46

Я думаю, что эта проблема как-то связана с настройкой цели рендеринга SDL. Проблема возникает, когда при рендеринге одного прямоугольника имеется более двух текстур — после изменения размера окна он исчезает. Переключение в развернутое состояние также приведет к его исчезновению, а при восстановлении оно снова появится. Никаких событий SDL_RENDER_TARGETS_RESET или SDL_RENDER_DEVICE_RESET не происходит.

user7860670 27.07.2024 19:05

В данном примере ваши текстуры заполнены статическим цветом — вы можете сгенерировать массив пиксельных данных и использовать его в качестве источника статической текстуры. Если ваши текстуры сложные (но данные по-прежнему статичны) — вы можете сгенерировать их, как делаете сейчас, а затем считать данные текстуры в системную память и создать из них статическую текстуру. Если текстура динамическая - лучше просто регенерировать ее либо периодически, либо при получении события потери устройства. Это не проблема в OpenGL, поскольку драйвер OpenGL решает эту проблему за вас и делает вид, что устройство никогда не теряется.

keltar 27.07.2024 19:11

@keltar Вероятно, проще просто переключиться на opengl, но мне не нужно редактировать текстуру после ее однократного рендеринга. Мои текстуры в реальных сценариях сложные, но статичные. Вы предлагаете сделать текстуру SDL_TEXTUREACCESS_STREAMING без цели и копировать туда пиксели вместо того, чтобы сохранять SDL_TEXTUREACCESS_TARGET?

SavedowW 27.07.2024 19:27

@SavedowW Я не вижу здесь никакой пользы от потоковой передачи текстур, статика подойдет. Я собрал все для ответа с некоторыми примерами. Если что-то неясно, не стесняйтесь спрашивать дополнительную информацию в комментариях.

keltar 28.07.2024 08:12
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
6
85
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Эту проблему обычно называют «потерей устройства». Это было довольно обычным явлением при переключении в полноэкранный режим (в прошлом, когда полноэкранный режим означал не «полноэкранное окно без границ», а настоящий эксклюзивный полноэкранный режим со сменой режима видео) с Direct3D. D3D предоставляет различные флаги для использования текстур, некоторые из которых автоматически восстанавливаются при потере устройства, а некоторые нет.

SDL делает нечто подобное, автоматически воссоздавая все текстуры, не предназначенные для рендеринга, при потере устройства. См. https://github.com/libsdl-org/SDL/blob/5d1ca1c8c744c51c1d4046b8a10d37f2aea79d0b/src/render/direct3d/SDL_render_d3d.c#L1481. Но для целей рендеринга это не может сделать это эффективно (поскольку содержимое цели рендеринга может меняться очень часто, непрактично считывать содержимое цели рендеринга обратно в системную память только для того, чтобы иметь возможность восстановить его, если устройство когда-либо потеряется), поэтому это дает вам особое событие SDL_RENDER_TARGETS_RESET. В этом случае ваше приложение может перестроить содержимое цели рендеринга или воссоздать новую цель рендеринга другого размера, если это необходимо для правильной работы.

Это не проблема в OpenGL, поскольку драйвер GL сам обрабатывает потерю устройства, и для вашего приложения кажется, что устройство никогда не теряется (но с GL ES на мобильных устройствах оно может быть потеряно; ​​это касается только настольных систем).

Итак, как я понимаю, у вас есть 3 варианта:

  1. используйте бэкэнд рендеринга OpenGL SDL, например. проблема
SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl");

перед созданием рендерера. Вероятно, это не лучшее решение, но оно должно работать.

  1. обновите содержимое текстур рендеринга, когда получите событие SDL_RENDER_TARGETS_RESET. Если ваш код создания текстуры содержит некоторую случайность, вам придется сохранить случайное начальное значение, чтобы иметь возможность воссоздать точно такой же контент.

  2. Если содержимое текстуры не меняется, после подготовки содержимого текстуры рендеринга преобразуйте ее в статическую текстуру. Например. что-то вроде:

// ... render to RT texture
Uint32 format;
SDL_QueryTexture(render_texture, &format, NULL, NULL, NULL);

SDL_Texture *static_texture = SDL_CreateTexture(renderer, format, SDL_TEXTUREACCESS_STATIC, w, h);
SDL_SetTextureBlendMode(static_texture, SDL_BLENDMODE_BLEND);
// in case render_texture and static_texture formats are actually different
SDL_QueryTexture(static_texture, &format, NULL, NULL, NULL);

const int pitch = w * 4;  // presume 4 as we requested RGBA8888
void *pixels = malloc(h * pitch);
// read from current RT (render_texture)
SDL_RenderReadPixels(renderer, NULL, format, pixels, pitch);
SDL_UpdateTexture(static_texture, NULL, pixels, pitch);
free(pixels);

«Потеря устройства» обычно не происходит при изменении размера окна. И это, конечно, не происходит, когда возникает проблема ОП. Нет событий SDL_RENDER_TARGETS_RESET или SDL_RENDER_DEVICE_RESET. Более того, содержимое текстуры остается нетронутым и может быть отображено, если окно вернется к исходному размеру.

user7860670 28.07.2024 09:41

@user7860670 user7860670 кажется, вы знаете о проблеме ОП больше, чем сами ОП, так что, возможно, вам следует описать ее лучше. Или у вас другая проблема. В данном примере я получаю событие RENDER_TARGETS_RESET при каждом изменении размера, и после него содержимое рендеринговых текстур становится недействительным.

keltar 28.07.2024 10:06

@user7860670 github.com/libsdl-org/SDL/blob/… он выполняет D3D_Reset, если размер изменился. Это, конечно, если ваш драйвер рендеринга — Direct3D, который используется по умолчанию для SDL2.

keltar 28.07.2024 10:26

Хорошо, однако воссоздание рендерера при изменении размера окна кажется отдельной проблемой.

user7860670 28.07.2024 10:45

@user7860670 user7860670 у меня действительно есть событие SDL_RENDER_TARGETS_RESET, я проверил его

SavedowW 28.07.2024 10:56

@keltar большое спасибо за объяснение, я не знал, что таким образом можно редактировать текстуру

SavedowW 28.07.2024 11:02

Другие вопросы по теме

Похожие вопросы

C++ для бета-распространения
Почему алгоритмы STL без ранжирования не ограничены концепциями C++20?
Могу ли я написать библиотеку для предварительной загрузки на C++? Есть ли что-нибудь, что мне нужно сделать, кроме добавления `extern "C"` к функциям для перехвата?
Почему clang думает, что у меня есть конструктор копирования?
Почему типы выходят из пространства имен при включении после заголовка вектора?
Инициализация вектора с помощью класса, который имеет параметр конструктора передачи по ссылке и сохраняет эту ссылку как член
Как я могу использовать VSCode с CMake и иметь разные цели, каждая из которых имеет разную архитектуру и набор инструментов?
Неточные вычисления последовательности Фибоначчи во время компиляции в рекурсивной лямбда-выражении
Синхронизируйте три потока в C++ один за другим
Явные удаленные конструкторы – имеет ли это значение?