17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-26 19:24:54 +01:00
Files
maszyna/rendering/vulkan14/vulkan14particles.cpp

343 lines
13 KiB
C++

/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14particles.h"
#include "rendering/vulkan14/vulkan14commands.h"
#include "rendering/vulkan14/vulkan14context.h"
#include "utilities/Logs.h"
#include <cstring>
namespace vk14
{
namespace
{
// the push constants of particles_smoke_simulate.comp (std430)
struct simulate_constants
{
std::uint32_t places;
std::uint32_t pad0[3];
glm::vec4 step;
glm::vec4 wind;
std::uint32_t seed;
std::uint32_t pad1[3];
};
static_assert(offsetof(simulate_constants, step) == 16 && offsetof(simulate_constants, seed) == 48, "the layout of the constants of particles_smoke_simulate.comp");
// the push constants of particles_smoke_prepare.comp (std430)
struct prepare_constants
{
std::uint32_t places;
std::uint32_t pad0[3];
glm::vec3 origin;
float pad1;
glm::mat4 view;
glm::vec4 options;
glm::vec4 planes[5];
};
static_assert(offsetof(prepare_constants, origin) == 16 && offsetof(prepare_constants, view) == 32 && offsetof(prepare_constants, options) == 96 && offsetof(prepare_constants, planes) == 112,
"the layout of the constants of particles_smoke_prepare.comp");
// the block of particles_precipitation.comp (std140)
struct precipitation_block
{
glm::uvec4 counts;
glm::vec4 offsets[3];
glm::vec4 sizes;
glm::vec4 velocity;
glm::vec4 timing;
glm::mat4 view;
glm::vec4 options;
glm::vec4 planes[5];
};
static_assert(offsetof(precipitation_block, view) == 112 && offsetof(precipitation_block, planes) == 192 && sizeof(precipitation_block) == 272, "the layout of the block of particles_precipitation.comp");
std::uint32_t groups(std::uint32_t const Count)
{
return (Count + gl::PARTICLE_GROUP_SIZE - 1) / gl::PARTICLE_GROUP_SIZE;
}
VkBufferUsageFlags constexpr storage_usage{VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT};
} // namespace
gpu_particles::~gpu_particles()
{
destroy();
}
void gpu_particles::init(VkDevice const Device, pipeline_library &Pipelines, shader_library &Shaders, VkDescriptorSetLayout const Passlayout, allocator &Memory, uploader &Uploads, deletion_queue &Deletions,
frame_ring &Ring, VkDeviceSize const Alignment, std::uint32_t const Frames, host Host)
{
m_device = Device;
m_pipelines = &Pipelines;
m_shaders = &Shaders;
m_passlayout = Passlayout;
m_memory = &Memory;
m_uploads = &Uploads;
m_deletions = &Deletions;
m_ring = &Ring;
m_alignment = std::max<VkDeviceSize>(Alignment, 16);
m_frames = Frames;
m_host = std::move(Host);
}
void gpu_particles::destroy()
{
if (m_device == VK_NULL_HANDLE)
{
return;
}
// the device is idle: everything goes at once
m_deletions = nullptr;
m_smokesimulate.destroy(m_device);
m_smokeprepare.destroy(m_device);
m_precipitationprepare.destroy(m_device);
m_smokedraw.reset();
m_precipitationdraw.reset();
for (auto *buffer : {&m_particles, &m_sources, &m_owners, &m_smokeoutput, &m_smokecommand, &m_precipitationoutput, &m_precipitationcommand})
{
buffer->reset();
}
m_smokereadback.destroy();
m_precipitationreadback.destroy();
m_device = VK_NULL_HANDLE;
}
std::shared_ptr<buffer> gpu_particles::make_buffer(VkDeviceSize const Size, VkBufferUsageFlags const Usage, char const *Name)
{
auto result{std::make_shared<buffer>()};
if (false == result->create(*m_memory, std::max<VkDeviceSize>(Size, 16), Usage, memory_kind::device, Name))
{
ErrorLog(std::string("particles: no buffer for the ") + Name);
return nullptr;
}
return result;
}
void gpu_particles::let_go(std::shared_ptr<buffer> &Buffer)
{
if ((Buffer != nullptr) && (m_deletions != nullptr))
{
// the frames in flight may still read it
m_deletions->push(m_uploads->current_value(), [old = std::move(Buffer)]() {});
}
Buffer.reset();
}
bool gpu_particles::gpu_init()
{
if ((m_device == VK_NULL_HANDLE) || (false == bool(m_host.compute)) || (false == bool(m_host.draw)))
{
return false;
}
try
{
auto kernel = [&](compute_kernel &Kernel, char const *Shader, char const *Name) {
if (false == Kernel.create(m_device, *m_pipelines, m_shaders->get(Shader), m_passlayout, {}, Name))
{
throw gfx::shader_exception(std::string("no compute pipeline of ") + Name);
}
};
kernel(m_smokesimulate, "particles_smoke_simulate.comp", "smoke simulation");
kernel(m_smokeprepare, "particles_smoke_prepare.comp", "smoke");
kernel(m_precipitationprepare, "particles_precipitation.comp", "precipitation");
auto program = [&](char const *Vertex, char const *Fragment) {
auto result{std::make_shared<scene_program>()};
result->vertex = m_shaders->get(Vertex);
result->fragment = m_shaders->get(Fragment);
result->fragment_name = Fragment;
result->vertex_name = Vertex;
result->vertices = vertex_kind::particle_instance;
result->name = std::string(Vertex) + " + " + Fragment;
return result;
};
m_smokedraw = program("particles_smoke.vert", "particles_smoke.frag");
m_precipitationdraw = program("particles_precipitation.vert", "particles_precipitation.frag");
}
catch (gfx::shader_exception const &e)
{
ErrorLog("particles: can't use the programs of the particles of the gpu, the smoke stays on the cpu: " + std::string(e.what()));
return false;
}
auto const commandusage{VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT};
m_smokecommand = make_buffer(4 * sizeof(std::uint32_t), commandusage, "command of the smoke");
m_precipitationcommand = make_buffer(4 * sizeof(std::uint32_t), commandusage, "command of the precipitation");
if ((m_smokecommand == nullptr) || (m_precipitationcommand == nullptr) || (false == m_smokereadback.create(*m_memory, 4 * sizeof(std::uint32_t), m_frames, "smoke read back")) ||
(false == m_precipitationreadback.create(*m_memory, 4 * sizeof(std::uint32_t), m_frames, "precipitation read back")))
{
ErrorLog("particles: no buffers of the commands of the particles of the gpu, the smoke stays on the cpu");
return false;
}
return true;
}
void gpu_particles::gpu_clear()
{
for (auto *buffer : {&m_particles, &m_sources, &m_owners, &m_smokeoutput, &m_precipitationoutput})
{
let_go(*buffer);
}
}
void gpu_particles::gpu_layout(std::uint32_t const Places, std::uint32_t const Kept, std::vector<std::uint32_t> const &Owners, std::size_t const Sources)
{
auto const commands{m_host.compute()};
auto particles{make_buffer(static_cast<VkDeviceSize>(Places) * sizeof(gl::particle_smoke), storage_usage, "smoke particles")};
if (particles == nullptr)
{
return;
}
// the particles of the places kept go over to the new storage, the new places are dead
memory_barrier(commands, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_MEMORY_WRITE_BIT, VK_PIPELINE_STAGE_2_COPY_BIT | VK_PIPELINE_STAGE_2_CLEAR_BIT, VK_ACCESS_2_TRANSFER_READ_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT);
commands->fill_buffer(particles->handle(), 0, VK_WHOLE_SIZE, 0);
if ((m_particles != nullptr) && (Kept > 0))
{
memory_barrier(commands, VK_PIPELINE_STAGE_2_CLEAR_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_2_COPY_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT);
VkBufferCopy2 region{};
region.sType = VK_STRUCTURE_TYPE_BUFFER_COPY_2;
region.size = std::min<VkDeviceSize>(static_cast<VkDeviceSize>(Kept) * sizeof(gl::particle_smoke), m_particles->size());
VkCopyBufferInfo2 copy{};
copy.sType = VK_STRUCTURE_TYPE_COPY_BUFFER_INFO_2;
copy.srcBuffer = m_particles->handle();
copy.dstBuffer = particles->handle();
copy.regionCount = 1;
copy.pRegions = &region;
commands->copy_buffer2(&copy);
}
memory_barrier(commands, VK_PIPELINE_STAGE_2_COPY_BIT | VK_PIPELINE_STAGE_2_CLEAR_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, VK_ACCESS_2_SHADER_STORAGE_READ_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT);
let_go(m_particles);
m_particles = std::move(particles);
let_go(m_owners);
let_go(m_smokeoutput);
let_go(m_sources);
m_owners = make_buffer(static_cast<VkDeviceSize>(Places) * sizeof(std::uint32_t), storage_usage, "smoke owners");
m_smokeoutput = make_buffer(static_cast<VkDeviceSize>(Places) * sizeof(gl::particle_draw), storage_usage | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, "smoke draws");
m_sources = make_buffer(static_cast<VkDeviceSize>(std::max<std::size_t>(Sources, 1)) * sizeof(gl::particle_smoke_source), storage_usage, "smoke sources");
if (m_owners != nullptr)
{
// before the commands of the frame, the kernels of the frame read it
m_uploads->upload_buffer(m_owners, Owners.data(), static_cast<VkDeviceSize>(Owners.size() * sizeof(std::uint32_t)));
}
}
void gpu_particles::gpu_simulate(smoke_simulation const &Simulation, std::vector<gl::particle_smoke_source> const &Records)
{
if ((m_particles == nullptr) || (m_sources == nullptr) || (m_owners == nullptr))
{
return;
}
if (false == Records.empty())
{
m_uploads->upload_buffer(m_sources, Records.data(), static_cast<VkDeviceSize>(Records.size() * sizeof(gl::particle_smoke_source)));
}
auto const commands{m_host.compute()};
simulate_constants constants{};
constants.places = Simulation.places;
constants.step = Simulation.step;
constants.wind = Simulation.wind;
constants.seed = Simulation.seed;
m_host.bind(m_smokesimulate);
m_smokesimulate.dispatch(commands,
{buffer_descriptor(m_particles->handle(), 0, VK_WHOLE_SIZE), buffer_descriptor(m_sources->handle(), 0, VK_WHOLE_SIZE), buffer_descriptor(m_owners->handle(), 0, VK_WHOLE_SIZE)},
constants, groups(Simulation.places));
compute_barrier(commands);
}
void gpu_particles::reset_command(command_stream *const Commands, buffer const &Command)
{
std::uint32_t const command[4]{4, 0, 0, 0};
update_buffer(Commands, Command, command, sizeof(command), 0);
}
void gpu_particles::collect(readback_ring &Readback, std::uint64_t const Completed, std::size_t &Drawn)
{
std::uint32_t command[4];
if (Readback.pop(Completed, command, sizeof(command)))
{
Drawn = command[1];
}
}
void gpu_particles::gpu_render_smoke(smoke_draw const &Draw, std::size_t &Drawn)
{
collect(m_smokereadback, m_host.completed(), Drawn);
if ((m_particles == nullptr) || (m_sources == nullptr) || (m_owners == nullptr) || (m_smokeoutput == nullptr))
{
return;
}
auto const commands{m_host.compute()};
reset_command(commands, *m_smokecommand);
prepare_constants constants{};
constants.places = Draw.places;
constants.origin = Draw.origin;
constants.view = Draw.view;
constants.options = Draw.options;
std::copy(Draw.planes.begin(), Draw.planes.end(), constants.planes);
m_host.bind(m_smokeprepare);
m_smokeprepare.dispatch(commands,
{buffer_descriptor(m_particles->handle(), 0, VK_WHOLE_SIZE), buffer_descriptor(m_sources->handle(), 0, VK_WHOLE_SIZE), buffer_descriptor(m_owners->handle(), 0, VK_WHOLE_SIZE),
buffer_descriptor(m_smokecommand->handle(), 0, VK_WHOLE_SIZE), buffer_descriptor(m_smokeoutput->handle(), 0, VK_WHOLE_SIZE)},
constants, groups(Draw.places));
compute_barrier(commands);
// the count of the instances is what the draw takes, read back once the gpu is done with the frame
m_smokereadback.push(commands, m_host.slot(), m_host.value(), m_smokecommand->handle(), 0, 4 * sizeof(std::uint32_t));
m_host.draw(*m_smokedraw, m_smokeoutput->handle(), m_smokecommand->handle(), Draw.soft, glm::vec4{0.f});
}
void gpu_particles::gpu_collect_precipitation(std::size_t &Drawn)
{
collect(m_precipitationreadback, m_host.completed(), Drawn);
}
void gpu_particles::gpu_precipitation_storage(std::uint32_t const Capacity)
{
let_go(m_precipitationoutput);
m_precipitationoutput = make_buffer(static_cast<VkDeviceSize>(Capacity) * sizeof(gl::particle_draw), storage_usage | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, "precipitation draws");
}
void gpu_particles::gpu_render_precipitation(precipitation_draw const &Draw)
{
if (m_precipitationoutput == nullptr)
{
return;
}
precipitation_block block{};
block.counts = Draw.counts;
std::copy(Draw.offsets.begin(), Draw.offsets.end(), block.offsets);
block.sizes = Draw.sizes;
block.velocity = Draw.velocity;
block.timing = Draw.timing;
block.view = Draw.view;
block.options = Draw.options;
std::copy(Draw.planes.begin(), Draw.planes.end(), block.planes);
auto const slice{m_ring->allocate(sizeof(block), m_alignment)};
if (false == bool(slice))
{
return;
}
std::memcpy(slice.data, &block, sizeof(block));
auto const commands{m_host.compute()};
reset_command(commands, *m_precipitationcommand);
m_host.bind(m_precipitationprepare);
m_precipitationprepare.run(commands,
{buffer_descriptor(m_precipitationcommand->handle(), 0, VK_WHOLE_SIZE), buffer_descriptor(m_precipitationoutput->handle(), 0, VK_WHOLE_SIZE),
buffer_descriptor(slice.buffer, slice.offset, sizeof(block))},
groups(Draw.count));
compute_barrier(commands);
m_precipitationreadback.push(commands, m_host.slot(), m_host.value(), m_precipitationcommand->handle(), 0, 4 * sizeof(std::uint32_t));
m_host.draw(*m_precipitationdraw, m_precipitationoutput->handle(), m_precipitationcommand->handle(), Draw.soft, Draw.pixel);
}
} // namespace vk14