Я пытаюсь создать и визуализировать большое количество текстур в окне с флагом 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.
@keltar, ты имеешь в виду текстуры без SDL_TEXTUREACCESS_TARGET
под «текстурами без рендеринга»? Как можно удалить это свойство или скопировать в текстуру без него? Я не нашел другого способа скопировать текстуру, кроме как установить ее в качестве цели рендеринга и отрендерить напрямую. Кроме того, разве это не проблема OpenGL?
Я думаю, что эта проблема как-то связана с настройкой цели рендеринга SDL. Проблема возникает, когда при рендеринге одного прямоугольника имеется более двух текстур — после изменения размера окна он исчезает. Переключение в развернутое состояние также приведет к его исчезновению, а при восстановлении оно снова появится. Никаких событий SDL_RENDER_TARGETS_RESET
или SDL_RENDER_DEVICE_RESET
не происходит.
В данном примере ваши текстуры заполнены статическим цветом — вы можете сгенерировать массив пиксельных данных и использовать его в качестве источника статической текстуры. Если ваши текстуры сложные (но данные по-прежнему статичны) — вы можете сгенерировать их, как делаете сейчас, а затем считать данные текстуры в системную память и создать из них статическую текстуру. Если текстура динамическая - лучше просто регенерировать ее либо периодически, либо при получении события потери устройства. Это не проблема в OpenGL, поскольку драйвер OpenGL решает эту проблему за вас и делает вид, что устройство никогда не теряется.
@keltar Вероятно, проще просто переключиться на opengl, но мне не нужно редактировать текстуру после ее однократного рендеринга. Мои текстуры в реальных сценариях сложные, но статичные. Вы предлагаете сделать текстуру SDL_TEXTUREACCESS_STREAMING
без цели и копировать туда пиксели вместо того, чтобы сохранять SDL_TEXTUREACCESS_TARGET
?
@SavedowW Я не вижу здесь никакой пользы от потоковой передачи текстур, статика подойдет. Я собрал все для ответа с некоторыми примерами. Если что-то неясно, не стесняйтесь спрашивать дополнительную информацию в комментариях.
Эту проблему обычно называют «потерей устройства». Это было довольно обычным явлением при переключении в полноэкранный режим (в прошлом, когда полноэкранный режим означал не «полноэкранное окно без границ», а настоящий эксклюзивный полноэкранный режим со сменой режима видео) с 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 варианта:
SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl");
перед созданием рендерера. Вероятно, это не лучшее решение, но оно должно работать.
обновите содержимое текстур рендеринга, когда получите событие SDL_RENDER_TARGETS_RESET
. Если ваш код создания текстуры содержит некоторую случайность, вам придется сохранить случайное начальное значение, чтобы иметь возможность воссоздать точно такой же контент.
Если содержимое текстуры не меняется, после подготовки содержимого текстуры рендеринга преобразуйте ее в статическую текстуру. Например. что-то вроде:
// ... 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 user7860670 кажется, вы знаете о проблеме ОП больше, чем сами ОП, так что, возможно, вам следует описать ее лучше. Или у вас другая проблема. В данном примере я получаю событие RENDER_TARGETS_RESET при каждом изменении размера, и после него содержимое рендеринговых текстур становится недействительным.
@user7860670 github.com/libsdl-org/SDL/blob/… он выполняет D3D_Reset, если размер изменился. Это, конечно, если ваш драйвер рендеринга — Direct3D, который используется по умолчанию для SDL2.
Хорошо, однако воссоздание рендерера при изменении размера окна кажется отдельной проблемой.
@user7860670 user7860670 у меня действительно есть событие SDL_RENDER_TARGETS_RESET
, я проверил его
@keltar большое спасибо за объяснение, я не знал, что таким образом можно редактировать текстуру
Обычно это называется «потерей устройства». Найдите события SDL_RENDER_TARGETS_RESET и SDL_RENDER_DEVICE_RESET. Это означает, что все текстуры, предназначенные для рендеринга, больше не действительны и их необходимо повторно отрисовать. Есть несколько способов обойти это, например. использование средства рендеринга OpenGL или копирование ваших данных в текстуры без рендеринга, если содержимое не меняется.