Окно SDL2 становится черным при изменении размера

Я начал работать с SDL2 и не имею с этим опыта. Я работаю над системой Mac. Почти все было хорошо, но у меня есть проблема, что при изменении размера окна с изменяемым размером во время перетаскивания ручки окно становится черным, и я могу перекрасить его только после отпускания. И я проверил, что во время изменения размера окна никакое событие не создается, и у меня нет возможности вмешаться или обнаружить это, поскольку цикл событий просто приостановлен. Есть ли возможные решения?

Вот код (почти копия учебника по обработке события изменения размера):

SDL_Event event;

SDL_Rect nativeSize;
SDL_Rect newWindowSize;

float scaleRatioW;//This is to change anything that might rely on something like mouse coords
float scaleRatioH; //(such as a button on screen) over to the new coordinate system scaling would create

SDL_Window * window; //Our beautiful window
SDL_Renderer * renderer; //The renderer for our window
SDL_Texture * backBuffer; //The back buffer that we will be rendering everything to before scaling up

SDL_Texture * ballImage; //A nice picture to demonstrate the scaling;

bool resize;

void InitValues(); //Initialize all the variables needed
void InitSDL();     //Initialize the window, renderer, backBuffer, and image;
bool HandleEvents(); //Handle the window changed size event
void Render();            //Switches the render target back to the window and renders the back buffer, then switches back.
void Resize();      //The important part for stretching. Changes the viewPort, changes the scale ratios

void InitValues()
{
    nativeSize.x = 0;
    nativeSize.y = 0;
    nativeSize.w = 256;
    nativeSize.h = 224; //A GameBoy size window width and height

    scaleRatioW = 1.0f;
    scaleRatioH = 1.0f;

    newWindowSize.x = 0;
    newWindowSize.y = 0;
    newWindowSize.w = nativeSize.w;
    newWindowSize.h = nativeSize.h;

    window = NULL;
    renderer = NULL;
    backBuffer = NULL;
    ballImage = NULL;

    resize = false;
}

void InitSDL()
{
    if(SDL_Init(SDL_INIT_EVERYTHING) < 0)
    {
        //cout << "Failed to initialize SDL" << endl;
        printf("%d\r\n", __LINE__);
    }

    //Set the scaling quality to nearest-pixel
    if(SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0") < 0)
    {
        //cout << "Failed to set Render Scale Quality" << endl;
         printf("%d\r\n", __LINE__);
    }

    //Window needs to be resizable
    window = SDL_CreateWindow("Rescaling Windows!",
                                                    SDL_WINDOWPOS_CENTERED,
                                                    SDL_WINDOWPOS_CENTERED,
                                                    256,
                                                    224,
                                                    SDL_WINDOW_RESIZABLE);

    //You must use the SDL_RENDERER_TARGETTEXTURE flag in order to target the backbuffer
    renderer = SDL_CreateRenderer(window,
                                                      -1,
                                                      SDL_RENDERER_ACCELERATED |
                                                      SDL_RENDERER_TARGETTEXTURE);

    //Set to blue so it's noticeable if it doesn't do right.
    SDL_SetRenderDrawColor(renderer, 0, 0, 200, 255);

    //Similarly, you must use SDL_TEXTUREACCESS_TARGET when you create the texture
    backBuffer = SDL_CreateTexture(renderer,
                                                       SDL_GetWindowPixelFormat(window),
                                                       SDL_TEXTUREACCESS_TARGET,
                                                       nativeSize.w,
                                                       nativeSize.h);

    //IMPORTANT Set the back buffer as the target
    SDL_SetRenderTarget(renderer, backBuffer);

    //Load an image yay
    SDL_Surface * image = SDL_LoadBMP("Ball.bmp");

    ballImage = SDL_CreateTextureFromSurface(renderer, image);

    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);

    SDL_FreeSurface(image);
}

