C++SDL2尝试渲染SDL_Texture时出错:无效纹理

C++ SDL2 Error when trying to render SDL_Texture: Invalid texture

本文关键字:出错 无效 纹理 Texture SDL C++SDL2      更新时间:2023-10-16

我正在尝试制作一个简单的游戏,当我尝试渲染SDL_Texture时,我遇到了一个莫名其妙的错误。我已经把一切都设置好了,我能够用SDL_RenderClear成功地清除屏幕,而且我的纹理不是空的,所以它应该被正确创建。但当我尝试调用render()函数时,我得到了一个错误,SDL_GetError()返回"无效纹理"。

编辑:我现在已经按照要求创建了一个MCVE,我对它进行了测试,以验证它是否再现了错误。它应该在窗口中显示路径为"gfx/grid.bmp"的图像,但它却给了我错误。这是完整的MCVE:

#include <SDL.h>
#include <string>
#include <iostream>
#undef main
class Texture{
private:
    SDL_Texture* texture;
    SDL_Renderer* renderer;
    int width;
    int height;
public:
    Texture(){
        texture = nullptr;
    }
    Texture (SDL_Renderer* ren, std::string path){
        texture = nullptr;
        SDL_Surface* surface = nullptr;
        surface = SDL_LoadBMP(path.c_str());
        if(surface == nullptr) return;
        renderer = ren;
        texture = SDL_CreateTextureFromSurface( renderer, surface );
        if(texture == 0) return;
        width = surface->w;
        height = surface->h;
        SDL_FreeSurface(surface);
        std::cout << "Texture '" << path << "' successfully loadedn";
    }
    bool loaded(){
        return texture != 0;
    }
    bool render(int x = 0, int y = 0){
        SDL_Rect dest;
        dest.x = x;
        dest.y = y;
        dest.w = width;
        dest.h = height;
        return SDL_RenderCopy(renderer, texture, nullptr, &dest) == 0;
    }
    void unload(){
        if(loaded()){
            SDL_DestroyTexture(texture);
            texture = nullptr;
            renderer = nullptr;
            width = 0;
            height = 0;
        }
    }
    ~Texture(){
        unload();
    }
};
class CApp{
private:
    bool running = true;
    SDL_Window* window = nullptr;
    SDL_Renderer* renderer = nullptr;
    Texture graphic_grid;
    bool render = true;
public:
    int OnExecute();
    bool OnInit();
    void OnEvent(SDL_Event* event);
    void OnRender();
    void OnCleanup();
};
bool CApp::OnInit(){
    if(SDL_Init(SDL_INIT_EVERYTHING) < 0){
        return false;
    }
    if((window = SDL_CreateWindow("Simple Tic-Tac-Toe", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 600, 600, SDL_WINDOW_SHOWN)) == nullptr){
        return false;
    }
    renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
    if(renderer == nullptr){
        std::cout << "Renderer failed to load! Error: " << SDL_GetError();
        return false;
    }
    SDL_SetRenderDrawColor( renderer, 0xFF, 0xFF, 0xFF, 0xFF );
    graphic_grid = Texture(renderer, "gfx/grid.bmp");
    if(!graphic_grid.loaded()){
        std::cout << "Image failed to load! Error: " << SDL_GetError();
        return false;
    }
    std::cout << "Initialized fullyn";
    return true;
}
void CApp::OnEvent(SDL_Event* event){
    switch(event->type == SDL_QUIT){
        running = false;
    }
}
void CApp::OnRender(){
    if(!render) return;
    if(SDL_RenderClear(renderer) == 0){
        std::cout << "Successfully cleared screenn";
    }
    if(!graphic_grid.loaded()){
        std::cout << "Texture not loaded!n";
    }else{
        std::cout << "Texture not null!n";
    }
    //This is the place where I get the "Invalid texture" error, everything else works fine
    if(!graphic_grid.render()){
        std::cout << "Failed to render image! Error: " << SDL_GetError() << 'n';
    }
    SDL_RenderPresent(renderer);
    render = false;
}
void CApp::OnCleanup(){
    graphic_grid.unload();
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    renderer = nullptr;
    window = nullptr;
    SDL_Quit();
}
int CApp::OnExecute(){
    if(OnInit() == false){
        return -1;
    }
    SDL_Event event;
    while(running){
        while(SDL_PollEvent(&event)){
            OnEvent(&event);
        }
        OnRender();
    }
    OnCleanup();
    return 0;
}
int main(){
    CApp theApp;
    return theApp.OnExecute();
}

