<commit_before>////////////////////////////////////////////////////////////////////////////////
/// Reaper
///
/// Copyright (c) 2015-2022 Thibault Schueller
/// This file is distributed under the MIT License
////////////////////////////////////////////////////////////////////////////////

#include "Swapchain.h"

#include "Backend.h"

#include "api/Vulkan.h"
#include "api/VulkanStringConversion.h"

#include "common/Log.h"

#include "core/Profile.h"

namespace
{
VkSurfaceFormatKHR vulkan_swapchain_choose_surface_format(std::vector<VkSurfaceFormatKHR>& surface_formats,
                                                          VkSurfaceFormatKHR               preferredFormat)
{
    // If the list contains only one entry with undefined format
    // it means that there are no preferred surface formats and any can be chosen
    if ((surface_formats.size() == 1) && (surface_formats[0].format == VK_FORMAT_UNDEFINED))
        return preferredFormat;

    for (const VkSurfaceFormatKHR& surface_format : surface_formats)
    {
        if (surface_format.format == preferredFormat.format && surface_format.colorSpace == preferredFormat.colorSpace)
            return surface_format;
    }

    return surface_formats[0]; // Return first available format
}

uint32_t clamp(uint32_t v, uint32_t min, uint32_t max)
{
    if (v < min)
        return min;
    if (v > max)
        return max;
    else
        return v;
}

VkExtent2D clamp(VkExtent2D value, VkExtent2D min, VkExtent2D max)
{
    return {
        clamp(value.width, min.width, max.width),
        clamp(value.height, min.height, max.height),
    };
}

VkExtent2D vulkan_swapchain_choose_extent(const VkSurfaceCapabilitiesKHR& surface_capabilities,
                                          VkExtent2D                      preferredExtent)
{
    // Special value of surface extent is width == height == -1
    // If this is so we define the size by ourselves but it must fit within defined confines
    if (surface_capabilities.currentExtent.width == uint32_t(-1))
        return clamp(preferredExtent, surface_capabilities.minImageExtent, surface_capabilities.maxImageExtent);

    // Most of the cases we define size of the swap_chain images equal to current window's size
    return surface_capabilities.currentExtent;
}

VkSurfaceTransformFlagBitsKHR vulkan_swapchain_choose_transform(const VkSurfaceCapabilitiesKHR& surface_capabilities)
{
    // Sometimes images must be transformed before they are presented (i.e. due to device's orienation
    // being other than default orientation)
    // If the specified transform is other than current transform, presentation engine will transform image
    // during presentation operation; this operation may hit performance on some platforms
    // Here we don't want any transformations to occur so if the identity transform is supported use it
    // otherwise just use the same transform as current transform
    if (surface_capabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)
    {
        return VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
    }
    else
    {
        return surface_capabilities.currentTransform;
    }
}

VkPresentModeKHR vulkan_swapchain_choose_present_mode(std::vector<VkPresentModeKHR>& present_modes)
{
    // Prefer MAILBOX over FIFO
    for (VkPresentModeKHR& present_mode : present_modes)
    {
        if (present_mode == VK_PRESENT_MODE_MAILBOX_KHR)
            return present_mode;
    }

    for (VkPresentModeKHR& present_mode : present_modes)
    {
        if (present_mode == VK_PRESENT_MODE_FIFO_KHR)
            return present_mode;
    }

    Assert(false, "FIFO present mode is not supported by the swap chain!"); // Normally enforced by the API
    return VK_PRESENT_MODE_FIFO_KHR;
}
} // namespace

using namespace vk;

namespace Reaper
{
void configure_vulkan_wm_swapchain(ReaperRoot& root, const VulkanBackend& backend,
                                   const SwapchainDescriptor& swapchainDesc, PresentationInfo& presentInfo)
{
    log_debug(root, "vulkan: configuring wm swapchain");

    Assert(
        vkGetPhysicalDeviceSurfaceCapabilitiesKHR(backend.physicalDevice, presentInfo.surface, &presentInfo.surfaceCaps)
        == VK_SUCCESS);

    VkSurfaceCapabilitiesKHR& surfaceCaps = presentInfo.surfaceCaps;

    // Choose surface format
    VkSurfaceFormatKHR& surfaceFormat = presentInfo.surfaceFormat;
    {
        uint32_t formats_count;
        Assert(
            vkGetPhysicalDeviceSurfaceFormatsKHR(backend.physicalDevice, presentInfo.surface, &formats_count, nullptr)
            == VK_SUCCESS);
        Assert(formats_count > 0);

        std::vector<VkSurfaceFormatKHR> surface_formats(formats_count);
        Assert(vkGetPhysicalDeviceSurfaceFormatsKHR(backend.physicalDevice, presentInfo.surface, &formats_count,
                                                    &surface_formats[0])
               == VK_SUCCESS);

        log_debug(root, "vulkan: swapchain supports {} formats", formats_count);
        for (auto& format : surface_formats)
            log_debug(root, "- format = {}, colorspace = {}", GetFormatToString(format.format),
                      GetColorSpaceKHRToString(format.colorSpace));

        surfaceFormat = vulkan_swapchain_choose_surface_format(surface_formats, swapchainDesc.preferredFormat);

        if (surfaceFormat.format != swapchainDesc.preferredFormat.format
            || surfaceFormat.colorSpace != swapchainDesc.preferredFormat.colorSpace)
        {
            // TODO format to_string() function
            log_warning(root, "vulkan: incompatible swapchain format: format = {}, colorspace = {}",
                        swapchainDesc.preferredFormat.format, swapchainDesc.preferredFormat.colorSpace);
            log_warning(root, "- falling back to: format = {}, colorspace = {}", surfaceFormat.format,
                        surfaceFormat.colorSpace);
        }

        // Choose view format
        {
            VkFormat view_format = surfaceFormat.format;

            // VK_KHR_swapchain_mutable_format is needed for this
            if (view_format == VK_FORMAT_B8G8R8A8_UNORM)
                view_format = VK_FORMAT_B8G8R8A8_SRGB;

            presentInfo.view_format = view_format;
        }

        log_debug(root, "vulkan: selecting swapchain format = {}, colorspace = {}",
                  GetFormatToString(surfaceFormat.format), GetColorSpaceKHRToString(surfaceFormat.colorSpace));
        log_debug(root, "vulkan: selecting swapchain view format = {}", GetFormatToString(presentInfo.view_format));
    }

    // Image count
    uint32_t imageCount = 0;
    {
        log_debug(root, "vulkan: swapchain image count support: min = {}, max = {}", surfaceCaps.minImageCount,
                  surfaceCaps.maxImageCount);

        imageCount = swapchainDesc.preferredImageCount;
        if (imageCount < surfaceCaps.minImageCount)
            imageCount = surfaceCaps.minImageCount;
        else if (surfaceCaps.maxImageCount > 0 && imageCount > surfaceCaps.maxImageCount)
            imageCount = surfaceCaps.maxImageCount;

        if (imageCount != swapchainDesc.preferredImageCount)
            log_warning(root, "vulkan: swapchain image count {} is unsupported. falling back to {}",
                        swapchainDesc.preferredImageCount, imageCount);

        Assert(imageCount > 0);
        Assert(imageCount <= 5); // Seems like a reasonable limit
        presentInfo.imageCount = imageCount;
    }

    {
        uint32_t presentModeCount;
        Assert(vkGetPhysicalDeviceSurfacePresentModesKHR(backend.physicalDevice, presentInfo.surface, &presentModeCount,
                                                         nullptr)
               == VK_SUCCESS);
        Assert(presentModeCount > 0);

        std::vector<VkPresentModeKHR> availablePresentModes(presentModeCount);
        Assert(vkGetPhysicalDeviceSurfacePresentModesKHR(backend.physicalDevice, presentInfo.surface, &presentModeCount,
                                                         &availablePresentModes[0])
               == VK_SUCCESS);

        log_debug(root, "vulkan: swapchain supports {} present modes", presentModeCount);
        for (auto& mode : availablePresentModes)
            log_debug(root, "- {}", GetPresentModeKHRToString(mode));

        presentInfo.presentMode = vulkan_swapchain_choose_present_mode(availablePresentModes);
    }

    {
        VkExtent2D extent = vulkan_swapchain_choose_extent(surfaceCaps, swapchainDesc.preferredExtent);

        if (extent.width != swapchainDesc.preferredExtent.width
            || extent.height != swapchainDesc.preferredExtent.height)
        {
            log_warning(root, "vulkan: swapchain extent {}x{} is not supported", swapchainDesc.preferredExtent.width,
                        swapchainDesc.preferredExtent.height);
            log_warning(root, "- falling back to {}x{}", extent.width, extent.height);
        }
        presentInfo.surfaceExtent = extent;
    }

    // Usage flags
    // NOTE: Some flags might be added at runtime by external forces (driver, layers?)
    // and might trigger a validation warning down the line.
    // I suspect this is primarily done for frame capture purposes, like adding TRANSFER_SRC to allow copies.
    // It should be safe to ignore in this case.
    presentInfo.swapchainUsageFlags = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
    Assert((surfaceCaps.supportedUsageFlags & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) != 0, "Vulkan API error");

    // Transform
    presentInfo.transform = vulkan_swapchain_choose_transform(surfaceCaps);
}

void create_vulkan_wm_swapchain(ReaperRoot& root, const VulkanBackend& backend, PresentationInfo& presentInfo)
{
    REAPER_PROFILE_SCOPE("Vulkan", MP_RED);
    log_debug(root, "vulkan: creating wm swapchain");

    const std::vector<VkFormat> view_formats = {
        presentInfo.view_format,
    };

    const VkImageFormatListCreateInfo format_list = {
        VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO,
        nullptr,
        static_cast<u32>(view_formats.size()),
        view_formats.data(),
    };

    VkSwapchainCreateInfoKHR swap_chain_create_info = {
        VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, // VkStructureType                sType
        &format_list,                                // const void                    *pNext
        VK_SWAPCHAIN_CREATE_MUTABLE_FORMAT_BIT_KHR,  // VkSwapchainCreateFlagsKHR      flags
        presentInfo.surface,                         // VkSurfaceKHR                   surface
        presentInfo.imageCount,                      // uint32_t                       minImageCount
        presentInfo.surfaceFormat.format,            // VkFormat                       imageFormat
        presentInfo.surfaceFormat.colorSpace,        // VkColorSpaceKHR                imageColorSpace
        presentInfo.surfaceExtent,                   // VkExtent2D                     imageExtent
        1,                                           // uint32_t                       imageArrayLayers
        presentInfo.swapchainUsageFlags,             // VkImageUsageFlags              imageUsage
        VK_SHARING_MODE_EXCLUSIVE,                   // VkSharingMode                  imageSharingMode
        0,                                           // uint32_t                       queueFamilyIndexCount
        nullptr,                                     // const uint32_t                *pQueueFamilyIndices
        presentInfo.transform,                       // VkSurfaceTransformFlagBitsKHR  preTransform
        VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,           // VkCompositeAlphaFlagBitsKHR    compositeAlpha
        presentInfo.presentMode,                     // VkPresentModeKHR               presentMode
        VK_TRUE,                                     // VkBool32                       clipped
        VK_NULL_HANDLE                               // VkSwapchainKHR                 oldSwapchain
    };

    Assert(vkCreateSwapchainKHR(backend.device, &swap_chain_create_info, nullptr, &presentInfo.swapchain)
           == VK_SUCCESS);

    Assert(vkGetSwapchainImagesKHR(backend.device, presentInfo.swapchain, &presentInfo.imageCount, nullptr)
           == VK_SUCCESS);
    Assert(presentInfo.imageCount > 0);

    presentInfo.images.resize(presentInfo.imageCount);
    Assert(
        vkGetSwapchainImagesKHR(backend.device, presentInfo.swapchain, &presentInfo.imageCount, &presentInfo.images[0])
        == VK_SUCCESS);

    VkSemaphoreCreateInfo semaphore_create_info = {
        VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, // VkStructureType          sType
        nullptr,                                 // const void*              pNext
        0                                        // VkSemaphoreCreateFlags   flags
    };

    create_swapchain_views(backend, presentInfo);

    Assert(vkCreateSemaphore(backend.device, &semaphore_create_info, nullptr, &presentInfo.imageAvailableSemaphore)
           == VK_SUCCESS);

    log_debug(root, "vulkan: created semaphore with handle: {}",
              static_cast<void*>(presentInfo.imageAvailableSemaphore));

    Assert(vkCreateSemaphore(backend.device, &semaphore_create_info, nullptr, &presentInfo.renderingFinishedSemaphore)
           == VK_SUCCESS);

    log_debug(root, "vulkan: created semaphore with handle: {}",
              static_cast<void*>(presentInfo.renderingFinishedSemaphore));
}

void destroy_vulkan_wm_swapchain(ReaperRoot& root, const VulkanBackend& backend, PresentationInfo& presentInfo)
{
    REAPER_PROFILE_SCOPE("Vulkan", MP_RED);
    log_debug(root, "vulkan: destroying wm swapchain");

    vkDestroySemaphore(backend.device, presentInfo.imageAvailableSemaphore, nullptr);
    vkDestroySemaphore(backend.device, presentInfo.renderingFinishedSemaphore, nullptr);

    destroy_swapchain_views(backend, presentInfo);

    vkDestroySwapchainKHR(backend.device, presentInfo.swapchain, nullptr);
    presentInfo.swapchain = VK_NULL_HANDLE;
}

void resize_vulkan_wm_swapchain(ReaperRoot& root, const VulkanBackend& backend, PresentationInfo& presentInfo,
                                VkExtent2D extent)
{
    REAPER_PROFILE_SCOPE("Vulkan", MP_RED);
    log_debug(root, "vulkan: resizing wm swapchain");

    // Destroy what needs to be
    Assert(vkDeviceWaitIdle(backend.device) == VK_SUCCESS);

    vkDestroySemaphore(backend.device, presentInfo.imageAvailableSemaphore, nullptr);
    vkDestroySemaphore(backend.device, presentInfo.renderingFinishedSemaphore, nullptr);

    destroy_swapchain_views(backend, presentInfo);

    vkDestroySwapchainKHR(backend.device, presentInfo.swapchain, nullptr);
    presentInfo.swapchain = VK_NULL_HANDLE;

    // Reconfigure even if we know most of what we expect/need
    SwapchainDescriptor swapchainDesc;
    swapchainDesc.preferredImageCount = presentInfo.imageCount;
    swapchainDesc.preferredFormat = presentInfo.surfaceFormat;
    swapchainDesc.preferredExtent = {extent.width, extent.height}; // New extent

    configure_vulkan_wm_swapchain(root, backend, swapchainDesc, presentInfo);

    create_vulkan_wm_swapchain(root, backend, presentInfo);
}

void create_swapchain_views(const VulkanBackend& backend, PresentationInfo& presentInfo)
{
    const size_t imgCount = presentInfo.imageCount;

    Assert(imgCount > 0);

    presentInfo.imageViews.resize(imgCount);

    for (size_t i = 0; i < imgCount; ++i)
    {
        VkImageViewCreateInfo image_view_create_info = {
            VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, // VkStructureType                sType
            nullptr,                                  // const void                    *pNext
            0,                                        // VkImageViewCreateFlags         flags
            presentInfo.images[i],                    // VkImage                        image
            VK_IMAGE_VIEW_TYPE_2D,                    // VkImageViewType                viewType
            presentInfo.view_format,                  // VkFormat                       format
            {
                // VkComponentMapping             components
                VK_COMPONENT_SWIZZLE_IDENTITY, // VkComponentSwizzle             r
                VK_COMPONENT_SWIZZLE_IDENTITY, // VkComponentSwizzle             g
                VK_COMPONENT_SWIZZLE_IDENTITY, // VkComponentSwizzle             b
                VK_COMPONENT_SWIZZLE_IDENTITY  // VkComponentSwizzle             a
            },
            {
                // VkImageSubresourceRange        subresourceRange
                VK_IMAGE_ASPECT_COLOR_BIT, // VkImageAspectFlags             aspectMask
                0,                         // uint32_t                       baseMipLevel
                1,                         // uint32_t                       levelCount
                0,                         // uint32_t                       baseArrayLayer
                1                          // uint32_t                       layerCount
            }};

        Assert(vkCreateImageView(backend.device, &image_view_create_info, nullptr, &presentInfo.imageViews[i])
               == VK_SUCCESS);
    }
}

void destroy_swapchain_views(const VulkanBackend& backend, PresentationInfo& presentInfo)
{
    for (size_t i = 0; i < presentInfo.imageViews.size(); i++)
        vkDestroyImageView(backend.device, presentInfo.imageViews[i], nullptr);

    presentInfo.imageViews.clear();
}
} // namespace Reaper
<commit_msg>vulkan: don't check VK_PRESENT_MODE_FIFO_KHR support<commit_after>////////////////////////////////////////////////////////////////////////////////
/// Reaper
///
/// Copyright (c) 2015-2022 Thibault Schueller
/// This file is distributed under the MIT License
////////////////////////////////////////////////////////////////////////////////

