Compare commits

..

5 commits

Author SHA1 Message Date
bd7afeb8bd Vulkan compute: move buffer init to before fn call 2024-04-01 12:50:54 +09:00
320a33842b Vulkan compute: resize max/min out buffers
Change max/min out buffers to have same size as max/min in buffers.
2024-04-01 12:50:54 +09:00
2abf3de665 Vulkan compute: minor refactoring 2024-04-01 12:50:54 +09:00
11de490e94 Vulkan compute: combine all minmax calls
This commit combines the minmax execution via Vulkan compute. The
previous implementation executed compute in vulkan_minmax with a new
command buffer each time. This implementation combines all required
executions of compute in vulkan_minmax in a single command buffer and
uses a pipeline to ensure the enqueued compute calls stay in order.
2024-04-01 12:50:54 +09:00
7bcb385625 Do "minmax" on Vulkan compute
Was an attempt to speed up blue-noise-generation with Vulkan compute,
but this implementation seems to slow it down instead.
2024-04-01 12:50:54 +09:00
2 changed files with 65 additions and 204 deletions

View file

@ -161,49 +161,6 @@ void dither::internal::vulkan_copy_buffer(VkDevice device,
vkFreeCommandBuffers(device, command_pool, 1, &command_buf); vkFreeCommandBuffers(device, command_pool, 1, &command_buf);
} }
void dither::internal::vulkan_copy_buffer_pieces(
VkDevice device, VkCommandPool command_pool, VkQueue queue,
VkBuffer src_buf, VkBuffer dst_buf,
const std::vector<std::tuple<VkDeviceSize, VkDeviceSize>> &pieces) {
VkCommandBufferAllocateInfo alloc_info{};
alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
alloc_info.commandPool = command_pool;
alloc_info.commandBufferCount = 1;
VkCommandBuffer command_buf;
vkAllocateCommandBuffers(device, &alloc_info, &command_buf);
VkCommandBufferBeginInfo begin_info{};
begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
vkBeginCommandBuffer(command_buf, &begin_info);
std::vector<VkBufferCopy> regions;
for (auto tuple : pieces) {
VkBufferCopy copy_region{};
copy_region.size = std::get<0>(tuple);
copy_region.srcOffset = std::get<1>(tuple);
copy_region.dstOffset = std::get<1>(tuple);
regions.push_back(copy_region);
}
vkCmdCopyBuffer(command_buf, src_buf, dst_buf, regions.size(),
regions.data());
vkEndCommandBuffer(command_buf);
VkSubmitInfo submit_info{};
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submit_info.commandBufferCount = 1;
submit_info.pCommandBuffers = &command_buf;
vkQueueSubmit(queue, 1, &submit_info, VK_NULL_HANDLE);
vkQueueWaitIdle(queue);
vkFreeCommandBuffers(device, command_pool, 1, &command_buf);
}
void dither::internal::vulkan_flush_buffer(VkDevice device, void dither::internal::vulkan_flush_buffer(VkDevice device,
VkDeviceMemory memory) { VkDeviceMemory memory) {
VkMappedMemoryRange range{}; VkMappedMemoryRange range{};
@ -218,40 +175,6 @@ void dither::internal::vulkan_flush_buffer(VkDevice device,
} }
} }
void dither::internal::vulkan_flush_buffer_pieces(
VkDevice device, const VkDeviceSize phys_atom_size, VkDeviceMemory memory,
const std::vector<std::tuple<VkDeviceSize, VkDeviceSize>> &pieces) {
std::vector<VkMappedMemoryRange> ranges;
for (auto tuple : pieces) {
VkMappedMemoryRange range{};
range.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
range.pNext = nullptr;
range.memory = memory;
range.offset = std::get<1>(tuple);
range.size = std::get<0>(tuple);
// TODO dynamically handle multiple pieces for more efficient flushes.
// This may not be necessary if pieces is always size 1.
if (range.offset % phys_atom_size != 0) {
range.offset = (range.offset / phys_atom_size) * phys_atom_size;
}
if (range.size < phys_atom_size) {
range.size = phys_atom_size;
} else if (range.size % phys_atom_size != 0) {
range.size = (range.size / phys_atom_size) * phys_atom_size;
}
ranges.push_back(range);
}
if (vkFlushMappedMemoryRanges(device, ranges.size(), ranges.data()) !=
VK_SUCCESS) {
std::clog << "WARNING: vulkan_flush_buffer failed!\n";
}
}
void dither::internal::vulkan_invalidate_buffer(VkDevice device, void dither::internal::vulkan_invalidate_buffer(VkDevice device,
VkDeviceMemory memory) { VkDeviceMemory memory) {
VkMappedMemoryRange range{}; VkMappedMemoryRange range{};
@ -344,15 +267,6 @@ std::vector<unsigned int> dither::internal::blue_noise_vulkan_impl(
&staging_filter_buffer_mem); &staging_filter_buffer_mem);
float *filter_mapped_float = (float *)filter_mapped; float *filter_mapped_float = (float *)filter_mapped;
std::vector<std::size_t> changed_indices;
VkDeviceSize phys_atom_size;
{
VkPhysicalDeviceProperties props;
vkGetPhysicalDeviceProperties(phys_device, &props);
phys_atom_size = props.limits.nonCoherentAtomSize;
}
{ {
#ifndef NDEBUG #ifndef NDEBUG
printf("Inserting %d pixels into image of max count %d\n", pixel_count, printf("Inserting %d pixels into image of max count %d\n", pixel_count,
@ -371,12 +285,12 @@ std::vector<unsigned int> dither::internal::blue_noise_vulkan_impl(
#endif #endif
} }
if (!vulkan_get_filter( if (!vulkan_get_filter(device, command_buffer, command_pool, queue, pbp_buf,
device, phys_atom_size, command_buffer, command_pool, queue, pbp_buf, pipeline, pipeline_layout, descriptor_set,
pipeline, pipeline_layout, descriptor_set, filter_out_buf, size, pbp, filter_out_buf, size, pbp, reversed_pbp, global_size,
reversed_pbp, global_size, pbp_mapped_int, staging_pbp_buffer, pbp_mapped_int, staging_pbp_buffer,
staging_pbp_buffer_mem, staging_filter_buffer_mem, staging_pbp_buffer_mem, staging_filter_buffer_mem,
staging_filter_buffer, nullptr)) { staging_filter_buffer)) {
std::cerr << "Vulkan: Failed to execute get_filter at start!\n"; std::cerr << "Vulkan: Failed to execute get_filter at start!\n";
} else { } else {
#ifndef NDEBUG #ifndef NDEBUG
@ -395,13 +309,12 @@ std::vector<unsigned int> dither::internal::blue_noise_vulkan_impl(
printf("Iteration %d\n", ++iterations); printf("Iteration %d\n", ++iterations);
#endif #endif
if (!vulkan_get_filter(device, phys_atom_size, command_buffer, command_pool, if (!vulkan_get_filter(device, command_buffer, command_pool, queue, pbp_buf,
queue, pbp_buf, pipeline, pipeline_layout, pipeline, pipeline_layout, descriptor_set,
descriptor_set, filter_out_buf, size, pbp, filter_out_buf, size, pbp, reversed_pbp, global_size,
reversed_pbp, global_size, pbp_mapped_int, pbp_mapped_int, staging_pbp_buffer,
staging_pbp_buffer, staging_pbp_buffer_mem, staging_pbp_buffer_mem, staging_filter_buffer_mem,
staging_filter_buffer_mem, staging_filter_buffer, staging_filter_buffer)) {
&changed_indices)) {
std::cerr << "Vulkan: Failed to execute do_filter\n"; std::cerr << "Vulkan: Failed to execute do_filter\n";
break; break;
} }
@ -430,15 +343,12 @@ std::vector<unsigned int> dither::internal::blue_noise_vulkan_impl(
pbp[max] = false; pbp[max] = false;
changed_indices.push_back(max); if (!vulkan_get_filter(device, command_buffer, command_pool, queue, pbp_buf,
pipeline, pipeline_layout, descriptor_set,
if (!vulkan_get_filter(device, phys_atom_size, command_buffer, command_pool, filter_out_buf, size, pbp, reversed_pbp, global_size,
queue, pbp_buf, pipeline, pipeline_layout, pbp_mapped_int, staging_pbp_buffer,
descriptor_set, filter_out_buf, size, pbp, staging_pbp_buffer_mem, staging_filter_buffer_mem,
reversed_pbp, global_size, pbp_mapped_int, staging_filter_buffer)) {
staging_pbp_buffer, staging_pbp_buffer_mem,
staging_filter_buffer_mem, staging_filter_buffer,
&changed_indices)) {
std::cerr << "Vulkan: Failed to execute do_filter\n"; std::cerr << "Vulkan: Failed to execute do_filter\n";
break; break;
} }
@ -459,11 +369,9 @@ std::vector<unsigned int> dither::internal::blue_noise_vulkan_impl(
if (second_min == max) { if (second_min == max) {
pbp[max] = true; pbp[max] = true;
changed_indices.push_back(max);
break; break;
} else { } else {
pbp[second_min] = true; pbp[second_min] = true;
changed_indices.push_back(second_min);
} }
#ifndef NDEBUG #ifndef NDEBUG
@ -485,12 +393,12 @@ std::vector<unsigned int> dither::internal::blue_noise_vulkan_impl(
#endif #endif
} }
if (!vulkan_get_filter( if (!vulkan_get_filter(device, command_buffer, command_pool, queue, pbp_buf,
device, phys_atom_size, command_buffer, command_pool, queue, pbp_buf, pipeline, pipeline_layout, descriptor_set,
pipeline, pipeline_layout, descriptor_set, filter_out_buf, size, pbp, filter_out_buf, size, pbp, reversed_pbp, global_size,
reversed_pbp, global_size, pbp_mapped_int, staging_pbp_buffer, pbp_mapped_int, staging_pbp_buffer,
staging_pbp_buffer_mem, staging_filter_buffer_mem, staging_pbp_buffer_mem, staging_filter_buffer_mem,
staging_filter_buffer, &changed_indices)) { staging_filter_buffer)) {
std::cerr << "Vulkan: Failed to execute do_filter (at end)\n"; std::cerr << "Vulkan: Failed to execute do_filter (at end)\n";
} else { } else {
#ifndef NDEBUG #ifndef NDEBUG
@ -529,12 +437,12 @@ std::vector<unsigned int> dither::internal::blue_noise_vulkan_impl(
#ifndef NDEBUG #ifndef NDEBUG
std::cout << i << ' '; std::cout << i << ' ';
#endif #endif
vulkan_get_filter(device, phys_atom_size, command_buffer, command_pool, vulkan_get_filter(device, command_buffer, command_pool, queue, pbp_buf,
queue, pbp_buf, pipeline, pipeline_layout, pipeline, pipeline_layout, descriptor_set,
descriptor_set, filter_out_buf, size, pbp, reversed_pbp, filter_out_buf, size, pbp, reversed_pbp, global_size,
global_size, pbp_mapped_int, staging_pbp_buffer, pbp_mapped_int, staging_pbp_buffer,
staging_pbp_buffer_mem, staging_filter_buffer_mem, staging_pbp_buffer_mem, staging_filter_buffer_mem,
staging_filter_buffer, &changed_indices); staging_filter_buffer);
auto vulkan_minmax_opt = vulkan_minmax( auto vulkan_minmax_opt = vulkan_minmax(
device, phys_device, command_buffer, command_pool, queue, device, phys_device, command_buffer, command_pool, queue,
minmax_pipeline, minmax_pipeline_layout, minmax_desc_sets, max_in_buf, minmax_pipeline, minmax_pipeline_layout, minmax_desc_sets, max_in_buf,
@ -548,7 +456,6 @@ std::vector<unsigned int> dither::internal::blue_noise_vulkan_impl(
std::tie(std::ignore, max) = vulkan_minmax_opt.value(); std::tie(std::ignore, max) = vulkan_minmax_opt.value();
pbp.at(max) = false; pbp.at(max) = false;
dither_array.at(max) = i; dither_array.at(max) = i;
changed_indices.push_back(max);
#ifndef NDEBUG #ifndef NDEBUG
if (set.find(max) != set.end()) { if (set.find(max) != set.end()) {
std::cout << "\nWARNING: Reusing index " << max << '\n'; std::cout << "\nWARNING: Reusing index " << max << '\n';
@ -568,12 +475,11 @@ std::vector<unsigned int> dither::internal::blue_noise_vulkan_impl(
#ifndef NDEBUG #ifndef NDEBUG
std::cout << i << ' '; std::cout << i << ' ';
#endif #endif
vulkan_get_filter(device, phys_atom_size, command_buffer, command_pool, vulkan_get_filter(device, command_buffer, command_pool, queue, pbp_buf,
queue, pbp_buf, pipeline, pipeline_layout, descriptor_set, pipeline, pipeline_layout, descriptor_set, filter_out_buf,
filter_out_buf, size, pbp, reversed_pbp, global_size, size, pbp, reversed_pbp, global_size, pbp_mapped_int,
pbp_mapped_int, staging_pbp_buffer, staging_pbp_buffer, staging_pbp_buffer_mem,
staging_pbp_buffer_mem, staging_filter_buffer_mem, staging_filter_buffer_mem, staging_filter_buffer);
staging_filter_buffer, &changed_indices);
auto vulkan_minmax_opt = vulkan_minmax( auto vulkan_minmax_opt = vulkan_minmax(
device, phys_device, command_buffer, command_pool, queue, device, phys_device, command_buffer, command_pool, queue,
minmax_pipeline, minmax_pipeline_layout, minmax_desc_sets, max_in_buf, minmax_pipeline, minmax_pipeline_layout, minmax_desc_sets, max_in_buf,
@ -587,7 +493,6 @@ std::vector<unsigned int> dither::internal::blue_noise_vulkan_impl(
std::tie(min, std::ignore) = vulkan_minmax_opt.value(); std::tie(min, std::ignore) = vulkan_minmax_opt.value();
pbp.at(min) = true; pbp.at(min) = true;
dither_array.at(min) = i; dither_array.at(min) = i;
changed_indices.push_back(min);
#ifndef NDEBUG #ifndef NDEBUG
if (set.find(min) != set.end()) { if (set.find(min) != set.end()) {
std::cout << "\nWARNING: Reusing index " << min << '\n'; std::cout << "\nWARNING: Reusing index " << min << '\n';
@ -600,12 +505,11 @@ std::vector<unsigned int> dither::internal::blue_noise_vulkan_impl(
{ {
image::Bl min_pixels = internal::rangeToBl(dither_array, width); image::Bl min_pixels = internal::rangeToBl(dither_array, width);
min_pixels.writeToFile(image::file_type::PNG, true, "da_mid_pixels.png"); min_pixels.writeToFile(image::file_type::PNG, true, "da_mid_pixels.png");
vulkan_get_filter(device, phys_atom_size, command_buffer, command_pool, vulkan_get_filter(device, command_buffer, command_pool, queue, pbp_buf,
queue, pbp_buf, pipeline, pipeline_layout, descriptor_set, pipeline, pipeline_layout, descriptor_set, filter_out_buf,
filter_out_buf, size, pbp, reversed_pbp, global_size, size, pbp, reversed_pbp, global_size, pbp_mapped_int,
pbp_mapped_int, staging_pbp_buffer, staging_pbp_buffer, staging_pbp_buffer_mem,
staging_pbp_buffer_mem, staging_filter_buffer_mem, staging_filter_buffer_mem, staging_filter_buffer);
staging_filter_buffer, &changed_indices);
internal::write_filter(vulkan_buf_to_vec(filter_mapped_float, size), width, internal::write_filter(vulkan_buf_to_vec(filter_mapped_float, size), width,
"filter_mid.pgm"); "filter_mid.pgm");
image::Bl pbp_image = toBl(pbp, width); image::Bl pbp_image = toBl(pbp, width);
@ -614,21 +518,15 @@ std::vector<unsigned int> dither::internal::blue_noise_vulkan_impl(
#endif #endif
std::cout << "\nRanking last half of pixels...\n"; std::cout << "\nRanking last half of pixels...\n";
reversed_pbp = true; reversed_pbp = true;
bool first_reversed_run = true;
for (unsigned int i = (size + 1) / 2; i < (unsigned int)size; ++i) { for (unsigned int i = (size + 1) / 2; i < (unsigned int)size; ++i) {
#ifndef NDEBUG #ifndef NDEBUG
std::cout << i << ' '; std::cout << i << ' ';
#endif #endif
if (first_reversed_run) { vulkan_get_filter(device, command_buffer, command_pool, queue, pbp_buf,
changed_indices.clear(); pipeline, pipeline_layout, descriptor_set, filter_out_buf,
first_reversed_run = false; size, pbp, reversed_pbp, global_size, pbp_mapped_int,
} staging_pbp_buffer, staging_pbp_buffer_mem,
vulkan_get_filter(device, phys_atom_size, command_buffer, command_pool, staging_filter_buffer_mem, staging_filter_buffer);
queue, pbp_buf, pipeline, pipeline_layout, descriptor_set,
filter_out_buf, size, pbp, reversed_pbp, global_size,
pbp_mapped_int, staging_pbp_buffer,
staging_pbp_buffer_mem, staging_filter_buffer_mem,
staging_filter_buffer, &changed_indices);
auto vulkan_minmax_opt = vulkan_minmax( auto vulkan_minmax_opt = vulkan_minmax(
device, phys_device, command_buffer, command_pool, queue, device, phys_device, command_buffer, command_pool, queue,
minmax_pipeline, minmax_pipeline_layout, minmax_desc_sets, max_in_buf, minmax_pipeline, minmax_pipeline_layout, minmax_desc_sets, max_in_buf,
@ -642,7 +540,6 @@ std::vector<unsigned int> dither::internal::blue_noise_vulkan_impl(
std::tie(std::ignore, max) = vulkan_minmax_opt.value(); std::tie(std::ignore, max) = vulkan_minmax_opt.value();
pbp.at(max) = true; pbp.at(max) = true;
dither_array.at(max) = i; dither_array.at(max) = i;
changed_indices.push_back(max);
#ifndef NDEBUG #ifndef NDEBUG
if (set.find(max) != set.end()) { if (set.find(max) != set.end()) {
std::cout << "\nWARNING: Reusing index " << max << '\n'; std::cout << "\nWARNING: Reusing index " << max << '\n';
@ -655,12 +552,11 @@ std::vector<unsigned int> dither::internal::blue_noise_vulkan_impl(
#ifndef NDEBUG #ifndef NDEBUG
{ {
vulkan_get_filter(device, phys_atom_size, command_buffer, command_pool, vulkan_get_filter(device, command_buffer, command_pool, queue, pbp_buf,
queue, pbp_buf, pipeline, pipeline_layout, descriptor_set, pipeline, pipeline_layout, descriptor_set, filter_out_buf,
filter_out_buf, size, pbp, reversed_pbp, global_size, size, pbp, reversed_pbp, global_size, pbp_mapped_int,
pbp_mapped_int, staging_pbp_buffer, staging_pbp_buffer, staging_pbp_buffer_mem,
staging_pbp_buffer_mem, staging_filter_buffer_mem, staging_filter_buffer_mem, staging_filter_buffer);
staging_filter_buffer, nullptr);
internal::write_filter(vulkan_buf_to_vec(filter_mapped_float, size), width, internal::write_filter(vulkan_buf_to_vec(filter_mapped_float, size), width,
"filter_after.pgm"); "filter_after.pgm");
image::Bl pbp_image = toBl(pbp, width); image::Bl pbp_image = toBl(pbp, width);

View file

@ -21,7 +21,6 @@
#include <random> #include <random>
#include <stdexcept> #include <stdexcept>
#include <thread> #include <thread>
#include <tuple>
#include <unordered_set> #include <unordered_set>
#include <vector> #include <vector>
@ -67,15 +66,8 @@ void vulkan_copy_buffer(VkDevice device, VkCommandPool command_pool,
VkQueue queue, VkBuffer src_buf, VkBuffer dst_buf, VkQueue queue, VkBuffer src_buf, VkBuffer dst_buf,
VkDeviceSize size, VkDeviceSize src_offset = 0, VkDeviceSize size, VkDeviceSize src_offset = 0,
VkDeviceSize dst_offset = 0); VkDeviceSize dst_offset = 0);
void vulkan_copy_buffer_pieces(
VkDevice device, VkCommandPool command_pool, VkQueue queue,
VkBuffer src_buf, VkBuffer dst_buf,
const std::vector<std::tuple<VkDeviceSize, VkDeviceSize> > &pieces);
void vulkan_flush_buffer(VkDevice device, VkDeviceMemory memory); void vulkan_flush_buffer(VkDevice device, VkDeviceMemory memory);
void vulkan_flush_buffer_pieces(
VkDevice device, const VkDeviceSize phys_atom_size, VkDeviceMemory memory,
const std::vector<std::tuple<VkDeviceSize, VkDeviceSize> > &pieces);
void vulkan_invalidate_buffer(VkDevice device, VkDeviceMemory memory); void vulkan_invalidate_buffer(VkDevice device, VkDeviceMemory memory);
std::vector<unsigned int> blue_noise_vulkan_impl( std::vector<unsigned int> blue_noise_vulkan_impl(
@ -93,28 +85,15 @@ std::vector<unsigned int> blue_noise_vulkan_impl(
std::vector<float> vulkan_buf_to_vec(float *mapped, unsigned int size); std::vector<float> vulkan_buf_to_vec(float *mapped, unsigned int size);
inline bool vulkan_get_filter( inline bool vulkan_get_filter(
VkDevice device, const VkDeviceSize phys_atom_size, VkDevice device, VkCommandBuffer command_buffer, VkCommandPool command_pool,
VkCommandBuffer command_buffer, VkCommandPool command_pool, VkQueue queue, VkQueue queue, VkBuffer pbp_buf, VkPipeline pipeline,
VkBuffer pbp_buf, VkPipeline pipeline, VkPipelineLayout pipeline_layout, VkPipelineLayout pipeline_layout, VkDescriptorSet descriptor_set,
VkDescriptorSet descriptor_set, VkBuffer filter_out_buf, const int size, VkBuffer filter_out_buf, const int size, std::vector<bool> &pbp,
std::vector<bool> &pbp, bool reversed_pbp, const std::size_t global_size, bool reversed_pbp, const std::size_t global_size, int *pbp_mapped_int,
int *pbp_mapped_int, VkBuffer staging_pbp_buffer, VkBuffer staging_pbp_buffer, VkDeviceMemory staging_pbp_buffer_mem,
VkDeviceMemory staging_pbp_buffer_mem, VkDeviceMemory staging_filter_buffer_mem, VkBuffer staging_filter_buffer) {
VkDeviceMemory staging_filter_buffer_mem, VkBuffer staging_filter_buffer,
std::vector<std::size_t> *changed) {
vkResetCommandBuffer(command_buffer, 0); vkResetCommandBuffer(command_buffer, 0);
if (changed != nullptr && changed->size() > 0) {
if (reversed_pbp) {
for (auto idx : *changed) {
pbp_mapped_int[idx] = pbp[idx] ? 0 : 1;
}
} else {
for (auto idx : *changed) {
pbp_mapped_int[idx] = pbp[idx] ? 1 : 0;
}
}
} else {
if (reversed_pbp) { if (reversed_pbp) {
for (unsigned int i = 0; i < pbp.size(); ++i) { for (unsigned int i = 0; i < pbp.size(); ++i) {
pbp_mapped_int[i] = pbp[i] ? 0 : 1; pbp_mapped_int[i] = pbp[i] ? 0 : 1;
@ -124,26 +103,12 @@ inline bool vulkan_get_filter(
pbp_mapped_int[i] = pbp[i] ? 1 : 0; pbp_mapped_int[i] = pbp[i] ? 1 : 0;
} }
} }
}
vulkan_flush_buffer(device, staging_pbp_buffer_mem);
// Copy pbp buffer. // Copy pbp buffer.
if (changed != nullptr && changed->size() > 0) {
std::vector<std::tuple<VkDeviceSize, VkDeviceSize> > pieces;
for (auto idx : *changed) {
pieces.emplace_back(std::make_tuple(sizeof(int), idx * sizeof(int)));
}
vulkan_flush_buffer_pieces(device, phys_atom_size, staging_pbp_buffer_mem,
pieces);
vulkan_copy_buffer_pieces(device, command_pool, queue, staging_pbp_buffer,
pbp_buf, pieces);
changed->clear();
} else {
vulkan_flush_buffer(device, staging_pbp_buffer_mem);
vulkan_copy_buffer(device, command_pool, queue, staging_pbp_buffer, pbp_buf, vulkan_copy_buffer(device, command_pool, queue, staging_pbp_buffer, pbp_buf,
size * sizeof(int)); size * sizeof(int));
}
VkCommandBufferBeginInfo begin_info{}; VkCommandBufferBeginInfo begin_info{};
begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;