0 / 0 / 0
Регистрация: 20.09.2022
Сообщений: 39
1

Как можно реализовать подгрузку текстур и мешей в дочернем потоке (Vulkan API) ?

01.11.2023, 08:11. Показов 753. Ответов 1

Author24 — интернет-сервис помощи студентам
Доброго времени суток!
Изучаю Vulkan, сейчас хотел бы разобраться как в отдельном потоке можно реализовать подгрузку ресурсов.
Сейчас сделано по туториалу(vk-guide) immidiate-загрузка ресурсов в том же главном потоке программы с блокировкой потока пока ресурс не загрузится. Что не очень хорошо, вместо этого хочу в главном подгрузить текстуру пустышку и пока в фоновом не загрузится оригинальная подставлять ее.

полагаю что логика должна быть примерно такой.
в еще главном потоке:
1. берется трансфер-очередь и семейство
C++
1
2
    _transferQueue = vkbDevice.get_queue(vkb::QueueType::transfer).value(); //using VkBootstrap.h
    _transferQueueFamily = vkbDevice.get_queue_index(vkb::QueueType::transfer).value();
2. в (трансфер) комманд-пуле создается трансфер-комманд-буфер
C++
1
2
3
4
5
6
7
8
9
10
11
12
    // Transfer command buffer
    VkCommandPoolCreateInfo transferCommandPoolInfo = vkinit::command_pool_create_info(_transferQueueFamily, VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT);
    {
        VK_CHECK(vkCreateCommandPool(_device, &transferCommandPoolInfo, nullptr, &_transferCommandPool));
        //allocate the default command buffer that we will use for transfering data to vram
        VkCommandBufferAllocateInfo cmdAllocInfo = vkinit::command_buffer_allocate_info(_transferCommandPool, 1);
        VK_CHECK(vkAllocateCommandBuffers(_device, &cmdAllocInfo, &_transferCommandBuffer));
 
        _mainDeletionQueue.push_function([=]() {
            vkDestroyCommandPool(_device, _transferCommandPool, nullptr);
            });
    }
3. далее мы создаем доп. thread который будет наполнять комманд буффер по запросу из главного потока, тут нужны какие-то объекты синхронизации и shared-данные которые будут захватывать то главным потоком то доп. потоком. И тут вопрос какие и как это можно синхронизировать? мьютексы + кондишн-вариабле? я себе смутно это представляю

4. загрузка я думаю в доп. потоке будет примерно такой же как и до этого в основном потоке, в главный по идее будет возвращяться - аллокатор от VMA(<vma/vk_mem_alloc.h>) + image view в виде структуры. но так как загрузок может быть много то вектор таких структур.
C++
1
2
3
4
5
6
7
8
9
10
11
// для мешей
struct AllocatedBuffer {
    VkBuffer _buffer;
    VmaAllocation _allocation;
};
 
// для текстур
struct AllocatedImage {
    VkImage _image;
    VmaAllocation _allocation;
};
Спс за внимание и счастливого тыквенного-спаса!)
0
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
01.11.2023, 08:11
Ответы с готовыми решениями:

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

Как реализовать ajax подгрузку ?
есть vue компонент, как сделать ajax пагинацию во vue.js при скроллинге внизу данные вывожу вот...

Как можно с помощью API реализовать WhoIs?
Подскажите или направьте на пусть истинный, как можно с помощью API реализовать WhoIs? Help в виде...

Работа с БД в дочернем потоке
Как реализовать работу с БД в другом потоке?

Производить вычисления в дочернем потоке
Здравствуйте. Есть кусок кода: TimeWindow aTimeWindow = new TimeWindow(); aTimeWindow.Show(); ...

1
0 / 0 / 0
Регистрация: 20.09.2022
Сообщений: 39
03.11.2023, 12:15  [ТС] 2
В общем. Я пока для синхронизации использую одну переменную.

C++
1
    std::atomic<bool> _mainThread_request_upload_textures = false;
