Program Listing for File Window.cpp

Return to documentation for file (src\Window.cpp)

#include "Window.h"
#include <map>
#include <iostream>

namespace prism {
    namespace view {

        Window::Window(const char* title, int width, int height)
            : Window(title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_SHOWN) {
        }

        Window::Window(const char* title, int width, int height, Uint32 sdlFlags)
            : Window(title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, sdlFlags) {
        }

        Window::Window(const char* title, int x, int y, int width, int height)
            : Window(title, x, y, width, height, SDL_WINDOW_SHOWN) {
        }

        Window::Window(const char* title, int width, int height, const std::string& iconPath)
            : Window(title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_SHOWN) {
            SDL_Surface* icon = SDL_LoadBMP(iconPath.c_str());
            if (icon) {
                SDL_SetWindowIcon(m_sdlWindow, icon);
                SDL_FreeSurface(icon);
            }
        }

        Window Window::CreateCentered(const char* title, int width, int height) {
            return Window(title, width, height);
        }

        Window::Window(const char* title, int width, int height,
            int minWidth, int minHeight, int maxWidth, int maxHeight)
            : Window(title, width, height) {
            SDL_SetWindowMinimumSize(m_sdlWindow, minWidth, minHeight);
            SDL_SetWindowMaximumSize(m_sdlWindow, maxWidth, maxHeight);
        }

        Window::Window(const char* title, int x, int y, int width, int height, Uint32 sdlFlags)
            : m_isDestroyed(false), instance(VK_NULL_HANDLE), physicalDevice(VK_NULL_HANDLE),
            device(VK_NULL_HANDLE), surface(VK_NULL_HANDLE) {
            if (!s_sdlInitialized) {
                if (SDL_Init(SDL_INIT_VIDEO) < 0) {
                    throw std::runtime_error("SDL could not initialize!");
                }
                s_sdlInitialized = true;
            }

            m_sdlWindow = SDL_CreateWindow(title, x, y, width, height, sdlFlags | SDL_WINDOW_VULKAN);
            if (!m_sdlWindow) {
                throw std::runtime_error("Window could not be created!");
            }

            vulkanInut();
        }

        Window::~Window() {
            destroy();
        }

        const char* Window::getTitle() const {
            return SDL_GetWindowTitle(m_sdlWindow);
        }

        void Window::setTitle(const char* title) {
            SDL_SetWindowTitle(m_sdlWindow, title);
        }

        int Window::getWidth() {
            int width;
            SDL_GetWindowSize(m_sdlWindow, &width, nullptr);
            return width;
        }

        int Window::getHeight() {
            int height;
            SDL_GetWindowSize(m_sdlWindow, nullptr, &height);
            return height;
        }

        void Window::getSize(int* width, int* height) {
            SDL_GetWindowSize(m_sdlWindow, width, height);
        }

        void Window::setWidth(int width) {
            int height;
            SDL_GetWindowSize(m_sdlWindow, nullptr, &height);
            SDL_SetWindowSize(m_sdlWindow, width, height);
        }

        void Window::setHeight(int height) {
            int width;
            SDL_GetWindowSize(m_sdlWindow, &width, nullptr);
            SDL_SetWindowSize(m_sdlWindow, width, height);
        }

        void Window::setSize(int width, int height) {
            SDL_SetWindowSize(m_sdlWindow, width, height);
        }

        void Window::setFullscreen(bool enabled) {
            SDL_SetWindowFullscreen(m_sdlWindow, enabled ? SDL_WINDOW_FULLSCREEN : 0);
        }

        void Window::setBorderless(bool enabled) {
            SDL_SetWindowBordered(m_sdlWindow, enabled ? SDL_FALSE : SDL_TRUE);
        }

        void Window::setResizable(bool enabled) {
            SDL_SetWindowResizable(m_sdlWindow, enabled ? SDL_TRUE : SDL_FALSE);
        }

