// Copyright 2018 The clvk authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <cmath>

#include "image_format.hpp"
#include "memory.hpp"
#include "queue.hpp"

bool cvk_mem::map_memory() {
    std::lock_guard<std::mutex> lock(m_map_lock);
    cvk_debug("%p::map", this);

    if (m_map_count == 0) {
        if (m_parent != nullptr) {
            if (!m_parent->map()) {
                return false;
            }
            m_map_ptr = pointer_offset(m_parent->host_va(), m_parent_offset);
            cvk_debug("%p::map, sub-buffer, map_ptr = %p", this, m_map_ptr);
        } else {
            auto res = m_memory->map(&m_map_ptr);
            if (res != VK_SUCCESS) {
                return false;
            }
            cvk_debug("%p::map, map_ptr = %p", this, m_map_ptr);
        }
    }

    m_map_count++;
    retain();
    cvk_debug("%p::map, new map_count = %u", this, m_map_count);

    return true;
}

void cvk_mem::unmap_memory() {
    std::lock_guard<std::mutex> lock(m_map_lock);
    cvk_debug("%p::unmap", this);

    CVK_ASSERT(m_map_count > 0);
    m_map_count--;
    release();
    if (m_map_count == 0) {
        if (m_parent != nullptr) {
            m_parent->unmap();
            cvk_debug("%p::unmap, sub-buffer", this);
        } else {
            m_memory->unmap();
            m_map_ptr = nullptr;
        }
    }
    cvk_debug("%p::unmap, new map_count = %u", this, m_map_count);
}

void cvk_mem::invalidate_memory(VkDeviceSize offset, VkDeviceSize size) {
    if (m_parent != nullptr) {
        m_parent->invalidate_memory(offset + m_parent_offset, size);
    } else {
        m_memory->invalidate(offset, size);
    }
}

void cvk_mem::flush_memory(VkDeviceSize offset, VkDeviceSize size) {
    if (m_parent != nullptr) {
        m_parent->flush_memory(offset + m_parent_offset, size);
    } else {
        m_memory->flush(offset, size);
    }
}

std::unique_ptr<cvk_buffer>
cvk_buffer::create(cvk_context* context, cl_mem_flags flags, size_t size,
                   void* host_ptr, std::vector<cl_mem_properties>&& properties,
                   cl_int* errcode_ret) {
    auto buffer = std::make_unique<cvk_buffer>(
        context, flags, size, host_ptr, nullptr, 0, std::move(properties));

    if (!buffer->init()) {
        *errcode_ret = CL_OUT_OF_RESOURCES;
        return nullptr;
    }

    *errcode_ret = CL_SUCCESS;
    return buffer;
}

bool cvk_buffer::init() {
    auto device = m_context->device();
    auto vkdev = device->vulkan_device();

    // Create the buffer
    const VkBufferCreateInfo createInfo = {
        VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, // sType
        nullptr,                              // pNext
        0,                                    // flags
        m_size,
        prepare_usage_flags(), // usage
        VK_SHARING_MODE_EXCLUSIVE,
        0,       // queueFamilyIndexCount
        nullptr, // pQueueFamilyIndices
    };

    VkResult res = vkCreateBuffer(vkdev, &createInfo, nullptr, &m_buffer);

    if (res != VK_SUCCESS) {
        return false;
    }

    // Select memory type
    cvk_device::allocation_parameters params =
        device->select_memory_for(m_buffer, flags());
    if (params.memory_type_index == VK_MAX_MEMORY_TYPES) {
        return false;
    }

    // Allocate memory
    m_memory = std::make_shared<cvk_memory_allocation>(
        vkdev, params.size, params.memory_type_index, params.memory_coherent,
        device->keep_memory_allocations_mapped());
    res = m_memory->allocate(device->uses_physical_addressing());

    if (res != VK_SUCCESS) {
        return false;
    }

    // Bind the buffer to memory
    res = vkBindBufferMemory(vkdev, m_buffer, m_memory->vulkan_memory(), 0);

    if (res != VK_SUCCESS) {
        return false;
    }

    if (has_any_flag(CL_MEM_COPY_HOST_PTR | CL_MEM_USE_HOST_PTR)) {
        if (!copy_from(m_host_ptr, 0, m_size)) {
            return false;
        }
    }

    return true;
}