на пробел пока что создаю фейковый список из 10 текстур, но внутри просто 10 раз одна и та же загружается. около 200+мб vram отъедает за раз.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
                    if (!_mainThread_request_upload_textures) 
                    {
                        request_to_bgloader_upload_texture("texture 1");
                        request_to_bgloader_upload_texture("texture 2");
                        request_to_bgloader_upload_texture("texture 3");
                        request_to_bgloader_upload_texture("texture 4");
                        request_to_bgloader_upload_texture("texture 5");
                        request_to_bgloader_upload_texture("texture 6");
                        request_to_bgloader_upload_texture("texture 7");
                        request_to_bgloader_upload_texture("texture 8");
                        request_to_bgloader_upload_texture("texture 9");
                        request_to_bgloader_upload_texture("texture 10");
 
                        _mainThread_request_upload_textures = true; // should assign on frame end
                    }
сам фоновый поток так пока выглядит
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
void VulkanEngine::backgroud_transfer_thread()
{
    while (_backgroundThread_running)
    {
 
        if (_mainThread_request_upload_textures) //  atomic<bool> FLAG: if mainThread have data for uploading textures
        {
            _backgroundThread_textures_ready = false; // not ready yet
            _backgroundThread_upload_textures = _mainThread_textures; // copy path 2 upload
            _mainThread_textures.clear();   // reset data from mt
            _mainThread_request_upload_textures = false;
            _backgroundThread_loaded_textures.resize(_backgroundThread_upload_textures.size()); // set same size for uploaded data as requested 2 load vector
 
            if (_backgroundThread_upload_textures.size() > 0)
            {
                for (uint32_t i = 0; i < _backgroundThread_upload_textures.size(); i++)
                {
                    //std::this_thread::sleep_for(std::chrono::seconds(1));
                    std::cout << "backgroud_thread load : " << _backgroundThread_upload_textures[i] << std::endl;
 
                    Texture& uploadTex = _backgroundThread_loaded_textures[i];
 
                    vkutil::load_image_from_file_background(this, "../../assets/lost_empire-RGBA.png", uploadTex.image); // тут надо пофиксить грузить по списку имен файлов, но у меня текстура пока одна
 
                    VkImageViewCreateInfo imageinfo = vkinit::imageview_create_info(VK_FORMAT_R8G8B8A8_SRGB, uploadTex.image._image, VK_IMAGE_ASPECT_COLOR_BIT);
                    vkCreateImageView(_device, &imageinfo, nullptr, &uploadTex.imageView);
 
                    _mainDeletionQueue.push_function([=]() {
                        vkDestroyImageView(_device, uploadTex.imageView, nullptr);
                        });
 
                }
            }
 
            _backgroundThread_textures_ready = true;
        }
 
        std::this_thread::yield();
    }
    
}
магия основная тут происходит это немедленная загрузка по созданному на лету комманд-буферу.
исполняет его transfer - очередь драйвера видяхи.
C++
1
vkutil::load_image_from_file_background(this, "../../assets/lost_empire-RGBA.png", uploadTex.image);
тут правда пришлось пару барьеров подправить и я не очень понимаю насколько адекватно их задал, но валидация перестала сыпать ошибками пайплайна.