#include "Swapchain.h"

#include "Backend.h"

#include "api/Vulkan.h"
#include "api/VulkanStringConversion.h"

#include "common/Log.h"

#include "core/Profile.h"

namespace
{
VkSurfaceFormatKHR vulkan_swapchain_choose_surface_format(std::vector<VkSurfaceFormatKHR>& surface_formats,
                                                          VkSurfaceFormatKHR               preferredFormat)
{
    // If the list contains only one entry with undefined format
    // it means that there are no preferred surface formats and any can be chosen
    if ((surface_formats.size() == 1) && (surface_formats[0].format == VK_FORMAT_UNDEFINED))
        return preferredFormat;

    for (const VkSurfaceFormatKHR& surface_format : surface_formats)
    {
        if (surface_format.format == preferredFormat.format && surface_format.colorSpace == preferredFormat.colorSpace)
            return surface_format;
    }

    return surface_formats[0]; // Return first available format
}

uint32_t clamp(uint32_t v, uint32_t min, uint32_t max)
{
    if (v < min)
        return min;
    if (v > max)
        return max;
    else
        return v;
}

VkExtent2D clamp(VkExtent2D value, VkExtent2D min, VkExtent2D max)
{
    return {
        clamp(value.width, min.width, max.width),
        clamp(value.height, min.height, max.height),
    };
}

VkExtent2D vulkan_swapchain_choose_extent(const VkSurfaceCapabilitiesKHR& surface_capabilities,
                                          VkExtent2D                      preferredExtent)
{
    // Special value of surface extent is width == height == -1
    // If this is so we define the size by ourselves but it must fit within defined confines
    if (surface_capabilities.currentExtent.width == uint32_t(-1))
        return clamp(preferredExtent, surface_capabilities.minImageExtent, surface_capabilities.maxImageExtent);

    // Most of the cases we define size of the swap_chain images equal to current window's size
    return surface_capabilities.currentExtent;
}

VkSurfaceTransformFlagBitsKHR vulkan_swapchain_choose_transform(const VkSurfaceCapabilitiesKHR& surface_capabilities)
{
    // Sometimes images must be transformed before they are presented (i.e. due to device's orienation
    // being other than default orientation)
    // If the specified transform is other than current transform, presentation engine will transform image
    // during presentation operation; this operation may hit performance on some platforms
    // Here we don't want any transformations to occur so if the identity transform is supported use it
    // otherwise just use the same transform as current transform
    if (surface_capabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)
    {
        return VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
    }
    else
    {
        return surface_capabilities.currentTransform;
    }
}

VkPresentModeKHR vulkan_swapchain_choose_present_mode(std::vector<VkPresentModeKHR>& present_modes)
{
    // Prefer MAILBOX over FIFO
    for (VkPresentModeKHR& present_mode : present_modes)
    {
        if (present_mode == VK_PRESENT_MODE_MAILBOX_KHR)
            return present_mode;
    }

    return VK_PRESENT_MODE_FIFO_KHR;
}
} // namespace

using namespace vk;