bool HandleEvents()
{
    while(SDL_PollEvent(&event) )
    {
       printf("%d\r\n", __LINE__);
       if(event.type == SDL_QUIT)
       {
             printf("%d\r\n", __LINE__);
            return true;
       }
        else if(event.type == SDL_WINDOWEVENT)
        {
            if(event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)
            {
                resize = true;
                printf("%d\r\n", __LINE__);
            }
        }

        return false;
    }
    return false;
}

void Render()
{
    SDL_RenderCopy(renderer, ballImage, NULL, NULL); //Render the entire ballImage to the backBuffer at (0, 0)
    printf("%d\r\n", __LINE__);

    SDL_SetRenderTarget(renderer, NULL); //Set the target back to the window

    if(resize)
    {
        Resize();
        resize = false;
    }
    printf("%d\r\n", __LINE__);

    SDL_RenderCopy(renderer, backBuffer, &nativeSize, &newWindowSize); //Render the backBuffer onto the screen at (0,0)
    SDL_RenderPresent(renderer);
    SDL_RenderClear(renderer); //Clear the window buffer

    SDL_SetRenderTarget(renderer, backBuffer); //Set the target back to the back buffer
    SDL_RenderClear(renderer); //Clear the back buffer
    printf("%d\r\n", __LINE__);

}

void Resize()
{
    int w, h;
    printf("%d\r\n", __LINE__);

    SDL_GetWindowSize(window, &w, &h);

    scaleRatioW = w / nativeSize.w;
    scaleRatioH = h / nativeSize.h;  //The ratio from the native size to the new size

    newWindowSize.w = w;
    newWindowSize.h = h;

    //In order to do a resize, you must destroy the back buffer. Try without it, it doesn't work
    SDL_DestroyTexture(backBuffer);
    backBuffer = SDL_CreateTexture(renderer,
                                   SDL_GetWindowPixelFormat(window),
                                   SDL_TEXTUREACCESS_TARGET, //Again, must be created using this
                                   nativeSize.w,
                                   nativeSize.h);

    SDL_Rect viewPort;
    SDL_RenderGetViewport(renderer, &viewPort);

    if(viewPort.w != newWindowSize.w || viewPort.h != newWindowSize.h)
    {
        //VERY IMPORTANT - Change the viewport over to the new size. It doesn't do this for you.
        SDL_RenderSetViewport(renderer, &newWindowSize);
    }
}

int main(int argc, char * argv[])
{
    InitValues();
    InitSDL();

    bool quit = false;
    printf("%d\r\n", __LINE__);

    while(!quit)
    {
        printf("%d\r\n", __LINE__);
        quit = HandleEvents();
        Render();
    }

    return 0;
}

person Aozi    schedule 23.01.2016    source источник
comment
Я почти уверен, что вы должны вызывать SDL_GL_SetAttribute перед созданием окно.   -  person Nicol Bolas    schedule 24.01.2016
comment
Да, верно, но я не думаю, что это связано с проблемой. Это оставшийся код какого-то теста. Но я еще раз испытаю.   -  person Aozi    schedule 24.01.2016
comment
Да, перемещение его перед созданием окна не изменило ситуацию. Но спасибо за тщательную проверку кода.   -  person Aozi    schedule 24.01.2016


Ответы (2)


Хорошо, немного поборовшись с SDL2, я заработал с macOS 10.12.

Вот проблема:

  1. SDL2 перехватывает события изменения размера и повторно отправляет только последние 3, когда вы опрашиваете события, используя, например, SDL_PollEvent(&event).
  2. В это время (вы нажимали левую кнопку мыши на области изменения размера и удерживали мышь) SDL_PollEvent блокируется.

Вот обходной путь:

К счастью, вы можете подключиться к обработчику событий, используя SDL_SetEventFilter. Это будет срабатывать каждый раз, когда будет получено событие. Таким образом, для всех событий изменения размера по мере их возникновения.