        void Window::setGrabMouse(bool enabled) {
            SDL_SetWindowGrab(m_sdlWindow, enabled ? SDL_TRUE : SDL_FALSE);
        }

        bool Window::isFullscreen() const {
            return (SDL_GetWindowFlags(m_sdlWindow) & SDL_WINDOW_FULLSCREEN) != 0;
        }

        bool Window::isBorderless() const {
            return (SDL_GetWindowFlags(m_sdlWindow) & SDL_WINDOW_BORDERLESS) != 0;
        }

        bool Window::isResizable() const {
            return (SDL_GetWindowFlags(m_sdlWindow) & SDL_WINDOW_RESIZABLE) != 0;
        }

        bool Window::isGrabMouse() const {
            return SDL_GetWindowGrab(m_sdlWindow) == SDL_TRUE;
        }

        void Window::setPosition(int x, int y) {
            SDL_SetWindowPosition(m_sdlWindow, x, y);
        }

        std::pair<int, int> Window::getPosition() const {
            int x, y;
            SDL_GetWindowPosition(m_sdlWindow, &x, &y);
            return { x, y };
        }

        void Window::centerWindow() {
            SDL_SetWindowPosition(m_sdlWindow, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
        }

        void Window::setMinSize(int minWidth, int minHeight) {
            SDL_SetWindowMinimumSize(m_sdlWindow, minWidth, minHeight);
        }

        void Window::setMaxSize(int maxWidth, int maxHeight) {
            SDL_SetWindowMaximumSize(m_sdlWindow, maxWidth, maxHeight);
        }

        bool Window::shouldClose() const {
            return m_isDestroyed;
        }

        void Window::setCloseRequested(bool value) {
            m_isDestroyed = value;
        }

        void Window::handleEvents() {
            SDL_Event event;
            while (SDL_PollEvent(&event)) {
                if (event.type == SDL_QUIT) {
                    m_isDestroyed = true;
                }
                else if (event.type == SDL_WINDOWEVENT) {
                    if (event.window.event == SDL_WINDOWEVENT_MINIMIZED) {
                        m_windowMinimized = true;
                    }
                    else if (event.window.event == SDL_WINDOWEVENT_RESTORED) {
                        m_windowMinimized = false;
                    }
                }
            }
        }

        void Window::clear() {
            // Реализация очистки окна будет зависеть от используемого API рендеринга
        }

        void Window::update() {
            // Реализация обновления окна будет зависеть от используемого API рендеринга
        }

        void Window::destroy() {
            if (m_sdlWindow) {
                if (device != VK_NULL_HANDLE) {
                    vkDestroyDevice(device, nullptr);
                    device = VK_NULL_HANDLE;
                }
                if (surface != VK_NULL_HANDLE) {
                    vkDestroySurfaceKHR(instance, surface, nullptr);
                    surface = VK_NULL_HANDLE;
                }
                if (instance != VK_NULL_HANDLE) {
                    vkDestroyInstance(instance, nullptr);
                    instance = VK_NULL_HANDLE;
                }
                SDL_DestroyWindow(m_sdlWindow);
                m_sdlWindow = nullptr;
                m_isDestroyed = true;
            }
        }

        void Window::vulkanInut()
        {
            createInstance();
            createSurface();
            pickPhysicalDevice();
            createLogicalDevice();
        }

        void Window::createInstance() {
            VkApplicationInfo appInfo{};
            appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
            appInfo.pApplicationName = SDL_GetWindowTitle(m_sdlWindow);
            appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
            appInfo.pEngineName = "No Engine";
            appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
            appInfo.apiVersion = VK_API_VERSION_1_0;

            VkInstanceCreateInfo createInfo{};
            createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
            createInfo.pApplicationInfo = &appInfo;

            uint32_t extensionCount = 0;
            SDL_Vulkan_GetInstanceExtensions(m_sdlWindow, &extensionCount, nullptr);
            std::vector<const char*> extensions(extensionCount);
            SDL_Vulkan_GetInstanceExtensions(m_sdlWindow, &extensionCount, extensions.data());

            createInfo.enabledExtensionCount = extensionCount;
            createInfo.ppEnabledExtensionNames = extensions.data();
            createInfo.enabledLayerCount = 0;

            if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
                throw std::runtime_error("failed to create Vulkan instance!");
            }
        }