namespace Reaper
{
void configure_vulkan_wm_swapchain(ReaperRoot& root, const VulkanBackend& backend,
                                   const SwapchainDescriptor& swapchainDesc, PresentationInfo& presentInfo)
{
    log_debug(root, "vulkan: configuring wm swapchain");

    Assert(
        vkGetPhysicalDeviceSurfaceCapabilitiesKHR(backend.physicalDevice, presentInfo.surface, &presentInfo.surfaceCaps)
        == VK_SUCCESS);

    VkSurfaceCapabilitiesKHR& surfaceCaps = presentInfo.surfaceCaps;

    // Choose surface format
    VkSurfaceFormatKHR& surfaceFormat = presentInfo.surfaceFormat;
    {
        uint32_t formats_count;
        Assert(
            vkGetPhysicalDeviceSurfaceFormatsKHR(backend.physicalDevice, presentInfo.surface, &formats_count, nullptr)
            == VK_SUCCESS);
        Assert(formats_count > 0);

        std::vector<VkSurfaceFormatKHR> surface_formats(formats_count);
        Assert(vkGetPhysicalDeviceSurfaceFormatsKHR(backend.physicalDevice, presentInfo.surface, &formats_count,
                                                    &surface_formats[0])
               == VK_SUCCESS);

        log_debug(root, "vulkan: swapchain supports {} formats", formats_count);
        for (auto& format : surface_formats)
            log_debug(root, "- format = {}, colorspace = {}", GetFormatToString(format.format),
                      GetColorSpaceKHRToString(format.colorSpace));

        surfaceFormat = vulkan_swapchain_choose_surface_format(surface_formats, swapchainDesc.preferredFormat);

        if (surfaceFormat.format != swapchainDesc.preferredFormat.format
            || surfaceFormat.colorSpace != swapchainDesc.preferredFormat.colorSpace)
        {
            // TODO format to_string() function
            log_warning(root, "vulkan: incompatible swapchain format: format = {}, colorspace = {}",
                        swapchainDesc.preferredFormat.format, swapchainDesc.preferredFormat.colorSpace);
            log_warning(root, "- falling back to: format = {}, colorspace = {}", surfaceFormat.format,
                        surfaceFormat.colorSpace);
        }

        // Choose view format
        {
            VkFormat view_format = surfaceFormat.format;

            // VK_KHR_swapchain_mutable_format is needed for this
            if (view_format == VK_FORMAT_B8G8R8A8_UNORM)
                view_format = VK_FORMAT_B8G8R8A8_SRGB;

            presentInfo.view_format = view_format;
        }

        log_debug(root, "vulkan: selecting swapchain format = {}, colorspace = {}",
                  GetFormatToString(surfaceFormat.format), GetColorSpaceKHRToString(surfaceFormat.colorSpace));
        log_debug(root, "vulkan: selecting swapchain view format = {}", GetFormatToString(presentInfo.view_format));
    }

    // Image count
    uint32_t imageCount = 0;
    {
        log_debug(root, "vulkan: swapchain image count support: min = {}, max = {}", surfaceCaps.minImageCount,
                  surfaceCaps.maxImageCount);

        imageCount = swapchainDesc.preferredImageCount;
        if (imageCount < surfaceCaps.minImageCount)
            imageCount = surfaceCaps.minImageCount;
        else if (surfaceCaps.maxImageCount > 0 && imageCount > surfaceCaps.maxImageCount)
            imageCount = surfaceCaps.maxImageCount;

        if (imageCount != swapchainDesc.preferredImageCount)
            log_warning(root, "vulkan: swapchain image count {} is unsupported. falling back to {}",
                        swapchainDesc.preferredImageCount, imageCount);

        Assert(imageCount > 0);
        Assert(imageCount <= 5); // Seems like a reasonable limit
        presentInfo.imageCount = imageCount;
    }

    {
        uint32_t presentModeCount;
        Assert(vkGetPhysicalDeviceSurfacePresentModesKHR(backend.physicalDevice, presentInfo.surface, &presentModeCount,
                                                         nullptr)
               == VK_SUCCESS);
        Assert(presentModeCount > 0);

        std::vector<VkPresentModeKHR> availablePresentModes(presentModeCount);
        Assert(vkGetPhysicalDeviceSurfacePresentModesKHR(backend.physicalDevice, presentInfo.surface, &presentModeCount,
                                                         &availablePresentModes[0])
               == VK_SUCCESS);

        log_debug(root, "vulkan: swapchain supports {} present modes", presentModeCount);
        for (auto& mode : availablePresentModes)
            log_debug(root, "- {}", GetPresentModeKHRToString(mode));

        presentInfo.presentMode = vulkan_swapchain_choose_present_mode(availablePresentModes);
    }

    {
        VkExtent2D extent = vulkan_swapchain_choose_extent(surfaceCaps, swapchainDesc.preferredExtent);

        if (extent.width != swapchainDesc.preferredExtent.width
            || extent.height != swapchainDesc.preferredExtent.height)
        {
            log_warning(root, "vulkan: swapchain extent {}x{} is not supported", swapchainDesc.preferredExtent.width,
                        swapchainDesc.preferredExtent.height);
            log_warning(root, "- falling back to {}x{}", extent.width, extent.height);
        }
        presentInfo.surfaceExtent = extent;
    }

    // Usage flags
    // NOTE: Some flags might be added at runtime by external forces (driver, layers?)
    // and might trigger a validation warning down the line.
    // I suspect this is primarily done for frame capture purposes, like adding TRANSFER_SRC to allow copies.
    // It should be safe to ignore in this case.
    presentInfo.swapchainUsageFlags = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
    Assert((surfaceCaps.supportedUsageFlags & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) != 0, "Vulkan API error");

    // Transform
    presentInfo.transform = vulkan_swapchain_choose_transform(surfaceCaps);
}

void create_vulkan_wm_swapchain(ReaperRoot& root, const VulkanBackend& backend, PresentationInfo& presentInfo)
{
    REAPER_PROFILE_SCOPE("Vulkan", MP_RED);
    log_debug(root, "vulkan: creating wm swapchain");

    const std::vector<VkFormat> view_formats = {
        presentInfo.view_format,
    };

    const VkImageFormatListCreateInfo format_list = {
        VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO,
        nullptr,
        static_cast<u32>(view_formats.size()),
        view_formats.data(),
    };

    VkSwapchainCreateInfoKHR swap_chain_create_info = {
        VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, // VkStructureType                sType
        &format_list,                                // const void                    *pNext
        VK_SWAPCHAIN_CREATE_MUTABLE_FORMAT_BIT_KHR,  // VkSwapchainCreateFlagsKHR      flags
        presentInfo.surface,                         // VkSurfaceKHR                   surface
        presentInfo.imageCount,                      // uint32_t                       minImageCount
        presentInfo.surfaceFormat.format,            // VkFormat                       imageFormat
        presentInfo.surfaceFormat.colorSpace,        // VkColorSpaceKHR                imageColorSpace
        presentInfo.surfaceExtent,                   // VkExtent2D                     imageExtent
        1,                                           // uint32_t                       imageArrayLayers
        presentInfo.swapchainUsageFlags,             // VkImageUsageFlags              imageUsage
        VK_SHARING_MODE_EXCLUSIVE,                   // VkSharingMode                  imageSharingMode
        0,                                           // uint32_t                       queueFamilyIndexCount
        nullptr,                                     // const uint32_t                *pQueueFamilyIndices
        presentInfo.transform,                       // VkSurfaceTransformFlagBitsKHR  preTransform
        VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,           // VkCompositeAlphaFlagBitsKHR    compositeAlpha
        presentInfo.presentMode,                     // VkPresentModeKHR               presentMode
        VK_TRUE,                                     // VkBool32                       clipped
        VK_NULL_HANDLE                               // VkSwapchainKHR                 oldSwapchain
    };

    Assert(vkCreateSwapchainKHR(backend.device, &swap_chain_create_info, nullptr, &presentInfo.swapchain)
           == VK_SUCCESS);

    Assert(vkGetSwapchainImagesKHR(backend.device, presentInfo.swapchain, &presentInfo.imageCount, nullptr)
           == VK_SUCCESS);
    Assert(presentInfo.imageCount > 0);

    presentInfo.images.resize(presentInfo.imageCount);
    Assert(
        vkGetSwapchainImagesKHR(backend.device, presentInfo.swapchain, &presentInfo.imageCount, &presentInfo.images[0])
        == VK_SUCCESS);

    VkSemaphoreCreateInfo semaphore_create_info = {
        VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, // VkStructureType          sType
        nullptr,                                 // const void*              pNext
        0                                        // VkSemaphoreCreateFlags   flags
    };

    create_swapchain_views(backend, presentInfo);

    Assert(vkCreateSemaphore(backend.device, &semaphore_create_info, nullptr, &presentInfo.imageAvailableSemaphore)
           == VK_SUCCESS);

    log_debug(root, "vulkan: created semaphore with handle: {}",
              static_cast<void*>(presentInfo.imageAvailableSemaphore));

    Assert(vkCreateSemaphore(backend.device, &semaphore_create_info, nullptr, &presentInfo.renderingFinishedSemaphore)
           == VK_SUCCESS);

    log_debug(root, "vulkan: created semaphore with handle: {}",
              static_cast<void*>(presentInfo.renderingFinishedSemaphore));
}

void destroy_vulkan_wm_swapchain(ReaperRoot& root, const VulkanBackend& backend, PresentationInfo& presentInfo)
{
    REAPER_PROFILE_SCOPE("Vulkan", MP_RED);
    log_debug(root, "vulkan: destroying wm swapchain");

    vkDestroySemaphore(backend.device, presentInfo.imageAvailableSemaphore, nullptr);
    vkDestroySemaphore(backend.device, presentInfo.renderingFinishedSemaphore, nullptr);

    destroy_swapchain_views(backend, presentInfo);

    vkDestroySwapchainKHR(backend.device, presentInfo.swapchain, nullptr);
    presentInfo.swapchain = VK_NULL_HANDLE;
}

void resize_vulkan_wm_swapchain(ReaperRoot& root, const VulkanBackend& backend, PresentationInfo& presentInfo,
                                VkExtent2D extent)
{
    REAPER_PROFILE_SCOPE("Vulkan", MP_RED);
    log_debug(root, "vulkan: resizing wm swapchain");

    // Destroy what needs to be
    Assert(vkDeviceWaitIdle(backend.device) == VK_SUCCESS);

    vkDestroySemaphore(backend.device, presentInfo.imageAvailableSemaphore, nullptr);
    vkDestroySemaphore(backend.device, presentInfo.renderingFinishedSemaphore, nullptr);

    destroy_swapchain_views(backend, presentInfo);

    vkDestroySwapchainKHR(backend.device, presentInfo.swapchain, nullptr);
    presentInfo.swapchain = VK_NULL_HANDLE;

    // Reconfigure even if we know most of what we expect/need
    SwapchainDescriptor swapchainDesc;
    swapchainDesc.preferredImageCount = presentInfo.imageCount;
    swapchainDesc.preferredFormat = presentInfo.surfaceFormat;
    swapchainDesc.preferredExtent = {extent.width, extent.height}; // New extent

    configure_vulkan_wm_swapchain(root, backend, swapchainDesc, presentInfo);

    create_vulkan_wm_swapchain(root, backend, presentInfo);
}

void create_swapchain_views(const VulkanBackend& backend, PresentationInfo& presentInfo)
{
    const size_t imgCount = presentInfo.imageCount;

    Assert(imgCount > 0);

    presentInfo.imageViews.resize(imgCount);

    for (size_t i = 0; i < imgCount; ++i)
    {
        VkImageViewCreateInfo image_view_create_info = {
            VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, // VkStructureType                sType
            nullptr,                                  // const void                    *pNext
            0,                                        // VkImageViewCreateFlags         flags
            presentInfo.images[i],                    // VkImage                        image
            VK_IMAGE_VIEW_TYPE_2D,                    // VkImageViewType                viewType
            presentInfo.view_format,                  // VkFormat                       format
            {
                // VkComponentMapping             components
                VK_COMPONENT_SWIZZLE_IDENTITY, // VkComponentSwizzle             r
                VK_COMPONENT_SWIZZLE_IDENTITY, // VkComponentSwizzle             g
                VK_COMPONENT_SWIZZLE_IDENTITY, // VkComponentSwizzle             b
                VK_COMPONENT_SWIZZLE_IDENTITY  // VkComponentSwizzle             a
            },
            {
                // VkImageSubresourceRange        subresourceRange
                VK_IMAGE_ASPECT_COLOR_BIT, // VkImageAspectFlags             aspectMask
                0,                         // uint32_t                       baseMipLevel
                1,                         // uint32_t                       levelCount
                0,                         // uint32_t                       baseArrayLayer
                1                          // uint32_t                       layerCount
            }};

        Assert(vkCreateImageView(backend.device, &image_view_create_info, nullptr, &presentInfo.imageViews[i])
               == VK_SUCCESS);
    }
}

void destroy_swapchain_views(const VulkanBackend& backend, PresentationInfo& presentInfo)
{
    for (size_t i = 0; i < presentInfo.imageViews.size(); i++)
        vkDestroyImageView(backend.device, presentInfo.imageViews[i], nullptr);

    presentInfo.imageViews.clear();
}
} // namespace Reaper
<|endoftext|>


<commit_before>#include "IR.h"
#include "IRMutator.h"
#include "Function.h"
#include "Scope.h"
#include "IRPrinter.h"
#include "IREquality.h"
#include "Simplify.h"
#include "ModulusRemainder.h"
#include "Debug.h"
#include "CSE.h"
#include "IROperator.h"
#include <set>

namespace Halide {
namespace Internal {

static void assertf(bool cond, const char* msg, const std::string& name) {
  if (!cond) {
    std::cerr << msg << " (Func: " << name << ")" << std::endl;
    assert(false);
  }
}

using std::vector;
using std::string;
using std::set;

template<>
EXPORT RefCount &ref_count<FunctionContents>(const FunctionContents *f) {return f->ref_count;}

template<>
EXPORT void destroy<FunctionContents>(const FunctionContents *f) {delete f;}

// All variables present in any part of a function definition must
// either be pure args, elements of the reduction domain, parameters
// (i.e. attached to some Parameter object), or part of a let node
// internal to the expression
struct CheckVars : public IRGraphVisitor {
    vector<string> pure_args;
    ReductionDomain reduction_domain;
    Scope<int> defined_internally;
    const std::string name;

    CheckVars(const std::string& n) : name(n) {}

    using IRVisitor::visit;

    void visit(const Let *let) {
        let->value.accept(this);
        defined_internally.push(let->name, 0);
        let->body.accept(this);
        defined_internally.pop(let->name);
    }

    void visit(const Call *op) {
        IRGraphVisitor::visit(op);
        if (op->name == name) {
            for (size_t i = 0; i < op->args.size(); i++) {
                const Variable *var = op->args[i].as<Variable>();
                if (!pure_args[i].empty()) {
                    assertf(var && var->name == pure_args[i],
                            "All of a functions recursive references to itself"
                            " must contain the same pure variables in the same"
                            " places as on the left-hand-side.", name);
                }
            }
        }
    }

