Home | History | Annotate | Download | only in smoke
      1 /*
      2  * Copyright (C) 2016 Google, Inc.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *     http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #include <array>
     18 
     19 #include <glm/gtc/type_ptr.hpp>
     20 #include <glm/gtc/matrix_transform.hpp>
     21 
     22 #include "Helpers.h"
     23 #include "Smoke.h"
     24 #include "Meshes.h"
     25 #include "Shell.h"
     26 
     27 namespace {
     28 
     29 // TODO do not rely on compiler to use std140 layout
     30 // TODO move lower frequency data to another descriptor set
     31 struct ShaderParamBlock {
     32     float light_pos[4];
     33     float light_color[4];
     34     float model[4 * 4];
     35     float view_projection[4 * 4];
     36 };
     37 
     38 }  // namespace
     39 
     40 Smoke::Smoke(const std::vector<std::string> &args)
     41     : Game("Smoke", args),
     42       multithread_(true),
     43       use_push_constants_(false),
     44       sim_paused_(false),
     45       sim_(5000),
     46       camera_(2.5f),
     47       frame_data_(),
     48       render_pass_clear_value_({{{0.0f, 0.1f, 0.2f, 1.0f}}}),
     49       render_pass_begin_info_(),
     50       primary_cmd_begin_info_(),
     51       primary_cmd_submit_info_() {
     52     for (auto it = args.begin(); it != args.end(); ++it) {
     53         if (*it == "-s")
     54             multithread_ = false;
     55         else if (*it == "-p")
     56             use_push_constants_ = true;
     57     }
     58 
     59     init_workers();
     60 }
     61 
     62 Smoke::~Smoke() {}
     63 
     64 void Smoke::init_workers() {
     65     int worker_count = std::thread::hardware_concurrency();
     66 
     67     // not enough cores
     68     if (!multithread_ || worker_count < 2) {
     69         multithread_ = false;
     70         worker_count = 1;
     71     }
     72 
     73     const int object_per_worker = static_cast<int>(sim_.objects().size()) / worker_count;
     74     int object_begin = 0, object_end = 0;
     75 
     76     workers_.reserve(worker_count);
     77     for (int i = 0; i < worker_count; i++) {
     78         object_begin = object_end;
     79         if (i < worker_count - 1)
     80             object_end += object_per_worker;
     81         else
     82             object_end = static_cast<int>(sim_.objects().size());
     83 
     84         Worker *worker = new Worker(*this, i, object_begin, object_end);
     85         workers_.emplace_back(std::unique_ptr<Worker>(worker));
     86     }
     87 }
     88 
     89 void Smoke::attach_shell(Shell &sh) {
     90     Game::attach_shell(sh);
     91 
     92     const Shell::Context &ctx = sh.context();
     93     physical_dev_ = ctx.physical_dev;
     94     dev_ = ctx.dev;
     95     queue_ = ctx.game_queue;
     96     queue_family_ = ctx.game_queue_family;
     97     format_ = ctx.format.format;
     98 
     99     vk::GetPhysicalDeviceProperties(physical_dev_, &physical_dev_props_);
    100 
    101     if (use_push_constants_ && sizeof(ShaderParamBlock) > physical_dev_props_.limits.maxPushConstantsSize) {
    102         shell_->log(Shell::LOG_WARN, "cannot enable push constants");
    103         use_push_constants_ = false;
    104     }
    105 
    106     VkPhysicalDeviceMemoryProperties mem_props;
    107     vk::GetPhysicalDeviceMemoryProperties(physical_dev_, &mem_props);
    108     mem_flags_.reserve(mem_props.memoryTypeCount);
    109     for (uint32_t i = 0; i < mem_props.memoryTypeCount; i++) mem_flags_.push_back(mem_props.memoryTypes[i].propertyFlags);
    110 
    111     meshes_ = new Meshes(dev_, mem_flags_);
    112 
    113     create_render_pass();
    114     create_shader_modules();
    115     create_descriptor_set_layout();
    116     create_pipeline_layout();
    117     create_pipeline();
    118 
    119     create_frame_data(2);
    120 
    121     render_pass_begin_info_.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
    122     render_pass_begin_info_.renderPass = render_pass_;
    123     render_pass_begin_info_.clearValueCount = 1;
    124     render_pass_begin_info_.pClearValues = &render_pass_clear_value_;
    125 
    126     primary_cmd_begin_info_.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
    127     primary_cmd_begin_info_.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
    128 
    129     // we will render to the swapchain images
    130     primary_cmd_submit_wait_stages_ = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
    131 
    132     primary_cmd_submit_info_.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
    133     primary_cmd_submit_info_.waitSemaphoreCount = 1;
    134     primary_cmd_submit_info_.pWaitDstStageMask = &primary_cmd_submit_wait_stages_;
    135     primary_cmd_submit_info_.commandBufferCount = 1;
    136     primary_cmd_submit_info_.signalSemaphoreCount = 1;
    137 
    138     if (multithread_) {
    139         for (auto &worker : workers_) worker->start();
    140     }
    141 }
    142 
    143 void Smoke::detach_shell() {
    144     if (multithread_) {
    145         for (auto &worker : workers_) worker->stop();
    146     }
    147 
    148     destroy_frame_data();
    149 
    150     vk::DestroyPipeline(dev_, pipeline_, nullptr);
    151     vk::DestroyPipelineLayout(dev_, pipeline_layout_, nullptr);
    152     if (!use_push_constants_) vk::DestroyDescriptorSetLayout(dev_, desc_set_layout_, nullptr);
    153     vk::DestroyShaderModule(dev_, fs_, nullptr);
    154     vk::DestroyShaderModule(dev_, vs_, nullptr);
    155     vk::DestroyRenderPass(dev_, render_pass_, nullptr);
    156 
    157     delete meshes_;
    158 
    159     Game::detach_shell();
    160 }
    161 
    162 void Smoke::create_render_pass() {
    163     VkAttachmentDescription attachment = {};
    164     attachment.format = format_;
    165     attachment.samples = VK_SAMPLE_COUNT_1_BIT;
    166     attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
    167     attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
    168     attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
    169     attachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
    170 
    171     VkAttachmentReference attachment_ref = {};
    172     attachment_ref.attachment = 0;
    173     attachment_ref.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
    174 
    175     VkSubpassDescription subpass = {};
    176     subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
    177     subpass.colorAttachmentCount = 1;
    178     subpass.pColorAttachments = &attachment_ref;
    179 
    180     std::array<VkSubpassDependency, 2> subpass_deps;
    181     subpass_deps[0].srcSubpass = VK_SUBPASS_EXTERNAL;
    182     subpass_deps[0].dstSubpass = 0;
    183     subpass_deps[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
    184     subpass_deps[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
    185     subpass_deps[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
    186     subpass_deps[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
    187     subpass_deps[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
    188 
    189     subpass_deps[1].srcSubpass = 0;
    190     subpass_deps[1].dstSubpass = VK_SUBPASS_EXTERNAL;
    191     subpass_deps[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
    192     subpass_deps[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
    193     subpass_deps[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
    194     subpass_deps[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
    195     subpass_deps[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
    196 
    197     VkRenderPassCreateInfo render_pass_info = {};
    198     render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
    199     render_pass_info.attachmentCount = 1;
    200     render_pass_info.pAttachments = &attachment;
    201     render_pass_info.subpassCount = 1;
    202     render_pass_info.pSubpasses = &subpass;
    203     render_pass_info.dependencyCount = (uint32_t)subpass_deps.size();
    204     render_pass_info.pDependencies = subpass_deps.data();
    205 
    206     vk::assert_success(vk::CreateRenderPass(dev_, &render_pass_info, nullptr, &render_pass_));
    207 }
    208 
    209 void Smoke::create_shader_modules() {
    210     VkShaderModuleCreateInfo sh_info = {};
    211     sh_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
    212     if (use_push_constants_) {
    213 #include "Smoke.push_constant.vert.h"
    214         sh_info.codeSize = sizeof(Smoke_push_constant_vert);
    215         sh_info.pCode = Smoke_push_constant_vert;
    216     } else {
    217 #include "Smoke.vert.h"
    218         sh_info.codeSize = sizeof(Smoke_vert);
    219         sh_info.pCode = Smoke_vert;
    220     }
    221     vk::assert_success(vk::CreateShaderModule(dev_, &sh_info, nullptr, &vs_));
    222 
    223 #include "Smoke.frag.h"
    224     sh_info.codeSize = sizeof(Smoke_frag);
    225     sh_info.pCode = Smoke_frag;
    226     vk::assert_success(vk::CreateShaderModule(dev_, &sh_info, nullptr, &fs_));
    227 }
    228 
    229 void Smoke::create_descriptor_set_layout() {
    230     if (use_push_constants_) return;
    231 
    232     VkDescriptorSetLayoutBinding layout_binding = {};
    233     layout_binding.binding = 0;
    234     layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC;
    235     layout_binding.descriptorCount = 1;
    236     layout_binding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
    237 
    238     VkDescriptorSetLayoutCreateInfo layout_info = {};
    239     layout_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
    240     layout_info.bindingCount = 1;
    241     layout_info.pBindings = &layout_binding;
    242 
    243     vk::assert_success(vk::CreateDescriptorSetLayout(dev_, &layout_info, nullptr, &desc_set_layout_));
    244 }
    245 
    246 void Smoke::create_pipeline_layout() {
    247     VkPushConstantRange push_const_range = {};
    248 
    249     VkPipelineLayoutCreateInfo pipeline_layout_info = {};
    250     pipeline_layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
    251 
    252     if (use_push_constants_) {
    253         push_const_range.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
    254         push_const_range.offset = 0;
    255         push_const_range.size = sizeof(ShaderParamBlock);
    256 
    257         pipeline_layout_info.pushConstantRangeCount = 1;
    258         pipeline_layout_info.pPushConstantRanges = &push_const_range;
    259     } else {
    260         pipeline_layout_info.setLayoutCount = 1;
    261         pipeline_layout_info.pSetLayouts = &desc_set_layout_;
    262     }
    263 
    264     vk::assert_success(vk::CreatePipelineLayout(dev_, &pipeline_layout_info, nullptr, &pipeline_layout_));
    265 }
    266 
    267 void Smoke::create_pipeline() {
    268     VkPipelineShaderStageCreateInfo stage_info[2] = {};
    269     stage_info[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
    270     stage_info[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
    271     stage_info[0].module = vs_;
    272     stage_info[0].pName = "main";
    273     stage_info[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
    274     stage_info[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
    275     stage_info[1].module = fs_;
    276     stage_info[1].pName = "main";
    277 
    278     VkPipelineViewportStateCreateInfo viewport_info = {};
    279     viewport_info.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
    280     // both dynamic
    281     viewport_info.viewportCount = 1;
    282     viewport_info.scissorCount = 1;
    283 
    284     VkPipelineRasterizationStateCreateInfo rast_info = {};
    285     rast_info.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
    286     rast_info.depthClampEnable = false;
    287     rast_info.rasterizerDiscardEnable = false;
    288     rast_info.polygonMode = VK_POLYGON_MODE_FILL;
    289     rast_info.cullMode = VK_CULL_MODE_NONE;
    290     rast_info.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
    291     rast_info.depthBiasEnable = false;
    292     rast_info.lineWidth = 1.0f;
    293 
    294     VkPipelineMultisampleStateCreateInfo multisample_info = {};
    295     multisample_info.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
    296     multisample_info.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
    297     multisample_info.sampleShadingEnable = false;
    298     multisample_info.pSampleMask = nullptr;
    299     multisample_info.alphaToCoverageEnable = false;
    300     multisample_info.alphaToOneEnable = false;
    301 
    302     VkPipelineColorBlendAttachmentState blend_attachment = {};
    303     blend_attachment.blendEnable = true;
    304     blend_attachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
    305     blend_attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
    306     blend_attachment.colorBlendOp = VK_BLEND_OP_ADD;
    307     blend_attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
    308     blend_attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
    309     blend_attachment.alphaBlendOp = VK_BLEND_OP_ADD;
    310     blend_attachment.colorWriteMask =
    311         VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
    312 
    313     VkPipelineColorBlendStateCreateInfo blend_info = {};
    314     blend_info.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
    315     blend_info.logicOpEnable = false;
    316     blend_info.attachmentCount = 1;
    317     blend_info.pAttachments = &blend_attachment;
    318 
    319     std::array<VkDynamicState, 2> dynamic_states = {{VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}};
    320     struct VkPipelineDynamicStateCreateInfo dynamic_info = {};
    321     dynamic_info.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
    322     dynamic_info.dynamicStateCount = (uint32_t)dynamic_states.size();
    323     dynamic_info.pDynamicStates = dynamic_states.data();
    324 
    325     VkGraphicsPipelineCreateInfo pipeline_info = {};
    326     pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
    327     pipeline_info.stageCount = 2;
    328     pipeline_info.pStages = stage_info;
    329     pipeline_info.pVertexInputState = &meshes_->vertex_input_state();
    330     pipeline_info.pInputAssemblyState = &meshes_->input_assembly_state();
    331     pipeline_info.pTessellationState = nullptr;
    332     pipeline_info.pViewportState = &viewport_info;
    333     pipeline_info.pRasterizationState = &rast_info;
    334     pipeline_info.pMultisampleState = &multisample_info;
    335     pipeline_info.pDepthStencilState = nullptr;
    336     pipeline_info.pColorBlendState = &blend_info;
    337     pipeline_info.pDynamicState = &dynamic_info;
    338     pipeline_info.layout = pipeline_layout_;
    339     pipeline_info.renderPass = render_pass_;
    340     pipeline_info.subpass = 0;
    341     vk::assert_success(vk::CreateGraphicsPipelines(dev_, VK_NULL_HANDLE, 1, &pipeline_info, nullptr, &pipeline_));
    342 }
    343 
    344 void Smoke::create_frame_data(int count) {
    345     frame_data_.resize(count);
    346 
    347     create_fences();
    348     create_command_buffers();
    349 
    350     if (!use_push_constants_) {
    351         create_buffers();
    352         create_buffer_memory();
    353         create_descriptor_sets();
    354     }
    355 
    356     frame_data_index_ = 0;
    357 }
    358 
    359 void Smoke::destroy_frame_data() {
    360     if (!use_push_constants_) {
    361         vk::DestroyDescriptorPool(dev_, desc_pool_, nullptr);
    362 
    363         vk::UnmapMemory(dev_, frame_data_mem_);
    364         vk::FreeMemory(dev_, frame_data_mem_, nullptr);
    365 
    366         for (auto &data : frame_data_) vk::DestroyBuffer(dev_, data.buf, nullptr);
    367     }
    368 
    369     for (auto cmd_pool : worker_cmd_pools_) vk::DestroyCommandPool(dev_, cmd_pool, nullptr);
    370     worker_cmd_pools_.clear();
    371     vk::DestroyCommandPool(dev_, primary_cmd_pool_, nullptr);
    372 
    373     for (auto &data : frame_data_) vk::DestroyFence(dev_, data.fence, nullptr);
    374 
    375     frame_data_.clear();
    376 }
    377 
    378 void Smoke::create_fences() {
    379     VkFenceCreateInfo fence_info = {};
    380     fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
    381     fence_info.flags = VK_FENCE_CREATE_SIGNALED_BIT;
    382 
    383     for (auto &data : frame_data_) vk::assert_success(vk::CreateFence(dev_, &fence_info, nullptr, &data.fence));
    384 }
    385 
    386 void Smoke::create_command_buffers() {
    387     VkCommandPoolCreateInfo cmd_pool_info = {};
    388     cmd_pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
    389     cmd_pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
    390     cmd_pool_info.queueFamilyIndex = queue_family_;
    391 
    392     VkCommandBufferAllocateInfo cmd_info = {};
    393     cmd_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
    394     cmd_info.commandBufferCount = static_cast<uint32_t>(frame_data_.size());
    395 
    396     // create command pools and buffers
    397     std::vector<VkCommandPool> cmd_pools(workers_.size() + 1, VK_NULL_HANDLE);
    398     std::vector<std::vector<VkCommandBuffer>> cmds_vec(workers_.size() + 1,
    399                                                        std::vector<VkCommandBuffer>(frame_data_.size(), VK_NULL_HANDLE));
    400     for (size_t i = 0; i < cmd_pools.size(); i++) {
    401         auto &cmd_pool = cmd_pools[i];
    402         auto &cmds = cmds_vec[i];
    403 
    404         vk::assert_success(vk::CreateCommandPool(dev_, &cmd_pool_info, nullptr, &cmd_pool));
    405 
    406         cmd_info.commandPool = cmd_pool;
    407         cmd_info.level = (cmd_pool == cmd_pools.back()) ? VK_COMMAND_BUFFER_LEVEL_PRIMARY : VK_COMMAND_BUFFER_LEVEL_SECONDARY;
    408 
    409         vk::assert_success(vk::AllocateCommandBuffers(dev_, &cmd_info, cmds.data()));
    410     }
    411 
    412     // update frame_data_
    413     for (size_t i = 0; i < frame_data_.size(); i++) {
    414         for (const auto &cmds : cmds_vec) {
    415             if (cmds == cmds_vec.back()) {
    416                 frame_data_[i].primary_cmd = cmds[i];
    417             } else {
    418                 frame_data_[i].worker_cmds.push_back(cmds[i]);
    419             }
    420         }
    421     }
    422 
    423     primary_cmd_pool_ = cmd_pools.back();
    424     cmd_pools.pop_back();
    425     worker_cmd_pools_ = cmd_pools;
    426 }
    427 
    428 void Smoke::create_buffers() {
    429     VkDeviceSize object_data_size = sizeof(ShaderParamBlock);
    430     // align object data to device limit
    431     const VkDeviceSize &alignment = physical_dev_props_.limits.minStorageBufferOffsetAlignment;
    432     if (object_data_size % alignment) object_data_size += alignment - (object_data_size % alignment);
    433 
    434     // update simulation
    435     sim_.set_frame_data_size(static_cast<uint32_t>(object_data_size));
    436 
    437     VkBufferCreateInfo buf_info = {};
    438     buf_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
    439     buf_info.size = object_data_size * sim_.objects().size();
    440     buf_info.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
    441     buf_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
    442 
    443     for (auto &data : frame_data_) vk::assert_success(vk::CreateBuffer(dev_, &buf_info, nullptr, &data.buf));
    444 }
    445 
    446 void Smoke::create_buffer_memory() {
    447     VkMemoryRequirements mem_reqs;
    448     vk::GetBufferMemoryRequirements(dev_, frame_data_[0].buf, &mem_reqs);
    449 
    450     frame_data_aligned_size_ = mem_reqs.size;
    451     if (frame_data_aligned_size_ % mem_reqs.alignment)
    452         frame_data_aligned_size_ += mem_reqs.alignment - (frame_data_aligned_size_ % mem_reqs.alignment);
    453 
    454     // allocate memory
    455     VkMemoryAllocateInfo mem_info = {};
    456     mem_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
    457     mem_info.allocationSize = frame_data_aligned_size_ * (frame_data_.size() - 1) + mem_reqs.size;
    458 
    459     for (uint32_t idx = 0; idx < mem_flags_.size(); idx++) {
    460         if ((mem_reqs.memoryTypeBits & (1 << idx)) && (mem_flags_[idx] & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) &&
    461             (mem_flags_[idx] & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) {
    462             // TODO is this guaranteed to exist?
    463             mem_info.memoryTypeIndex = idx;
    464             break;
    465         }
    466     }
    467 
    468     vk::AllocateMemory(dev_, &mem_info, nullptr, &frame_data_mem_);
    469 
    470     void *ptr;
    471     vk::MapMemory(dev_, frame_data_mem_, 0, VK_WHOLE_SIZE, 0, &ptr);
    472 
    473     VkDeviceSize offset = 0;
    474     for (auto &data : frame_data_) {
    475         vk::BindBufferMemory(dev_, data.buf, frame_data_mem_, offset);
    476         data.base = reinterpret_cast<uint8_t *>(ptr) + offset;
    477         offset += frame_data_aligned_size_;
    478     }
    479 }
    480 
    481 void Smoke::create_descriptor_sets() {
    482     VkDescriptorPoolSize desc_pool_size = {};
    483     desc_pool_size.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC;
    484     desc_pool_size.descriptorCount = static_cast<uint32_t>(frame_data_.size());
    485 
    486     VkDescriptorPoolCreateInfo desc_pool_info = {};
    487     desc_pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
    488     desc_pool_info.maxSets = static_cast<uint32_t>(frame_data_.size());
    489     desc_pool_info.poolSizeCount = 1;
    490     desc_pool_info.pPoolSizes = &desc_pool_size;
    491 
    492     // create descriptor pool
    493     vk::assert_success(vk::CreateDescriptorPool(dev_, &desc_pool_info, nullptr, &desc_pool_));
    494 
    495     std::vector<VkDescriptorSetLayout> set_layouts(frame_data_.size(), desc_set_layout_);
    496     VkDescriptorSetAllocateInfo set_info = {};
    497     set_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
    498     set_info.descriptorPool = desc_pool_;
    499     set_info.descriptorSetCount = static_cast<uint32_t>(set_layouts.size());
    500     set_info.pSetLayouts = set_layouts.data();
    501 
    502     // create descriptor sets
    503     std::vector<VkDescriptorSet> desc_sets(frame_data_.size(), VK_NULL_HANDLE);
    504     vk::assert_success(vk::AllocateDescriptorSets(dev_, &set_info, desc_sets.data()));
    505 
    506     std::vector<VkDescriptorBufferInfo> desc_bufs(frame_data_.size());
    507     std::vector<VkWriteDescriptorSet> desc_writes(frame_data_.size());
    508 
    509     for (size_t i = 0; i < frame_data_.size(); i++) {
    510         auto &data = frame_data_[i];
    511 
    512         data.desc_set = desc_sets[i];
    513 
    514         VkDescriptorBufferInfo desc_buf = {};
    515         desc_buf.buffer = data.buf;
    516         desc_buf.offset = 0;
    517         desc_buf.range = VK_WHOLE_SIZE;
    518         desc_bufs[i] = desc_buf;
    519 
    520         VkWriteDescriptorSet desc_write = {};
    521         desc_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
    522         desc_write.dstSet = data.desc_set;
    523         desc_write.dstBinding = 0;
    524         desc_write.dstArrayElement = 0;
    525         desc_write.descriptorCount = 1;
    526         desc_write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC;
    527         desc_write.pBufferInfo = &desc_bufs[i];
    528         desc_writes[i] = desc_write;
    529     }
    530 
    531     vk::UpdateDescriptorSets(dev_, static_cast<uint32_t>(desc_writes.size()), desc_writes.data(), 0, nullptr);
    532 }
    533 
    534 void Smoke::attach_swapchain() {
    535     const Shell::Context &ctx = shell_->context();
    536 
    537     prepare_viewport(ctx.extent);
    538     prepare_framebuffers(ctx.swapchain);
    539 
    540     update_camera();
    541 }
    542 
    543 void Smoke::detach_swapchain() {
    544     for (auto fb : framebuffers_) vk::DestroyFramebuffer(dev_, fb, nullptr);
    545     for (auto view : image_views_) vk::DestroyImageView(dev_, view, nullptr);
    546 
    547     framebuffers_.clear();
    548     image_views_.clear();
    549     images_.clear();
    550 }
    551 
    552 void Smoke::prepare_viewport(const VkExtent2D &extent) {
    553     extent_ = extent;
    554 
    555     viewport_.x = 0.0f;
    556     viewport_.y = 0.0f;
    557     viewport_.width = static_cast<float>(extent.width);
    558     viewport_.height = static_cast<float>(extent.height);
    559     viewport_.minDepth = 0.0f;
    560     viewport_.maxDepth = 1.0f;
    561 
    562     scissor_.offset = {0, 0};
    563     scissor_.extent = extent_;
    564 }
    565 
    566 void Smoke::prepare_framebuffers(VkSwapchainKHR swapchain) {
    567     // get swapchain images
    568     vk::get(dev_, swapchain, images_);
    569 
    570     assert(framebuffers_.empty());
    571     image_views_.reserve(images_.size());
    572     framebuffers_.reserve(images_.size());
    573     for (auto img : images_) {
    574         VkImageViewCreateInfo view_info = {};
    575         view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
    576         view_info.image = img;
    577         view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
    578         view_info.format = format_;
    579         view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    580         view_info.subresourceRange.levelCount = 1;
    581         view_info.subresourceRange.layerCount = 1;
    582 
    583         VkImageView view;
    584         vk::assert_success(vk::CreateImageView(dev_, &view_info, nullptr, &view));
    585         image_views_.push_back(view);
    586 
    587         VkFramebufferCreateInfo fb_info = {};
    588         fb_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
    589         fb_info.renderPass = render_pass_;
    590         fb_info.attachmentCount = 1;
    591         fb_info.pAttachments = &view;
    592         fb_info.width = extent_.width;
    593         fb_info.height = extent_.height;
    594         fb_info.layers = 1;
    595 
    596         VkFramebuffer fb;
    597         vk::assert_success(vk::CreateFramebuffer(dev_, &fb_info, nullptr, &fb));
    598         framebuffers_.push_back(fb);
    599     }
    600 }
    601 
    602 void Smoke::update_camera() {
    603     const glm::vec3 center(0.0f);
    604     const glm::vec3 up(0.f, 0.0f, 1.0f);
    605     const glm::mat4 view = glm::lookAt(camera_.eye_pos, center, up);
    606 
    607     float aspect = static_cast<float>(extent_.width) / static_cast<float>(extent_.height);
    608     const glm::mat4 projection = glm::perspective(0.4f, aspect, 0.1f, 100.0f);
    609 
    610     // Vulkan clip space has inverted Y and half Z.
    611     const glm::mat4 clip(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 0.5f, 1.0f);
    612 
    613     camera_.view_projection = clip * projection * view;
    614 }
    615 
    616 void Smoke::draw_object(const Simulation::Object &obj, FrameData &data, VkCommandBuffer cmd) const {
    617     if (use_push_constants_) {
    618         ShaderParamBlock params;
    619         memcpy(params.light_pos, glm::value_ptr(obj.light_pos), sizeof(obj.light_pos));
    620         memcpy(params.light_color, glm::value_ptr(obj.light_color), sizeof(obj.light_color));
    621         memcpy(params.model, glm::value_ptr(obj.model), sizeof(obj.model));
    622         memcpy(params.view_projection, glm::value_ptr(camera_.view_projection), sizeof(camera_.view_projection));
    623 
    624         vk::CmdPushConstants(cmd, pipeline_layout_, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(params), &params);
    625     } else {
    626         ShaderParamBlock *params = reinterpret_cast<ShaderParamBlock *>(data.base + obj.frame_data_offset);
    627         memcpy(params->light_pos, glm::value_ptr(obj.light_pos), sizeof(obj.light_pos));
    628         memcpy(params->light_color, glm::value_ptr(obj.light_color), sizeof(obj.light_color));
    629         memcpy(params->model, glm::value_ptr(obj.model), sizeof(obj.model));
    630         memcpy(params->view_projection, glm::value_ptr(camera_.view_projection), sizeof(camera_.view_projection));
    631 
    632         vk::CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout_, 0, 1, &data.desc_set, 1,
    633                                   &obj.frame_data_offset);
    634     }
    635 
    636     meshes_->cmd_draw(cmd, obj.mesh);
    637 }
    638 
    639 void Smoke::update_simulation(const Worker &worker) {
    640     sim_.update(worker.tick_interval_, worker.object_begin_, worker.object_end_);
    641 }
    642 
    643 void Smoke::draw_objects(Worker &worker) {
    644     auto &data = frame_data_[frame_data_index_];
    645     auto cmd = data.worker_cmds[worker.index_];
    646 
    647     VkCommandBufferInheritanceInfo inherit_info = {};
    648     inherit_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO;
    649     inherit_info.renderPass = render_pass_;
    650     inherit_info.framebuffer = worker.fb_;
    651 
    652     VkCommandBufferBeginInfo begin_info = {};
    653     begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
    654     begin_info.flags = VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT;
    655     begin_info.pInheritanceInfo = &inherit_info;
    656 
    657     vk::BeginCommandBuffer(cmd, &begin_info);
    658 
    659     vk::CmdSetViewport(cmd, 0, 1, &viewport_);
    660     vk::CmdSetScissor(cmd, 0, 1, &scissor_);
    661 
    662     vk::CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_);
    663 
    664     meshes_->cmd_bind_buffers(cmd);
    665 
    666     for (int i = worker.object_begin_; i < worker.object_end_; i++) {
    667         auto &obj = sim_.objects()[i];
    668 
    669         draw_object(obj, data, cmd);
    670     }
    671 
    672     vk::EndCommandBuffer(cmd);
    673 }
    674 
    675 void Smoke::on_key(Key key) {
    676     switch (key) {
    677         case KEY_SHUTDOWN:
    678         case KEY_ESC:
    679             quit();
    680             break;
    681         case KEY_UP:
    682             camera_.eye_pos -= glm::vec3(0.05f);
    683             update_camera();
    684             break;
    685         case KEY_DOWN:
    686             camera_.eye_pos += glm::vec3(0.05f);
    687             update_camera();
    688             break;
    689         case KEY_SPACE:
    690             sim_paused_ = !sim_paused_;
    691             break;
    692         default:
    693             break;
    694     }
    695 }
    696 
    697 void Smoke::on_tick() {
    698     if (sim_paused_) return;
    699 
    700     for (auto &worker : workers_) worker->update_simulation();
    701 }
    702 
    703 void Smoke::on_frame(float frame_pred) {
    704     frame_count++;
    705 
    706     // Limit number of frames if argument was specified
    707     if (settings_.max_frame_count != -1 && frame_count == settings_.max_frame_count) {
    708         // Tell the Game we're done after this frame is drawn.
    709         Game::quit();
    710     }
    711 
    712     auto &data = frame_data_[frame_data_index_];
    713 
    714     // wait for the last submission since we reuse frame data
    715     vk::assert_success(vk::WaitForFences(dev_, 1, &data.fence, true, UINT64_MAX));
    716     vk::assert_success(vk::ResetFences(dev_, 1, &data.fence));
    717 
    718     const Shell::BackBuffer &back = shell_->context().acquired_back_buffer;
    719 
    720     // ignore frame_pred
    721     for (auto &worker : workers_) worker->draw_objects(framebuffers_[back.image_index]);
    722 
    723     VkResult res = vk::BeginCommandBuffer(data.primary_cmd, &primary_cmd_begin_info_);
    724 
    725     if (!use_push_constants_) {
    726         VkBufferMemoryBarrier buf_barrier = {};
    727         buf_barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
    728         buf_barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT;
    729         buf_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
    730         buf_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    731         buf_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    732         buf_barrier.buffer = data.buf;
    733         buf_barrier.offset = 0;
    734         buf_barrier.size = VK_WHOLE_SIZE;
    735         vk::CmdPipelineBarrier(data.primary_cmd, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_VERTEX_SHADER_BIT, 0, 0, nullptr, 1,
    736                                &buf_barrier, 0, nullptr);
    737     }
    738 
    739     render_pass_begin_info_.framebuffer = framebuffers_[back.image_index];
    740     render_pass_begin_info_.renderArea.extent = extent_;
    741     vk::CmdBeginRenderPass(data.primary_cmd, &render_pass_begin_info_, VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS);
    742 
    743     // record render pass commands
    744     for (auto &worker : workers_) worker->wait_idle();
    745 
    746     // Flush buffers if enabled
    747     if (settings_.flush_buffers) {
    748         VkMappedMemoryRange range = {};
    749         range.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
    750         range.pNext = nullptr;
    751         range.memory = frame_data_mem_;
    752         range.offset = frame_data_index_ * frame_data_aligned_size_;
    753         range.size = frame_data_aligned_size_;
    754 
    755         vk::FlushMappedMemoryRanges(dev_, 1, &range);
    756     }
    757 
    758     vk::CmdExecuteCommands(data.primary_cmd, static_cast<uint32_t>(data.worker_cmds.size()), data.worker_cmds.data());
    759 
    760     vk::CmdEndRenderPass(data.primary_cmd);
    761     vk::EndCommandBuffer(data.primary_cmd);
    762 
    763     // wait for the image to be owned and signal for render completion
    764     primary_cmd_submit_info_.pWaitSemaphores = &back.acquire_semaphore;
    765     primary_cmd_submit_info_.pCommandBuffers = &data.primary_cmd;
    766     primary_cmd_submit_info_.pSignalSemaphores = &back.render_semaphore;
    767 
    768     res = vk::QueueSubmit(queue_, 1, &primary_cmd_submit_info_, data.fence);
    769 
    770     frame_data_index_ = (frame_data_index_ + 1) % frame_data_.size();
    771 
    772     (void)res;
    773 }
    774 
    775 Smoke::Worker::Worker(Smoke &smoke, int index, int object_begin, int object_end)
    776     : smoke_(smoke),
    777       index_(index),
    778       object_begin_(object_begin),
    779       object_end_(object_end),
    780       tick_interval_(1.0f / smoke.settings_.ticks_per_second),
    781       state_(INIT) {}
    782 
    783 void Smoke::Worker::start() {
    784     state_ = IDLE;
    785     thread_ = std::thread(Smoke::Worker::thread_loop, this);
    786 }
    787 
    788 void Smoke::Worker::stop() {
    789     {
    790         std::lock_guard<std::mutex> lock(mutex_);
    791         state_ = INIT;
    792     }
    793     state_cv_.notify_one();
    794 
    795     thread_.join();
    796 }
    797 
    798 void Smoke::Worker::update_simulation() {
    799     {
    800         std::lock_guard<std::mutex> lock(mutex_);
    801         bool started = (state_ != INIT);
    802 
    803         state_ = STEP;
    804 
    805         // step directly
    806         if (!started) {
    807             smoke_.update_simulation(*this);
    808             state_ = INIT;
    809         }
    810     }
    811     state_cv_.notify_one();
    812 }
    813 
    814 void Smoke::Worker::draw_objects(VkFramebuffer fb) {
    815     // wait for step_objects first
    816     wait_idle();
    817 
    818     {
    819         std::lock_guard<std::mutex> lock(mutex_);
    820         bool started = (state_ != INIT);
    821 
    822         fb_ = fb;
    823         state_ = DRAW;
    824 
    825         // render directly
    826         if (!started) {
    827             smoke_.draw_objects(*this);
    828             state_ = INIT;
    829         }
    830     }
    831     state_cv_.notify_one();
    832 }
    833 
    834 void Smoke::Worker::wait_idle() {
    835     std::unique_lock<std::mutex> lock(mutex_);
    836     bool started = (state_ != INIT);
    837 
    838     if (started) state_cv_.wait(lock, [this] { return (state_ == IDLE); });
    839 }
    840 
    841 void Smoke::Worker::update_loop() {
    842     while (true) {
    843         std::unique_lock<std::mutex> lock(mutex_);
    844 
    845         state_cv_.wait(lock, [this] { return (state_ != IDLE); });
    846         if (state_ == INIT) break;
    847 
    848         assert(state_ == STEP || state_ == DRAW);
    849         if (state_ == STEP)
    850             smoke_.update_simulation(*this);
    851         else
    852             smoke_.draw_objects(*this);
    853 
    854         state_ = IDLE;
    855         lock.unlock();
    856         state_cv_.notify_one();
    857     }
    858 }
    859