cvk_mem* cvk_buffer::create_subbuffer(cl_mem_flags flags, size_t origin,
                                      size_t size) {
    std::vector<cl_mem_properties> properties;
    auto buffer = std::make_unique<cvk_buffer>(
        m_context, flags, size, nullptr, this, origin, std::move(properties));

    return buffer.release();
}

cvk_sampler*
cvk_sampler::create(cvk_context* context, bool normalized_coords,
                    cl_addressing_mode addressing_mode,
                    cl_filter_mode filter_mode,
                    std::vector<cl_sampler_properties>&& properties) {
    auto sampler = std::make_unique<cvk_sampler>(context, normalized_coords,
                                                 addressing_mode, filter_mode,
                                                 std::move(properties));

    if (!sampler->init()) {
        return nullptr;
    }

    return sampler.release();
}

bool cvk_sampler::init(bool force_normalized_coordinates) {
    auto vkdev = context()->device()->vulkan_device();

    // Translate addressing mode
    VkSamplerAddressMode address_mode;
    switch (m_addressing_mode) {
    default:
    case CL_ADDRESS_NONE:
        address_mode = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
        break;
    case CL_ADDRESS_CLAMP_TO_EDGE:
        address_mode = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
        break;
    case CL_ADDRESS_CLAMP:
        address_mode = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
        break;
    case CL_ADDRESS_REPEAT:
        address_mode = VK_SAMPLER_ADDRESS_MODE_REPEAT;
        break;
    case CL_ADDRESS_MIRRORED_REPEAT:
        address_mode = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
        break;
    }

    // Translate filtering
    VkFilter filter;
    VkSamplerMipmapMode mipmap_mode;
    switch (m_filter_mode) {
    case CL_FILTER_LINEAR:
        if (config.supports_filter_linear) {
            filter = VK_FILTER_LINEAR;
            mipmap_mode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
            break;
        } else {
            cvk_warn_fn(
                "linear filter is not supported, using nearest filter instead");
        }
        [[fallthrough]];
    default:
    case CL_FILTER_NEAREST:
        filter = VK_FILTER_NEAREST;
        mipmap_mode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
        break;
    }

    // Translate coordinate type
    VkBool32 unnormalized_coordinates;
    if (m_normalized_coords || force_normalized_coordinates) {
        unnormalized_coordinates = VK_FALSE;
    } else {
        unnormalized_coordinates = VK_TRUE;
        // VUID-01073: unnormalized coords must use nearest mipmap filtering.
        mipmap_mode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
        // VUID-01075: unnormalized coords must use clamp to edge or border
        // addressing
        if ((address_mode != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE) &&
            (address_mode != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER)) {
            address_mode = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
        }
    }

    // TODO this is a rough first pass, dig into the details
    const VkSamplerCreateInfo create_info = {
        VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
        nullptr,                               // pNext
        0,                                     // flags
        filter,                                // magFilter
        filter,                                // minFilter
        mipmap_mode,                           // mipmapMode
        address_mode,                          // addressModeU
        address_mode,                          // addressModeV
        address_mode,                          // addressModeW
        0.0f,                                  // mipLodBias
        VK_FALSE,                              // anisotropyEnable
        0.0f,                                  // maxAnisotropy
        VK_FALSE,                              // compareEnable
        VK_COMPARE_OP_NEVER,                   // compareOp
        0.0f,                                  // minLod
        0.0f,                                  // maxLod
        VK_BORDER_COLOR_INT_TRANSPARENT_BLACK, // borderColor
        unnormalized_coordinates,              // unnormalizedCoordinates
    };

    VkSampler* sampler =
        force_normalized_coordinates ? &m_sampler_norm : &m_sampler;
    auto res = vkCreateSampler(vkdev, &create_info, nullptr, sampler);

    return (res == VK_SUCCESS);
}