Итак, что вы можете сделать, так это зарегистрировать свой собственный обратный вызов фильтра событий, который в основном разрешает каждое событие (путем возврата 1), прослушивает события изменения размера и отправляет их в ваш цикл отрисовки.

Пример:

//register this somewhere
int filterEvent(void *userdata, SDL_Event * event) {
    if (event->type == SDL_WINDOWEVENT && event->window.event == SDL_WINDOWEVENT_RESIZED) {
        //convert userdata pointer to yours and trigger your own draw function
        //this is called very often now
        //IMPORTANT: Might be called from a different thread, see SDL_SetEventFilter docs
        ((MyApplicationClass *)userdata)->myDrawFunction(); 

        //return 0 if you don't want to handle this event twice
        return 0;
    }

    //important to allow all events, or your SDL_PollEvent doesn't get any event
    return 1;
}


///after SDL_Init
SDL_SetEventFilter(filterEvent, this) //this is instance of MyApplicationClass for example

Важно: Не вызывайте SDL_PollEvent в обратном вызове filterEvent, так как это приведет к странному поведению зависших событий. (изменение размера иногда не останавливается, например)

person Marc J. Schmidt    schedule 14.06.2018
comment
@HolyBlackCat Это работает в Windows. Но в Windows фильтр всегда работает в основном потоке. - person Bernard; 20.08.2018
comment
Я нигде не могу найти никакой гарантии, что SDL_PollEvent() не вернется, пока выполняется код в filterEvent. Отсутствие этой гарантии синхронизации означает, что нам нужно будет использовать мьютексы (при условии, что myDrawFunction не может обрабатывать вызовы из двух потоков одновременно). - person Bernard; 20.08.2018
comment
Этот ответ работает, но я не думаю, что он технически правильный. Изменение размера отправляет несколько SDL_WINDOWEVENTS, последним из которых будет SDL_WINDOWEVENT_EXPOSED. Если мы прочитаем исходный код SDL для этого события, то, конечно же, там будет сказано, что окно было открыто и должно быть перерисовано. Тот факт, что вы добавили вызов myDrawFunction();, также работает, потому что SDL_WINDOWEVENT_RESIZED также является одним из событий, отправляемых до EXPOSED. - person suprjami; 22.05.2021

Оказывается, это не относится к моему коду, а является частью более широкой проблемы со всеми библиотеками OpenGL в MacOSX. Хотя недавние исправления в GLFW исправили это, а в версии GLUT, которая поставляется с самим XCode, это намного лучше, и вы просто наблюдаете мерцание в окне при изменении размера.

https://github.com/openframeworks/openFrameworks/issues/2800 https://github.com/openframeworks/openFrameworks/issues/2456

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

Чтобы решить эту проблему, вы должны манипулировать используемой библиотекой и перекомпилировать ее. Вы должны добавить это или что-то подобное (в зависимости от вашей среды разработки) в обработчик изменения размера, чтобы обойти блокировку:

ofNotifyUpdate();
instance->display();

что губительно, если вы новичок и хотите иметь возможность использовать обновления библиотеки без особых трудностей. Другое решение состоит в том, чтобы переопределить поведение SDL, написав другой обработчик событий, который делает это. Это лучше, так как не нужно редактировать код SDL, но добавляется куча специфичного для платформы кода, к которому я лично не стремлюсь, и который вызовет много проблем, на исправление которых я не хочу тратить время.

После 2 дней поиска, поскольку я только начал проект и не реализовал много, полагаясь на SDL, я решил переключиться на GLFW, который имеет самую плавную обработку изменения размера, и я не наблюдаю мерцания.

person Aozi    schedule 25.01.2016
comment
Что вы имеете в виду под обработчиком изменения размера? Не очень понятно, куда нужно поместить эти две строчки кода. SDL_PollEvent отправляет только одно событие при изменении размера, поэтому вы не можете подключиться к нему в пользовательском пространстве. - person Marc J. Schmidt; 14.06.2018