我正在尝试在带有
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 wiki 上没有找到任何有关创建纹理或窗口大小调整的注释。我也尝试捕捉
SDL_WINDOWEVENT_EXPOSED
事件,但没有。是什么原因导致这个问题,有什么解决办法吗?
编辑。我尝试在 Linux(EndeavourOS、6.9.3-arch1-1、plasma 6.0.5)上构建此代码,但没有注意到那里有任何问题,所以我猜它是特定于 Windows 的
这个问题通常被称为“设备丢失”。使用 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);