VkFormatFeatureFlags
cvk_image::required_format_feature_flags_for(cl_mem_object_type type,
                                             cl_mem_flags flags) {
    // 1Dbuffer requires
    //  RW / RaW: STORAGE_TEXEL_BUFFER
    //  RO: UNIFORM_TEXEL_BUFFER
    // All other images require TRANSFER_SRC, TRANSFER_DST
    //  read-only: SAMPLED_IMAGE, SAMPLED_IMAGE_FILTER_LINEAR
    //  write-only: STORAGE_IMAGE
    //  read-write: STORAGE_IMAGE, SAMPLED_IMAGE, SAMPLED_IMAGE_FILTER_LINEAR
    //  read-and-write: STORAGE_IMAGE
    VkFormatFeatureFlags format_feature_flags = 0;
    if (type != CL_MEM_OBJECT_IMAGE1D_BUFFER) {
        format_feature_flags = VK_FORMAT_FEATURE_TRANSFER_SRC_BIT |
                               VK_FORMAT_FEATURE_TRANSFER_DST_BIT;
    }
    VkFormatFeatureFlags format_feature_flags_RO;
    if (type == CL_MEM_OBJECT_IMAGE1D_BUFFER) {
        format_feature_flags_RO = VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT;
    } else {
        format_feature_flags_RO = VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT;
        if (config.supports_filter_linear()) {
            format_feature_flags_RO |=
                VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT;
        }
    }
    VkFormatFeatureFlags format_feature_flags_WO;
    if (type == CL_MEM_OBJECT_IMAGE1D_BUFFER) {
        format_feature_flags_WO = VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT;
    } else {
        format_feature_flags_WO = VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT;
    }

    if (flags & (CL_MEM_KERNEL_READ_AND_WRITE | CL_MEM_WRITE_ONLY)) {
        format_feature_flags |= format_feature_flags_WO;
    } else if (flags & CL_MEM_READ_ONLY) {
        format_feature_flags |= format_feature_flags_RO;
    } else {
        format_feature_flags |=
            format_feature_flags_RO | format_feature_flags_WO;
    }

    return format_feature_flags;
}

cvk_image* cvk_image::create(cvk_context* ctx, cl_mem_flags flags,
                             const cl_image_desc* desc,
                             const cl_image_format* format, void* host_ptr,
                             std::vector<cl_mem_properties>&& properties) {
    auto image = std::make_unique<cvk_image>(ctx, flags, desc, format, host_ptr,
                                             std::move(properties));

    if (!image->init()) {
        return nullptr;
    }

    return image.release();
}