我四处搜索,找不到任何解释我的错误的东西。我能找到的最接近的东西是这个问题,但这个错误是由渲染器在纹理之前被破坏引起的,而我的情况并非如此。

我真的希望有人能告诉我是什么导致了这个错误,任何建议都将不胜感激!

您的问题是在中复制纹理

graphic_grid = Texture(renderer, "gfx/grid.bmp");

由于您没有定义复制赋值运算符或移动赋值,编译器为您提供了一个运算符,它只是按原样复制成员。

一旦Texture(renderer, "gfx/grid.bmp");创建的临时文件被销毁,graphic_grid.texture就不再有效。你可以通过去掉Texture析构函数来测试这一点,你应该会发现一切都如预期的那样工作(当然你实际上并不想这样做,这不是一个修复)。

您将需要实现移动赋值或复制赋值构造函数,如果您的Texture不打算被复制,最好是前者(由于您需要赋值,您也应该提供移动/复制构造函数,以及其他两个移动/复制方法,按照五条规则,或者至少显式地提供delete)。

请阅读0/3/5 规则

用C++11,三条规则变成五条规则?

如果您正在使用GCC,-Weffc++标志将警告您此类不可靠类设计的某些部分。

就我个人而言,我尽量将句柄和指针封装到RAII类型中,只要每个成员都对自己负责,通常就不必费力地使用复制和移动构造函数。std::unique_ptr对此非常有效,例如:

template<typename T, void(*destroy)(T*), typename Ptr = std::unique_ptr<T, decltype(destroy)>>
struct Handle : public Ptr
{
  Handle(T*&& p): Ptr{p, destroy}
  {
    if (!*this)
      throw std::runtime_error{SDL_GetError()};
  }
};
using Window_h  = Handle<SDL_Window, SDL_DestroyWindow>;
using Surface_h = Handle<SDL_Surface, SDL_FreeSurface>;
// And so on for other handles

可按以下方式使用

Window_h window{SDL_CreateWindow(
  "Hello world", 
  SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
  800, 600,
 0 
)};

如果按照上面的方式包装SDL_Texture,那么Texture类将有一个隐式的移动构造函数和移动赋值运算符,这将是开箱即用的(但是,您必须去掉renderer成员,或者以不同的方式处理它,例如,使其成为std::shared_ptr,这样它就不会与纹理一起被破坏,除非它的引用计数为0)。

您的代码还有一些其他奇怪的地方,比如过早地从构造函数返回。如果无法构造对象,则应抛出异常。

我不知道您正在调用哪个构造函数来创建Texture类的对象,我认为你所创造的表面存在一些问题。我已经尝试过你的代码进行了一些更改,它运行良好,

void CApp::OnRender(){
    SDL_Window* pWindow;
    SDL_Renderer* render;
    SDL_Surface* surface;
    SDL_Init(SDL_INIT_VIDEO);
    // Create wndow 
    pWindow = SDL_CreateWindow( 
        "Dummy", 
        100, 
        100, 
        640, 480, 
        SDL_WINDOW_SHOWN); 
    // Create renderer 
    render = SDL_CreateRenderer(pWindow, -1, 0); 
    // Create Surface
    surface = SDL_GetWindowSurface(pWindow);
    // Creating object of class texture
    Texture graphic_grid(render, surface);
    if(SDL_RenderClear(render) == 0){
        std::cout << "Successfully cleared screenn";
    }
    if(!graphic_grid.loaded()){
        std::cout << "Texture not loaded!n";
    }else{
        std::cout << "Texture not null!n";
    }
    if(!graphic_grid.render()){
        std::cout << "Failed to render image! Error: " << SDL_GetError() << 'n';
    }
    for(int i = 0; i < 9; i++){
        if(grid[i] == 0) continue;
        int x = i%3*200;
        int y = i/3*200;
        if(grid[i] == 1){
            graphic_x.render(x, y);
        }else{
            graphic_o.render(x, y);
        }
    }
    SDL_RenderPresent(render);
    render = false;
}