        void Window::pickPhysicalDevice() {
            uint32_t deviceCount = 0;
            vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);

            if (deviceCount == 0) {
                throw std::runtime_error("failed to find GPUs with Vulkan support!");
            }

            std::vector<VkPhysicalDevice> devices(deviceCount);
            vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());

            // Вывод информации о всех доступных устройствах
            std::cout << "Available Vulkan devices:\n";
            for (const auto& device : devices) {
                VkPhysicalDeviceProperties deviceProps;
                vkGetPhysicalDeviceProperties(device, &deviceProps);

                std::cout << "  - " << deviceProps.deviceName
                    << " (API: " << VK_VERSION_MAJOR(deviceProps.apiVersion) << "."
                    << VK_VERSION_MINOR(deviceProps.apiVersion) << "."
                    << VK_VERSION_PATCH(deviceProps.apiVersion) << ")\n";
            }

            // Выбор устройства с максимальным рейтингом
            std::multimap<int, VkPhysicalDevice> candidates;
            for (const auto& device : devices) {
                int score = rateDeviceSuitability(device);
                candidates.insert(std::make_pair(score, device));
            }

            // Проверка, что лучшее устройство подходит
            if (candidates.rbegin()->first > 0) {
                physicalDevice = candidates.rbegin()->second;
            }
            else {
                throw std::runtime_error("failed to find a suitable GPU!");
            }

