【问题标题】:How to release SDL_Texture from VRAM?如何从 VRAM 中释放 SDL_Texture?
【发布时间】:2019-05-11 06:21:15
【问题描述】:

我对 SDL_Texture 的理解是:

  1. 当您调用 SDL_CreateTextureFromSurface() 时,SDL_Texture 位于主内存中。
  2. 当您调用 SDL_RenderCopy() 时,纹理位于 VRAM 上。
  3. 当您调用 SDL_DestroyTexture() 时,纹理会从 VRAM 和主内存中释放。

我说的对吗?

我正在制作一个应用程序,在渲染它们之前从图像文件一次创建大量纹理,因为图像非常大并且 SDL_CreateTextureFromSurface() 需要很长时间。当我依次使用 SDL_RenderCopy() 和 SDL_RenderPresent() 渲染它们时,VRAM 的使用量逐渐增加,使用量达到最大限制后应用程序变慢。

我想从 VRAM 中释放纹理,但不想从主内存中释放它,因为重新创建纹理需要很长时间。有可能吗?

这是最少的代码。它一直工作到专用 GPU 内存使用率达到 100%。之后,它变得非常缓慢。 (Windows10、Visual Studio 2017、NVIDIA P6000)

#include <fstream>
#include <iostream>
#include <string>
#include <vector>
#include <filesystem>
#include <thread>
#include <chrono>

#include "SDL.h"
#include "SDL_image.h"

int main(int argc, char** argv) {


    std::vector<SDL_Texture*> texture_list;
    SDL_Init(SDL_INIT_VIDEO);

    SDL_Window* window = SDL_CreateWindow( "", 0, 0, 7680, 4320, SDL_WINDOW_BORDERLESS);

    SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);

    namespace fs = std::experimental::filesystem;

    for ( auto ent : fs::recursive_directory_iterator("c:\\picture\\1k") ) {
        if(!fs::is_directory(ent)){
            auto f = ent.path().generic_string();
            auto surface = IMG_Load(f.c_str());
            std::cout << "file:" << f << std::endl;
            auto tex = SDL_CreateTextureFromSurface(renderer, surface);
            if(!tex){
                std::cout << "SDL_CreateTextureFromSurface error" << std::endl;
                std::cout << SDL_GetError() << std::endl;
            }
            texture_list.push_back(tex);

            SDL_FreeSurface(surface);
        }
    }

    while (true) {
        for (auto tex : texture_list) {

            if(SDL_RenderClear(renderer) != 0){
                std::cout << "SDL_RenderClear error" << std::endl;
                std::cout << SDL_GetError() << std::endl;
            }
            if(SDL_RenderCopy(renderer, tex, NULL, NULL) < 0){
                std::cout << "SDL_RenderCopy error" << std::endl;
                std::cout << SDL_GetError() << std::endl;
            }
            SDL_RenderPresent(renderer);

            SDL_Delay(1);
            //std::cout << cnt++ << std::endl;
            SDL_Event event;

            if(SDL_PollEvent(&event)){
                std::cout << event.type << std::endl;
                switch (event.type) {
                    case SDL_KEYDOWN:
                        switch (event.key.keysym.sym) {
                            case SDLK_q:
                                exit(0);

                        }
                        break;
                }
            }
        }
    }

    return 0;
}