    void visit(const Variable *var) {
        // Is it a parameter?
        if (var->param.defined()) return;

        // Was it defined internally by a let expression?
        if (defined_internally.contains(var->name)) return;

        // Is it a pure argument?
        for (size_t i = 0; i < pure_args.size(); i++) {
            if (var->name == pure_args[i]) return;
        }

        // Is it in a reduction domain?
        if (var->reduction_domain.defined()) {
            if (!reduction_domain.defined()) {
                reduction_domain = var->reduction_domain;
                return;
            } else if (var->reduction_domain.same_as(reduction_domain)) {
                // It's in a reduction domain we already know about
                return;
            } else {
                assertf(false, "Multiple reduction domains found in function definition", name);
            }
        }

        std::cerr << "Undefined variable in function definition: " << var->name
            << " (Func: " << name << ")"
            << std::endl;
        assert(false);
    }
};

struct CountSelfReferences : public IRGraphVisitor {
    set<const Call *> calls;
    const Function *func;

    using IRVisitor::visit;

    void visit(const Call *c) {
        if (c->func.same_as(*func)) {
            calls.insert(c);
        }
    }
};

void Function::define(const vector<string> &args, vector<Expr> values) {
    assertf(!has_extern_definition(), "Function with extern definition cannot be given a pure definition", name());
    assertf(!name().empty(), "A function needs a name", name());
    for (size_t i = 0; i < values.size(); i++) {
        assertf(values[i].defined(), "Undefined expression in right-hand-side of function definition", name());
    }

    // Make sure all the vars in the value are either args or are
    // attached to some parameter
    CheckVars check(name());
    check.pure_args = args;
    for (size_t i = 0; i < values.size(); i++) {
        values[i].accept(&check);
    }

    // Make sure all the vars in the args have unique non-empty names
    for (size_t i = 0; i < args.size(); i++) {
        if (args[i].empty()) {
            std::cerr << "In the left-hand-side of the definition of " << name()
                      << ", argument " << i << " has an empty name\n";
            assert(false);
        }
        for (size_t j = 0; j < i; j++) {
            if (args[i] == args[j]) {
                std::cerr << "In the left-hand-side of the definition of " << name()
                          << ", arguments " << j << " and " << i << " have the same name: " << args[i] << "\n";
                assert(false);
            }
        }
    }

    for (size_t i = 0; i < values.size(); i++) {
        values[i] = common_subexpression_elimination(values[i]);
    }

    assertf(!check.reduction_domain.defined(), "Reduction domain referenced in pure function definition", name());

    if (!contents.defined()) {
        contents = new FunctionContents;
        contents.ptr->name = unique_name('f');
    }

    assertf(contents.ptr->values.empty(), "Function is already defined", name());
    contents.ptr->values = values;
    contents.ptr->args = args;

    contents.ptr->output_types.resize(values.size());
    for (size_t i = 0; i < contents.ptr->output_types.size(); i++) {
        contents.ptr->output_types[i] = values[i].type();
    }

    for (size_t i = 0; i < args.size(); i++) {
        Schedule::Dim d = {args[i], For::Serial};
        contents.ptr->schedule.dims.push_back(d);
        contents.ptr->schedule.storage_dims.push_back(args[i]);
    }

    for (size_t i = 0; i < values.size(); i++) {
        string buffer_name = name();
        if (values.size() > 1) {
            buffer_name += '.' + int_to_string((int)i);
        }
        contents.ptr->output_buffers.push_back(Parameter(values[i].type(), true, buffer_name));
    }
}

void Function::define_reduction(const vector<Expr> &_args, vector<Expr> values) {
    assertf(!name().empty(), "A function needs a name", name());
    assertf(has_pure_definition(), "Can't add a reduction definition without a regular definition first", name());

    for (size_t i = 0; i < values.size(); i++) {
        assertf(values[i].defined(), "Undefined expression in right-hand-side of reduction", name());
    }

    // Check the dimensionality matches
    assertf((int)_args.size() == dimensions(),
           "Dimensionality of reduction definition must match dimensionality of pure definition", name());

    assertf(values.size() == contents.ptr->values.size(),
            "Number of tuple elements for reduction definition must "
            "match number of tuple elements for pure definition", name());

    for (size_t i = 0; i < values.size(); i++) {
        // Check that pure value and the reduction value have the same
        // type.  Without this check, allocations may be the wrong size
        // relative to what update code expects.
        assertf(contents.ptr->values[i].type() == values[i].type(),
                "Reduction definition does not match type of pure function definition.",
                name());
        values[i] = common_subexpression_elimination(values[i]);
    }

    vector<Expr> args(_args.size());
    for (size_t i = 0; i < args.size(); i++) {
        args[i] = common_subexpression_elimination(_args[i]);
    }

    // The pure args are those naked vars in the args that are not in
    // a reduction domain and are not parameters and line up with the
    // pure args in the pure definition.
    vector<string> pure_args(args.size());
    for (size_t i = 0; i < args.size(); i++) {
        pure_args[i] = ""; // Will never match a var name
        assertf(args[i].defined(), "Undefined expression in left-hand-side of reduction", name());
        if (const Variable *var = args[i].as<Variable>()) {
            if (!var->param.defined() &&
                !var->reduction_domain.defined() &&
                var->name == contents.ptr->args[i]) {
                pure_args[i] = var->name;
            }
        }
    }

    // Make sure all the vars in the args and the value are either
    // pure args, in the reduction domain, or a parameter. Also checks
    // that recursive references to the function contain all the pure
    // vars in the LHS in the correct places.
    CheckVars check(name());
    check.pure_args = pure_args;
    for (size_t i = 0; i < args.size(); i++) {
        args[i].accept(&check);
    }
    for (size_t i = 0; i < values.size(); i++) {
        values[i].accept(&check);
    }

    ReductionDefinition r;
    r.args = args;
    r.values = values;
    r.domain = check.reduction_domain;

    // The reduction value and args probably refer back to the
    // function itself, introducing circular references and hence
    // memory leaks. We need to count the number of unique call nodes
    // that point back to this function in order to break the cycles.
    CountSelfReferences counter;
    counter.func = this;
    for (size_t i = 0; i < args.size(); i++) {
        args[i].accept(&counter);
    }
    for (size_t i = 0; i < values.size(); i++) {
        values[i].accept(&counter);
    }

    for (size_t i = 0; i < counter.calls.size(); i++) {
        contents.ptr->ref_count.decrement();
        assertf(!contents.ptr->ref_count.is_zero(),
                "Bug: removed too many circular references when defining reduction", name());
    }

    // First add any reduction domain
    if (r.domain.defined()) {
        for (size_t i = 0; i < r.domain.domain().size(); i++) {
            Schedule::Dim d = {r.domain.domain()[i].var, For::Serial};
            r.schedule.dims.push_back(d);
        }
    }

    // Then add the pure args outside of that
    for (size_t i = 0; i < pure_args.size(); i++) {
        if (!pure_args[i].empty()) {
            Schedule::Dim d = {pure_args[i], For::Serial};
            r.schedule.dims.push_back(d);
        }
    }

    contents.ptr->reductions.push_back(r);

}

void Function::define_extern(const std::string &function_name,
                             const std::vector<ExternFuncArgument> &args,
                             const std::vector<Type> &types,
                             int dimensionality) {

    assertf(!has_pure_definition() && !has_reduction_definition(),
            "Function with a pure definition cannot have an extern definition",
            name());

    assertf(!has_extern_definition(),
            "Function already has an extern definition",
            name());

    contents.ptr->extern_function_name = function_name;
    contents.ptr->extern_arguments = args;
    contents.ptr->output_types = types;

    for (size_t i = 0; i < types.size(); i++) {
        string buffer_name = name();
        if (types.size() > 1) {
            buffer_name += '.' + int_to_string((int)i);
        }
        contents.ptr->output_buffers.push_back(Parameter(types[i], true, buffer_name));
    }

    // Make some synthetic var names for scheduling purposes (e.g. reorder_storage).
    contents.ptr->args.resize(dimensionality);
    for (int i = 0; i < dimensionality; i++) {
        string arg = unique_name('e');
        contents.ptr->args[i] = arg;
        contents.ptr->schedule.storage_dims.push_back(arg);
    }

}

}
}
<commit_msg>Only check for recursive calls that are actual Halide calls.<commit_after>#include "IR.h"
#include "IRMutator.h"
#include "Function.h"
#include "Scope.h"
#include "IRPrinter.h"
#include "IREquality.h"
#include "Simplify.h"
#include "ModulusRemainder.h"
#include "Debug.h"
#include "CSE.h"
#include "IROperator.h"
#include <set>

namespace Halide {
namespace Internal {

static void assertf(bool cond, const char* msg, const std::string& name) {
  if (!cond) {
    std::cerr << msg << " (Func: " << name << ")" << std::endl;
    assert(false);
  }
}

using std::vector;
using std::string;
using std::set;

template<>
EXPORT RefCount &ref_count<FunctionContents>(const FunctionContents *f) {return f->ref_count;}

template<>
EXPORT void destroy<FunctionContents>(const FunctionContents *f) {delete f;}

// All variables present in any part of a function definition must
// either be pure args, elements of the reduction domain, parameters
// (i.e. attached to some Parameter object), or part of a let node
// internal to the expression
struct CheckVars : public IRGraphVisitor {
    vector<string> pure_args;
    ReductionDomain reduction_domain;
    Scope<int> defined_internally;
    const std::string name;

    CheckVars(const std::string& n) : name(n) {}

    using IRVisitor::visit;

    void visit(const Let *let) {
        let->value.accept(this);
        defined_internally.push(let->name, 0);
        let->body.accept(this);
        defined_internally.pop(let->name);
    }

    void visit(const Call *op) {
        IRGraphVisitor::visit(op);
        if (op->name == name && op->call_type == Call::Halide) {
            for (size_t i = 0; i < op->args.size(); i++) {
                const Variable *var = op->args[i].as<Variable>();
                if (!pure_args[i].empty()) {
                    assertf(var && var->name == pure_args[i],
                            "All of a functions recursive references to itself"
                            " must contain the same pure variables in the same"
                            " places as on the left-hand-side.", name);
                }
            }
        }
    }

    void visit(const Variable *var) {
        // Is it a parameter?
        if (var->param.defined()) return;

        // Was it defined internally by a let expression?
        if (defined_internally.contains(var->name)) return;

        // Is it a pure argument?
        for (size_t i = 0; i < pure_args.size(); i++) {
            if (var->name == pure_args[i]) return;
        }

        // Is it in a reduction domain?
        if (var->reduction_domain.defined()) {
            if (!reduction_domain.defined()) {
                reduction_domain = var->reduction_domain;
                return;
            } else if (var->reduction_domain.same_as(reduction_domain)) {
                // It's in a reduction domain we already know about
                return;
            } else {
                assertf(false, "Multiple reduction domains found in function definition", name);
            }
        }

        std::cerr << "Undefined variable in function definition: " << var->name
            << " (Func: " << name << ")"
            << std::endl;
        assert(false);
    }
};

struct CountSelfReferences : public IRGraphVisitor {
    set<const Call *> calls;
    const Function *func;

    using IRVisitor::visit;