bool cvk_image::init_vulkan_image() {
    // Translate image type and size
    VkImageType image_type;
    VkImageViewType view_type;
    VkExtent3D extent;

    extent.width = m_desc.image_width;
    extent.height = m_desc.image_height;
    extent.depth = m_desc.image_depth;

    uint32_t array_layers = 1;
    if ((m_desc.image_type == CL_MEM_OBJECT_IMAGE1D_ARRAY) ||
        (m_desc.image_type == CL_MEM_OBJECT_IMAGE2D_ARRAY)) {
        array_layers = m_desc.image_array_size;
    }

    uint32_t row_pitch = m_desc.image_row_pitch;
    if (row_pitch == 0) {
        row_pitch = m_desc.image_width * element_size();
    }
    uint32_t slice_pitch = m_desc.image_slice_pitch;
    if (slice_pitch == 0) {
        slice_pitch = row_pitch * m_desc.image_height;
    }

    size_t host_ptr_size = 0;

    switch (m_desc.image_type) {
    case CL_MEM_OBJECT_IMAGE1D:
        image_type = VK_IMAGE_TYPE_1D;
        view_type = VK_IMAGE_VIEW_TYPE_1D;
        extent.height = 1;
        extent.depth = 1;
        host_ptr_size = row_pitch;
        break;
    case CL_MEM_OBJECT_IMAGE1D_ARRAY:
        image_type = VK_IMAGE_TYPE_1D;
        view_type = VK_IMAGE_VIEW_TYPE_1D_ARRAY;
        extent.height = 1;
        extent.depth = 1;
        host_ptr_size = row_pitch * array_layers;
        break;
    case CL_MEM_OBJECT_IMAGE2D:
        image_type = VK_IMAGE_TYPE_2D;
        view_type = VK_IMAGE_VIEW_TYPE_2D;
        extent.depth = 1;
        host_ptr_size = slice_pitch;
        break;
    case CL_MEM_OBJECT_IMAGE2D_ARRAY:
        image_type = VK_IMAGE_TYPE_2D;
        view_type = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
        extent.depth = 1;
        host_ptr_size = slice_pitch * array_layers;
        break;
    case CL_MEM_OBJECT_IMAGE3D:
        image_type = VK_IMAGE_TYPE_3D;
        view_type = VK_IMAGE_VIEW_TYPE_3D;
        host_ptr_size = slice_pitch * m_desc.image_depth;
        break;
    default:
        CVK_ASSERT(false);
        image_type = VK_IMAGE_TYPE_MAX_ENUM;
        view_type = VK_IMAGE_VIEW_TYPE_MAX_ENUM;
        break;
    }

    // Translate format
    image_format_support fmt;
    VkComponentMapping components_sampled, components_storage;

    auto device = m_context->device();

    auto success = cl_image_format_to_vulkan_format(
        m_format, m_desc.image_type, device, &fmt, &components_sampled,
        &components_storage);
    if (!success) {
        return false; // TODO error code
    }

    // Create Image
    VkImageCreateInfo imageCreateInfo = {
        VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
        nullptr,                   // pNext
        0,                         // flags
        image_type,                // imageType
        fmt.vkfmt,                 // format
        extent,                    // extent
        1,                         // mipLevels
        array_layers,              // arrayLayers
        VK_SAMPLE_COUNT_1_BIT,     // samples
        VK_IMAGE_TILING_OPTIMAL,   // tiling
        prepare_usage_flags(),     // usage
        VK_SHARING_MODE_EXCLUSIVE, // sharingMode
        0,                         // queueFamilyIndexCount
        nullptr,                   // pQueueFamilyIndices
        VK_IMAGE_LAYOUT_UNDEFINED, // initialLayout
    };

    auto vkdev = device->vulkan_device();

    auto res = vkCreateImage(vkdev, &imageCreateInfo, nullptr, &m_image);
    if (res != VK_SUCCESS) {
        cvk_error_fn("Could not create image!");
        return false;
    }

    CVK_ASSERT(m_desc.image_type != CL_MEM_OBJECT_IMAGE1D_BUFFER);
    // Select memory type
    cvk_device::allocation_parameters params =
        device->select_memory_for(m_image);
    if (params.memory_type_index == VK_MAX_MEMORY_TYPES) {
        cvk_error_fn("Could not get memory type!");
        return false;
    }

    // Allocate memory
    m_memory = std::make_unique<cvk_memory_allocation>(
        vkdev, params.size, params.memory_type_index, params.memory_coherent,
        device->keep_memory_allocations_mapped());

    res = m_memory->allocate(device->uses_physical_addressing());

    if (res != VK_SUCCESS) {
        cvk_error_fn("Could not allocate memory!");
        return false;
    }

    // Bind the image to memory
    res = vkBindImageMemory(vkdev, m_image, m_memory->vulkan_memory(), 0);

    if (res != VK_SUCCESS) {
        return false;
    }

    // Create image view
    VkImageSubresourceRange subresource = {
        VK_IMAGE_ASPECT_COLOR_BIT, // aspectMask
        0,                         // baseMipLevel
        1,                         // levelCount
        0,                         // baseArrayLayer
        array_layers,              // layerCount
    };

    VkImageViewCreateInfo imageViewCreateInfo = {
        VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
        nullptr,            // pNext
        0,                  // flags
        m_image,            // image
        view_type,          // viewType;
        fmt.vkfmt,          // format
        components_sampled, // components
        subresource,        // subresourceRange
    };

    res = vkCreateImageView(vkdev, &imageViewCreateInfo, nullptr,
                            &m_sampled_view);

    if (res != VK_SUCCESS) {
        return false;
    }

    imageViewCreateInfo.components = components_storage;

    res = vkCreateImageView(vkdev, &imageViewCreateInfo, nullptr,
                            &m_storage_view);

    if (res != VK_SUCCESS) {
        return false;
    }

    if (has_any_flag(CL_MEM_COPY_HOST_PTR | CL_MEM_USE_HOST_PTR)) {
        // Create a staging buffer to copy to the device later.
        cl_int ret;
        m_init_data = cvk_buffer::create(m_context, CL_MEM_READ_ONLY,
                                         host_ptr_size, nullptr, &ret);
        if (ret != CL_SUCCESS) {
            cvk_error("Could not create staging buffer for image host_ptr");
            return false;
        }

        if (!m_init_data->copy_from(m_host_ptr, 0, host_ptr_size)) {
            cvk_error(
                "Could not copy image host_ptr data to the staging buffer");
            return false;
        }

        if (config.init_image_at_creation()) {
            auto queue = m_context->get_or_create_image_init_command_queue();
            if (queue == nullptr) {
                return false;
            }

            auto initimage = new cvk_command_image_init(queue, this);
            ret = queue->enqueue_command_with_deps(initimage, 0, nullptr,
                                                   nullptr);
            if (ret != CL_SUCCESS) {
                return false;
            }
            ret = queue->finish();
            if (ret != CL_SUCCESS) {
                return false;
            }
            std::lock_guard<std::mutex> lock(m_init_tracker.mutex());
            m_init_tracker.set_state(cvk_mem_init_state::completed);
        }
    }

    return true;
}