【问题讨论】:

  • 重复 RenderCopy 不会增加您的内存占用。如果您在问题中添加一些代码(最好的情况是最小的完整可编译可验证示例),那么可能会说出您的内存使用量来自哪里。也不清楚您如何衡量内存使用情况,尤其是 VRAM。
  • 您不必担心为各种后端特意从您那里抽象出来的实现细节。您的内存使用与调用SDL_RenderCopy() 几乎没有关系。我怀疑您实际上是在创建多个重复的纹理。
  • 感谢您的建议。我添加了我制作的代码。
  • @yamasv 我认为给定的代码示例没有任何问题。只是为了确保,您使用您提供的代码对其进行了测试,它显示了非常快速的幻灯片,但内存使用量随着每一帧的通过而不断增加?您如何衡量 VRAM 的使用率? RAM使用量也增加了吗?如果是这样,则可能是图形驱动程序问题;您可以通过例如尝试不同的渲染后端吗? SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl"); 在创建窗口/渲染器之前(但在 SDL_Init 之后)?
  • 不,不正确。 SDL_Textures 始终驻留在 VRAM 中(除非您使用软件渲染器),而 SDL_Surfaces 则存储在常规 RAM 中。 SDL_RenderCopy 只是渲染图像(“复制”到屏幕上)的函数的一个有趣名称。

标签: sdl sdl-2


【解决方案1】:

每个纹理都发生一次!

(也许你是故意的,但它似乎没有用;幻灯片有更短更好的设计)

您应该在主循环开始的每个主循环迭代中只调用一次SDL_RenderClear。同样,您应该仅在主循环的末尾附近调用 SDL_RenderPresent 以及 SDL_PollEvent

while (true) {
    if(SDL_RenderClear(renderer) != 0){
        std::cout << "SDL_RenderClear error" << std::endl;
        std::cout << SDL_GetError() << std::endl;
    }

    for (auto tex : texture_list) {
        if(SDL_RenderCopy(renderer, tex, NULL, NULL) < 0){
            std::cout << "SDL_RenderCopy error" << std::endl;
            std::cout << SDL_GetError() << std::endl;
        }
    }

    SDL_RenderPresent(renderer);

    SDL_Delay(1);
    //std::cout << cnt++ << std::endl;
    SDL_Event event;

    if(SDL_PollEvent(&event)){
        std::cout << event.type << std::endl;
        switch (event.type) {
            case SDL_KEYDOWN:
                switch (event.key.keysym.sym) {
                    case SDLK_q:
                        exit(0);

                }
                break;
        }
    }
}

请注意,SDL_RenderCopy 缺少目标矩形,因此每个纹理都将绘制在前一个之上...

如果您确实想要幻灯片。您可能预先创建所有纹理并尝试在滑动切换时按需构建它们或创建单个流纹理并更新像素(可能如果您的图像尺寸不同,则效果不佳)

【讨论】:

  • 谢谢你,布拉德。不是每次调用 SDL_RenderCopy() 后都需要调用 SDL_RenderPresent() 吗?我不想每次都创建纹理,因为每个图像都非常大(8K,未压缩的 TIFF)并且 SDL_CreateTextureFromSurface() 需要很长时间。
  • @yamasv 不,您应该每帧只调用一次。我建议您将图像组合成尽可能少的纹理图集,否则根本无法很好地缩放。你能详细说明你真正想要完成的事情吗?如果您想要幻灯片(您的代码现在的工作方式),那么不要担心SDL_CreateTextureFromSurface 有多慢。如果你不这样做,那么使用地图集。
  • 我真正想做的是一个播放器,可以正常播放,快速(跳过),反向播放等。在正常播放模式下,我想以60 fps播放。如果图像数量不是很多并且所有纹理都可以存储在 VRAM 中,我的应用程序可以执行它们。 (对不起,我的英语水平不足以解释我真正想做的事情。)
  • @yamasv 我相信你已经足够清楚了。您当前的架构不会完全允许这样做(您需要避免迭代所有图像;使用只有 1 个清除/呈现/轮询的简单循环)。我不知道你有多少图像或它们的大小,但最好的办法是将它们全部放在一个纹理上,然后只渲染代表每个单独图像(图集)的纹理的一部分。您可能需要多个图集以避免纹理大小限制(和 GPU 内存)。使用 SDL_SurfaceSDL_Texture 作为渲染目标预构建图集图像编辑器或在运行时。
  • @yamasv 同样,您尝试限制 FPS 的方式也行不通。这是一个单独的问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-10-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-06
  • 1970-01-01
相关资源
最近更新 更多