    void visit(const Call *c) {
        if (c->func.same_as(*func)) {
            calls.insert(c);
        }
    }
};

void Function::define(const vector<string> &args, vector<Expr> values) {
    assertf(!has_extern_definition(), "Function with extern definition cannot be given a pure definition", name());
    assertf(!name().empty(), "A function needs a name", name());
    for (size_t i = 0; i < values.size(); i++) {
        assertf(values[i].defined(), "Undefined expression in right-hand-side of function definition", name());
    }

    // Make sure all the vars in the value are either args or are
    // attached to some parameter
    CheckVars check(name());
    check.pure_args = args;
    for (size_t i = 0; i < values.size(); i++) {
        values[i].accept(&check);
    }

    // Make sure all the vars in the args have unique non-empty names
    for (size_t i = 0; i < args.size(); i++) {
        if (args[i].empty()) {
            std::cerr << "In the left-hand-side of the definition of " << name()
                      << ", argument " << i << " has an empty name\n";
            assert(false);
        }
        for (size_t j = 0; j < i; j++) {
            if (args[i] == args[j]) {
                std::cerr << "In the left-hand-side of the definition of " << name()
                          << ", arguments " << j << " and " << i << " have the same name: " << args[i] << "\n";
                assert(false);
            }
        }
    }

    for (size_t i = 0; i < values.size(); i++) {
        values[i] = common_subexpression_elimination(values[i]);
    }

    assertf(!check.reduction_domain.defined(), "Reduction domain referenced in pure function definition", name());

    if (!contents.defined()) {
        contents = new FunctionContents;
        contents.ptr->name = unique_name('f');
    }

    assertf(contents.ptr->values.empty(), "Function is already defined", name());
    contents.ptr->values = values;
    contents.ptr->args = args;

    contents.ptr->output_types.resize(values.size());
    for (size_t i = 0; i < contents.ptr->output_types.size(); i++) {
        contents.ptr->output_types[i] = values[i].type();
    }

    for (size_t i = 0; i < args.size(); i++) {
        Schedule::Dim d = {args[i], For::Serial};
        contents.ptr->schedule.dims.push_back(d);
        contents.ptr->schedule.storage_dims.push_back(args[i]);
    }

    for (size_t i = 0; i < values.size(); i++) {
        string buffer_name = name();
        if (values.size() > 1) {
            buffer_name += '.' + int_to_string((int)i);
        }
        contents.ptr->output_buffers.push_back(Parameter(values[i].type(), true, buffer_name));
    }
}

void Function::define_reduction(const vector<Expr> &_args, vector<Expr> values) {
    assertf(!name().empty(), "A function needs a name", name());
    assertf(has_pure_definition(), "Can't add a reduction definition without a regular definition first", name());

    for (size_t i = 0; i < values.size(); i++) {
        assertf(values[i].defined(), "Undefined expression in right-hand-side of reduction", name());
    }

    // Check the dimensionality matches
    assertf((int)_args.size() == dimensions(),
           "Dimensionality of reduction definition must match dimensionality of pure definition", name());

    assertf(values.size() == contents.ptr->values.size(),
            "Number of tuple elements for reduction definition must "
            "match number of tuple elements for pure definition", name());

    for (size_t i = 0; i < values.size(); i++) {
        // Check that pure value and the reduction value have the same
        // type.  Without this check, allocations may be the wrong size
        // relative to what update code expects.
        assertf(contents.ptr->values[i].type() == values[i].type(),
                "Reduction definition does not match type of pure function definition.",
                name());
        values[i] = common_subexpression_elimination(values[i]);
    }

    vector<Expr> args(_args.size());
    for (size_t i = 0; i < args.size(); i++) {
        args[i] = common_subexpression_elimination(_args[i]);
    }

    // The pure args are those naked vars in the args that are not in
    // a reduction domain and are not parameters and line up with the
    // pure args in the pure definition.
    vector<string> pure_args(args.size());
    for (size_t i = 0; i < args.size(); i++) {
        pure_args[i] = ""; // Will never match a var name
        assertf(args[i].defined(), "Undefined expression in left-hand-side of reduction", name());
        if (const Variable *var = args[i].as<Variable>()) {
            if (!var->param.defined() &&
                !var->reduction_domain.defined() &&
                var->name == contents.ptr->args[i]) {
                pure_args[i] = var->name;
            }
        }
    }

    // Make sure all the vars in the args and the value are either
    // pure args, in the reduction domain, or a parameter. Also checks
    // that recursive references to the function contain all the pure
    // vars in the LHS in the correct places.
    CheckVars check(name());
    check.pure_args = pure_args;
    for (size_t i = 0; i < args.size(); i++) {
        args[i].accept(&check);
    }
    for (size_t i = 0; i < values.size(); i++) {
        values[i].accept(&check);
    }

    ReductionDefinition r;
    r.args = args;
    r.values = values;
    r.domain = check.reduction_domain;

    // The reduction value and args probably refer back to the
    // function itself, introducing circular references and hence
    // memory leaks. We need to count the number of unique call nodes
    // that point back to this function in order to break the cycles.
    CountSelfReferences counter;
    counter.func = this;
    for (size_t i = 0; i < args.size(); i++) {
        args[i].accept(&counter);
    }
    for (size_t i = 0; i < values.size(); i++) {
        values[i].accept(&counter);
    }

    for (size_t i = 0; i < counter.calls.size(); i++) {
        contents.ptr->ref_count.decrement();
        assertf(!contents.ptr->ref_count.is_zero(),
                "Bug: removed too many circular references when defining reduction", name());
    }

    // First add any reduction domain
    if (r.domain.defined()) {
        for (size_t i = 0; i < r.domain.domain().size(); i++) {
            Schedule::Dim d = {r.domain.domain()[i].var, For::Serial};
            r.schedule.dims.push_back(d);
        }
    }

    // Then add the pure args outside of that
    for (size_t i = 0; i < pure_args.size(); i++) {
        if (!pure_args[i].empty()) {
            Schedule::Dim d = {pure_args[i], For::Serial};
            r.schedule.dims.push_back(d);
        }
    }

    contents.ptr->reductions.push_back(r);

}

void Function::define_extern(const std::string &function_name,
                             const std::vector<ExternFuncArgument> &args,
                             const std::vector<Type> &types,
                             int dimensionality) {

    assertf(!has_pure_definition() && !has_reduction_definition(),
            "Function with a pure definition cannot have an extern definition",
            name());

    assertf(!has_extern_definition(),
            "Function already has an extern definition",
            name());

    contents.ptr->extern_function_name = function_name;
    contents.ptr->extern_arguments = args;
    contents.ptr->output_types = types;

    for (size_t i = 0; i < types.size(); i++) {
        string buffer_name = name();
        if (types.size() > 1) {
            buffer_name += '.' + int_to_string((int)i);
        }
        contents.ptr->output_buffers.push_back(Parameter(types[i], true, buffer_name));
    }

    // Make some synthetic var names for scheduling purposes (e.g. reorder_storage).
    contents.ptr->args.resize(dimensionality);
    for (int i = 0; i < dimensionality; i++) {
        string arg = unique_name('e');
        contents.ptr->args[i] = arg;
        contents.ptr->schedule.storage_dims.push_back(arg);
    }

}

}
}
<|endoftext|>


<commit_before>#include "GameCtrl.h"
#include "util/util.h"
#include <stdexcept>
#include <cstdio>
#include <chrono>
#include <cstdlib>
#ifdef _WIN32
#include <Windows.h>
#endif

using std::string;
using std::list;

// Error statements
const string GameCtrl::MSG_BAD_ALLOC = "Not enough memory to run the game.";
const string GameCtrl::MSG_LOSE = "Oops! You lose!";
const string GameCtrl::MSG_WIN = "Congratulations! You Win!";
const string GameCtrl::MSG_ESC = "Game ended.";
const string GameCtrl::MAP_INFO_FILENAME = "movements.txt";
int select_path=0; //Select Min Path or Max Path

GameCtrl::GameCtrl() {}

// class Destroyer
GameCtrl::~GameCtrl() {
    delete map;
    map = nullptr;
    if (movementFile) {
        fclose(movementFile);
        movementFile = nullptr;
    }
}