bool cvk_image::init_vulkan_texel_buffer() {
    VkResult res;

    auto device = m_context->device();
    auto vkdev = device->vulkan_device();

    image_format_support fmt;
    VkComponentMapping components_sampled, components_storage;

    auto success = cl_image_format_to_vulkan_format(
        m_format, m_desc.image_type, device, &fmt, &components_sampled,
        &components_storage);
    if (!success) {
        return false;
    }

    CVK_ASSERT(buffer());
    CVK_ASSERT(buffer()->is_buffer_type());

    auto vkbuf = static_cast<cvk_buffer*>(buffer())->vulkan_buffer();
    auto offset = static_cast<cvk_buffer*>(buffer())->vulkan_buffer_offset();
    // The range should cover exactly the number of texels specified at
    // image creation time.  Don't use WHOLE_SIZE because the row pitch
    // might include additional padding large enough for one or more texels.
    auto range = element_size() * width();

    VkBufferViewCreateInfo createInfo = {
        VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO,
        nullptr,
        0,         // flags
        vkbuf,     // buffer
        fmt.vkfmt, // format
        offset,    // offset
        range      // range
    };

    res = vkCreateBufferView(vkdev, &createInfo, nullptr, &m_buffer_view);
    if (res != VK_SUCCESS) {
        cvk_error_fn("Could not create buffer view");
        return false;
    }

    buffer()->retain();

    return true;
}

bool cvk_image::init() {
    if (is_backed_by_buffer_view()) {
        return init_vulkan_texel_buffer();
    } else {
        return init_vulkan_image();
    }
}