            // Вывод информации о выбранном устройстве
            VkPhysicalDeviceProperties deviceProps;
            vkGetPhysicalDeviceProperties(physicalDevice, &deviceProps);
            std::cout << "\nSelected Vulkan device:\n";
            std::cout << "  - Name: " << deviceProps.deviceName << "\n";
            std::cout << "  - Type: ";
            switch (deviceProps.deviceType) {
            case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: std::cout << "Integrated GPU"; break;
            case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: std::cout << "Discrete GPU"; break;
            case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: std::cout << "Virtual GPU"; break;
            case VK_PHYSICAL_DEVICE_TYPE_CPU: std::cout << "CPU"; break;
            default: std::cout << "Other"; break;
            }
            std::cout << "\n";
            std::cout << "  - API Version: " << VK_VERSION_MAJOR(deviceProps.apiVersion) << "."
                << VK_VERSION_MINOR(deviceProps.apiVersion) << "."
                << VK_VERSION_PATCH(deviceProps.apiVersion) << "\n";
            std::cout << "  - Driver Version: " << deviceProps.driverVersion << "\n";
            std::cout << "  - Vendor ID: " << deviceProps.vendorID << "\n";
            std::cout << "  - Device ID: " << deviceProps.deviceID << "\n";
            std::cout << "  - Score: " << candidates.rbegin()->first << "\n\n";
        }

        int Window::rateDeviceSuitability(VkPhysicalDevice device) {
            VkPhysicalDeviceProperties deviceProps;
            VkPhysicalDeviceFeatures deviceFeatures;
            vkGetPhysicalDeviceProperties(device, &deviceProps);
            vkGetPhysicalDeviceFeatures(device, &deviceFeatures);

            int score = 0;

            // Вывод дополнительной информации при оценке
            std::cout << "\nEvaluating device: " << deviceProps.deviceName << "\n";
            std::cout << "  - Device type: ";
            switch (deviceProps.deviceType) {
            case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: std::cout << "Integrated GPU"; break;
            case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: std::cout << "Discrete GPU"; break;
            case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: std::cout << "Virtual GPU"; break;
            case VK_PHYSICAL_DEVICE_TYPE_CPU: std::cout << "CPU"; break;
            default: std::cout << "Other"; break;
            }
            std::cout << "\n";

            // Дискретные GPU имеют преимущество в производительности
            if (deviceProps.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
                score += 1000;
                std::cout << "  + 1000 points for discrete GPU\n";
            }

            // Максимальный размер текстур влияет на качество графики
            score += deviceProps.limits.maxImageDimension2D;
            std::cout << "  + " << deviceProps.limits.maxImageDimension2D
                << " points for max image dimension ("
                << deviceProps.limits.maxImageDimension2D << ")\n";

            // Приложение не может работать без geometry shader
            if (!deviceFeatures.geometryShader) {
                std::cout << "  - Device rejected (no geometry shader support)\n";
                return 0;
            }

            // Проверка поддержки необходимых очередей
            if (!isDeviceSuitable(device)) {
                std::cout << "  - Device rejected (missing required queue families)\n";
                return 0;
            }

            std::cout << "  - Total score: " << score << "\n";

            return score;
        }

        QueueFamilyIndices Window::findQueueFamilies(VkPhysicalDevice device) {
            QueueFamilyIndices indices;

            uint32_t queueFamilyCount = 0;
            vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);

            std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
            vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());

            int i = 0;
            for (const auto& queueFamily : queueFamilies) {
                if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
                    indices.graphicsFamily = i;
                }

                VkBool32 presentSupport = false;
                vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport);
                if (presentSupport) {
                    indices.presentFamily = i;
                }

                if (indices.isComplete()) {
                    break;
                }

                i++;
            }

            return indices;
        }

        void Window::createLogicalDevice() {
            if (!isDeviceSuitable(physicalDevice)) {
                throw std::runtime_error("queue families not complete!");
            }

            QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
            std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
            std::set<uint32_t> uniqueQueueFamilies = {
                indices.graphicsFamily.value(),
                indices.presentFamily.value()
            };

            float queuePriority = 1.0f;
            for (uint32_t queueFamily : uniqueQueueFamilies) {
                VkDeviceQueueCreateInfo queueCreateInfo{};
                queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
                queueCreateInfo.queueFamilyIndex = queueFamily;
                queueCreateInfo.queueCount = 1;
                queueCreateInfo.pQueuePriorities = &queuePriority;
                queueCreateInfos.push_back(queueCreateInfo);
            }

            VkPhysicalDeviceFeatures deviceFeatures{};

            VkDeviceCreateInfo createInfo{};
            createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
            createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());
            createInfo.pQueueCreateInfos = queueCreateInfos.data();
            createInfo.pEnabledFeatures = &deviceFeatures;
            createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
            createInfo.ppEnabledExtensionNames = deviceExtensions.data();
            createInfo.enabledLayerCount = 0;

            if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {
                throw std::runtime_error("failed to create logical device!");
            }

            vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue);
            vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue);
        }

        void Window::createSurface() {
            if (!SDL_Vulkan_CreateSurface(m_sdlWindow, instance, &surface)) {
                throw std::runtime_error("failed to create window surface!");
            }
        }

        bool Window::isDeviceSuitable(VkPhysicalDevice device) {
            QueueFamilyIndices indices = findQueueFamilies(device);

            bool extensionsSupported = checkDeviceExtensionSupport(device);

            return indices.isComplete() && extensionsSupported;
        }

        bool Window::checkDeviceExtensionSupport(VkPhysicalDevice device) {
            uint32_t extensionCount;
            vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr);

            std::vector<VkExtensionProperties> availableExtensions(extensionCount);
            vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data());

            std::set<std::string> requiredExtensions(deviceExtensions.begin(), deviceExtensions.end());

            for (const auto& extension : availableExtensions) {
                requiredExtensions.erase(extension.extensionName);
            }

            return requiredExtensions.empty();
        }

    } // namespace view
} // namespace prism