// Return the class instance's address
// Return instance will be used for creating and running game in main function
GameCtrl* GameCtrl::getInstance() {
=======
GameCtrl* GameCtrl::getInstance(int n) {

    static GameCtrl instance;
	select_path=n;
    return &instance;
}

void GameCtrl::setFPS(const double fps_) {
    fps = fps_;
}

// set program enable AI based search to decide next point to visit
// enableAI_(parameter) is boolean type whether the program use AI based search or not
void GameCtrl::setEnableAI(const bool enableAI_) {
    enableAI = enableAI_;
}

// set program enable Hamilton search to decide next point to visit
// enableHamilton_(parameter) is boolean type whether the program use Hamilton based search or not
void GameCtrl::setEnableHamilton(const bool enableHamilton_) {
    enableHamilton = enableHamilton_;
}

void GameCtrl::setMoveInterval(const long ms) {
    moveInterval = ms;
}

// set program records the snake's movement in text file
// b(parameter) is boolean type whether the program record snake's movement or not
void GameCtrl::setRecordMovements(const bool b) {
    recordMovements = b;
}

void GameCtrl::setRunTest(const bool b) {
    runTest = b;
}

void GameCtrl::setMapRow(const SizeType n) {
    mapRowCnt = n;
}

void GameCtrl::setMapCol(const SizeType n) {
    mapColCnt = n;
}

// essential function to working the program. this function call init() to initialize some variables to start game
// return 0 : successfully exit game
// return 1 : error is occured during game is running
int GameCtrl::run() {
    try {
        init();
        if (runTest) {
            test();
        }
        while (runMainThread) {}
        return 0;
    } catch (const std::exception &e) {
        exitGameErr(e.what());
        return -1;
    }
}

void GameCtrl::sleepFPS() const {
    util::sleep((long)((1.0 / fps) * 1000));
}

// this function is called when the program is needed to end
// msg(parameter) contains error message
// this function use mutex lock because when a thread access critical section, other two thread must not access same crtical section.
// ex) when draw thread is errored, and at the same time , search thread is also errored,
//     the first errored thread access critical section, and then search thread access critical section
void GameCtrl::exitGame(const std::string &msg) {
    mutexExit.lock();
    if (runMainThread) {
        util::sleep(100);
        runSubThread = false;
        util::sleep(100);
        printMsg(msg);
    }
    mutexExit.unlock();
    runMainThread = false;
}

void GameCtrl::exitGameErr(const std::string &err) {
    exitGame("ERROR: " + err);
}

void GameCtrl::printMsg(const std::string &msg) {
    Console::setCursor(0, (int)mapRowCnt);
    Console::writeWithColor(msg + "\n", ConsoleColor(WHITE, BLACK, true, false));
}

// this function is for moving snake one step more closer to food
// this function also use mutex lock because manually moving mode,
// user input many keys, but the process must execute move(), writeMapToFile(), CreateRandFood() one by one
void GameCtrl::moveSnake() {
    mutexMove.lock();
    if (map->isAllBody()) {
        mutexMove.unlock();
        exitGame(MSG_WIN);
    } else if (snake.isDead()) {
        mutexMove.unlock();
        exitGame(MSG_LOSE);
    } else {
        try {
            snake.move();
            if (recordMovements && snake.getDirection() != NONE) {
                writeMapToFile();
            }
            if (!map->hasFood()) {
                map->createRandFood();
            }
            mutexMove.unlock();
        } catch (const std::exception) {
            mutexMove.unlock();
            throw;
        }
    }
}

// this function is for writing snake's movement to text file format
void GameCtrl::writeMapToFile() const {
    if (!movementFile) {
        return;
    }
    SizeType rows = map->getRowCount();
    SizeType cols = map->getColCount();
    for (SizeType i = 0; i < rows; ++i) {
        for (SizeType j = 0; j < cols; ++j) {
            switch (map->getPoint(Pos(i, j)).getType()) {
                case Point::Type::EMPTY:
                    fwrite("  ", sizeof(char), 2, movementFile); break;
                case Point::Type::WALL:
                    fwrite("# ", sizeof(char), 2, movementFile); break;
                case Point::Type::FOOD:
                    fwrite("F ", sizeof(char), 2, movementFile); break;
                case Point::Type::SNAKE_HEAD:
                    fwrite("H ", sizeof(char), 2, movementFile); break;
                case Point::Type::SNAKE_BODY:
                    fwrite("B ", sizeof(char), 2, movementFile); break;
                case Point::Type::SNAKE_TAIL:
                    fwrite("T ", sizeof(char), 2, movementFile); break;
                default:
                    break;
            }
        }
        fwrite("\n", sizeof(char), 1, movementFile);
    }
    fwrite("\n", sizeof(char), 1, movementFile);
}

// this function is calling functions to initialize map and snake, and optionally calling initFiles()
void GameCtrl::init() {
    Console::clear();
    initMap();
    if (!runTest) {
        initSnake();
        if (recordMovements) {
            initFiles();
        }
    }
    startThreads();
}

// this function is for initialize map
void GameCtrl::initMap() {
    if (mapRowCnt < 5 || mapColCnt < 5) {
        string msg = "GameCtrl.initMap(): Map size at least 5*5. Current size "
            + util::toString(mapRowCnt) + "*" + util::toString(mapColCnt) + ".";
        throw std::range_error(msg.c_str());
    }
    map = new Map(mapRowCnt, mapColCnt);
    if (!map) {
        exitGameErr(MSG_BAD_ALLOC);
    } else {
        // Add some extra walls manully
    }
}

// this function is for initialize snake and optionally call snake.hamilton search
void GameCtrl::initSnake() {
    snake.setMap(map);
    snake.addBody(Pos(1, 3));
    snake.addBody(Pos(1, 2));
    snake.addBody(Pos(1, 1));
    if (enableHamilton) {
        snake.enableHamilton();
    }
}

// this function is for initialize movement file to record snake's movement
// initializing file's main work is write content type description to file
void GameCtrl::initFiles() {
    movementFile = fopen(MAP_INFO_FILENAME.c_str(), "w");
    if (!movementFile) {
        throw std::runtime_error("GameCtrl.initFiles(): Fail to open file: " + MAP_INFO_FILENAME);
    } else {
        // Write content description to the file
        string str = "Content description:\n";
        str += "#: wall\nH: snake head\nB: snake body\nT: snake tail\nF: food\n\n";
        str += "Movements:\n\n";
        fwrite(str.c_str(), sizeof(char), str.length(), movementFile);
    }
}

// this function creates three thrads which are necessary to run the game
// the three thread is drawThread, keyboardThread, moveThread
// drawThread's main work is draw map in console
// keyboardThread's main work is inputing key and calling function to move snake position
// moveThread's main work is to move snake position based on auto search methods(Hamilton, graph search, BFS)
// runSubThread is very important thing to run program. as long as this value is true, each thread is keep running and it means no error is occured.
void GameCtrl::startThreads() {
    runSubThread = true;
    drawThread = std::thread(&GameCtrl::draw, this);
    drawThread.detach();
    keyboardThread = std::thread(&GameCtrl::keyboard, this);
    keyboardThread.detach();
    if (!runTest) {
        moveThread = std::thread(&GameCtrl::autoMove, this);
        moveThread.detach();
    }
}
// this function calls drawMapContent() function to draw Map
void GameCtrl::draw() {
    try {
        while (runSubThread) {
            drawMapContent();
            sleepFPS();
        }
    } catch (const std::exception &e) {
        exitGameErr(e.what());
    }
}
// this function draws Map in console according to point type
// setCursor needs x,y parameter, but when parameter is no, the default is x,y = 0 and this function just position console's cursor to write map
void GameCtrl::drawMapContent() const {
    Console::setCursor();
    SizeType row = map->getRowCount();
	SizeType col = map->getColCount();
    for (SizeType i = 0; i < row; ++i) {
        for (SizeType j = 0; j < col; ++j) {
            const Point &point = map->getPoint(Pos(i, j));
            switch (point.getType()) {
                case Point::Type::EMPTY:
                    Console::writeWithColor("  ", ConsoleColor(BLACK, BLACK));
                    break;
                case Point::Type::WALL:
                    Console::writeWithColor("  ", ConsoleColor(WHITE, WHITE, true, true));
                    break;
                case Point::Type::FOOD:
                    Console::writeWithColor("  ", ConsoleColor(YELLOW, YELLOW, true, true));
                    break;
                case Point::Type::SNAKE_HEAD:
                    Console::writeWithColor("  ", ConsoleColor(RED, RED, true, true));
                    break;
                case Point::Type::SNAKE_BODY:
                    Console::writeWithColor("  ", ConsoleColor(GREEN, GREEN, true, true));
                    break;
                case Point::Type::SNAKE_TAIL:
                    Console::writeWithColor("  ", ConsoleColor(BLUE, BLUE, true, true));
                    break;
                case Point::Type::TEST_VISIT:
                    drawTestPoint(point, ConsoleColor(BLUE, GREEN, true, true));
                    break;
                case Point::Type::TEST_PATH:
                    drawTestPoint(point, ConsoleColor(BLUE, RED, true, true));
                    break;
                default:
                    break;
            }
        }
        Console::write("\n");
    }
}

// this function is for drawing point in test mode
// after debugging, I notice that the code never enter the MAX_VALUE
void GameCtrl::drawTestPoint(const Point &p, const ConsoleColor &consoleColor) const {
    string pointStr = "";
    if (p.getDist() == Point::MAX_VALUE) {
        pointStr = "In";
    } else if (p.getDist() == EMPTY_VALUE) {
        pointStr = "  ";
    } else {
        Point::ValueType dist = p.getDist();
        pointStr = util::toString(p.getDist());
        if (dist / 10 == 0) {
            pointStr.insert(0, " ");
        }
    }
    Console::writeWithColor(pointStr, consoleColor);
}

// this function is call keyboardMove function according to key
void GameCtrl::keyboard() {
    try {
        while (runSubThread) {
            if (Console::kbhit()) {
                switch (Console::getch()) {
					case 'w': case 'W':
                        keyboardMove(snake, Direction::UP);
                        break;
					case 'a': case 'A':
                        keyboardMove(snake, Direction::LEFT);
                        break;
					case 's': case 'S':
                        keyboardMove(snake, Direction::DOWN);
                        break;
					case 'd': case 'D':
                        keyboardMove(snake, Direction::RIGHT);
                        break;
                    case ' ':
                        pause = !pause;  // Pause or resume game
                        break;
                    case 27:  // Esc
                        exitGame(MSG_ESC);
                        break;
                    default:
                        break;
                }
            }
            sleepFPS();
        }
    } catch (const std::exception &e) {
        exitGameErr(e.what());
    }
}

// this function is for moving snake 1 step toward manual input key
// when pause mode, snake moves toward manual input key or
// when not pause mode and when BFS mode(not AI search mode), move snake toward input key
void GameCtrl::keyboardMove(Snake &s, const Direction d) {
    if (pause) {
        s.setDirection(d);
        moveSnake();
    } else if (!enableAI) {
        if (s.getDirection() == d) {
            moveSnake();  // Accelerate
        } else {
            s.setDirection(d);
        }
    }
}

// this function is for auto moving snake 1 step toward AI based search path
// snake.cpp's decideNext function is only for AI based search(hamilton, graph)
// when enableAI == False, it determine path using BFS search method.
void GameCtrl::autoMove() {
    try {
        while (runSubThread) {
            util::sleep(moveInterval);
            if (!pause) {
                if (enableAI) {
                    snake.decideNext();
                }
                moveSnake();
            }
        }
    } catch (const std::exception &e) {
        exitGameErr(e.what());
    }
}

// this function is for testing several tests such as Food creating test, searching minimum/maximum path test, search using Hamilton test
void GameCtrl::test() {
    //testFood();
    testSearch();
    //testHamilton();
}

// this function is for creating food randomly test
void GameCtrl::testFood() {
    SizeType cnt = 0;
    while (runMainThread && cnt++ < map->getSize()) {
        map->createRandFood();
        sleepFPS();
    }
    exitGame("testFood() finished.");
}

// this function is for searching minimum/maximum path test calling snake.testMinPath
// you can choose BFS method with minimum path search or search maximum path using BFS method and extra extension algorithm.
void GameCtrl::testSearch() {
    if (mapRowCnt != 20 || mapColCnt != 20) {
        throw std::range_error("GameCtrl.testSearch(): Require map size 20*20.");
    }

    list<Direction> path;
    snake.setMap(map);

    // Add walls for testing
    for (int i = 4; i < 16; ++i) {
        map->getPoint(Pos(i, 9)).setType(Point::Type::WALL);   // vertical
        map->getPoint(Pos(4, i)).setType(Point::Type::WALL);   // horizontal #1
        map->getPoint(Pos(15, i)).setType(Point::Type::WALL);  // horizontal #2
    }

    Pos from(6, 7);
	Pos to(14, 13);
	if(select_path==0) snake.testMinPath(from, to, path);
    else if(select_path==1) snake.testMaxPath(from, to, path);

    // Print path info
    string info = "Path from " + from.toString() + " to " + to.toString()
        + " of length " + util::toString(path.size()) + ":\n";
    for (const Direction &d : path) {
        switch (d) {
            case LEFT:
                info += "L "; break;
            case UP:
                info += "U "; break;
            case RIGHT:
                info += "R "; break;
            case DOWN:
                info += "D "; break;
            case NONE:
            default:
                info += "NONE "; break;
        }
    }
    info += "\ntestSearch() finished.";
    exitGame(info);
}

// this function is for testing hamilton path search
void GameCtrl::testHamilton() {
    snake.setMap(map);
    snake.addBody(Pos(1, 3));
    snake.addBody(Pos(1, 2));
    snake.addBody(Pos(1, 1));
    snake.testHamilton();
    exitGame("testHamilton() finished.");
}
<commit_msg>Remove duplicate areas<commit_after>#include "GameCtrl.h"
#include "util/util.h"
#include <stdexcept>
#include <cstdio>
#include <chrono>
#include <cstdlib>
#ifdef _WIN32
#include <Windows.h>
#endif

using std::string;
using std::list;

// Error statements
const string GameCtrl::MSG_BAD_ALLOC = "Not enough memory to run the game.";
const string GameCtrl::MSG_LOSE = "Oops! You lose!";
const string GameCtrl::MSG_WIN = "Congratulations! You Win!";
const string GameCtrl::MSG_ESC = "Game ended.";
const string GameCtrl::MAP_INFO_FILENAME = "movements.txt";
int select_path=0; //Select Min Path or Max Path

GameCtrl::GameCtrl() {}

// class Destroyer
GameCtrl::~GameCtrl() {
    delete map;
    map = nullptr;
    if (movementFile) {
        fclose(movementFile);
        movementFile = nullptr;
    }
}

// Return the class instance's address
// Return instance will be used for creating and running game in main functio
GameCtrl* GameCtrl::getInstance(int n) {

    static GameCtrl instance;
	select_path=n;
    return &instance;
}

void GameCtrl::setFPS(const double fps_) {
    fps = fps_;
}

// set program enable AI based search to decide next point to visit
// enableAI_(parameter) is boolean type whether the program use AI based search or not
void GameCtrl::setEnableAI(const bool enableAI_) {
    enableAI = enableAI_;
}

// set program enable Hamilton search to decide next point to visit
// enableHamilton_(parameter) is boolean type whether the program use Hamilton based search or not
void GameCtrl::setEnableHamilton(const bool enableHamilton_) {
    enableHamilton = enableHamilton_;
}

void GameCtrl::setMoveInterval(const long ms) {
    moveInterval = ms;
}

// set program records the snake's movement in text file
// b(parameter) is boolean type whether the program record snake's movement or not
void GameCtrl::setRecordMovements(const bool b) {
    recordMovements = b;
}

void GameCtrl::setRunTest(const bool b) {
    runTest = b;
}

void GameCtrl::setMapRow(const SizeType n) {
    mapRowCnt = n;
}

void GameCtrl::setMapCol(const SizeType n) {
    mapColCnt = n;
}

// essential function to working the program. this function call init() to initialize some variables to start game
// return 0 : successfully exit game
// return 1 : error is occured during game is running
int GameCtrl::run() {
    try {
        init();
        if (runTest) {
            test();
        }
        while (runMainThread) {}
        return 0;
    } catch (const std::exception &e) {
        exitGameErr(e.what());
        return -1;
    }
}

void GameCtrl::sleepFPS() const {
    util::sleep((long)((1.0 / fps) * 1000));
}

// this function is called when the program is needed to end
// msg(parameter) contains error message
// this function use mutex lock because when a thread access critical section, other two thread must not access same crtical section.
// ex) when draw thread is errored, and at the same time , search thread is also errored,
//     the first errored thread access critical section, and then search thread access critical section
void GameCtrl::exitGame(const std::string &msg) {
    mutexExit.lock();
    if (runMainThread) {
        util::sleep(100);
        runSubThread = false;
        util::sleep(100);
        printMsg(msg);
    }
    mutexExit.unlock();
    runMainThread = false;
}

void GameCtrl::exitGameErr(const std::string &err) {
    exitGame("ERROR: " + err);
}

void GameCtrl::printMsg(const std::string &msg) {
    Console::setCursor(0, (int)mapRowCnt);
    Console::writeWithColor(msg + "\n", ConsoleColor(WHITE, BLACK, true, false));
}

// this function is for moving snake one step more closer to food
// this function also use mutex lock because manually moving mode,
// user input many keys, but the process must execute move(), writeMapToFile(), CreateRandFood() one by one
void GameCtrl::moveSnake() {
    mutexMove.lock();
    if (map->isAllBody()) {
        mutexMove.unlock();
        exitGame(MSG_WIN);
    } else if (snake.isDead()) {
        mutexMove.unlock();
        exitGame(MSG_LOSE);
    } else {
        try {
            snake.move();
            if (recordMovements && snake.getDirection() != NONE) {
                writeMapToFile();
            }
            if (!map->hasFood()) {
                map->createRandFood();
            }
            mutexMove.unlock();
        } catch (const std::exception) {
            mutexMove.unlock();
            throw;
        }
    }
}

// this function is for writing snake's movement to text file format
void GameCtrl::writeMapToFile() const {
    if (!movementFile) {
        return;
    }
    SizeType rows = map->getRowCount();
    SizeType cols = map->getColCount();
    for (SizeType i = 0; i < rows; ++i) {
        for (SizeType j = 0; j < cols; ++j) {
            switch (map->getPoint(Pos(i, j)).getType()) {
                case Point::Type::EMPTY:
                    fwrite("  ", sizeof(char), 2, movementFile); break;
                case Point::Type::WALL:
                    fwrite("# ", sizeof(char), 2, movementFile); break;
                case Point::Type::FOOD:
                    fwrite("F ", sizeof(char), 2, movementFile); break;
                case Point::Type::SNAKE_HEAD:
                    fwrite("H ", sizeof(char), 2, movementFile); break;
                case Point::Type::SNAKE_BODY:
                    fwrite("B ", sizeof(char), 2, movementFile); break;
                case Point::Type::SNAKE_TAIL:
                    fwrite("T ", sizeof(char), 2, movementFile); break;
                default:
                    break;
            }
        }
        fwrite("\n", sizeof(char), 1, movementFile);
    }
    fwrite("\n", sizeof(char), 1, movementFile);
}

// this function is calling functions to initialize map and snake, and optionally calling initFiles()
void GameCtrl::init() {
    Console::clear();
    initMap();
    if (!runTest) {
        initSnake();
        if (recordMovements) {
            initFiles();
        }
    }
    startThreads();
}

// this function is for initialize map
void GameCtrl::initMap() {
    if (mapRowCnt < 5 || mapColCnt < 5) {
        string msg = "GameCtrl.initMap(): Map size at least 5*5. Current size "
            + util::toString(mapRowCnt) + "*" + util::toString(mapColCnt) + ".";
        throw std::range_error(msg.c_str());
    }
    map = new Map(mapRowCnt, mapColCnt);
    if (!map) {
        exitGameErr(MSG_BAD_ALLOC);
    } else {
        // Add some extra walls manully
    }
}

// this function is for initialize snake and optionally call snake.hamilton search
void GameCtrl::initSnake() {
    snake.setMap(map);
    snake.addBody(Pos(1, 3));
    snake.addBody(Pos(1, 2));
    snake.addBody(Pos(1, 1));
    if (enableHamilton) {
        snake.enableHamilton();
    }
}

// this function is for initialize movement file to record snake's movement
// initializing file's main work is write content type description to file
void GameCtrl::initFiles() {
    movementFile = fopen(MAP_INFO_FILENAME.c_str(), "w");
    if (!movementFile) {
        throw std::runtime_error("GameCtrl.initFiles(): Fail to open file: " + MAP_INFO_FILENAME);
    } else {
        // Write content description to the file
        string str = "Content description:\n";
        str += "#: wall\nH: snake head\nB: snake body\nT: snake tail\nF: food\n\n";
        str += "Movements:\n\n";
        fwrite(str.c_str(), sizeof(char), str.length(), movementFile);
    }
}

// this function creates three thrads which are necessary to run the game
// the three thread is drawThread, keyboardThread, moveThread
// drawThread's main work is draw map in console
// keyboardThread's main work is inputing key and calling function to move snake position
// moveThread's main work is to move snake position based on auto search methods(Hamilton, graph search, BFS)
// runSubThread is very important thing to run program. as long as this value is true, each thread is keep running and it means no error is occured.
void GameCtrl::startThreads() {
    runSubThread = true;
    drawThread = std::thread(&GameCtrl::draw, this);
    drawThread.detach();
    keyboardThread = std::thread(&GameCtrl::keyboard, this);
    keyboardThread.detach();
    if (!runTest) {
        moveThread = std::thread(&GameCtrl::autoMove, this);
        moveThread.detach();
    }
}
// this function calls drawMapContent() function to draw Map
void GameCtrl::draw() {
    try {
        while (runSubThread) {
            drawMapContent();
            sleepFPS();
        }
    } catch (const std::exception &e) {
        exitGameErr(e.what());
    }
}
// this function draws Map in console according to point type
// setCursor needs x,y parameter, but when parameter is no, the default is x,y = 0 and this function just position console's cursor to write map
void GameCtrl::drawMapContent() const {
    Console::setCursor();
    SizeType row = map->getRowCount();
	SizeType col = map->getColCount();
    for (SizeType i = 0; i < row; ++i) {
        for (SizeType j = 0; j < col; ++j) {
            const Point &point = map->getPoint(Pos(i, j));
            switch (point.getType()) {
                case Point::Type::EMPTY:
                    Console::writeWithColor("  ", ConsoleColor(BLACK, BLACK));
                    break;
                case Point::Type::WALL:
                    Console::writeWithColor("  ", ConsoleColor(WHITE, WHITE, true, true));
                    break;
                case Point::Type::FOOD:
                    Console::writeWithColor("  ", ConsoleColor(YELLOW, YELLOW, true, true));
                    break;
                case Point::Type::SNAKE_HEAD:
                    Console::writeWithColor("  ", ConsoleColor(RED, RED, true, true));
                    break;
                case Point::Type::SNAKE_BODY:
                    Console::writeWithColor("  ", ConsoleColor(GREEN, GREEN, true, true));
                    break;
                case Point::Type::SNAKE_TAIL:
                    Console::writeWithColor("  ", ConsoleColor(BLUE, BLUE, true, true));
                    break;
                case Point::Type::TEST_VISIT:
                    drawTestPoint(point, ConsoleColor(BLUE, GREEN, true, true));
                    break;
                case Point::Type::TEST_PATH:
                    drawTestPoint(point, ConsoleColor(BLUE, RED, true, true));
                    break;
                default:
                    break;
            }
        }
        Console::write("\n");
    }
}

// this function is for drawing point in test mode
// after debugging, I notice that the code never enter the MAX_VALUE
void GameCtrl::drawTestPoint(const Point &p, const ConsoleColor &consoleColor) const {
    string pointStr = "";
    if (p.getDist() == Point::MAX_VALUE) {
        pointStr = "In";
    } else if (p.getDist() == EMPTY_VALUE) {
        pointStr = "  ";
    } else {
        Point::ValueType dist = p.getDist();
        pointStr = util::toString(p.getDist());
        if (dist / 10 == 0) {
            pointStr.insert(0, " ");
        }
    }
    Console::writeWithColor(pointStr, consoleColor);
}

// this function is call keyboardMove function according to key
void GameCtrl::keyboard() {
    try {
        while (runSubThread) {
            if (Console::kbhit()) {
                switch (Console::getch()) {
					case 'w': case 'W':
                        keyboardMove(snake, Direction::UP);
                        break;
					case 'a': case 'A':
                        keyboardMove(snake, Direction::LEFT);
                        break;
					case 's': case 'S':
                        keyboardMove(snake, Direction::DOWN);
                        break;
					case 'd': case 'D':
                        keyboardMove(snake, Direction::RIGHT);
                        break;
                    case ' ':
                        pause = !pause;  // Pause or resume game
                        break;
                    case 27:  // Esc
                        exitGame(MSG_ESC);
                        break;
                    default:
                        break;
                }
            }
            sleepFPS();
        }
    } catch (const std::exception &e) {
        exitGameErr(e.what());
    }
}

// this function is for moving snake 1 step toward manual input key
// when pause mode, snake moves toward manual input key or
// when not pause mode and when BFS mode(not AI search mode), move snake toward input key
void GameCtrl::keyboardMove(Snake &s, const Direction d) {
    if (pause) {
        s.setDirection(d);
        moveSnake();
    } else if (!enableAI) {
        if (s.getDirection() == d) {
            moveSnake();  // Accelerate
        } else {
            s.setDirection(d);
        }
    }
}

// this function is for auto moving snake 1 step toward AI based search path
// snake.cpp's decideNext function is only for AI based search(hamilton, graph)
// when enableAI == False, it determine path using BFS search method.
void GameCtrl::autoMove() {
    try {
        while (runSubThread) {
            util::sleep(moveInterval);
            if (!pause) {
                if (enableAI) {
                    snake.decideNext();
                }
                moveSnake();
            }
        }
    } catch (const std::exception &e) {
        exitGameErr(e.what());
    }
}

// this function is for testing several tests such as Food creating test, searching minimum/maximum path test, search using Hamilton test
void GameCtrl::test() {
    //testFood();
    testSearch();
    //testHamilton();
}

// this function is for creating food randomly test
void GameCtrl::testFood() {
    SizeType cnt = 0;
    while (runMainThread && cnt++ < map->getSize()) {
        map->createRandFood();
        sleepFPS();
    }
    exitGame("testFood() finished.");
}

// this function is for searching minimum/maximum path test calling snake.testMinPath
// you can choose BFS method with minimum path search or search maximum path using BFS method and extra extension algorithm.
void GameCtrl::testSearch() {
    if (mapRowCnt != 20 || mapColCnt != 20) {
        throw std::range_error("GameCtrl.testSearch(): Require map size 20*20.");
    }

    list<Direction> path;
    snake.setMap(map);

    // Add walls for testing
    for (int i = 4; i < 16; ++i) {
        map->getPoint(Pos(i, 9)).setType(Point::Type::WALL);   // vertical
        map->getPoint(Pos(4, i)).setType(Point::Type::WALL);   // horizontal #1
        map->getPoint(Pos(15, i)).setType(Point::Type::WALL);  // horizontal #2
    }

    Pos from(6, 7);
	Pos to(14, 13);
	if(select_path==0) snake.testMinPath(from, to, path);
    else if(select_path==1) snake.testMaxPath(from, to, path);

    // Print path info
    string info = "Path from " + from.toString() + " to " + to.toString()
        + " of length " + util::toString(path.size()) + ":\n";
    for (const Direction &d : path) {
        switch (d) {
            case LEFT:
                info += "L "; break;
            case UP:
                info += "U "; break;
            case RIGHT:
                info += "R "; break;
            case DOWN:
                info += "D "; break;
            case NONE:
            default:
                info += "NONE "; break;
        }
    }
    info += "\ntestSearch() finished.";
    exitGame(info);
}

// this function is for testing hamilton path search
void GameCtrl::testHamilton() {
    snake.setMap(map);
    snake.addBody(Pos(1, 3));
    snake.addBody(Pos(1, 2));
    snake.addBody(Pos(1, 1));
    snake.testHamilton();
    exitGame("testHamilton() finished.");
}<|endoftext|>


<commit_before>/*
 * Copyright 2008 The Native Client Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can
 * be found in the LICENSE file.
 */

// NaCl inter-module communication primitives.
//
// This file implements common parts of IMC for "unix like systems" (i.e. not
// used on Windows).

// TODO(shiki): Perhaps this file should go into a platform-specific directory
// (posix? unixlike?)  We have a little convention going where mac/linux stuff
// goes in the linux directory and is referenced by the mac build but that's a
// little sloppy.

#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>

#include <algorithm>

#include "native_client/src/include/atomic_ops.h"

#include "native_client/src/shared/imc/nacl_imc.h"

#if NACL_LINUX && !defined(NACL_STANDALONE)
#include "chrome/renderer/renderer_sandbox_support_linux.h"
#endif

namespace nacl {

namespace {

// The pathname or SHM-namespace prefixes for memory objects created
// by CreateMemoryObject().
const char kShmTempPrefix[] = "/tmp/google-nacl-shm-";
const char kShmOpenPrefix[] = "/google-nacl-shm-";

}  // namespace

bool WouldBlock() {
  return (errno == EAGAIN) ? true : false;
}

int GetLastErrorString(char* buffer, size_t length) {
#if NACL_LINUX
  // Note some Linux distributions provide only GNU version of strerror_r().
  if (buffer == NULL || length == 0) {
    errno = ERANGE;
    return -1;
  }
  char* message = strerror_r(errno, buffer, length);
  if (message != buffer) {
    size_t message_bytes = strlen(message) + 1;
    length = std::min(message_bytes, length);
    memmove(buffer, message, length);
    buffer[length - 1] = '\0';
  }
  return 0;
#else
  return strerror_r(errno, buffer, length);
#endif
}

static AtomicWord memory_object_count = 0;

static int TryShmOrTempOpen(size_t length, bool use_temp) {
  if (0 == length) {
    return -1;
  }

  char name[PATH_MAX];
  for (;;) {
    const char *prefix = use_temp ? kShmTempPrefix : kShmOpenPrefix;
    snprintf(name, sizeof name, "%s-%u.%u", prefix,
             getpid(),
             static_cast<uint32_t>(AtomicIncrement(&memory_object_count, 1)));
    int m;
    if (use_temp) {
      m = open(name, O_RDWR | O_CREAT | O_EXCL, 0);
    } else {
      m = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0);
    }
    if (0 <= m) {
      (void) shm_unlink(name);
      if (ftruncate(m, length) == -1) {
        close(m);
        m = -1;
      }
      return m;
    }
    if (errno != EEXIST) {
      return -1;
    }
    // Retry only if we got EEXIST.
  }
}

static CreateMemoryObjectFunc g_create_memory_object_func = NULL;

void SetCreateMemoryObjectFunc(CreateMemoryObjectFunc func) {
  g_create_memory_object_func = func;
}

Handle CreateMemoryObject(size_t length) {
  if (0 == length) {
    return -1;
  }
  int fd;

  if (g_create_memory_object_func != NULL) {
    // TODO(mseaborn): Plumb in the second argument, which will be
    // true for nacl_text.c.
    fd = g_create_memory_object_func(length, false);
    if (fd >= 0)
      return fd;
  }

  // On Mac OS X, shm_open() gives us file descriptors that the OS
  // won't mmap() with PROT_EXEC, which is no good for the dynamic
  // code region.  Try open()ing a file in /tmp first, but fall back
  // to using shm_open() if /tmp is not available, which will be the
  // case inside the Chromium sandbox.  This means that dynamic
  // loading will only work with --no-sandbox.
  //
  // TODO(mseaborn): We will probably need to do IPC to acquire SHM FDs
  // inside the Chromium Mac sandbox, as on Linux.

#if NACL_OSX
  // Try /tmp first.  It would be OK to enable this for Linux, but
  // there's no need because shm_open() (which uses /dev/shm rather
  // than /tmp) is fine on Linux.
  fd = TryShmOrTempOpen(length, true);
  if (fd >= 0)
    return fd;
#endif

  // Try shm_open().
  fd = TryShmOrTempOpen(length, false);
  if (fd >= 0)
    return fd;

#if NACL_LINUX && !defined(NACL_STANDALONE)
  // As a temporary measure, we try shm_open() as well as calling
  // the unsandboxed browser process.  This code runs in the
  // context of both the renderer and (Chromium's compiled-in)
  // sel_ldr.
  // TODO(mseaborn): Remove this once g_create_memory_object_func
  // is set when in Chromium's sandbox.
  return renderer_sandbox_support::MakeSharedMemorySegmentViaIPC(length);
#endif

  return -1;
}

void* Map(void* start, size_t length, int prot, int flags,
          Handle memory, off_t offset) {
  static const int kPosixProt[] = {
    PROT_NONE,
    PROT_READ,
    PROT_WRITE,
    PROT_READ | PROT_WRITE,
    PROT_EXEC,
    PROT_READ | PROT_EXEC,
    PROT_WRITE | PROT_EXEC,
    PROT_READ | PROT_WRITE | PROT_EXEC
  };

  int adjusted = 0;
  if (flags & kMapShared) {
    adjusted |= MAP_SHARED;
  }
  if (flags & kMapPrivate) {
    adjusted |= MAP_PRIVATE;
  }
  if (flags & kMapFixed) {
    adjusted |= MAP_FIXED;
  }
  return mmap(start, length, kPosixProt[prot & 7], adjusted, memory, offset);
}

int Unmap(void* start, size_t length) {
  return munmap(start, length);
}

}  // namespace nacl
<commit_msg>Remove now-unneeded #include of a Chromium header<commit_after>/*
 * Copyright 2008 The Native Client Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can
 * be found in the LICENSE file.
 */

