C++11 + SDL2 + Windows: многопоточная программа зависает после любого события ввода

Я работаю над программой захвата экрана, используя С++ 11, MinGW и Windows API. Я пытаюсь использовать SDL2, чтобы посмотреть, как моя программа захвата экрана работает в режиме реального времени.

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

Я настроил журналирование событий, чтобы понять, что происходит. Я никогда не получаю никаких событий, кроме SDL_WINDOW_FOCUS_GAINED, SDL_TEXTEDITING и SDL_WINDOWEVENT_SHOWN в таком порядке. Все это получено на старте.

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

Может ли кто-нибудь увидеть причину этого зависания в моем коде и объяснить, как это исправить?

Краткое объяснение структуры моей программы сделано для того, чтобы охватить код, который я пропустил. Класс Captor запускает и запускает поток для захвата снимка экрана, который передается в класс Encoder. Encoder запускает переменное количество потоков, которые получают снимок экрана от Captor, кодируют снимок экрана, а затем передают кодировку Screen. Механизм передачи — это класс SynchronousQueue<T>, предоставляющий парные методы put(const T&) и T get(), позволяющие производителю и потребителю синхронизироваться с использованием ресурса; время ожидания этих методов истекает, чтобы система могла реагировать на сообщения об уничтожении.

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

main.cpp

#include "RTSC.hpp"

int main(int argc, char** argv) {
    RTSC rtsc {
        (uint32_t) stoi(argv[1]),
        (uint32_t) stoi(argv[2]),
        (uint32_t) stoi(argv[3]),
        (uint32_t) stoi(argv[4]),
        (uint32_t) stoi(argv[5]),
        (uint32_t) stoi(argv[6])
    };

    while (rtsc.isRunning()) {
        SwitchToThread();
    }

    return 0;
}

RTSC.hpp

#ifndef RTSC_HPP
#define RTSC_HPP

#include "Captor.hpp"
#include "Encoder.hpp"
#include "Screen.hpp"

#include <iostream>
using namespace std;


class RTSC {
    private:
        Captor *captor;
        Encoder *encoder;

        SynchronousQueue<uint8_t*> imageQueue {1};
        SynchronousQueue<RegionList> regionQueue {1};

        Screen *screen;

    public:
        RTSC(
            uint32_t width,
            uint32_t height,
            uint32_t maxRegionCount,
            uint32_t threadCount,
            uint32_t divisionsAlongThreadWidth,
            uint32_t divisionsAlongThreadHeight
        ) {
            captor = new Captor(width, height, imageQueue);
            encoder = new Encoder(
                width,
                height,
                maxRegionCount,
                threadCount,
                divisionsAlongThreadWidth,
                divisionsAlongThreadHeight,
                imageQueue,
                regionQueue
            );

            screen = new Screen(
                width,
                height,
                width >> 1,
                height >> 1,
                regionQueue
            );

            captor->start();
        }

        ~RTSC() {
            delete screen;
            delete encoder;
            delete captor;
        }

        bool isRunning() const {
            return screen->isRunning();
        }
};

#endif

Screen.hpp

#ifndef SCREEN_HPP
#define SCREEN_HPP

#include <atomic>
#include <SDL.h>
#include <windows.h>

#include "Region.hpp"
#include "SynchronousQueue.hpp"

using namespace std;

class Screen {
    private:
        atomic_bool running {false};
        HANDLE thread;
        SynchronousQueue<RegionList>* inputQueue;
        uint32_t inputHeight;
        uint32_t inputWidth;
        uint32_t screenHeight;
        uint32_t screenWidth;

        SDL_Renderer* renderer;
        SDL_Surface* surface;
        SDL_Texture* texture;
        SDL_Window* window;