void cvk_image::prepare_fill_pattern(const void* input_pattern,
                                     fill_pattern_array& pattern,
                                     size_t* size_ret) const {

    auto pat_float = static_cast<const cl_float*>(input_pattern);
    auto pat_int = static_cast<const cl_int*>(input_pattern);
    auto pat_uint = static_cast<const cl_uint*>(input_pattern);

    cl_uchar pat_uchar[4] = {
        static_cast<cl_uchar>(pat_uint[0]),
        static_cast<cl_uchar>(pat_uint[1]),
        static_cast<cl_uchar>(pat_uint[2]),
        static_cast<cl_uchar>(pat_uint[3]),
    };
    cl_ushort pat_ushort[4] = {
        static_cast<cl_ushort>(pat_uint[0]),
        static_cast<cl_ushort>(pat_uint[1]),
        static_cast<cl_ushort>(pat_uint[2]),
        static_cast<cl_ushort>(pat_uint[3]),
    };
    cl_char pat_char[4] = {
        static_cast<cl_char>(pat_int[0]),
        static_cast<cl_char>(pat_int[1]),
        static_cast<cl_char>(pat_int[2]),
        static_cast<cl_char>(pat_int[3]),
    };
    cl_short pat_short[4] = {
        static_cast<cl_short>(pat_int[0]),
        static_cast<cl_short>(pat_int[1]),
        static_cast<cl_short>(pat_int[2]),
        static_cast<cl_short>(pat_int[3]),
    };
    cl_half pat_half[4] = {
        cl_half_from_float(pat_float[0], CL_HALF_RTE),
        cl_half_from_float(pat_float[1], CL_HALF_RTE),
        cl_half_from_float(pat_float[2], CL_HALF_RTE),
        cl_half_from_float(pat_float[3], CL_HALF_RTE),
    };

    size_t size = element_size();
    *size_ret = size;

    auto saturate = [](float x, float min, float max) {
        if (std::isnan(x)) {
            return 0.f;
        } else if (x < min) {
            return min;
        } else if (x > max) {
            return max;
        }
        return x;
    };
    cl_uchar pat_unorm_int8[4] = {
        static_cast<cl_uchar>(saturate(pat_float[0], 0.f, 1.f) * 255.0f),
        static_cast<cl_uchar>(saturate(pat_float[1], 0.f, 1.f) * 255.0f),
        static_cast<cl_uchar>(saturate(pat_float[2], 0.f, 1.f) * 255.0f),
        static_cast<cl_uchar>(saturate(pat_float[3], 0.f, 1.f) * 255.0f)};
    cl_char pat_snorm_int8[4] = {
        static_cast<cl_char>(saturate(pat_float[0], -1.f, 1.f) * 127.0f),
        static_cast<cl_char>(saturate(pat_float[1], -1.f, 1.f) * 127.0f),
        static_cast<cl_char>(saturate(pat_float[2], -1.f, 1.f) * 127.0f),
        static_cast<cl_char>(saturate(pat_float[3], -1.f, 1.f) * 127.0f)};
    cl_ushort pat_unorm_int16[4] = {
        static_cast<cl_ushort>(saturate(pat_float[0], 0.f, 1.f) * 65535.0f),
        static_cast<cl_ushort>(saturate(pat_float[1], 0.f, 1.f) * 65535.0f),
        static_cast<cl_ushort>(saturate(pat_float[2], 0.f, 1.f) * 65535.0f),
        static_cast<cl_ushort>(saturate(pat_float[3], 0.f, 1.f) * 65535.0f)};
    cl_short pat_snorm_int16[4] = {
        static_cast<cl_short>(saturate(pat_float[0], -1.f, 1.f) * 32767.0f),
        static_cast<cl_short>(saturate(pat_float[1], -1.f, 1.f) * 32767.0f),
        static_cast<cl_short>(saturate(pat_float[2], -1.f, 1.f) * 32767.0f),
        static_cast<cl_short>(saturate(pat_float[3], -1.f, 1.f) * 32767.0f)};
    cl_short pat_unorm_short_565 =
        (static_cast<cl_ushort>(saturate(pat_float[0], 0.f, 1.f) * 31.0f)
         << 11) |
        (static_cast<cl_ushort>(saturate(pat_float[1], 0.f, 1.f) * 63.0f)
         << 5) |
        (static_cast<cl_ushort>(saturate(pat_float[2], 0.f, 1.f) * 31.0f));
    cl_short pat_unorm_short_555 =
        (static_cast<cl_ushort>(saturate(pat_float[0], 0.f, 1.f) * 31.0f)
         << 10) |
        (static_cast<cl_ushort>(saturate(pat_float[1], 0.f, 1.f) * 31.0f)
         << 5) |
        (static_cast<cl_ushort>(saturate(pat_float[2], 0.f, 1.f) * 31.0f));
    cl_short pat_unorm_int_101010 =
        (static_cast<cl_uint>(saturate(pat_float[0], 0.f, 1.f) * 1023.0f)
         << 20) |
        (static_cast<cl_uint>(saturate(pat_float[1], 0.f, 1.f) * 1023.0f)
         << 10) |
        (static_cast<cl_uint>(saturate(pat_float[2], 0.f, 1.f) * 1023.0f));
    cl_short pat_unorm_int_101010_2 =
        (static_cast<cl_uint>(saturate(pat_float[0], 0.f, 1.f) * 1023.0f)
         << 22) |
        (static_cast<cl_uint>(saturate(pat_float[1], 0.f, 1.f) * 1023.0f)
         << 12) |
        (static_cast<cl_uint>(saturate(pat_float[1], 0.f, 1.f) * 1023.0f)
         << 2) |
        (static_cast<cl_uint>(saturate(pat_float[3], 0.f, 1.f) * 3.0f));

    const void* cast_pattern = nullptr;
    switch (format().image_channel_data_type) {
    case CL_UNSIGNED_INT8:
        cast_pattern = &pat_uchar;
        break;
    case CL_UNSIGNED_INT16:
        cast_pattern = &pat_ushort;
        break;
    case CL_SIGNED_INT8:
        cast_pattern = &pat_char;
        break;
    case CL_SIGNED_INT16:
        cast_pattern = &pat_short;
        break;
    case CL_FLOAT:
    case CL_UNSIGNED_INT32:
    case CL_SIGNED_INT32:
        cast_pattern = input_pattern;
        break;
    case CL_UNORM_INT8:
        cast_pattern = pat_unorm_int8;
        break;
    case CL_UNORM_INT16:
        cast_pattern = pat_unorm_int16;
        break;
    case CL_SNORM_INT8:
        cast_pattern = pat_snorm_int8;
        break;
    case CL_SNORM_INT16:
        cast_pattern = pat_snorm_int16;
        break;
    case CL_HALF_FLOAT:
        cast_pattern = pat_half;
        break;
    case CL_UNORM_SHORT_565:
        cast_pattern = &pat_unorm_short_565;
        break;
    case CL_UNORM_SHORT_555:
        cast_pattern = &pat_unorm_short_555;
        break;
    case CL_UNORM_INT_101010:
        cast_pattern = &pat_unorm_int_101010;
        break;
    case CL_UNORM_INT_101010_2:
        cast_pattern = &pat_unorm_int_101010_2;
        break;
    default:
        CVK_ASSERT(false);
        return;
    }

    size_t csize = element_size_per_channel();

    switch (format().image_channel_order) {
    case CL_R:
    case CL_RG:
    case CL_RGB:
    case CL_RGBA:
    case CL_LUMINANCE:
    case CL_INTENSITY:
        memcpy(pattern.data(), cast_pattern, size);
        break;
    case CL_BGRA:
        memcpy(pattern.data() + 0 * csize,
               pointer_offset(cast_pattern, 2 * csize), csize);
        memcpy(pattern.data() + 1 * csize,
               pointer_offset(cast_pattern, 1 * csize), csize);
        memcpy(pattern.data() + 2 * csize,
               pointer_offset(cast_pattern, 0 * csize), csize);
        memcpy(pattern.data() + 3 * csize,
               pointer_offset(cast_pattern, 3 * csize), csize);
        break;
    default:
        CVK_ASSERT(false);
    }
}