// NaCl inter-module communication primitives.
//
// This file implements common parts of IMC for "unix like systems" (i.e. not
// used on Windows).

// TODO(shiki): Perhaps this file should go into a platform-specific directory
// (posix? unixlike?)  We have a little convention going where mac/linux stuff
// goes in the linux directory and is referenced by the mac build but that's a
// little sloppy.

#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>

#include <algorithm>

#include "native_client/src/include/atomic_ops.h"

#include "native_client/src/shared/imc/nacl_imc.h"

namespace nacl {

namespace {

// The pathname or SHM-namespace prefixes for memory objects created
// by CreateMemoryObject().
const char kShmTempPrefix[] = "/tmp/google-nacl-shm-";
const char kShmOpenPrefix[] = "/google-nacl-shm-";

}  // namespace

bool WouldBlock() {
  return (errno == EAGAIN) ? true : false;
}

int GetLastErrorString(char* buffer, size_t length) {
#if NACL_LINUX
  // Note some Linux distributions provide only GNU version of strerror_r().
  if (buffer == NULL || length == 0) {
    errno = ERANGE;
    return -1;
  }
  char* message = strerror_r(errno, buffer, length);
  if (message != buffer) {
    size_t message_bytes = strlen(message) + 1;
    length = std::min(message_bytes, length);
    memmove(buffer, message, length);
    buffer[length - 1] = '\0';
  }
  return 0;
#else
  return strerror_r(errno, buffer, length);
#endif
}

static AtomicWord memory_object_count = 0;

static int TryShmOrTempOpen(size_t length, bool use_temp) {
  if (0 == length) {
    return -1;
  }

  char name[PATH_MAX];
  for (;;) {
    const char *prefix = use_temp ? kShmTempPrefix : kShmOpenPrefix;
    snprintf(name, sizeof name, "%s-%u.%u", prefix,
             getpid(),
             static_cast<uint32_t>(AtomicIncrement(&memory_object_count, 1)));
    int m;
    if (use_temp) {
      m = open(name, O_RDWR | O_CREAT | O_EXCL, 0);
    } else {
      m = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0);
    }
    if (0 <= m) {
      (void) shm_unlink(name);
      if (ftruncate(m, length) == -1) {
        close(m);
        m = -1;
      }
      return m;
    }
    if (errno != EEXIST) {
      return -1;
    }
    // Retry only if we got EEXIST.
  }
}

static CreateMemoryObjectFunc g_create_memory_object_func = NULL;

void SetCreateMemoryObjectFunc(CreateMemoryObjectFunc func) {
  g_create_memory_object_func = func;
}

Handle CreateMemoryObject(size_t length) {
  if (0 == length) {
    return -1;
  }
  int fd;

  if (g_create_memory_object_func != NULL) {
    // TODO(mseaborn): Plumb in the second argument, which will be
    // true for nacl_text.c.
    fd = g_create_memory_object_func(length, false);
    if (fd >= 0)
      return fd;
  }

  // On Mac OS X, shm_open() gives us file descriptors that the OS
  // won't mmap() with PROT_EXEC, which is no good for the dynamic
  // code region.  Try open()ing a file in /tmp first, but fall back
  // to using shm_open() if /tmp is not available, which will be the
  // case inside the Chromium sandbox.  This means that dynamic
  // loading will only work with --no-sandbox.
  //
  // TODO(mseaborn): We will probably need to do IPC to acquire SHM FDs
  // inside the Chromium Mac sandbox, as on Linux.

#if NACL_OSX
  // Try /tmp first.  It would be OK to enable this for Linux, but
  // there's no need because shm_open() (which uses /dev/shm rather
  // than /tmp) is fine on Linux.
  fd = TryShmOrTempOpen(length, true);
  if (fd >= 0)
    return fd;
#endif

  // Try shm_open().
  return TryShmOrTempOpen(length, false);
}

void* Map(void* start, size_t length, int prot, int flags,
          Handle memory, off_t offset) {
  static const int kPosixProt[] = {
    PROT_NONE,
    PROT_READ,
    PROT_WRITE,
    PROT_READ | PROT_WRITE,
    PROT_EXEC,
    PROT_READ | PROT_EXEC,
    PROT_WRITE | PROT_EXEC,
    PROT_READ | PROT_WRITE | PROT_EXEC
  };

  int adjusted = 0;
  if (flags & kMapShared) {
    adjusted |= MAP_SHARED;
  }
  if (flags & kMapPrivate) {
    adjusted |= MAP_PRIVATE;
  }
  if (flags & kMapFixed) {
    adjusted |= MAP_FIXED;
  }
  return mmap(start, length, kPosixProt[prot & 7], adjusted, memory, offset);
}

int Unmap(void* start, size_t length) {
  return munmap(start, length);
}

}  // namespace nacl
<|endoftext|>