        void run() {
            SDL_Event event;
            while (running) {
                while (SDL_PollEvent(&event)) {
                    switch (event.type) {
                        case SDL_QUIT:
                            running = false;
                            break;

                        case SDL_WINDOWEVENT:
                            switch (event.window.event) {
                                case SDL_WINDOWEVENT_CLOSE:
                                    running = false;
                                    break;

                        default:
                            break;
                    }
                }

                try {
                    RegionList rl = inputQueue->get();

                    SDL_RenderClear(renderer);

                    SDL_LockSurface(surface);
                    SDL_FillRect(surface, nullptr, 0);

                    for (uint32_t i = 0; i < rl.count; ++i) {
                        Region &r = rl.regions[i];

                        SDL_Rect rect {
                            (int) r.getX(),
                            (int) r.getY(),
                            (int) r.getWidth(),
                            (int) r.getHeight()
                        };
                        uint32_t color =
                            (r.getRed() << 16) +
                            (r.getGreen() << 8) +
                            r.getBlue();
                        SDL_FillRect(surface, &rect, color);
                    }

                    SDL_UnlockSurface(surface);
                    SDL_UpdateTexture(
                        texture,
                        nullptr,
                        surface->pixels,
                        surface->pitch
                    );
                    SDL_RenderCopyEx(
                        renderer,
                        texture,
                        nullptr,
                        nullptr,
                        0,
                        nullptr,
                        SDL_FLIP_VERTICAL
                    );
                } catch (exception &e) {}

                SDL_RenderPresent(renderer);
                SwitchToThread();
            }
        }

        static DWORD startThread(LPVOID self) {
            ((Screen*) self)->run();
            return (DWORD) 0;
        }

    public:
        Screen(
            uint32_t inputWidth,
            uint32_t inputHeight,
            uint32_t windowWidth,
            uint32_t windowHeight,
            SynchronousQueue<RegionList> &inputQueue
        ): inputQueue {&inputQueue}, inputHeight {inputHeight} {
            SDL_Init(SDL_INIT_VIDEO);

            window = SDL_CreateWindow(
                "RTSC",
                SDL_WINDOWPOS_CENTERED,
                SDL_WINDOWPOS_CENTERED,
                windowWidth,
                windowHeight,
                SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE |
                    SDL_WINDOW_INPUT_FOCUS | SDL_WINDOW_MOUSE_FOCUS
            );

            renderer = SDL_CreateRenderer(window, -1, 0);

            surface = SDL_CreateRGBSurface(
                0,
                inputWidth,
                inputHeight,
                24,
                0xFF << 16,
                0xFF << 8,
                0xFF,
                0
            );

            texture = SDL_CreateTexture(
                renderer,
                surface->format->format,
                SDL_TEXTUREACCESS_STREAMING,
                inputWidth,
                inputHeight
            );

            running = true;
            thread = CreateThread(nullptr, 0, startThread, this, 0, nullptr);
        }

        ~Screen() {
            running = false;
            WaitForSingleObject(thread, INFINITE);
            CloseHandle(thread);

            SDL_FreeSurface(surface);
            SDL_DestroyRenderer(renderer);
            SDL_DestroyWindow(window);
            SDL_Quit();
        }

        bool isRunning() const {
            return running;
        }
};

#endif

person sadakatsu    schedule 27.02.2014    source источник
comment
В качестве первого шага, почему бы вам не приостановить выполнение программы и не проверить, где зависают ваши потоки? В Visual Studio и многих других отладчиках это вполне возможно, вы должны сделать это, если можете в своей среде MinGW. Это первое, что нужно попробовать, если вы попали в воспроизводимую тупиковую ситуацию.   -  person pasztorpisti    schedule 27.02.2014


Ответы (1)


У меня нет опыта использования SDL API в многопоточной среде, но это не большая проблема, как вы увидите позже. Я проверил ваш код и, по моему мнению, вы должны изменить по крайней мере одну вещь.

  1. Как правило, в случае систем с графическим интерфейсом (и частично SDL также является системой графического интерфейса) вы всегда должны обращаться к графическому интерфейсу только из основного потока и ожидать, что события графического интерфейса будут поступать из основного потока. Большинство GUI API являются однопоточными, и я не удивлюсь, если это применимо и к SDL. Обратите внимание, что многие системы графического интерфейса по умолчанию работают в основном потоке вашего процесса, и вы не можете выбрать свой собственный поток. Не запускайте код класса Screen в рабочем потоке, запускайте его в основном потоке и выполняйте КАЖДЫЙ вызов SDL API из основного потока.
  2. Если вы пишете игру или подобное программное обеспечение, то (сначала) пишите так, как если бы оно было однопоточным. Подсистемы вашего движка (симуляция физики, та-то и та-система, игровая логика, рендеринг) должны выполняться последовательно одна за другой в вашем основном потоке (из вашего основного цикла). Если вы хотите использовать многопоточность, которая делает это в «другом измерении»: преобразуйте некоторые из подсистем или меньшую единицу работы (например, сортировку слиянием) в многопоточность, например, задачи физической системы часто можно разделить на несколько небольших задач. поэтому, когда система физики обновляется основным потоком, система физики может сжечь все ваши ядра...

Выполнение большинства ваших задач в основном потоке имеет еще одно преимущество: ваш код гораздо проще портировать на любую платформу. При желании, если вы пишете свой код так, чтобы он мог выполняться в однопоточном режиме, во многих случаях это может упростить отладку, а затем у вас также есть «эталонная сборка» для сравнения многопоточной сборки с производительностью.

person pasztorpisti    schedule 27.02.2014
comment
Большое спасибо! Я изменил его так, чтобы Screen никогда не порождал поток, сделал run() общедоступным, дал run() вызов оболочки в RTSC, и основной вызов этого цикла. Теперь он работает отлично. - person sadakatsu; 27.02.2014