Кликните здесь для просмотра всего текста
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
bool vkutil::load_image_from_file_background(VulkanEngine* engine, const char* file, AllocatedImage& outImage)
{
    int texWidth, texHeight, texChannels;
 
    stbi_uc* pixels = stbi_load(file, &texWidth, &texHeight, &texChannels, STBI_rgb_alpha);
 
    if (!pixels) {
        std::cout << "Failed to load texture file " << file << std::endl;
        return false;
    }
 
    void* pixel_ptr = pixels;
    VkDeviceSize imageSize = uint64_t(texWidth * texHeight) * (uint64_t)4;
 
    //the format R8G8B8A8 matches exactly with the pixels loaded from stb_image lib
    VkFormat image_format = VK_FORMAT_R8G8B8A8_SRGB;
 
    //allocate temporary buffer for holding texture data to upload
    AllocatedBuffer stagingBuffer = engine->create_buffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VMA_MEMORY_USAGE_CPU_ONLY);
 
    //copy data to buffer
    void* data;
    vmaMapMemory(engine->_allocator, stagingBuffer._allocation, &data);
 
    memcpy(data, pixel_ptr, static_cast<size_t>(imageSize));
 
    vmaUnmapMemory(engine->_allocator, stagingBuffer._allocation);
    //we no longer need the loaded data, so we can free the pixels as they are now in the staging buffer
    stbi_image_free(pixels);
 
    VkExtent3D imageExtent;
    imageExtent.width = static_cast<uint32_t>(texWidth);
    imageExtent.height = static_cast<uint32_t>(texHeight);
    imageExtent.depth = 1;
 
    VkImageCreateInfo dimg_info = vkinit::image_create_info(image_format, VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, imageExtent);
 
    AllocatedImage newImage;
 
    VmaAllocationCreateInfo dimg_allocinfo = {};
    dimg_allocinfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
 
    //allocate and create the image
    vmaCreateImage(engine->_allocator, &dimg_info, &dimg_allocinfo, &newImage._image, &newImage._allocation, nullptr);
 
    engine->immediate_submit_background([&](VkCommandBuffer cmd) {
        VkImageSubresourceRange range;
        range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
        range.baseMipLevel = 0;
        range.levelCount = 1;
        range.baseArrayLayer = 0;
        range.layerCount = 1;
 
        VkImageMemoryBarrier imageBarrier_toTransfer = {};
        imageBarrier_toTransfer.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
 
        imageBarrier_toTransfer.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
        imageBarrier_toTransfer.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
        imageBarrier_toTransfer.image = newImage._image;
        imageBarrier_toTransfer.subresourceRange = range;
 
        imageBarrier_toTransfer.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
        imageBarrier_toTransfer.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
 
        //barrier the image into the transfer-receive layout
        //vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &imageBarrier_toTransfer);
        vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &imageBarrier_toTransfer);
        
 
        VkBufferImageCopy copyRegion = {};
        copyRegion.bufferOffset = 0;
        copyRegion.bufferRowLength = 0;
        copyRegion.bufferImageHeight = 0;
 
        copyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
        copyRegion.imageSubresource.mipLevel = 0;
        copyRegion.imageSubresource.baseArrayLayer = 0;
        copyRegion.imageSubresource.layerCount = 1;
        copyRegion.imageExtent = imageExtent;
 
        //copy the buffer into the image
        vkCmdCopyBufferToImage(cmd, stagingBuffer._buffer, newImage._image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copyRegion);
 
        VkImageMemoryBarrier imageBarrier_toReadable = imageBarrier_toTransfer;
 
        imageBarrier_toReadable.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
        imageBarrier_toReadable.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
 
        //imageBarrier_toReadable.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
        //imageBarrier_toReadable.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
 
        //imageBarrier_toReadable.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
        //imageBarrier_toReadable.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
 
        imageBarrier_toReadable.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
        imageBarrier_toReadable.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
 
        //barrier the image into the shader readable layout
        vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &imageBarrier_toReadable);
 
        });
 
 
    engine->_mainDeletionQueue.push_function([=]() {
 
        vmaDestroyImage(engine->_allocator, newImage._image, newImage._allocation);
        });
 
    vmaDestroyBuffer(engine->_allocator, stagingBuffer._buffer, stagingBuffer._allocation);
 
    std::cout << "Texture loaded successfully " << file << std::endl;
 
    outImage = newImage;
    return true;
}




я еще не чекал что там загрузилось и в какой кондиции, но факт на лицо. рендер практически не тормозится фоновой работой. по идее потом просто перекинуть загруженные данные в пул загруженных текстур и использовать уже для генерации комманд буферов в основном или в других.
0
03.11.2023, 12:15
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
03.11.2023, 12:15
Помогаю со студенческими работами здесь

Вывод результата в дочернем потоке
Имеется программа которая вычисляет три суммы для i, j, k. Программа разбита на два потока. Главный...

Реализация операций в дочернем потоке
Помогите пожалуйста исправить ошибку, в общем нужно ввод числа N и вывод S (т.е. суммы) реализовать...

Как реализовать подгрузку данных при условии определенного значения поля?
Здравствуйте. Недавно начал работать с 1с Предприятием. И вот первая сложность с которой появились...

Khronos Group API Vulkan
Состоялся релиз спецификации API Vulkan версии 1.0. Компании AMD и NVIDIA заявили о выпуске...

Vulkan API — уроки (перевод и дополнение)
Доброго времени суток! Недавно решил сделать уроки по новому Vulkan API. Сначала он может...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
2
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru