18
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-14 01:57:58 +01:00

Vulkan: parity with GL (P1–P7), shared GPU culling records (N6c), threaded command recording and render thread (M1, M4)

This commit is contained in:
2026-10-25 03:06:01 +01:00
parent 49f9df7d98
commit c48c58776a
146 changed files with 20748 additions and 4606 deletions

View File

@@ -0,0 +1,214 @@
/*
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/.
*/
// the ambient occlusion of the vulkan renderer (the stage P3 of the plan, opengl46_ao and Render_ambient_occlusion() of the
// opengl renderer): ground truth ambient occlusion at half the resolution in two compute kernels (gtao_main.comp,
// gtao_denoise.comp) or the older ssao (postfx_ssao.frag and its blur), multiplied into the image of the scene
// (postfx_ssao_apply.frag) with the fog in front of the surfaces taken into account
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14context.h"
#include "rendering/vulkan14/vulkan14commands.h"
#include "utilities/Globals.h"
#include <random>
namespace
{
// work group of the compute passes, 8x8 (gtao_*.comp)
std::uint32_t const GROUP_SIZE{8};
struct gtao_constants
{
glm::mat4 inv_projection;
glm::vec2 params;
glm::vec2 pad;
glm::vec4 clipw_row;
glm::ivec2 quality;
};
static_assert(offsetof(gtao_constants, clipw_row) == 80, "the layout of the constants of gtao_main.comp");
VkPipelineStageFlags2 const compute_stage{VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT};
VkPipelineStageFlags2 const sampled_stage{VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT};
VkAccessFlags2 const sampled_access{VK_ACCESS_2_SHADER_SAMPLED_READ_BIT};
} // namespace
bool vulkan14_renderer::init_ao(std::string &Reason)
{
auto &ctx{*m_context};
auto &scene{ctx.scene};
auto const device{ctx.device.handle()};
// the noise of the older ssao: 4x4 random rotations in the plane of the surface, drawn after the hemisphere kernel the opengl
// renderer makes with the same generator (the shader makes the kernel itself), so they are the same numbers
{
std::uniform_real_distribution<float> rnd(0.0f, 1.0f);
std::default_random_engine gen(42); // NOSONAR fixed seed on purpose, the same as the opengl renderer
for (int i = 0; i < 32; i++)
{
glm::vec3 s(rnd(gen) * 2.0f - 1.0f, rnd(gen) * 2.0f - 1.0f, rnd(gen));
s = glm::normalize(s) * rnd(gen);
}
std::vector<glm::vec4> noise;
for (int i = 0; i < 16; i++)
{
noise.emplace_back(glm::normalize(glm::vec3(rnd(gen) * 2.0f - 1.0f, rnd(gen) * 2.0f - 1.0f, 0.0f)), 1.f);
}
vk14::image_desc desc;
desc.format = VK_FORMAT_R32G32B32A32_SFLOAT;
desc.extent = {4, 4, 1};
desc.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
scene.ssao_noise = std::make_shared<vk14::image>();
if ((false == scene.ssao_noise->create(ctx.memory, desc, "ssao noise")) || (false == ctx.uploads.upload(scene.ssao_noise, noise.data(), noise.size() * sizeof(glm::vec4), {{0, {4, 4, 1}}}, false)))
{
Reason = "no image of the noise of the ssao";
return false;
}
}
try
{
auto kernel = [&](vk14::compute_kernel &Kernel, char const *Shader, char const *Name) {
if (false == Kernel.create(device, ctx.pipelines, ctx.shaders.get(Shader), scene.pass_layout, {}, Name))
{
Reason = std::string("no compute pipeline of ") + Name;
return false;
}
return true;
};
if ((false == kernel(scene.gtao_main, "gtao_main.comp", "gtao")) || (false == kernel(scene.gtao_denoise, "gtao_denoise.comp", "gtao denoise")))
{
return false;
}
scene.ao_apply = std::make_shared<vk14::scene_program>();
scene.ao_apply->vertex = ctx.shaders.get("quad.vert", "#define QUAD_SCENE 1\n");
scene.ao_apply->fragment = ctx.shaders.get("postfx_ssao_apply.frag");
scene.ao_apply->fragment_name = "postfx_ssao_apply.frag";
scene.ao_apply->vertices = vk14::vertex_kind::none;
scene.ao_apply->name = "quad.vert + postfx_ssao_apply.frag";
for (auto const *name : {"postfx_ssao.frag", "postfx_ssao_blur.frag"})
{
scene.post_fragments.emplace(name, ctx.shaders.get(name));
}
}
catch (gfx::shader_exception const &e)
{
Reason = std::string("invalid shader: ") + e.what();
return false;
}
return true;
}
void vulkan14_renderer::destroy_ao()
{
auto &ctx{*m_context};
auto &scene{ctx.scene};
auto const device{ctx.device.handle()};
scene.gtao_main.destroy(device);
scene.gtao_denoise.destroy(device);
scene.ao_apply.reset();
scene.ssao_noise.reset();
}
// the ambient occlusion of the viewport from the depth of its scene (resolved), multiplied into its colour
// (opengl46_renderer::Render_ambient_occlusion()). Mode: Global.gfx_postfx_ao. outside a rendering
void vulkan14_renderer::render_ao(std::size_t const Index, int const Mode)
{
auto &ctx{*m_context};
auto &scene{ctx.scene};
auto &targets{scene.targets[Index]};
auto const commands{ctx.commands};
if ((false == bool(targets.ssao)) || (false == bool(targets.ssao_blur)))
{
return;
}
// the projection the depth was drawn with
auto const &projection{m_colorpass.pass_camera.projection()};
auto const inverseprojection{glm::inverse(projection)};
auto const &depth{*targets.depth};
if (Mode == 1)
{
// the older ssao, which reads the projection from its constants
vk14::draw_constants constants;
for (int column = 0; column < 4; ++column)
{
constants.data[column] = projection[column];
}
post_pass("postfx_ssao.frag", *targets.ssao, {targets.depth.get(), scene.ssao_noise.get()}, constants, nullptr, 2u);
post_pass("postfx_ssao_blur.frag", *targets.ssao_blur, {targets.ssao.get()}, vk14::draw_constants{});
}
else
{
auto const width{targets.ssao->desc().extent.width};
auto const height{targets.ssao->desc().extent.height};
auto const groups_x{(width + GROUP_SIZE - 1) / GROUP_SIZE};
auto const groups_y{(height + GROUP_SIZE - 1) / GROUP_SIZE};
// horizon search
vk14::barriers(commands, {vk14::use_of(*targets.depth, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access),
vk14::use_of(*targets.ssao, VK_IMAGE_LAYOUT_GENERAL, compute_stage, VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT)});
gtao_constants constants{};
constants.inv_projection = inverseprojection;
constants.params = glm::vec2{Global.gfx_postfx_ao_radius, projection[0][0] * 0.5f * static_cast<float>(width)};
constants.clipw_row = glm::vec4{projection[0][3], projection[1][3], projection[2][3], projection[3][3]};
constants.quality = glm::ivec2{Global.gfx_postfx_ao_slices, Global.gfx_postfx_ao_steps};
auto const depthimage{vk14::image_descriptor(ctx.sampling.clamp_nearest, depth.view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)};
scene.gtao_main.dispatch(commands, {depthimage, vk14::storage_image(targets.ssao->view())}, constants, groups_x, groups_y);
// smoothing
vk14::barriers(commands, {vk14::use_of(*targets.ssao, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access),
vk14::use_of(*targets.ssao_blur, VK_IMAGE_LAYOUT_GENERAL, compute_stage, VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT)});
scene.gtao_denoise.dispatch(commands, {depthimage, vk14::image_descriptor(ctx.sampling.clamp_nearest, targets.ssao->view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL), vk14::storage_image(targets.ssao_blur->view())},
inverseprojection, groups_x, groups_y);
}
// result = image * occlusion, drawn over the colour of the scene with the pass set and the weather of its colour pass
vk14::barriers(commands, {vk14::use_of(*targets.ssao_blur, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access), vk14::use_of(*targets.depth, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access),
vk14::use_of(*targets.colour, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT)});
VkRenderingAttachmentInfo attachment{};
attachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
attachment.imageView = targets.colour->view();
attachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
attachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
VkRenderingInfo rendering{};
rendering.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
rendering.renderArea = {{0, 0}, targets.extent};
rendering.layerCount = 1;
rendering.colorAttachmentCount = 1;
rendering.pColorAttachments = &attachment;
commands->begin_rendering(&rendering);
auto &draw{*m_draw};
auto const set{m_viewports[Index].main ? vk14::scene_state::set_colour : vk14::scene_state::set_plain};
start_draws(targets.post_formats, static_cast<int>(targets.extent.width), static_cast<int>(targets.extent.height), set, false);
draw.glare = true; // no depth
draw.cullface = false;
if (bind_scene_pipeline(*scene.ao_apply, targets.post_formats, vk14::blend_mode::multiply, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP))
{
apply_draw_state();
draw.block = push_draw_block();
if (draw.block != ~0u)
{
bind_pass_set();
std::array<VkDescriptorImageInfo, gl::MAX_TEXTURES> images;
images.fill(VkDescriptorImageInfo{ctx.sampling.clamp_nearest, scene.white->view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL});
images[0] = VkDescriptorImageInfo{ctx.sampling.clamp_nearest, depth.view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL};
images[1] = VkDescriptorImageInfo{ctx.sampling.clamp_nearest, targets.ssao_blur->view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL};
scene.material_set.push(commands, scene.scene_layout, images.data());
vk14::draw_constants constants;
constants.draw = draw.block;
for (int column = 0; column < 4; ++column)
{
constants.data[column] = inverseprojection[column];
}
constants.data[4] = glm::vec4{(Mode == 1 ? 1.f : 0.f), Global.gfx_postfx_ao_strength, Global.gfx_postfx_ao_bounce, 0.f};
push_constants(&constants);
commands->draw(4, 1, 0, 0);
}
}
commands->end_rendering();
end_draws();
}

View File

@@ -47,7 +47,31 @@ constexpr std::uint32_t CLUSTER_INDEX_SSBO = 6;
constexpr std::uint32_t SHADOW_TEX = 7;
constexpr std::uint32_t ENV_TEX = 8;
constexpr std::uint32_t HEADLIGHT_TEX = 9;
constexpr std::uint32_t PASS_BINDING_COUNT = 10;
// the light of the sky (the stage P1): the directional part of the ambient light (gl::sky_data), the parameters of the
// atmosphere (gl::atmosphere_ubs), its sky-view table, the environment map prefiltered with the GGX lobe
constexpr std::uint32_t SKY_SSBO = 10;
constexpr std::uint32_t ATMOSPHERE_UBO = 11;
constexpr std::uint32_t SKY_VIEW_TEX = 12;
constexpr std::uint32_t ENV_GGX_TEX = 13;
// the weather (the stage P2): its block (gl::weather_ubs without the colours of the horizon, dynamic offset: it differs from
// pass to pass), the colours of the fog towards the horizon (written by fog_horizon.comp or by the cpu), the fog volume of the
// main view, the shadows of the clouds and the noises of the clouds
constexpr std::uint32_t WEATHER_UBO = 14;
constexpr std::uint32_t WEATHER_HORIZON_UBO = 15;
constexpr std::uint32_t FOG_VOLUME_TEX = 16;
constexpr std::uint32_t CLOUD_SHADOW_TEX = 17;
constexpr std::uint32_t CLOUD_SHAPE_TEX = 18;
constexpr std::uint32_t CLOUD_DETAIL_TEX = 19;
constexpr std::uint32_t CLOUD_COVERAGE_TEX = 20;
// the shadows of the lamps (the stage P4): the cube maps of the cab lamp and of the scenery lamps, the maps of the headlights
constexpr std::uint32_t LOCAL_SHADOW_TEX = 21;
constexpr std::uint32_t LOCAL_SHADOW_LAMPS_TEX = 22;
constexpr std::uint32_t HEADLIGHT_SHADOW_TEX = 23;
// the depth of the opaque scene drawn before its colour (the stage P3: the contact shadows; the soft particles of P6)
constexpr std::uint32_t SCENE_DEPTH_TEX = 24;
constexpr std::uint32_t PASS_BINDING_COUNT = 25;
// the offsets of the bindings above with dynamic offsets, in the order of the bindings
constexpr std::uint32_t PASS_DYNAMIC_OFFSETS = 6;
// the textures of a material take the bindings of their units (#pragma texture(name, unit, ...)), 0 .. MAX_TEXTURES - 1
constexpr std::uint32_t MATERIAL_TEXTURE_FIRST = 0;
@@ -59,9 +83,16 @@ constexpr std::uint32_t CL_INDEX_SSBO = 3;
constexpr std::uint32_t CL_COUNTER_SSBO = 4;
constexpr std::uint32_t CL_LIGHT_SSBO = 5;
// the post-processing and the copies of whole images: set 0 pushed, the images it reads from binding 0 on
// the post-processing and the copies of whole images: set 0 pushed, the images it reads from binding 0 on, and the exposure
// worked out on the gpu (gl::exposure_data, the stage P1)
constexpr std::uint32_t POST_SET = 0;
constexpr std::uint32_t POST_TEXTURE_FIRST = 0;
constexpr std::uint32_t POST_TEXTURES = 4;
constexpr std::uint32_t POST_EXPOSURE_SSBO = 4;
// the compute kernels (vk14::compute_kernel): set 0 the pass set of the scene, set 1 pushed with the dispatch, its bindings
// those the shader declares from 0 on
constexpr std::uint32_t COMPUTE_SET = 1;
// the push constants of the graphics pipelines of the scene and of the post-processing (vk14::draw_constants): the draw, and
// the parameters of the special shaders (the celestial bodies, the post-processing)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,186 @@
/*
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/.
*/
// the commands of a frame kept to be recorded into command buffers later (the stage M1 of the plan): the renderer makes the calls
// of vkCmd* on a stream, which keeps them with their data in the order they were made. the stream is cut into chunks as it's
// made, every so many draws: a chunk is recorded into a command buffer of its own, by a worker thread, and the command buffers go
// to the queue in one submit in the order of the chunks. a chunk which starts inside a rendering begins it again resumed
// (VK_RENDERING_RESUMING_BIT, the one before it ended it suspended) and sets again the state the draws before it left: the
// pipelines, the descriptor sets, the pushed descriptors and constants, the vertex and index buffers and the dynamic state, as
// the last calls of each kind made them. where the cuts are doesn't depend on the number of the threads, so the gpu gets the
// same commands with any of them
#pragma once
#include "rendering/vulkan14/vulkan14common.h"
#include <array>
#include <cstddef>
#include <vector>
namespace vk14
{
class command_stream
{
public:
// the draws, dispatches and copies a chunk holds at least before it's cut (inside a rendering, between two draws)
static constexpr std::uint32_t chunk_work{256};
// the calls, with the arguments vkCmd* takes (the command buffer left out); the data they point to is copied. chains of pNext
// aren't kept: none of the calls is made with one
void begin_rendering(VkRenderingInfo const *Info);
void end_rendering();
void pipeline_barrier2(VkDependencyInfo const *Dependency);
void bind_pipeline(VkPipelineBindPoint BindPoint, VkPipeline Pipeline);
void bind_descriptor_sets2(VkBindDescriptorSetsInfo const *Info);
// Size: the bytes of the data of the template; BindPoint: the one the template was made for
void push_descriptor_set_with_template2(VkPushDescriptorSetWithTemplateInfo const *Info, std::size_t Size, VkPipelineBindPoint BindPoint);
void push_constants2(VkPushConstantsInfo const *Info);
void bind_vertex_buffers(std::uint32_t First, std::uint32_t Count, VkBuffer const *Buffers, VkDeviceSize const *Offsets);
void bind_index_buffer(VkBuffer Buffer, VkDeviceSize Offset, VkIndexType Type);
void draw(std::uint32_t Vertices, std::uint32_t Instances, std::uint32_t FirstVertex, std::uint32_t FirstInstance);
void draw_indexed(std::uint32_t Indices, std::uint32_t Instances, std::uint32_t FirstIndex, std::int32_t VertexOffset, std::uint32_t FirstInstance);
void draw_indirect(VkBuffer Buffer, VkDeviceSize Offset, std::uint32_t Count, std::uint32_t Stride);
void draw_indexed_indirect(VkBuffer Buffer, VkDeviceSize Offset, std::uint32_t Count, std::uint32_t Stride);
void dispatch(std::uint32_t X, std::uint32_t Y, std::uint32_t Z);
void dispatch_indirect(VkBuffer Buffer, VkDeviceSize Offset);
void set_viewport(std::uint32_t First, std::uint32_t Count, VkViewport const *Viewports);
void set_scissor(std::uint32_t First, std::uint32_t Count, VkRect2D const *Scissors);
void set_cull_mode(VkCullModeFlags Mode);
void set_front_face(VkFrontFace Face);
void set_primitive_topology(VkPrimitiveTopology Topology);
void set_depth_test_enable(VkBool32 Enable);
void set_depth_write_enable(VkBool32 Enable);
void set_depth_compare_op(VkCompareOp Op);
void set_depth_bias_enable(VkBool32 Enable);
void set_depth_bias(float Constant, float Clamp, float Slope);
void set_primitive_restart_enable(VkBool32 Enable);
void set_stencil_test_enable(VkBool32 Enable);
void fill_buffer(VkBuffer Buffer, VkDeviceSize Offset, VkDeviceSize Size, std::uint32_t Data);
void update_buffer(VkBuffer Buffer, VkDeviceSize Offset, VkDeviceSize Size, void const *Data);
void copy_buffer2(VkCopyBufferInfo2 const *Info);
void copy_image2(VkCopyImageInfo2 const *Info);
void copy_buffer_to_image2(VkCopyBufferToImageInfo2 const *Info);
void copy_image_to_buffer2(VkCopyImageToBufferInfo2 const *Info);
void blit_image2(VkBlitImageInfo2 const *Info);
void clear_color_image(VkImage Image, VkImageLayout Layout, VkClearColorValue const *Color, std::uint32_t Count, VkImageSubresourceRange const *Ranges);
void clear_depth_stencil_image(VkImage Image, VkImageLayout Layout, VkClearDepthStencilValue const *Value, std::uint32_t Count, VkImageSubresourceRange const *Ranges);
void write_timestamp2(VkPipelineStageFlags2 Stage, VkQueryPool Pool, std::uint32_t Query);
void reset_query_pool(VkQueryPool Pool, std::uint32_t First, std::uint32_t Count);
void begin_query(VkQueryPool Pool, std::uint32_t Query, VkQueryControlFlags Flags);
void end_query(VkQueryPool Pool, std::uint32_t Query);
void begin_debug_utils_label(VkDebugUtilsLabelEXT const *Label);
void end_debug_utils_label();
// the stream is made anew
void clear();
bool empty() const
{
return m_chunks.empty() || (m_data.empty());
}
// the stream is ended: no calls after it
void finish();
// the chunks, valid after finish()
std::size_t chunks() const
{
return m_chunks.size();
}
// the calls recorded since the start
std::size_t calls() const
{
return m_calls;
}
// the chunk recorded into Buffer (begun already, ended by the caller)
void replay(std::size_t Chunk, VkCommandBuffer Buffer) const;
// all of it into one command buffer
void replay(VkCommandBuffer Buffer) const;
private:
// the calls of the state a chunk sets again; the dynamic states have a slot each
enum state_slot : std::uint32_t
{
pipeline_graphics,
pipeline_compute,
viewport,
scissor,
cull_mode,
front_face,
topology,
depth_test,
depth_write,
depth_compare,
depth_bias_enable,
depth_bias,
primitive_restart,
stencil_test,
index_buffer,
sets_graphics, // set_slots of each
sets_compute = sets_graphics + 8,
pushed_graphics = sets_compute + 8,
pushed_compute = pushed_graphics + 8,
vertex_buffers = pushed_compute + 8, // vertex_slots
slot_count = vertex_buffers + 8
};
static constexpr std::size_t no_call{~static_cast<std::size_t>(0)};
static constexpr std::uint32_t set_slots{8};
static constexpr std::uint32_t vertex_slots{8};
struct chunk
{
std::size_t first{0}; // offset of its first call in the data
std::size_t end{0}; // past its last one
// the begin of the rendering the chunk starts inside of, resumed, and the calls of the state set again, in their order
std::size_t resumed{no_call};
std::vector<std::size_t> state;
// the begin of the rendering the chunk ends inside of, suspended
std::size_t suspended{no_call};
};
// room for a call of Type with Size bytes of its own data after its header, aligned; the offset of the call
std::size_t add(std::uint32_t Type, std::size_t Size);
template <typename Type_> Type_ &payload(std::size_t Call)
{
return *reinterpret_cast<Type_ *>(m_data.data() + Call + header_size);
}
template <typename Type_> Type_ const &payload(std::size_t Call) const
{
return *reinterpret_cast<Type_ const *>(m_data.data() + Call + header_size);
}
template <typename Type_> Type_ const *at(std::size_t Offset) const
{
return reinterpret_cast<Type_ const *>(m_data.data() + Offset);
}
// copies Bytes of Data after the call made last (aligned), returns their offset in the data
std::size_t append(void const *Data, std::size_t Bytes);
// the call made last ends here
void close(std::size_t Call);
void remember(std::uint32_t Slot, std::size_t Call)
{
m_state[Slot] = Call;
}
// a draw, dispatch or copy was added: the chunk is cut after it when it has enough of them
void work();
void cut();
// Flags: added to those of a begin of a rendering
void execute(std::size_t Call, VkCommandBuffer Buffer, VkRenderingFlags Flags = 0) const;
static constexpr std::size_t header_size{8};
std::vector<std::uint8_t> m_data;
std::vector<chunk> m_chunks;
std::array<std::size_t, slot_count> m_state{};
// the pushes of the constants: the last one of each range and stages, in their order
std::vector<std::size_t> m_pushes;
std::size_t m_rendering{no_call}; // the begin of the rendering under way
std::uint32_t m_queries{0}; // queries active
std::uint32_t m_work{0}; // of the chunk under way
std::size_t m_calls{0};
bool m_finished{false};
};
} // namespace vk14

View File

@@ -9,6 +9,7 @@ http://mozilla.org/MPL/2.0/.
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14common.h"
#include "rendering/vulkan14/vulkan14commands.h"
#include "utilities/Logs.h"
namespace vk14
@@ -143,7 +144,7 @@ void name_object(VkDevice Device, VkObjectType Type, std::uint64_t Handle, std::
vkSetDebugUtilsObjectNameEXT(Device, &info);
}
void begin_label(VkCommandBuffer Commands, char const *Name)
void begin_label(command_stream *Commands, char const *Name)
{
if ((false == debug_utils_enabled) || (vkCmdBeginDebugUtilsLabelEXT == nullptr))
{
@@ -152,16 +153,16 @@ void begin_label(VkCommandBuffer Commands, char const *Name)
VkDebugUtilsLabelEXT label{};
label.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT;
label.pLabelName = Name;
vkCmdBeginDebugUtilsLabelEXT(Commands, &label);
Commands->begin_debug_utils_label(&label);
}
void end_label(VkCommandBuffer Commands)
void end_label(command_stream *Commands)
{
if ((false == debug_utils_enabled) || (vkCmdEndDebugUtilsLabelEXT == nullptr))
{
return;
}
vkCmdEndDebugUtilsLabelEXT(Commands);
Commands->end_debug_utils_label();
}
void log_info(std::string const &Message)

View File

@@ -22,6 +22,9 @@ extern "C" GLFWAPI int glfwGetPhysicalDevicePresentationSupport(VkInstance insta
namespace vk14
{
// the commands of a frame, recorded later into command buffers (vulkan14commands.h)
class command_stream;
// human readable VkResult, for the log
char const *result_string(VkResult Result);
// "1.4.309"
@@ -48,8 +51,8 @@ template <typename Handle_> std::uint64_t handle_value(Handle_ Handle)
// a name of an object for the messages of the validation layer and for captures; nothing without debug utils
void name_object(VkDevice Device, VkObjectType Type, std::uint64_t Handle, std::string const &Name);
// a label of a part of the commands (a pass), for captures and the messages of the validation layer
void begin_label(VkCommandBuffer Commands, char const *Name);
void end_label(VkCommandBuffer Commands);
void begin_label(command_stream *Commands, char const *Name);
void end_label(command_stream *Commands);
// the log of the renderer, with the "vulkan: " prefix
void log_info(std::string const &Message);

View File

@@ -0,0 +1,395 @@
/*
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/vulkan14compute.h"
#include "rendering/vulkan14/vulkan14commands.h"
#include <map>
#include <unordered_map>
namespace vk14
{
std::vector<VkDescriptorSetLayoutBinding> reflect_bindings(std::vector<std::uint32_t> const &Spirv, std::uint32_t const Set, VkShaderStageFlags const Stages)
{
std::uint32_t constexpr op_constant{43};
std::uint32_t constexpr op_type_image{25};
std::uint32_t constexpr op_type_sampler{26};
std::uint32_t constexpr op_type_sampled_image{27};
std::uint32_t constexpr op_type_array{28};
std::uint32_t constexpr op_type_runtime_array{29};
std::uint32_t constexpr op_type_struct{30};
std::uint32_t constexpr op_type_pointer{32};
std::uint32_t constexpr op_variable{59};
std::uint32_t constexpr op_decorate{71};
std::uint32_t constexpr decoration_block{2};
std::uint32_t constexpr decoration_buffer_block{3};
std::uint32_t constexpr decoration_binding{33};
std::uint32_t constexpr decoration_set{34};
std::uint32_t constexpr storage_uniform_constant{0};
std::uint32_t constexpr storage_uniform{2};
std::uint32_t constexpr storage_storage_buffer{12};
struct type_info
{
std::uint32_t opcode{0};
std::uint32_t operand1{0}; // pointer: the type pointed to; array: the element; sampled image: the image
std::uint32_t operand2{0}; // pointer: the storage class; array: the length; image: sampled
std::uint32_t dim{0};
};
std::unordered_map<std::uint32_t, type_info> types;
std::unordered_map<std::uint32_t, std::uint32_t> constants;
std::unordered_map<std::uint32_t, std::uint32_t> sets;
std::unordered_map<std::uint32_t, std::uint32_t> bindingsof;
std::unordered_map<std::uint32_t, std::uint32_t> blocks; // struct -> decoration
std::vector<std::pair<std::uint32_t, std::uint32_t>> variables; // id, pointer type
for (std::size_t word = 5; word < Spirv.size();)
{
auto const count{Spirv[word] >> 16};
auto const opcode{Spirv[word] & 0xffff};
if ((count == 0) || (word + count > Spirv.size()))
{
break;
}
auto const *op{&Spirv[word]};
switch (opcode)
{
case op_decorate:
if (count >= 3)
{
if ((op[2] == decoration_set) && (count >= 4))
{
sets[op[1]] = op[3];
}
else if ((op[2] == decoration_binding) && (count >= 4))
{
bindingsof[op[1]] = op[3];
}
else if ((op[2] == decoration_block) || (op[2] == decoration_buffer_block))
{
blocks[op[1]] = op[2];
}
}
break;
case op_constant:
if (count >= 4)
{
constants[op[2]] = op[3];
}
break;
case op_type_image:
if (count >= 9)
{
types[op[1]] = {opcode, 0, op[7], op[3]};
}
break;
case op_type_sampler:
types[op[1]] = {opcode, 0, 0, 0};
break;
case op_type_sampled_image:
types[op[1]] = {opcode, op[2], 0, 0};
break;
case op_type_array:
types[op[1]] = {opcode, op[2], op[3], 0};
break;
case op_type_runtime_array:
types[op[1]] = {opcode, op[2], 0, 0};
break;
case op_type_struct:
types[op[1]] = {opcode, 0, 0, 0};
break;
case op_type_pointer:
types[op[1]] = {opcode, op[3], op[2], 0};
break;
case op_variable:
if (count >= 4)
{
variables.emplace_back(op[2], op[1]);
}
break;
default:
break;
}
word += count;
}
std::map<std::uint32_t, VkDescriptorSetLayoutBinding> result;
for (auto const &[id, pointertype] : variables)
{
auto const set{sets.find(id)};
auto const binding{bindingsof.find(id)};
if ((set == sets.end()) || (binding == bindingsof.end()) || (set->second != Set))
{
continue;
}
auto const pointer{types.find(pointertype)};
if (pointer == types.end())
{
continue;
}
auto const storage{pointer->second.operand2};
auto type{pointer->second.operand1};
std::uint32_t descriptors{1};
auto lookup{types.find(type)};
if ((lookup != types.end()) && (lookup->second.opcode == op_type_array))
{
auto const length{constants.find(lookup->second.operand2)};
descriptors = (length != constants.end() ? length->second : 1);
type = lookup->second.operand1;
lookup = types.find(type);
}
if (lookup == types.end())
{
continue;
}
auto descriptortype{VK_DESCRIPTOR_TYPE_MAX_ENUM};
switch (lookup->second.opcode)
{
case op_type_sampled_image:
descriptortype = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
break;
case op_type_image:
descriptortype = (lookup->second.operand2 == 2 ? VK_DESCRIPTOR_TYPE_STORAGE_IMAGE : VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE);
break;
case op_type_sampler:
descriptortype = VK_DESCRIPTOR_TYPE_SAMPLER;
break;
case op_type_struct:
{
auto const block{blocks.find(type)};
if (storage == storage_storage_buffer)
{
descriptortype = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
}
else if (storage == storage_uniform)
{
descriptortype = ((block != blocks.end()) && (block->second == decoration_buffer_block) ? VK_DESCRIPTOR_TYPE_STORAGE_BUFFER : VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER);
}
break;
}
default:
break;
}
if ((descriptortype == VK_DESCRIPTOR_TYPE_MAX_ENUM) || ((storage != storage_uniform_constant) && (storage != storage_uniform) && (storage != storage_storage_buffer)))
{
continue;
}
result[binding->second] = VkDescriptorSetLayoutBinding{binding->second, descriptortype, descriptors, Stages, nullptr};
}
std::vector<VkDescriptorSetLayoutBinding> bindings;
for (auto const &entry : result)
{
bindings.push_back(entry.second);
}
return bindings;
}
bool compute_kernel::create(VkDevice Device, pipeline_library &Pipelines, std::shared_ptr<shader_code const> const &Shader, VkDescriptorSetLayout Passlayout, std::vector<std::uint32_t> const &Specialization, std::string const &Name)
{
m_name = Name;
auto const bindings{reflect_bindings(Shader->spirv, COMPUTE_PUSH_SET, VK_SHADER_STAGE_COMPUTE_BIT)};
for (std::size_t i = 0; i < bindings.size(); ++i)
{
// the data of the template is read in the order of the bindings, one descriptor each
if ((bindings[i].binding != i) || (bindings[i].descriptorCount != 1))
{
log_error("the compute shader of " + Name + " has to take the bindings of its pushed set one by one from 0");
return false;
}
}
m_count = bindings.size();
std::vector<VkDescriptorSetLayout> sets{Passlayout};
if (m_count > 0)
{
if (false == m_set.create(Device, bindings, COMPUTE_PUSH_SET, VK_PIPELINE_BIND_POINT_COMPUTE, sizeof(descriptor), Name))
{
return false;
}
sets.push_back(m_set.layout);
}
VkPushConstantRange const constants{VK_SHADER_STAGE_COMPUTE_BIT, 0, COMPUTE_PUSH_CONSTANTS};
m_layout = make_pipeline_layout(Device, sets, {constants}, Name);
if (m_layout == VK_NULL_HANDLE)
{
return false;
}
if ((m_count > 0) && (false == m_set.create_template(Device, m_layout, Name)))
{
return false;
}
m_pipeline = Pipelines.compute(Shader, m_layout, Specialization, Name);
return m_pipeline != VK_NULL_HANDLE;
}
void compute_kernel::destroy(VkDevice Device)
{
m_set.destroy(Device);
if (m_layout != VK_NULL_HANDLE)
{
vkDestroyPipelineLayout(Device, m_layout, nullptr);
m_layout = VK_NULL_HANDLE;
}
// the pipeline belongs to the pipeline library
m_pipeline = VK_NULL_HANDLE;
}
void compute_kernel::bind(command_stream *Commands, descriptor const *Descriptors, void const *Constants, std::uint32_t const Size) const
{
Commands->bind_pipeline(VK_PIPELINE_BIND_POINT_COMPUTE, m_pipeline);
if ((m_count > 0) && (Descriptors != nullptr))
{
m_set.push(Commands, m_layout, Descriptors);
}
if ((Size > 0) && (Constants != nullptr))
{
VkPushConstantsInfo info{};
info.sType = VK_STRUCTURE_TYPE_PUSH_CONSTANTS_INFO;
info.layout = m_layout;
info.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
info.offset = 0;
info.size = Size;
info.pValues = Constants;
Commands->push_constants2(&info);
}
}
void compute_kernel::dispatch(command_stream *Commands, descriptor const *Descriptors, void const *Constants, std::uint32_t const Size, std::uint32_t const X, std::uint32_t const Y, std::uint32_t const Z) const
{
if ((m_pipeline == VK_NULL_HANDLE) || (X == 0) || (Y == 0) || (Z == 0))
{
return;
}
bind(Commands, Descriptors, Constants, Size);
Commands->dispatch(X, Y, Z);
}
void compute_kernel::dispatch_indirect(command_stream *Commands, descriptor const *Descriptors, void const *Constants, std::uint32_t const Size, VkBuffer Buffer, VkDeviceSize const Offset) const
{
if (m_pipeline == VK_NULL_HANDLE)
{
return;
}
bind(Commands, Descriptors, Constants, Size);
Commands->dispatch_indirect(Buffer, Offset);
}
void memory_barrier(command_stream *Commands, VkPipelineStageFlags2 const SrcStage, VkAccessFlags2 const SrcAccess, VkPipelineStageFlags2 const DstStage, VkAccessFlags2 const DstAccess)
{
VkMemoryBarrier2 barrier{};
barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2;
barrier.srcStageMask = SrcStage;
barrier.srcAccessMask = SrcAccess;
barrier.dstStageMask = DstStage;
barrier.dstAccessMask = DstAccess;
VkDependencyInfo dependency{};
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
dependency.memoryBarrierCount = 1;
dependency.pMemoryBarriers = &barrier;
Commands->pipeline_barrier2(&dependency);
}
void compute_barrier(command_stream *Commands)
{
memory_barrier(Commands, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_2_COPY_BIT | VK_PIPELINE_STAGE_2_CLEAR_BIT,
VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT,
VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_2_DRAW_INDIRECT_BIT | VK_PIPELINE_STAGE_2_VERTEX_INPUT_BIT | VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_2_COPY_BIT | VK_PIPELINE_STAGE_2_CLEAR_BIT,
VK_ACCESS_2_SHADER_STORAGE_READ_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT | VK_ACCESS_2_INDIRECT_COMMAND_READ_BIT | VK_ACCESS_2_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_2_UNIFORM_READ_BIT |
VK_ACCESS_2_TRANSFER_READ_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT);
}
// the buffer written with the commands of the frame, between its reads by the shaders of the frames before and after
void update_buffer(command_stream *Commands, buffer const &Target, void const *Data, VkDeviceSize const Size, VkDeviceSize const Offset)
{
memory_barrier(Commands, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_UNIFORM_READ_BIT | VK_ACCESS_2_SHADER_STORAGE_READ_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT | VK_ACCESS_2_TRANSFER_READ_BIT,
VK_PIPELINE_STAGE_2_COPY_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT);
Commands->update_buffer(Target.handle(), Offset, Size, Data);
memory_barrier(Commands, VK_PIPELINE_STAGE_2_COPY_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_UNIFORM_READ_BIT | VK_ACCESS_2_SHADER_STORAGE_READ_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT);
}
// readback
bool readback_ring::create(allocator const &Allocator, VkDeviceSize const Size, std::uint32_t const Frames, std::string const &Name)
{
m_size = Size;
m_slots.clear();
m_slots.resize(Frames);
for (std::uint32_t i = 0; i < Frames; ++i)
{
if (false == m_slots[i].data.create(Allocator, Size, VK_BUFFER_USAGE_TRANSFER_DST_BIT, memory_kind::readback, Name + " " + std::to_string(i)))
{
return false;
}
}
return true;
}
void readback_ring::destroy()
{
m_slots.clear();
}
void readback_ring::push(command_stream *Commands, std::uint32_t const Slot, std::uint64_t const Value, VkBuffer Source, VkDeviceSize const Offset, VkDeviceSize const Size)
{
if (Slot >= m_slots.size())
{
return;
}
auto &slot{m_slots[Slot]};
VkBufferCopy2 region{};
region.sType = VK_STRUCTURE_TYPE_BUFFER_COPY_2;
region.srcOffset = Offset;
region.dstOffset = 0;
region.size = std::min(Size, m_size);
VkCopyBufferInfo2 copy{};
copy.sType = VK_STRUCTURE_TYPE_COPY_BUFFER_INFO_2;
copy.srcBuffer = Source;
copy.dstBuffer = slot.data.handle();
copy.regionCount = 1;
copy.pRegions = &region;
memory_barrier(Commands, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_2_COPY_BIT | VK_PIPELINE_STAGE_2_CLEAR_BIT | VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT,
VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_2_COPY_BIT, VK_ACCESS_2_TRANSFER_READ_BIT);
Commands->copy_buffer2(&copy);
memory_barrier(Commands, VK_PIPELINE_STAGE_2_COPY_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_2_HOST_BIT, VK_ACCESS_2_HOST_READ_BIT);
slot.value = Value;
slot.pending = true;
}
bool readback_ring::pop(std::uint64_t const Completed, void *Data, VkDeviceSize const Size)
{
slot *newest{nullptr};
for (auto &slot : m_slots)
{
if (slot.pending && (slot.value <= Completed) && ((newest == nullptr) || (slot.value > newest->value)))
{
newest = &slot;
}
}
if (newest == nullptr)
{
return false;
}
for (auto &slot : m_slots)
{
if (slot.pending && (slot.value <= newest->value))
{
slot.pending = false;
}
}
auto const size{std::min(Size, m_size)};
newest->data.invalidate(0, size);
std::memcpy(Data, newest->data.mapped(), static_cast<std::size_t>(size));
m_popped = newest->value;
m_poppedslot = static_cast<std::uint32_t>(newest - m_slots.data());
return true;
}
} // namespace vk14

View File

@@ -0,0 +1,156 @@
/*
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/.
*/
// the compute passes of the vulkan renderer (the stages P of the plan): a kernel is a compute shader of shaders/vk14 with its
// pipeline. its layout is the pass set of the scene (set 0, the persistent one, for the shaders which read the lights, the
// clusters, the shadow maps or the weather of the pass) and a pushed set of its own (set 1), whose bindings are read from the
// SPIR-V of the shader, with push constants for the parameters the opengl renderer gives its compute programs as loose
// uniforms. the data of the gpu read back by the cpu goes through a readback ring, a buffer seen by the cpu for each frame in
// flight, taken once the gpu is done with the frame
#pragma once
#include "rendering/vulkan14/vulkan14common.h"
#include "rendering/vulkan14/vulkan14descriptors.h"
#include "rendering/vulkan14/vulkan14memory.h"
#include "rendering/vulkan14/vulkan14shaders.h"
#include <optional>
namespace vk14
{
// what a template of a pushed set reads for each of its bindings: an image or a buffer, laid out alike
union descriptor
{
VkDescriptorImageInfo image;
VkDescriptorBufferInfo buffer;
};
static_assert(sizeof(VkDescriptorImageInfo) == sizeof(VkDescriptorBufferInfo), "the images and the buffers of the templates are laid out by one stride");
inline descriptor image_descriptor(VkSampler Sampler, VkImageView View, VkImageLayout Layout)
{
descriptor result;
result.image = VkDescriptorImageInfo{Sampler, View, Layout};
return result;
}
inline descriptor storage_image(VkImageView View)
{
descriptor result;
result.image = VkDescriptorImageInfo{VK_NULL_HANDLE, View, VK_IMAGE_LAYOUT_GENERAL};
return result;
}
inline descriptor buffer_descriptor(VkBuffer Buffer, VkDeviceSize Offset, VkDeviceSize Range)
{
descriptor result;
result.buffer = VkDescriptorBufferInfo{Buffer, Offset, Range};
return result;
}
// the bindings of the descriptor set Set the shader uses, from the decorations of its variables, in the order of the bindings
std::vector<VkDescriptorSetLayoutBinding> reflect_bindings(std::vector<std::uint32_t> const &Spirv, std::uint32_t Set, VkShaderStageFlags Stages);
// the set of the compute kernels pushed with their dispatches
constexpr std::uint32_t COMPUTE_PUSH_SET = 1;
// the push constants of the kernels at most, vulkan 1.4 guarantees them
constexpr std::uint32_t COMPUTE_PUSH_CONSTANTS = 256;
class compute_kernel
{
public:
compute_kernel() = default;
compute_kernel(compute_kernel const &) = delete;
compute_kernel &operator=(compute_kernel const &) = delete;
// the kernel of the shader: Passlayout is the layout of the pass set of the scene, set 0 of every kernel; the bindings of
// set 1 are those the shader declares. Specialization: the values of the constants 0, 1, 2 ...
bool create(VkDevice Device, pipeline_library &Pipelines, std::shared_ptr<shader_code const> const &Shader, VkDescriptorSetLayout Passlayout, std::vector<std::uint32_t> const &Specialization, std::string const &Name);
void destroy(VkDevice Device);
explicit operator bool() const
{
return m_pipeline != VK_NULL_HANDLE;
}
VkPipelineLayout layout() const
{
return m_layout;
}
std::size_t bindings() const
{
return m_count;
}
// the pipeline bound, the set pushed (Descriptors in the order of the bindings of the shader), the constants pushed (Size
// bytes) and the groups dispatched
void dispatch(command_stream *Commands, descriptor const *Descriptors, void const *Constants, std::uint32_t Size, std::uint32_t X, std::uint32_t Y = 1, std::uint32_t Z = 1) const;
template <typename Constants> void dispatch(command_stream *Commands, std::vector<descriptor> const &Descriptors, Constants const &Data, std::uint32_t X, std::uint32_t Y = 1, std::uint32_t Z = 1) const
{
static_assert(sizeof(Constants) <= COMPUTE_PUSH_CONSTANTS, "too many push constants");
dispatch(Commands, Descriptors.data(), &Data, static_cast<std::uint32_t>(sizeof(Constants)), X, Y, Z);
}
// the same without push constants
void run(command_stream *Commands, std::vector<descriptor> const &Descriptors, std::uint32_t X, std::uint32_t Y = 1, std::uint32_t Z = 1) const
{
dispatch(Commands, Descriptors.data(), nullptr, 0, X, Y, Z);
}
// the same with the groups taken from a buffer
void dispatch_indirect(command_stream *Commands, descriptor const *Descriptors, void const *Constants, std::uint32_t Size, VkBuffer Buffer, VkDeviceSize Offset) const;
private:
void bind(command_stream *Commands, descriptor const *Descriptors, void const *Constants, std::uint32_t Size) const;
push_set m_set;
VkPipelineLayout m_layout{VK_NULL_HANDLE};
VkPipeline m_pipeline{VK_NULL_HANDLE};
std::size_t m_count{0};
std::string m_name;
};
// a global memory barrier, between the passes which write and read buffers (the images have their own, vk14::barriers())
void memory_barrier(command_stream *Commands, VkPipelineStageFlags2 SrcStage, VkAccessFlags2 SrcAccess, VkPipelineStageFlags2 DstStage, VkAccessFlags2 DstAccess);
// the writes of the compute passes made seen by what follows: the compute passes, the draws, the indirect commands, the copies
void compute_barrier(command_stream *Commands);
// Size bytes of Data (a multiple of 4, at most 64 KiB) written to the buffer from Offset with the commands of the frame, between
// its reads by the shaders before and after. outside a rendering
void update_buffer(command_stream *Commands, buffer const &Target, void const *Data, VkDeviceSize Size, VkDeviceSize Offset);
// data copied from the gpu for the cpu: a buffer per frame in flight, the copy of a frame read once the gpu is done with it
class readback_ring
{
public:
bool create(allocator const &Allocator, VkDeviceSize Size, std::uint32_t Frames, std::string const &Name);
void destroy();
// the copy of Size bytes (at most the size of the ring) of Source from Offset, in the frame of the slot, done by the gpu once
// it reaches Value of the timeline
void push(command_stream *Commands, std::uint32_t Slot, std::uint64_t Value, VkBuffer Source, VkDeviceSize Offset, VkDeviceSize Size);
// the newest copy the gpu is done with (Completed: the value of the timeline it reached), copied to Data; false when there's
// none newer than the one taken last
bool pop(std::uint64_t Completed, void *Data, VkDeviceSize Size);
// the frame of the copy taken last, for the data which tell what they were made for
std::uint64_t popped_value() const
{
return m_popped;
}
std::uint32_t popped_slot() const
{
return m_poppedslot;
}
private:
struct slot
{
buffer data;
std::uint64_t value{0};
bool pending{false};
};
std::vector<slot> m_slots;
VkDeviceSize m_size{0};
std::uint64_t m_popped{0};
std::uint32_t m_poppedslot{0};
};
} // namespace vk14

View File

@@ -25,11 +25,23 @@ http://mozilla.org/MPL/2.0/.
#include "rendering/vulkan14/vulkan14imgui.h"
#include "rendering/vulkan14/vulkan14bindings.h"
#include "rendering/vulkan14/vulkan14geometry.h"
#include "rendering/vulkan14/vulkan14compute.h"
#include "rendering/vulkan14/vulkan14cull.h"
#include "rendering/vulkan14/vulkan14shadows.h"
#include "rendering/vulkan14/vulkan14terrain.h"
#include "rendering/vulkan14/vulkan14grass.h"
#include "rendering/vulkan14/vulkan14particles.h"
#include "rendering/vulkan14/vulkan14commands.h"
#include "utilities/workerpool.h"
#include "model/material.h"
#include "gl/ubo.h"
#include <array>
#include <atomic>
#include <condition_variable>
#include <memory>
#include <mutex>
#include <thread>
#include <unordered_map>
#include <vector>
@@ -42,8 +54,10 @@ enum class vertex_kind : std::uint8_t
none, // made by the vertex shader (the celestial bodies, the full screen quads)
scene, // gfx::basic_vertex and the user data, the geometry banks
colour, // position and colour, the sky dome
star, // direction with the magnitude and colour with the phase of the twinkling in one stream, the stars
particle, // position, colour and texture coordinates in one stream, the smoke
position_uv // position and texture coordinates in two streams, the precipitation
position_uv, // position and texture coordinates in two streams, the precipitation
particle_instance // three vec4 of gl::particle_draw for each instance, the particles of the gpu
};
// the push constants of the pipelines of the scene and of the post-processing (shaders/vk14/common.glsl, draw_constants_block)
@@ -65,6 +79,12 @@ struct scene_program : public gfx::shader_program
// the fragment shader of the passes without the velocity target (NO_MOTION_OUTPUT), made when it's first needed
std::string fragment_name;
std::shared_ptr<shader_code const> fragment_single;
// the vertex shader, and the definitions both stages were made with
std::string vertex_name;
std::string defines;
// the variant of the multi draws of the instances drawn from the gpu culling (MODEL_DRAWS), made when it's first needed
std::shared_ptr<scene_program> multidraw;
bool multidraw_tried{false};
vertex_kind vertices{vertex_kind::scene};
std::unordered_map<std::uint64_t, VkPipeline> pipelines;
};
@@ -76,6 +96,8 @@ struct target_formats
std::uint32_t colours{0};
VkFormat depth{VK_FORMAT_UNDEFINED};
VkSampleCountFlagBits samples{VK_SAMPLE_COUNT_1_BIT};
// the views drawn at once (multiview), 0: none
std::uint32_t view_mask{0};
// the number the pipelines of the targets are known by
std::uint32_t id{0};
};
@@ -102,6 +124,19 @@ struct viewport_targets
std::shared_ptr<image> output;
// the depth under the cursor of the editor: one sample of the depth of the ground resolved to it, and the buffer it's read back with
std::shared_ptr<image> cursor_depth;
// the depth of the opaque scene drawn before its colour, which the contact shadows read (the stage P3), and the formats of the
// pass drawing it (the depth alone)
std::shared_ptr<image> scene_depth;
target_formats depth_formats;
// the ambient occlusion (gfx.postfx.ao, the stage P3), half the size: the raw one and the smoothed one; made when it's on
std::shared_ptr<image> ssao;
std::shared_ptr<image> ssao_blur;
// the formats of the passes drawn over the colour of the scene after it (the ambient occlusion): the colour alone
target_formats post_formats;
// the bloom chain (gfx.postfx.bloom, the stage P1): the levels down, half the size each, and up, one fewer
std::vector<std::shared_ptr<image>> bloom_down;
std::vector<std::shared_ptr<image>> bloom_up;
std::vector<glm::ivec2> bloom_sizes;
target_formats formats;
target_formats output_formats;
bool srgb{true}; // the output encodes the colour (gfx.shadergamma no)
@@ -129,6 +164,16 @@ struct cluster_state
bool stale{true}; // the boxes have to be made anew
VkDeviceSize params_offset{0}; // in the ring of the frame, for the pass under way
std::uint32_t lights_total{0};
// the counters of the cullings read back, for the debug panel (gl::cluster_grid::cull_stats): entries of the index list in
// use, lights dropped from full clusters, the most of them seen
readback_ring counters_readback;
struct
{
std::uint32_t index_count{0};
std::uint32_t overflow_count{0};
std::uint32_t overflow_peak{0};
bool valid{false};
} stats;
};
// a picking or a reading of a value of the depth, waiting for the gpu to be done with the frame it was made in
@@ -140,6 +185,104 @@ struct readback
glm::ivec2 position{0};
};
// the light of the sky on the gpu (the stage P1, opengl46_sky): the buffers the shading reads (the directional ambient light,
// the parameters of the atmosphere), the look-up tables of the atmosphere with the kernels making them and the result read
// back, the environment map prefiltered with the GGX lobe, the stars
struct sky_state
{
buffer sky; // gl::sky_data
buffer atmosphere; // gl::atmosphere_ubs
buffer result; // gl::atmosphere_result, written by atmosphere_ambient.comp
readback_ring result_readback;
std::shared_ptr<image> transmittance;
std::shared_ptr<image> multiscattering;
std::shared_ptr<image> skyview;
bool tables_ready{false}; // the tables were made once (they are black before)
compute_kernel transmittance_kernel;
compute_kernel multiscattering_kernel;
compute_kernel skyview_kernel;
compute_kernel ambient_kernel;
// the prefiltered environment map, a cube view of each of its levels for the prefilter to write
std::shared_ptr<image> ggx;
std::vector<VkImageView> ggx_levels;
bool ggx_ready{false};
compute_kernel prefilter_kernel;
// what the buffers were last given
gl::sky_data uploaded{};
bool sky_uploaded{false};
// the dome drawn with the atmosphere, the stars of the catalogue
std::shared_ptr<scene_program> atmosphere_dome;
std::shared_ptr<scene_program> stars;
std::shared_ptr<buffer> star_vertices;
std::uint32_t star_count{0};
};
// the automatic exposure (the stage P1, opengl46_exposure): the histogram and the exposure on the gpu (gl::exposure_data), the
// kernels, the values read back for the debug window
struct exposure_state
{
buffer data;
compute_kernel histogram;
compute_kernel average;
readback_ring readback;
float values[4]{1.f, 1.f, 1.f, 0.f};
bool cleared{false};
};
// the weather on the gpu (the stage P2, opengl46_weather): the noises of the clouds, the map of their shadows, the clouds of the
// main view and their history, the panorama of the clouds for the other views, the fog volume of the main view and its
// history, with the kernels making them, and the program laying the clouds and the fog over the sky
struct weather_resources
{
std::shared_ptr<image> shape_noise;
std::shared_ptr<image> detail_noise;
std::shared_ptr<image> coverage_noise;
bool noise_made{false};
std::shared_ptr<image> shadow_map;
int shadow_size{0};
// the clouds of the main view, at a part of its resolution
glm::ivec2 view_size{0};
std::shared_ptr<image> clouds_color;
std::shared_ptr<image> clouds_depth;
std::array<std::shared_ptr<image>, 2> clouds_history;
int clouds_current{0}; // the history holding the latest result
std::shared_ptr<image> panorama;
glm::ivec2 panorama_size{0};
// what the panorama is marched with as its depth, which it doesn't write
std::shared_ptr<image> depth_dummy;
// the fog volume: the scattering of the cells and its history, the fog integrated along the view
glm::ivec3 fog_grid{0};
std::array<std::shared_ptr<image>, 2> fog_scattering;
int fog_current{0};
std::shared_ptr<image> fog_integrated;
compute_kernel noise;
compute_kernel march;
compute_kernel temporal;
compute_kernel shadow;
compute_kernel horizon;
compute_kernel inject;
compute_kernel integrate;
std::shared_ptr<scene_program> sky;
};
// the gpu culling of the instances (the stage P5, Global.gfx_gpucull): the cullings of the passes, which share the records and
// the kernels with the one of the main view, and the depth pyramid of its occlusion test
struct cull_resources
{
instance_cull main;
std::array<instance_cull, 3> cascades;
instance_cull reflections;
// the maps of headlights, by the slot of the map, and the scenery parts of the maps of lamps, by their order among those drawn
// in a frame: the passes of a frame don't share the buffers of a culling. made when first needed
std::vector<std::unique_ptr<instance_cull>> headlights;
std::vector<std::unique_ptr<instance_cull>> lamps;
hiz_pyramid hiz;
bool ready{false};
// the cascades of the sun shadows fitted to the depth of the main view (gfx.shadow.sdsm), made when they're first needed
shadow_fitter shadowfit;
bool shadowfit_tried{false};
};
// what the scene is drawn with
struct scene_state
{
@@ -163,6 +306,8 @@ struct scene_state
};
std::array<VkDescriptorSet, set_count> pass_sets{};
bool pass_sets_stale{true};
// the pass sets were made anew this many times (the ones of the cullings follow them)
std::uint64_t pass_generation{0};
// the rings of the draws: the blocks of the models (gl::model_ubs) and the modelview matrices of the instances
window_ring draws;
window_ring instances;
@@ -178,10 +323,48 @@ struct scene_state
std::array<VkImageView, 6> env_faces{};
std::shared_ptr<image> pick_colour;
std::shared_ptr<image> pick_depth;
// the stand-ins of the images of the stages P the pass sets hold until there are real ones: a volume (the fog volume, the noises
// of the clouds), the cube shadow maps of the lamps, the shadow maps of the headlights, the depth of the scene
std::shared_ptr<image> volume_empty;
std::shared_ptr<image> cube_shadow_empty;
std::shared_ptr<image> headlight_shadow_empty;
std::shared_ptr<image> depth_empty;
// the colours of the fog towards the horizon (the stage P2), a block of the pass sets
buffer weather_horizon;
VkImageView headlights_view{VK_NULL_HANDLE}; // the one the pass sets were made with
VkSampler headlights_sampler{VK_NULL_HANDLE};
// the views of the images of the bindings of each pass set it was made with (vulkan14scene.cpp, pass_set_images())
std::array<std::array<VkImageView, bindings::PASS_BINDING_COUNT>, 3> pass_images{};
cluster_state clusters;
// the ambient occlusion: the noise of the older ssao, the kernels of the ground truth one, the program applying it
std::shared_ptr<image> ssao_noise;
compute_kernel gtao_main;
compute_kernel gtao_denoise;
std::shared_ptr<scene_program> ao_apply;
sky_state sky;
exposure_state exposure;
weather_resources weather;
cull_resources cull;
// the shadow maps of the lamps and the headlights (the stage P4), and the programs drawing the cube maps
local_shadow_maps lamps;
std::shared_ptr<scene_program> cube_shadow, cube_alpha_shadow;
target_formats cube_formats;
// the materials of the heightmap terrain (the stage P6), made with the bank of the textures
std::unique_ptr<terrain_materials> terrain;
// the grass of the heightmap terrain, and its program, made when it's first drawn
grass_field grass;
std::shared_ptr<scene_program> grass_program;
bool grass_tried{false};
// the smoke and the rain or snow of the gpu
gpu_particles particles;
// the heatmap of the light clusters (gfx.lights.clusterheatmap), made when it's first drawn
std::shared_ptr<scene_program> cluster_heatmap;
bool cluster_heatmap_tried{false};
// the targets of the viewports, [0] the main window
std::vector<viewport_targets> targets;
// the format of the levels of the bloom chains: no alpha, no negative values, half the memory of rgba16f, where the device
// draws to it
VkFormat bloom_format{VK_FORMAT_B10G11R11_UFLOAT_PACK32};
// the formats of the passes which don't depend on the viewport
target_formats shadow_formats;
target_formats reflection_formats;
@@ -190,6 +373,9 @@ struct scene_state
// the programs: of the materials (by the name Fetch_Shader() is given), and the ones of the renderer
std::unordered_map<std::string, std::shared_ptr<scene_program>> materials;
std::shared_ptr<scene_program> shadow, alpha_shadow, pick, pick_surface, line, billboard, freespot, celestial, skydome, smoke, precipitation;
// the depth prepass of the scene (the stage P3): without and with the alpha test, and which of them a program of a material takes
std::shared_ptr<scene_program> prepass, prepass_alpha;
std::unordered_map<gfx::shader_program const *, int> prepass_classes;
// the post-processing, by the fragment shader
std::unordered_map<std::string, std::shared_ptr<shader_code const>> post_fragments;
std::shared_ptr<shader_code const> quad;
@@ -225,7 +411,20 @@ struct vulkan14_renderer::context
VkCommandPool pool{VK_NULL_HANDLE};
// the uploads of the frame go to the queue first, then the commands of the frame
VkCommandBuffer uploads{VK_NULL_HANDLE};
VkCommandBuffer commands{VK_NULL_HANDLE};
// the calls made while the frame is made, recorded into the command buffers once it's done (M1): the uploads into theirs,
// the commands chunk by chunk by the workers
vk14::command_stream upload_stream;
vk14::command_stream commands;
// a command pool for each worker, with the command buffers it made
struct worker_commands
{
VkCommandPool pool{VK_NULL_HANDLE};
std::vector<VkCommandBuffer> buffers;
std::size_t used{0};
};
std::vector<worker_commands> workers;
// the command buffers of the chunks of the frame, in their order
std::vector<VkCommandBuffer> chunks;
// the value of the timeline semaphore the gpu reaches when it's done with the frame; 0: never submitted
std::uint64_t done{0};
// the number of the frame, for the report of a lost device
@@ -262,7 +461,35 @@ struct vulkan14_renderer::context
bool devicelost{false};
// the frame being recorded: its index among the frames in flight, its commands
std::uint32_t slot{0};
VkCommandBuffer commands{VK_NULL_HANDLE};
vk14::command_stream *commands{nullptr};
// the threads recording the command buffers of the frames: a pool of its own, the one of the application is for the simulation
worker_pool workers;
// the cpu time the command buffers of the last frame took to record, in milliseconds; its calls and chunks
std::atomic<double> record_time{0.0};
std::atomic<std::size_t> record_calls{0};
std::atomic<std::size_t> record_chunks{0};
std::atomic<std::size_t> record_threads{0};
// what's left of a frame made by the main thread: recording its command buffers, submitting them, presenting its windows
struct render_job
{
frame *target{nullptr};
bool uploaded{false};
std::vector<VkSemaphoreSubmitInfo> waits;
std::vector<VkSemaphoreSubmitInfo> signals;
// the windows presented once the frame is submitted (the render thread)
std::vector<vk14::swapchain *> windows;
};
// the render thread and the job it's given
std::thread render_thread;
std::mutex render_mutex;
std::condition_variable render_wake;
std::condition_variable render_idle;
render_job render_pending;
bool render_queued{false}; // render_pending holds a job not done yet
bool render_stop{false};
char const *render_failure{nullptr};
// how long the main thread waited for the render thread at the start of the last frame, in milliseconds
double render_wait{0.0};
~context()
{
@@ -270,6 +497,26 @@ struct vulkan14_renderer::context
}
bool create_frames(std::uint32_t const Count, std::string &Reason);
// the streams of Frame recorded into its command buffers; false: no command buffer for them
bool record(frame &Frame, bool Uploads);
// the job done; false: Failure says where it failed
bool execute(render_job &Job, char const *&Failure);
// the windows of the frame submitted last presented; false: the device or a surface is lost
bool present(std::vector<vk14::swapchain *> const &Windows);
// the render thread (gfx.vulkan.renderthread, M4): the frames are recorded, submitted and presented by it while the main
// thread goes on with the simulation of the next one. the main thread waits for it to be done before it begins a frame and
// before anything else touches the queue, the swapchains or the device as a whole
void start_render_thread();
void stop_render_thread();
bool render_threaded() const
{
return render_thread.joinable();
}
// the job given to the render thread
void hand_over(render_job &&Job);
// waits until the render thread is done with the jobs given to it; where it failed, if it did
char const *drain();
// the parts of the renderer made once the device is there
bool create_core(std::uint32_t const Frames, std::string &Reason);
// the value of the timeline the gpu reached, 0 when it can't be read
@@ -282,7 +529,7 @@ struct vulkan14_renderer::context
// the state of the draws of the walk being recorded into a pass of the scene
struct vulkan14_renderer::draw_state
{
VkCommandBuffer commands{VK_NULL_HANDLE};
vk14::command_stream *commands{nullptr};
vk14::target_formats const *targets{nullptr};
VkExtent2D extent{0, 0};
vk14::scene_state::pass_set_kind set{vk14::scene_state::set_plain};
@@ -292,6 +539,7 @@ struct vulkan14_renderer::draw_state
std::uint32_t scene_offset{0};
std::uint32_t light_offset{0};
std::uint32_t cluster_offset{0};
std::uint32_t weather_offset{0};
bool set_dirty{true};
// the program and the textures for the next draw (walk_material(), walk_program(), walk_texture())
vk14::scene_program *program{nullptr};
@@ -319,4 +567,20 @@ struct vulkan14_renderer::draw_state
std::uint32_t query{~0u}; // the query under way
// the draws made in the pass, for the debug panel
std::uint32_t draws{0};
// the depth of the scene before its colour (the stage P3): the walk draws with the programs of the depth prepass, and what they
// don't draw is left out (depthskip, set by walk_material())
bool depthpass{false};
bool depthskip{true};
// the pass sets of a culling, the instance buffer its output, for the indirect draws of its instances; null: the pass sets
VkDescriptorSet const *cull_sets{nullptr};
// the rendering under way, begun again where it was after it was ended for the compute passes of the culling
struct rendering_resume
{
std::array<VkRenderingAttachmentInfo, 2> colours{};
VkRenderingAttachmentInfo depth{};
bool has_depth{false};
VkRenderingInfo info{};
std::vector<vk14::image_use> uses;
bool valid{false};
} resume;
};

View File

@@ -0,0 +1,604 @@
/*
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/vulkan14cull.h"
#include "rendering/vulkan14/vulkan14commands.h"
#include "rendering/frustum.h"
#include "utilities/Globals.h"
namespace vk14
{
namespace
{
constexpr VkDeviceSize RECORD_SIZE{sizeof(scene::basic_cell::instance_record)};
// the counters read back: the header and the entries compared
constexpr VkDeviceSize READBACK_SIZE{(gl::INSTANCE_COUNTER_HEADER + 16384) * sizeof(std::uint32_t)};
// the parameters of instance_cull.comp, its kernel_block (std140)
struct cull_block
{
glm::mat4 view;
glm::vec4 planes[6];
glm::vec4 distance_params;
glm::vec4 size_params;
glm::mat4 hiz_projection;
glm::ivec4 hiz_params;
glm::vec4 hiz_screen;
glm::uvec4 phases;
};
static_assert(sizeof(cull_block) == 304, "the block of the culling doesn't match the shader");
// the parameters of hiz_build.comp
struct hiz_constants
{
glm::ivec4 sizes;
glm::ivec4 params;
};
// what read and wrote the buffers of the culling before (the draws of the passes before, the copies for the cpu, the kernels) is
// done before they're written again
void before_writes(command_stream *Commands)
{
memory_barrier(Commands,
VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_2_DRAW_INDIRECT_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_2_COPY_BIT | VK_PIPELINE_STAGE_2_CLEAR_BIT,
VK_ACCESS_2_SHADER_STORAGE_READ_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT | VK_ACCESS_2_INDIRECT_COMMAND_READ_BIT | VK_ACCESS_2_TRANSFER_READ_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT,
VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_2_COPY_BIT | VK_PIPELINE_STAGE_2_CLEAR_BIT,
VK_ACCESS_2_SHADER_STORAGE_READ_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT);
}
} // namespace
instance_cull::~instance_cull() = default;
bool instance_cull::init(cull_environment const &Environment, VkDevice Device, pipeline_library &Pipelines, shader_library &Shaders, VkDescriptorSetLayout Passlayout, instance_cull const *Shared)
{
if (Shared != nullptr)
{
m_shared = Shared->m_shared;
share_records(*Shared);
}
else
{
auto shared{std::make_shared<shared_data>()};
shared->environment = Environment;
try
{
if ((false == shared->cull.create(Device, Pipelines, Shaders.get("instance_cull.comp"), Passlayout, {}, "instance cull")) ||
(false == shared->offsets.create(Device, Pipelines, Shaders.get("instance_cull_offsets.comp"), Passlayout, {}, "instance cull offsets")) ||
(false == shared->commands.create(Device, Pipelines, Shaders.get("instance_cull_commands.comp"), Passlayout, {}, "instance cull commands")))
{
return false;
}
}
catch (gfx::shader_exception const &Exception)
{
log_error(Exception.what());
return false;
}
shared->records = std::make_shared<buffer>();
if (false == shared->records->create(*Environment.memory, 65536 * RECORD_SIZE, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, memory_kind::device, "instance records"))
{
return false;
}
m_shared = std::move(shared);
}
auto const &environment{m_shared->environment};
return reserve(m_counters, READBACK_SIZE, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, "instance counters") &&
reserve(m_output, 65536 * sizeof(glm::mat4), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, "instance output") &&
reserve(m_commands, 3 * 4096 * sizeof(gl::instance_draw_command), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, "instance commands") &&
m_readback.create(*environment.memory, READBACK_SIZE, environment.frames, "instance counters readback");
}
void instance_cull::destroy(VkDevice Device)
{
if (m_shared && (m_shared.use_count() == 1))
{
m_shared->cull.destroy(Device);
m_shared->offsets.destroy(Device);
m_shared->commands.destroy(Device);
}
m_shared.reset();
m_counters.reset();
m_output.reset();
m_commands.reset();
m_readback.destroy();
if (set_pool != VK_NULL_HANDLE)
{
vkDestroyDescriptorPool(Device, set_pool, nullptr);
set_pool = VK_NULL_HANDLE;
}
sets = {};
}
void instance_cull::begin_frame(std::uint32_t const Slot, std::uint64_t const Value, std::uint64_t const Completed)
{
if (!m_shared)
{
return;
}
auto &environment{m_shared->environment};
environment.slot = Slot;
environment.value = Value;
environment.completed = Completed;
}
bool instance_cull::reserve(std::shared_ptr<buffer> &Buffer, VkDeviceSize const Size, VkBufferUsageFlags const Usage, char const *Name)
{
if (Buffer && (Size <= Buffer->size()))
{
return true;
}
auto const &environment{m_shared->environment};
auto replacement{std::make_shared<buffer>()};
if (false == replacement->create(*environment.memory, std::max<VkDeviceSize>(Size, Buffer ? 2 * Buffer->size() : 0), Usage, memory_kind::device, Name))
{
log_error(std::string("no memory for the buffer of the culling of the instances: ") + Name);
return false;
}
if (Buffer)
{
// the frames in flight may still read it
environment.deletions->push(environment.uploads->current_value(), [old = std::move(Buffer)]() {});
}
Buffer = std::move(replacement);
return true;
}
std::int64_t instance_cull::record_buffer_size() const
{
return (m_shared && m_shared->records) ? static_cast<std::int64_t>(m_shared->records->size()) : 0;
}
void instance_cull::resize_record_buffer(std::int64_t const Size)
{
auto &shared{*m_shared};
auto const &environment{shared.environment};
auto replacement{std::make_shared<buffer>()};
if (false == replacement->create(*environment.memory, static_cast<VkDeviceSize>(Size), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, memory_kind::device, "instance records"))
{
log_error("no memory for the records of the instances");
return;
}
// the passes culled before in the frame keep the old buffer, with its records
environment.deletions->push(environment.uploads->current_value(), [old = std::move(shared.records)]() {});
shared.records = std::move(replacement);
shared.pending.clear();
shared.parts.clear();
}
void instance_cull::restart_records()
{
// the records of the passes culled before in the frame go to the gpu before the frame (the uploads), where the records sent from
// the start of the buffer again would take their places
resize_record_buffer(record_buffer_size());
}
void instance_cull::send_records(std::uint32_t const First, scene::basic_cell::instance_record const *Records, std::uint32_t const Count)
{
if ((Count == 0) || !m_shared)
{
return;
}
auto &shared{*m_shared};
auto const *data{reinterpret_cast<std::uint8_t const *>(Records)};
shared.pending.insert(shared.pending.end(), data, data + Count * RECORD_SIZE);
shared.parts.emplace_back(First * RECORD_SIZE, Count * RECORD_SIZE);
}
void instance_cull::flush_records()
{
auto &shared{*m_shared};
if (shared.pending.empty())
{
return;
}
if (false == shared.environment.uploads->upload_buffer(shared.records, shared.pending.data(), shared.pending.size(), shared.parts))
{
log_error("no staging memory for the records of the instances");
}
shared.pending.clear();
shared.parts.clear();
}
void instance_cull::push_counts()
{
// copied with the last stage of the culling (read_counters())
}
bool instance_cull::pop_counts(std::uint32_t *Counts)
{
if (!m_shared)
{
return false;
}
return m_readback.pop(m_shared->environment.completed, Counts, READBACK_SIZE);
}
void instance_cull::read_counters(command_stream *Commands)
{
auto const &environment{m_shared->environment};
m_readback.push(Commands, environment.slot, environment.value, m_counters->handle(), 0, READBACK_SIZE);
}
std::vector<descriptor> instance_cull::cull_descriptors(cull_occlusion const *Occlusion) const
{
auto const &environment{m_shared->environment};
auto const slice = [](ring_slice const &Slice) { return buffer_descriptor(Slice.buffer, Slice.offset, Slice.size); };
return {buffer_descriptor(m_shared->records->handle(), 0, VK_WHOLE_SIZE),
slice(m_jobs),
slice(m_groups),
slice(m_lods),
buffer_descriptor(m_counters->handle(), 0, VK_WHOLE_SIZE),
buffer_descriptor(m_output->handle(), 0, VK_WHOLE_SIZE),
((Occlusion != nullptr) && (Occlusion->pyramid != VK_NULL_HANDLE)) ? image_descriptor(Occlusion->sampler, Occlusion->pyramid, VK_IMAGE_LAYOUT_GENERAL) : environment.no_pyramid,
slice(m_block)};
}
void instance_cull::dispatch(command_stream *Commands, glm::mat4 const &View, cull_occlusion const *Occlusion, bool const Twophase)
{
if (!m_shared || (false == dispatch_begin()))
{
return;
}
auto &shared{*m_shared};
auto const &environment{shared.environment};
flush_records();
m_countersize = (gl::INSTANCE_COUNTER_HEADER + 3 * m_entries) * sizeof(std::uint32_t);
if ((false == reserve(m_counters, std::max(m_countersize, READBACK_SIZE), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, "instance counters")) ||
(false == reserve(m_output, static_cast<VkDeviceSize>(std::max<std::uint32_t>(m_candidates, 1)) * sizeof(glm::mat4), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, "instance output")))
{
return;
}
// the data of the pass in the ring of the frame
auto const place = [&](ring_slice &Slice, void const *Data, std::size_t const Size, VkDeviceSize const Alignment) {
Slice = environment.ring->allocate(std::max<VkDeviceSize>(Size, 16), Alignment);
if (Slice && (Size > 0))
{
std::memcpy(Slice.data, Data, Size);
}
return bool(Slice);
};
cull_block block{};
block.view = View;
frustum_planes(block.planes);
block.distance_params = glm::vec4{m_setup.distancescale, m_setup.distanceoffset, m_setup.distancerange, m_setup.drawrange};
block.size_params = glm::vec4{m_setup.zoom, 0.f, 0.f, 0.f};
// the occlusion test only when the instances are drawn from the result, the cpu doesn't make it
m_occlusion = (m_setup.drawing && (Occlusion != nullptr) && (Occlusion->pyramid != VK_NULL_HANDLE));
if (m_occlusion)
{
block.hiz_projection = Occlusion->projection;
block.hiz_params = glm::ivec4{1, Occlusion->levels, Occlusion->size.x, Occlusion->size.y};
block.hiz_screen = glm::vec4{Occlusion->screen, 0.f, 0.f};
}
m_twophase = (m_occlusion && Twophase);
block.phases = glm::uvec4{m_twophase ? 1u : 0u, 0u, 0u, 0u};
if ((false == place(m_jobs, m_joblist.data(), m_joblist.size() * sizeof(gl::instance_cull_job), environment.storage_alignment)) ||
(false == place(m_groups, m_grouplist.data(), m_grouplist.size() * sizeof(gl::instance_cull_group), environment.storage_alignment)) ||
(false == place(m_lods, m_lodlist.data(), m_lodlist.size() * sizeof(float), environment.storage_alignment)) || (false == place(m_block, &block, sizeof(block), environment.uniform_alignment)))
{
log_error("the ring of the frame is full, the instances aren't culled");
m_occlusion = false;
m_twophase = false;
return;
}
// counts and cursors start at zero
before_writes(Commands);
std::uint32_t const header[gl::INSTANCE_COUNTER_HEADER]{m_setup.frame, 0, m_entries, 0, 0, 0, 0, 0};
Commands->update_buffer(m_counters->handle(), 0, sizeof(header), header);
Commands->fill_buffer(m_counters->handle(), sizeof(header), m_countersize - sizeof(header), 0);
memory_barrier(Commands, VK_PIPELINE_STAGE_2_CLEAR_BIT | VK_PIPELINE_STAGE_2_COPY_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);
// one invocation per record; past the count limit of one dimension the groups go into rows
auto const groups{(m_candidates + gl::INSTANCE_CULL_GROUP_SIZE - 1) / gl::INSTANCE_CULL_GROUP_SIZE};
auto const groups_x{std::min<std::uint32_t>(groups, 65535)};
auto const groups_y{(groups + groups_x - 1) / groups_x};
auto const descriptors{cull_descriptors(Occlusion)};
auto const jobs{static_cast<std::uint32_t>(m_joblist.size())};
shared.cull.dispatch(Commands, descriptors, glm::uvec4{jobs, m_candidates, m_entries, 0u}, groups_x, groups_y);
compute_barrier(Commands);
shared.offsets.dispatch(Commands, {buffer_descriptor(m_counters->handle(), 0, VK_WHOLE_SIZE)}, glm::uvec4{m_entries, 0u, 0u, 0u}, 1);
compute_barrier(Commands);
shared.cull.dispatch(Commands, descriptors, glm::uvec4{jobs, m_candidates, m_entries, 1u}, groups_x, groups_y);
// the output and the counts go to draws, indirect draws and the copy for the cpu
compute_barrier(Commands);
if (false == m_twophase)
{
read_counters(Commands);
}
dispatch_end();
}
void instance_cull::dispatch_second(command_stream *Commands, cull_occlusion const &Occlusion)
{
if (!m_dispatched || !m_twophase)
{
return;
}
auto &shared{*m_shared};
// the draws of the first phase read the output and the commands the second phase writes (other slots of them)
memory_barrier(Commands, VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_2_DRAW_INDIRECT_BIT, VK_ACCESS_2_SHADER_STORAGE_READ_BIT | VK_ACCESS_2_INDIRECT_COMMAND_READ_BIT,
VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, VK_ACCESS_2_SHADER_STORAGE_READ_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT);
auto const groups{(m_candidates + gl::INSTANCE_CULL_GROUP_SIZE - 1) / gl::INSTANCE_CULL_GROUP_SIZE};
auto const groups_x{std::min<std::uint32_t>(groups, 65535)};
auto const groups_y{(groups + groups_x - 1) / groups_x};
shared.cull.dispatch(Commands, cull_descriptors(&Occlusion), glm::uvec4{static_cast<std::uint32_t>(m_joblist.size()), m_candidates, m_entries, 2u}, groups_x, groups_y);
compute_barrier(Commands);
if (m_commandcount > 0)
{
shared.commands.dispatch(Commands, {buffer_descriptor(m_counters->handle(), 0, VK_WHOLE_SIZE), buffer_descriptor(m_commands->handle(), 0, VK_WHOLE_SIZE)}, glm::uvec4{m_commandcount, m_entries, 1u, 0u},
(m_commandcount + gl::INSTANCE_COMMAND_GROUP_SIZE - 1) / gl::INSTANCE_COMMAND_GROUP_SIZE);
}
// the commands go to indirect draws, the matrices to the vertex shaders, the counts to the copy for the cpu
compute_barrier(Commands);
read_counters(Commands);
}
void instance_cull::fill_commands(command_stream *Commands, std::vector<gl::instance_draw_command> const &Commands_)
{
m_commandcount = static_cast<std::uint32_t>(Commands_.size());
if (!m_dispatched || Commands_.empty())
{
return;
}
auto &shared{*m_shared};
auto const &environment{shared.environment};
// the first copy is sent, the others are made from it on the gpu
auto const size{static_cast<VkDeviceSize>(Commands_.size() * sizeof(gl::instance_draw_command))};
if (false == reserve(m_commands, 3 * size, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, "instance commands"))
{
m_commandcount = 0;
return;
}
before_writes(Commands);
auto const slice{environment.ring->allocate(size, 16)};
if (slice)
{
std::memcpy(slice.data, Commands_.data(), static_cast<std::size_t>(size));
VkBufferCopy2 region{};
region.sType = VK_STRUCTURE_TYPE_BUFFER_COPY_2;
region.srcOffset = slice.offset;
region.dstOffset = 0;
region.size = size;
VkCopyBufferInfo2 copy{};
copy.sType = VK_STRUCTURE_TYPE_COPY_BUFFER_INFO_2;
copy.srcBuffer = slice.buffer;
copy.dstBuffer = m_commands->handle();
copy.regionCount = 1;
copy.pRegions = &region;
Commands->copy_buffer2(&copy);
}
else
{
// the ring of the frame is full: the commands written in pieces with the commands of the frame
auto const *data{reinterpret_cast<std::uint8_t const *>(Commands_.data())};
for (VkDeviceSize offset = 0; offset < size; offset += 65536)
{
Commands->update_buffer(m_commands->handle(), offset, std::min<VkDeviceSize>(65536, size - offset), data + offset);
}
}
memory_barrier(Commands, VK_PIPELINE_STAGE_2_COPY_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);
shared.commands.dispatch(Commands, {buffer_descriptor(m_counters->handle(), 0, VK_WHOLE_SIZE), buffer_descriptor(m_commands->handle(), 0, VK_WHOLE_SIZE)}, glm::uvec4{m_commandcount, m_entries, 0u, 0u},
(m_commandcount + gl::INSTANCE_COMMAND_GROUP_SIZE - 1) / gl::INSTANCE_COMMAND_GROUP_SIZE);
// the commands go to indirect draws, the matrices to the vertex shaders
compute_barrier(Commands);
}
// the depth pyramid
bool hiz_pyramid::init(VkDevice Device, pipeline_library &Pipelines, shader_library &Shaders, VkDescriptorSetLayout Passlayout)
{
try
{
return m_kernel.create(Device, Pipelines, Shaders.get("hiz_build.comp"), Passlayout, {}, "hi-z");
}
catch (gfx::shader_exception const &Exception)
{
log_error(Exception.what());
return false;
}
}
void hiz_pyramid::destroy(VkDevice Device, deletion_queue *Deletions, std::uint64_t const Value)
{
if (Deletions != nullptr)
{
// the frames in flight may still read them
Deletions->push(Value, [Device, levels = std::move(m_levels), pyramid = std::move(m_pyramid)]() {
for (auto *view : levels)
{
vkDestroyImageView(Device, view, nullptr);
}
});
}
else
{
for (auto *view : m_levels)
{
vkDestroyImageView(Device, view, nullptr);
}
m_kernel.destroy(Device);
m_pyramid.reset();
}
m_levels.clear();
m_pyramid.reset();
m_sourcesize = glm::ivec2{0};
m_size = glm::ivec2{0};
}
bool hiz_pyramid::build(command_stream *Commands, VkDevice Device, allocator &Memory, deletion_queue &Deletions, std::uint64_t const Value, VkImageView const Depth, VkSampler const Sampler, int const Width, int const Height)
{
if ((Width <= 0) || (Height <= 0) || (false == bool(m_kernel)))
{
return false;
}
if ((m_sourcesize != glm::ivec2{Width, Height}) || (false == bool(m_pyramid)))
{
if (m_pyramid)
{
destroy(Device, &Deletions, Value);
}
m_sourcesize = glm::ivec2{Width, Height};
m_size = glm::ivec2{(Width + 1) / 2, (Height + 1) / 2};
std::uint32_t levels{1};
while ((std::max(m_size.x, m_size.y) >> levels) > 0)
{
++levels;
}
image_desc desc;
desc.format = VK_FORMAT_R32_SFLOAT;
desc.extent = {static_cast<std::uint32_t>(m_size.x), static_cast<std::uint32_t>(m_size.y), 1};
desc.mips = levels;
desc.usage = VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
m_pyramid = std::make_shared<image>();
if (false == m_pyramid->create(Memory, desc, "hi-z pyramid"))
{
m_pyramid.reset();
m_sourcesize = glm::ivec2{0};
return false;
}
for (std::uint32_t level = 0; level < levels; ++level)
{
VkImageViewCreateInfo info{};
info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
info.image = m_pyramid->handle();
info.viewType = VK_IMAGE_VIEW_TYPE_2D;
info.format = desc.format;
info.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, level, 1, 0, 1};
VkImageView view{VK_NULL_HANDLE};
VK14_VERIFY(vkCreateImageView(Device, &info, nullptr, &view));
m_levels.push_back(view);
}
// the pyramid stays in the general layout: the kernel reads the levels it wrote, the culling all of them
transition(Commands, *m_pyramid, VK_IMAGE_LAYOUT_GENERAL, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT | VK_ACCESS_2_SHADER_SAMPLED_READ_BIT);
}
else
{
// the culling done with the pyramid made before
memory_barrier(Commands, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT);
}
auto source{Depth};
auto sourcelayout{VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL};
auto sourcesize{m_sourcesize};
for (std::size_t level = 0; level < m_levels.size(); ++level)
{
auto const targetsize{glm::max(m_size >> static_cast<int>(level), glm::ivec2{1})};
hiz_constants constants{glm::ivec4{sourcesize, targetsize}, glm::ivec4{0}};
m_kernel.dispatch(Commands, {image_descriptor(Sampler, source, sourcelayout), storage_image(m_levels[level])}, constants, (targetsize.x + gl::HIZ_GROUP_SIZE - 1) / gl::HIZ_GROUP_SIZE,
(targetsize.y + gl::HIZ_GROUP_SIZE - 1) / gl::HIZ_GROUP_SIZE);
memory_barrier(Commands, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT);
source = m_levels[level];
sourcelayout = VK_IMAGE_LAYOUT_GENERAL;
sourcesize = targetsize;
}
return true;
}
cull_occlusion hiz_pyramid::occlusion(VkSampler const Sampler, glm::mat4 const &Projection) const
{
cull_occlusion result;
if (false == bool(m_pyramid))
{
return result;
}
result.pyramid = m_pyramid->view();
result.sampler = Sampler;
result.size = m_size;
result.levels = static_cast<int>(m_levels.size());
result.screen = glm::vec2{m_sourcesize};
result.projection = Projection;
return result;
}
// the cascades fitted to the depth of the view
namespace
{
struct fit_depth_constants
{
glm::mat4 inv_projection;
};
struct fit_bounds_constants
{
glm::mat4 inv_projection;
glm::mat4 view_to_light;
glm::vec4 split_params;
glm::vec4 margin_params;
};
} // namespace
bool shadow_fitter::init(VkDevice Device, pipeline_library &Pipelines, shader_library &Shaders, VkDescriptorSetLayout Passlayout, allocator const &Memory, std::uint32_t const Frames)
{
try
{
if ((false == m_depthkernel.create(Device, Pipelines, Shaders.get("shadow_fit_depth.comp"), Passlayout, {}, "shadow fit depth")) ||
(false == m_boundskernel.create(Device, Pipelines, Shaders.get("shadow_fit_bounds.comp"), Passlayout, {}, "shadow fit bounds")))
{
return false;
}
}
catch (gfx::shader_exception const &Exception)
{
log_error(Exception.what());
return false;
}
return m_buffer.create(Memory, sizeof(gl::shadow_fit_data), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, memory_kind::device, "shadow fit") &&
m_readback.create(Memory, sizeof(gl::shadow_fit_data), Frames, "shadow fit readback");
}
void shadow_fitter::destroy(VkDevice Device)
{
m_depthkernel.destroy(Device);
m_boundskernel.destroy(Device);
m_buffer.destroy();
m_readback.destroy();
}
void shadow_fitter::measure(command_stream *Commands, VkImageView const Depth, VkSampler const Sampler, int const Width, int const Height, view const &View, unsigned int const Frame, std::uint32_t const Slot,
std::uint64_t const Value)
{
if ((Width <= 0) || (Height <= 0) || (View.range <= 0.f) || (false == bool(*this)))
{
return;
}
auto const data{begin_measurement(View, Frame)};
update_buffer(Commands, m_buffer, &data, sizeof(data), 0);
auto const inverseprojection{glm::inverse(View.projection)};
auto const viewtolight{glm::mat4{View.light * glm::inverse(View.rotation)}};
auto const groupsx{(static_cast<std::uint32_t>(Width) + gl::SHADOW_FIT_GROUP_SIZE - 1) / gl::SHADOW_FIT_GROUP_SIZE};
auto const groupsy{(static_cast<std::uint32_t>(Height) + gl::SHADOW_FIT_GROUP_SIZE - 1) / gl::SHADOW_FIT_GROUP_SIZE};
std::vector<descriptor> const descriptors{image_descriptor(Sampler, Depth, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL), buffer_descriptor(m_buffer.handle(), 0, VK_WHOLE_SIZE)};
m_depthkernel.dispatch(Commands, descriptors, fit_depth_constants{inverseprojection}, groupsx, groupsy);
compute_barrier(Commands);
m_boundskernel.dispatch(Commands, descriptors, fit_bounds_constants{inverseprojection, viewtolight, bounds_params(View), margin_params()}, groupsx, groupsy);
compute_barrier(Commands);
m_readback.push(Commands, Slot, Value, m_buffer.handle(), 0, sizeof(gl::shadow_fit_data));
}
shadow_fitter::result const *shadow_fitter::latest(std::uint64_t const Completed)
{
gl::shadow_fit_data data;
if (m_readback.pop(Completed, &data, sizeof(data)))
{
arrived(data);
}
return gfx::shadow_fit::latest();
}
} // namespace vk14

View File

@@ -0,0 +1,196 @@
/*
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/.
*/
// the gpu culling of the instances in the vulkan renderer (the stage P5 of the plan): the buffers and the compute passes of
// gfx::instance_cull, the opengl46_instance_cull of this renderer, with the same shaders (instance_cull.comp,
// instance_cull_offsets.comp, instance_cull_commands.comp), and the depth pyramid of its occlusion test (hiz_build.comp, the
// opengl46_hiz of this renderer). what differs from the opengl renderer:
// - the records go to the gpu with the uploads of the frame, which come before its commands; a record buffer started over gets
// a new buffer, so the passes of the frame culled before keep the places they had
// - the jobs, the groups, the lod bounds and the parameters of a pass are read by the kernels from the ring of the frame
// - the counters are copied for the cpu at the end of the last stage of the culling, in the frame being recorded (the rendering
// of the pass is suspended for the dispatches)
// - the output is the instance buffer of the indirect draws in pass sets of its own (vulkan14_renderer::cull_pass_sets())
#pragma once
#include "rendering/common/instancecull.h"
#include "rendering/common/shadowfit.h"
#include "rendering/vulkan14/vulkan14compute.h"
#include "rendering/vulkan14/vulkan14memory.h"
#include "rendering/vulkan14/vulkan14texture.h"
#include <array>
#include <memory>
#include <vector>
namespace vk14
{
// what the cullings need of the renderer
struct cull_environment
{
allocator *memory{nullptr};
uploader *uploads{nullptr};
deletion_queue *deletions{nullptr};
frame_ring *ring{nullptr}; // the ring of the frame, the data of the passes go through it
VkDeviceSize uniform_alignment{256};
VkDeviceSize storage_alignment{256};
std::uint32_t frames{2}; // in flight
// the frame being recorded: its slot among the frames in flight, the value of the timeline the gpu reaches when it's done with it,
// and the value the gpu reached
std::uint32_t slot{0};
std::uint64_t value{0};
std::uint64_t completed{0};
// a sampled image to give the occlusion test of the kernel when there's no pyramid
descriptor no_pyramid{};
};
// the occlusion test of a pass: the pyramid (the view of all its levels, in the general layout) and what it was made from
struct cull_occlusion
{
VkImageView pyramid{VK_NULL_HANDLE};
VkSampler sampler{VK_NULL_HANDLE};
glm::ivec2 size{0}; // of its first level
int levels{0};
glm::vec2 screen{0.f}; // size of the depth it was made from, pixels
glm::mat4 projection{1.f}; // of the pass
};
class instance_cull : public gfx::instance_cull
{
public:
instance_cull() = default;
~instance_cull() override;
// the kernels and the record buffer made, or taken from Shared. Shaders: the shader library
bool init(cull_environment const &Environment, VkDevice Device, pipeline_library &Pipelines, shader_library &Shaders, VkDescriptorSetLayout Passlayout, instance_cull const *Shared = nullptr);
void destroy(VkDevice Device);
explicit operator bool() const
{
return bool(m_shared);
}
// the frame being recorded (cull_environment::slot, value, completed)
void begin_frame(std::uint32_t const Slot, std::uint64_t const Value, std::uint64_t const Completed);
// as opengl46_instance_cull::dispatch(), dispatch_second(), fill_commands(), outside a rendering
void dispatch(command_stream *Commands, glm::mat4 const &View, cull_occlusion const *Occlusion = nullptr, bool const Twophase = false);
void dispatch_second(command_stream *Commands, cull_occlusion const &Occlusion);
void fill_commands(command_stream *Commands, std::vector<gl::instance_draw_command> const &Commands_);
// the indirect commands (draw_command_offset()) and the matrices of the instances drawn
VkBuffer command_buffer() const
{
return m_commands ? m_commands->handle() : VK_NULL_HANDLE;
}
VkBuffer output_buffer() const
{
return m_output ? m_output->handle() : VK_NULL_HANDLE;
}
VkDeviceSize output_size() const
{
return m_output ? m_output->size() : 0;
}
// the pass sets of the renderer with the output as the instance buffer, made for the generation of the pass sets and the output
std::array<VkDescriptorSet, 3> sets{};
VkDescriptorPool set_pool{VK_NULL_HANDLE};
std::uint64_t sets_generation{0};
VkBuffer sets_output{VK_NULL_HANDLE};
// the gpu time of the passes, for the panel (the parts of the pass list)
std::string info_times(std::string const &Label = "instance cull") const
{
return {};
}
protected:
std::int64_t record_buffer_size() const override;
void resize_record_buffer(std::int64_t const Size) override;
void send_records(std::uint32_t const First, scene::basic_cell::instance_record const *Records, std::uint32_t const Count) override;
void restart_records() override;
void push_counts() override;
bool pop_counts(std::uint32_t *Counts) override;
private:
// what the cullings of all passes share: the kernels, the record buffer and the records sent since the last dispatch
struct shared_data
{
compute_kernel cull;
compute_kernel offsets;
compute_kernel commands;
std::shared_ptr<buffer> records;
std::vector<std::uint8_t> pending;
std::vector<std::pair<VkDeviceSize, VkDeviceSize>> parts;
cull_environment environment;
};
// a buffer of the culling grown to hold at least Size bytes, at least doubled; the old one goes once the gpu is done with it
bool reserve(std::shared_ptr<buffer> &Buffer, VkDeviceSize Size, VkBufferUsageFlags Usage, char const *Name);
// the records sent since the last dispatch go with the uploads of the frame
void flush_records();
// the descriptors of the culling kernel; Occlusion: the pyramid, or none
std::vector<descriptor> cull_descriptors(cull_occlusion const *Occlusion) const;
// the counters copied for the cpu
void read_counters(command_stream *Commands);
std::shared_ptr<shared_data> m_shared;
std::shared_ptr<buffer> m_counters;
std::shared_ptr<buffer> m_output;
std::shared_ptr<buffer> m_commands;
readback_ring m_readback;
// the slices of the ring the jobs, the groups, the lod bounds and the parameters of the pass were placed in
ring_slice m_jobs;
ring_slice m_groups;
ring_slice m_lods;
ring_slice m_block;
VkDeviceSize m_countersize{0};
};
// the depth pyramid of the occlusion test of the main view (opengl46_hiz): made from the resolved depth of the pass, the first
// level half its size, each level the farthest depth of the 2x2 texels below it
class hiz_pyramid
{
public:
bool init(VkDevice Device, pipeline_library &Pipelines, shader_library &Shaders, VkDescriptorSetLayout Passlayout);
void destroy(VkDevice Device, deletion_queue *Deletions = nullptr, std::uint64_t Value = 0);
// the pyramid made from Depth (a sampled image of Width x Height, in the read only layout), the images made anew for another size
bool build(command_stream *Commands, VkDevice Device, allocator &Memory, deletion_queue &Deletions, std::uint64_t Value, VkImageView Depth, VkSampler Sampler, int Width, int Height);
// the occlusion test of the pass
cull_occlusion occlusion(VkSampler Sampler, glm::mat4 const &Projection) const;
private:
compute_kernel m_kernel;
std::shared_ptr<image> m_pyramid;
std::vector<VkImageView> m_levels;
glm::ivec2 m_sourcesize{0};
glm::ivec2 m_size{0};
};
// the cascades of the sun shadows fitted to the depth of the main view (gfx.shadow.sdsm, opengl46_shadow_fit): the passes and the
// buffers of gfx::shadow_fit, the result read back through a ring
class shadow_fitter : public gfx::shadow_fit
{
public:
bool init(VkDevice Device, pipeline_library &Pipelines, shader_library &Shaders, VkDescriptorSetLayout Passlayout, allocator const &Memory, std::uint32_t Frames);
void destroy(VkDevice Device);
explicit operator bool() const
{
return bool(m_depthkernel) && bool(m_boundskernel) && bool(m_buffer);
}
// measures the depth of the view: Depth, the resolved depth of Width x Height pixels in the read only layout, outside a rendering.
// Frame: the frame of the renderer; Slot, Value: the frame being recorded, as the readback rings take it
void measure(command_stream *Commands, VkImageView Depth, VkSampler Sampler, int Width, int Height, view const &View, unsigned int Frame, std::uint32_t Slot, std::uint64_t Value);
// takes in the newest measurement the gpu has finished (Completed: the value of the timeline it reached), and returns the
// newest one taken in so far
result const *latest(std::uint64_t Completed);
private:
compute_kernel m_depthkernel;
compute_kernel m_boundskernel;
buffer m_buffer;
readback_ring m_readback;
};
} // namespace vk14

View File

@@ -0,0 +1,440 @@
/*
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/.
*/
// the tools of the vulkan renderer (the stage P7 of the plan): the debug windows of opengl46_renderer::draw_debug_ui() over the
// settings and the state shared with the opengl renderer, the lines of the statistics of the scene and of the cullings for
// the debug panel (Shift+F12), and the heatmap of the light clusters (gfx.lights.clusterheatmap)
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14renderer.h"
#include "rendering/vulkan14/vulkan14commands.h"
#include "rendering/vulkan14/vulkan14context.h"
#include "rendering/common/exposure.h"
#include "simulation/simulationenvironment.h"
#include "utilities/Globals.h"
#include "utilities/Logs.h"
#include "utilities/utilities.h"
#include "imgui/imgui.h"
namespace
{
VkPipelineStageFlags2 constexpr heatmap_colour_stage{VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT};
VkAccessFlags2 constexpr heatmap_colour_access{VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT};
} // namespace
void vulkan14_renderer::collect_cluster_stats()
{
if (!m_context)
{
return;
}
auto &clusters{m_context->scene.clusters};
gl::cluster_counters counters{};
if (false == clusters.counters_readback.pop(m_context->completed(), &counters, sizeof(counters)))
{
return;
}
auto &stats{clusters.stats};
stats.index_count = counters.index_count;
stats.overflow_count = counters.overflow_count;
stats.overflow_peak = std::max(counters.overflow_count, stats.valid ? stats.overflow_peak : 0u);
stats.valid = true;
}
// the lines of the cullings of the passes (the part of opengl46_renderer::Update_Debug_Info() after the scene statistics)
std::string vulkan14_renderer::cull_info()
{
std::string text;
if (!m_context || (false == m_context->scene.cull.ready))
{
return text;
}
auto &cull{m_context->scene.cull};
text += cull.main.info();
if (Global.gfx_gpucull_shadows)
{
for (std::size_t cascade = 0; cascade < cull.cascades.size(); ++cascade)
{
if (false == cull.cascades[cascade].ran())
{
continue;
}
cull.cascades[cascade].collect();
text += cull.cascades[cascade].info("gpu cull shadows " + std::to_string(cascade + 1), false);
}
}
if (Global.gfx_gpucull_reflections && cull.reflections.ran())
{
cull.reflections.collect();
text += cull.reflections.info("gpu cull reflections, last face", false);
}
if (Global.gfx_gpucull != 0)
{
// the maps are drawn now and then, the numbers are of the last drawing of each
auto const add = [&](std::vector<std::unique_ptr<vk14::instance_cull>> &Culls, bool const Enabled, std::string const &Label) {
if (false == Enabled)
{
return;
}
for (std::size_t idx = 0; idx < Culls.size(); ++idx)
{
if (!Culls[idx] || (false == Culls[idx]->ran()))
{
continue;
}
Culls[idx]->collect();
text += Culls[idx]->info("gpu cull " + Label + " " + std::to_string(idx + 1), false);
}
};
add(cull.headlights, Global.gfx_gpucull_shadows_headlights, "headlight shadows");
add(cull.lamps, Global.gfx_gpucull_shadows_lamps, "lamp shadows, scenery");
}
if (Global.gfx_gpucull == 1)
{
// all passes of the last frame
auto const &walks{m_lastwalkcounts};
text += "\nwalks of the submodel trees in the frame: " + std::to_string(walks.walked) + " made (" + std::to_string(walks.changing) + " not kept), " + std::to_string(walks.reused) +
" kept from earlier frames (" + std::to_string(m_walkrecords.size()) + " groups), cpu " + to_string(walks.walktime, 3) + " ms; the draws put in order and sent, cpu " +
to_string(walks.ordertime, 3) + " ms";
}
return text;
}
// the statistics of the scene (the part "Scene statistics" of opengl46_renderer::Update_Debug_Info())
std::string vulkan14_renderer::scene_info()
{
auto &scene{m_context->scene};
debug_stats shadowstats;
for (auto const &shadowpass : m_shadowpass)
{
shadowstats += shadowpass.draw_stats;
}
auto const &colour{m_laststats};
auto const line = [](char const *Name, int const Colour, int const Shadows) {
return std::string(Name) + to_string(Colour, 7) + " +" + to_string(Shadows, 7) + " =" + to_string(Colour + Shadows, 7) + "\n";
};
// the cascades of the sun shadows: where they end and the size of a texel of their maps, fixed or fitted to the depth
std::string cascades;
if (Global.gfx_shadowmap_enabled && (scene.shadow_size > 0))
{
auto const &fit{m_shadowfitstate};
auto const size{static_cast<float>(scene.shadow_size)};
cascades = " sun shadow cascades to";
for (std::size_t cascade = 0; cascade < gl::MAX_CASCADES; ++cascade)
{
cascades += " " + to_string(fit.splits[cascade + 1], 1);
}
cascades += " m, texels";
for (std::size_t cascade = 0; cascade < gl::MAX_CASCADES; ++cascade)
{
cascades += " " + to_string(100.f * fit.drawn[cascade] / size, 1);
}
cascades += " cm";
if (Global.gfx_shadow_sdsm)
{
if (fit.fitted)
{
cascades += " (whole slices";
for (std::size_t cascade = 0; cascade < gl::MAX_CASCADES; ++cascade)
{
cascades += " " + to_string(100.f * fit.slice[cascade] / size, 1);
}
cascades += "), fitted to pixels " + to_string(fit.depthmin, 2) + "-" + to_string(fit.depthmax, 1) + " m " + std::to_string(fit.age) + " frames ago, view turned " + to_string(fit.turn, 1) +
" deg since";
}
else
{
cascades += ", fixed: " + std::string{fit.reason != nullptr ? fit.reason : ""};
}
}
cascades += "\n";
}
std::string text{"triangles: " + to_string(static_cast<int>(m_geometry.primitives_count()), 7) + "\n" + line("vehicles: ", colour.dynamics, shadowstats.dynamics) +
line("models: ", colour.models, shadowstats.models) + line("drawcalls: ", colour.drawcalls, shadowstats.drawcalls) + " instanced:" + to_string(colour.instances, 7) + " +" +
to_string(shadowstats.instances, 7) + " =" + to_string(colour.instances + shadowstats.instances, 7) + " (" +
std::to_string(colour.instanced_drawcalls + shadowstats.instanced_drawcalls) + " batches)\n"};
if (Global.gfx_contactshadows > 0)
{
text += " contact shadows: " + std::string{Global.gfx_contactshadows == 3 ? "high" : Global.gfx_contactshadows == 2 ? "medium" : "low"} + ", rays " +
to_string(Global.gfx_contactshadows_length, 2) + " m within " + to_string(Global.gfx_contactshadows_range, 0) + " m\n";
}
text += cascades + line(" submodels:", colour.submodels, shadowstats.submodels) + line(" paths: ", colour.paths, shadowstats.paths) +
line(" shapes: ", colour.shapes, shadowstats.shapes) + " traction: " + to_string(colour.traction, 7) + "\n" + " lines: " + to_string(colour.lines, 7) + "\n" +
"particles: " + to_string(colour.particles, 7) + "\nlights: " + to_string(static_cast<int>(m_lights_lit), 7) + " (" + std::to_string(m_lights_dropped) + " over capacity), " +
(Global.gfx_lights_clustered ? "clustered" : "first " + std::to_string(std::max(Global.DynamicLightCount, 0))) + "\n" + m_locallights.info() + scene.lamps.info() +
scene.particles.info();
auto const &stats{scene.clusters.stats};
if (stats.valid && (Global.gfx_lights_clustered || Global.gfx_lights_clusterheatmap))
{
text += "cluster lists: " + std::to_string(stats.index_count) + ", overflow " + std::to_string(stats.overflow_count) + " (peak " + std::to_string(stats.overflow_peak) + ")\n";
}
return text;
}
void vulkan14_renderer::draw_debug_ui()
{
if ((false == m_debug_ui_active) || !m_context)
{
return;
}
auto &scene{m_context->scene};
collect_cluster_stats();
if (ImGui::Begin("Headlight config", &m_debug_ui_active))
{
ImGui::SetWindowSize(ImVec2(0, 0));
auto &conf{m_headlightconfig};
ImGui::SliderFloat("in_cutoff", &conf.in_cutoff, 0.9f, 1.1f);
ImGui::SliderFloat("out_cutoff", &conf.out_cutoff, 0.9f, 1.1f);
ImGui::SliderFloat("falloff_linear", &conf.falloff_linear, 0.0f, 1.0f, "%.3f", ImGuiSliderFlags_Logarithmic);
ImGui::SliderFloat("falloff_quadratic", &conf.falloff_quadratic, 0.0f, 1.0f, "%.3f", ImGuiSliderFlags_Logarithmic);
ImGui::SliderFloat("ambient", &conf.ambient, 0.0f, 3.0f);
ImGui::SliderFloat("intensity", &conf.intensity, 0.0f, 10.0f);
}
ImGui::End();
if (ImGui::Begin("Light clusters"))
{
ImGui::Checkbox("clustered lights (off: the first dynamiclights headlights)", &Global.gfx_lights_clustered);
ImGui::Checkbox("heatmap", &Global.gfx_lights_clusterheatmap);
ImGui::Checkbox("beams of vehicle headlights (gfx.lights.headlights)", &Global.gfx_lights_headlights);
auto const &params{scene.clusters.params};
ImGui::Text("grid %u x %u x %u, %s", gl::CLUSTER_GRID_X, gl::CLUSTER_GRID_Y, gl::CLUSTER_GRID_Z, params.valid ? "built" : "none (orthographic view)");
ImGui::Text("near %.3f m, far %.1f m", params.znear, params.zfar);
ImGui::Text("headlights %u, %u over capacity of %u", m_lights_lit, m_lights_dropped, static_cast<unsigned>(gl::MAX_LIGHTS - 1));
auto const &stats{scene.clusters.stats};
if (stats.valid)
{
ImGui::Text("index list %u of %u", stats.index_count, gl::CLUSTER_COUNT * gl::MAX_LIGHTS_PER_CLUSTER);
ImGui::Text("overflow: %u lights dropped from full clusters (limit %u each), peak %u", stats.overflow_count, gl::MAX_LIGHTS_PER_CLUSTER, stats.overflow_peak);
}
ImGui::Separator();
m_locallights.draw_debug_ui();
ImGui::Separator();
scene.lamps.draw_debug_ui();
ImGui::Separator();
char const *contactlevels[]{"no", "low (the sun)", "medium (the sun and 2 lamps)", "high (the sun and 4 lamps)"};
ImGui::Combo("contact shadows (gfx.contactshadows)", &Global.gfx_contactshadows, contactlevels, IM_ARRAYSIZE(contactlevels));
ImGui::SliderFloat("length of their rays, m (gfx.contactshadows.length)", &Global.gfx_contactshadows_length, 0.05f, 5.f, "%.2f", ImGuiSliderFlags_Logarithmic);
ImGui::SliderFloat("distance they reach, m (gfx.contactshadows.range)", &Global.gfx_contactshadows_range, 1.f, 1000.f, "%.0f", ImGuiSliderFlags_Logarithmic);
}
ImGui::End();
if (ImGui::Begin("Sun shadows"))
{
ImGui::Checkbox("cascades fitted to the depth of the view (gfx.shadow.sdsm)", &Global.gfx_shadow_sdsm);
ImGui::SliderFloat("splits, 0 equal lengths .. 1 equal ratios (gfx.shadow.sdsm.split)", &Global.gfx_shadow_sdsm_split, 0.f, 1.f, "%.2f");
auto const &fit{m_shadowfitstate};
if (Global.gfx_shadow_sdsm)
{
if (fit.fitted)
{
ImGui::Text("fitted to pixels %.2f - %.1f m, measured %u frames ago, view turned %.1f deg since", fit.depthmin, fit.depthmax, fit.age, fit.turn);
}
else
{
ImGui::Text("fixed cascades: %s", fit.reason != nullptr ? fit.reason : "");
}
}
if (scene.shadow_size > 0)
{
for (std::size_t cascade = 0; cascade < gl::MAX_CASCADES; ++cascade)
{
ImGui::Text("cascade %d: to %.1f m, map %.1f m across (whole slice %.1f m), texel %.1f cm", static_cast<int>(cascade + 1), fit.splits[cascade + 1], fit.drawn[cascade], fit.slice[cascade],
100.f * fit.drawn[cascade] / static_cast<float>(scene.shadow_size));
}
}
}
ImGui::End();
if (ImGui::Begin("Weather"))
{
m_weatherstate.draw_debug_ui();
}
ImGui::End();
if (ImGui::Begin("Particles"))
{
ImGui::Checkbox("smoke simulated and drawn on the gpu (gfx.smoke.gpu)", &Global.gfx_smoke_gpu);
if (Global.gfx_smoke_gpu)
{
ImGui::Checkbox("smoke lit by the sun, the sky and the lamps (gfx.smoke.lighting)", &Global.gfx_smoke_lighting);
}
ImGui::Checkbox("rain and snow as particles of the gpu (gfx.precipitation.gpu)", &Global.gfx_precipitation_gpu);
if (Global.gfx_precipitation_gpu)
{
ImGui::SliderInt("particles of the heaviest rain or snow (gfx.precipitation.particles)", &Global.gfx_precipitation_particles, 0, 262144);
}
ImGui::SliderFloat("soft particles, share of their size, 0: off (gfx.particles.soft)", &Global.gfx_particles_soft, 0.f, 4.f, "%.2f");
auto const info{scene.particles.info()};
if (false == info.empty())
{
ImGui::TextUnformatted(info.c_str() + 1);
}
}
ImGui::End();
if (ImGui::Begin("Sky and exposure"))
{
m_skylight.draw_debug_ui();
ImGui::SliderFloat("sun disc, 1 = physical (gfx.sun.disc)", &Global.gfx_sun_disc, 0.f, 2.f);
ImGui::SliderFloat("moon disc (gfx.moon.disc)", &Global.gfx_moon_disc, 0.f, 8.f);
ImGui::SliderFloat("moonlight on the scenery (gfx.moonlight)", &Global.gfx_moonlight, 0.f, 10.f);
{
// where the sun and the moon are: azimuth from the north to the east (the sky has x to the west, z to the north)
auto const azimuth = [](glm::vec3 const &Direction) { return glm::degrees(std::atan2(-Direction.x, Direction.z)) + (Direction.x > 0.f ? 360.f : 0.f); };
auto const &sun{simulation::Environment.sun()};
auto const &moon{simulation::Environment.moon()};
ImGui::Text("sun: elevation %.1f, azimuth %.1f", sun.getAngle(), azimuth(sun.getDirection()));
ImGui::Text("moon: elevation %.1f, azimuth %.1f, lit %.0f%%, age %.1f d", moon.getAngle(), azimuth(moon.getDirection()), 100.f * moon.getIllumination(), moon.getPhase());
}
ImGui::Separator();
gfx::exposure_settings::draw_debug_ui(scene.exposure.values);
ImGui::Separator();
// the ambient occlusion (opengl46_ao::draw_debug_ui()): its images are made with the targets of the viewports
if (scene.targets.empty() || (scene.targets.front().ssao == nullptr))
{
ImGui::TextUnformatted("ambient occlusion: off at the start, set gfx.postfx.ao in eu07.ini");
}
else
{
char const *modes[]{"off", "older ssao", "ground truth (gtao)"};
ImGui::Combo("ambient occlusion (gfx.postfx.ao)", &Global.gfx_postfx_ao, modes, IM_ARRAYSIZE(modes));
if (Global.gfx_postfx_ao == 2)
{
ImGui::SliderFloat("ao radius (m)", &Global.gfx_postfx_ao_radius, 0.1f, 10.f, "%.2f", ImGuiSliderFlags_Logarithmic);
ImGui::SliderFloat("ao strength", &Global.gfx_postfx_ao_strength, 0.f, 4.f);
ImGui::SliderFloat("ao bounce albedo", &Global.gfx_postfx_ao_bounce, 0.f, 1.f);
ImGui::SliderInt("ao slices", &Global.gfx_postfx_ao_slices, 1, 8);
ImGui::SliderInt("ao steps per side", &Global.gfx_postfx_ao_steps, 1, 16);
}
}
}
ImGui::End();
if (ImGui::Begin("Instance culling"))
{
char const *modes[]{"cpu", "gpu, drawn from its result", "cpu, compared with the gpu"};
ImGui::Combo("instance culling (gfx.gpucull)", &Global.gfx_gpucull, modes, IM_ARRAYSIZE(modes));
if (Global.gfx_gpucull == 1)
{
ImGui::Checkbox("leave out instances hidden behind the depth drawn before them (gfx.gpucull.occlusion)", &Global.gfx_gpucull_occlusion);
if (Global.gfx_gpucull_occlusion)
{
ImGui::Checkbox("draw the instances seen in the last frame first, to hide the others (gfx.gpucull.occlusion.instances)", &Global.gfx_gpucull_occlusion_instances);
}
ImGui::Checkbox("draw the depth of the instances before their colour (gfx.depthprepass)", &Global.gfx_depthprepass);
ImGui::Checkbox("draw the instances of the same material and model with one multi draw (gfx.gpucull.multidraw)", &Global.gfx_gpucull_multidraw);
}
if (Global.gfx_gpucull != 0)
{
ImGui::Checkbox("the same for the cascades of the sun shadows (gfx.gpucull.shadows)", &Global.gfx_gpucull_shadows);
ImGui::Checkbox("the same for the environment map, when it's drawn next (gfx.gpucull.reflections)", &Global.gfx_gpucull_reflections);
ImGui::Checkbox("the same for the shadow maps of headlights (gfx.gpucull.shadows.headlights)", &Global.gfx_gpucull_shadows_headlights);
ImGui::Checkbox("the same for the scenery of the shadow maps of lamps, when it's drawn next (gfx.gpucull.shadows.lamps)", &Global.gfx_gpucull_shadows_lamps);
ImGui::Checkbox("the same for the sleepers of the tracks of the view (gfx.gpucull.sleepers)", &Global.gfx_gpucull_sleepers);
ImGui::Checkbox("the same for the models drawn on their own, with lights, animations or skins of their own (gfx.gpucull.models)", &Global.gfx_gpucull_models);
ImGui::Checkbox("leave out the models of a cell lying wholly out of view, beyond their range or too small before the culling (gfx.gpucull.bucketrange)", &Global.gfx_gpucull_bucketrange);
ImGui::Checkbox("keep the walks of the submodel trees of the instances from frame to frame (gfx.gpucull.walkcache)", &Global.gfx_gpucull_walkcache);
}
ImGui::Checkbox("draw the depth of the whole scene before its colour (gfx.depthprepass.scene)", &Global.gfx_depthprepass_scene);
if (Global.gfx_contactshadows > 0)
{
ImGui::TextUnformatted("(drawn anyway for the contact shadows, gfx.contactshadows)");
}
ImGui::Checkbox("grass of the heightmap terrain (gfx.grass)", &Global.gfx_grass);
if (Global.gfx_grass)
{
ImGui::SliderFloat("the grass drawn whole up to, m (gfx.grass.distance)", &Global.gfx_grass_distance, 10.f, 300.f, "%.0f");
ImGui::SliderFloat("thinning out up to, m (gfx.grass.fadedistance)", &Global.gfx_grass_fadedistance, 10.f, 350.f, "%.0f");
ImGui::SliderFloat("density of the grass (gfx.grass.density)", &Global.gfx_grass_density, 0.1f, 2.f, "%.2f");
}
auto const info{cull_info()};
if (false == info.empty())
{
ImGui::TextUnformatted(info.c_str() + 1);
}
}
ImGui::End();
}
// the number of the lights of the cluster of each pixel tinted over the output of a viewport, from the depth of its scene
// (opengl46_renderer::Render_cluster_heatmap()), after its post-processing. outside a rendering
void vulkan14_renderer::render_cluster_heatmap(std::size_t const Index)
{
auto &ctx{*m_context};
auto &scene{ctx.scene};
auto &targets{scene.targets[Index]};
auto &draw{*m_draw};
auto const commands{ctx.commands};
if ((scene.clusters.params.valid == 0) || (false == bool(targets.output)) || (false == bool(targets.depth)))
{
return;
}
if ((scene.cluster_heatmap == nullptr) && (false == scene.cluster_heatmap_tried))
{
scene.cluster_heatmap_tried = true;
try
{
auto program{std::make_shared<vk14::scene_program>()};
program->vertex = ctx.shaders.get("quad.vert", "#define QUAD_SCENE 1\n");
program->fragment = ctx.shaders.get("cluster_heatmap.frag");
program->fragment_name = "cluster_heatmap.frag";
program->vertices = vk14::vertex_kind::none;
program->name = "quad.vert + cluster_heatmap.frag";
scene.cluster_heatmap = program;
}
catch (gfx::shader_exception const &e)
{
ErrorLog("light clusters: can't use the heatmap: " + std::string(e.what()));
}
}
if (scene.cluster_heatmap == nullptr)
{
return;
}
// the depth of the scene as the colour pass resolved it
vk14::barriers(commands, {vk14::use_of(*targets.output, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, heatmap_colour_stage, heatmap_colour_access),
vk14::use_of(*targets.depth, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT)});
VkRenderingAttachmentInfo attachment{};
attachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
attachment.imageView = targets.output->view();
attachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
attachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
VkRenderingInfo rendering{};
rendering.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
rendering.renderArea = {{0, 0}, targets.extent};
rendering.layerCount = 1;
rendering.colorAttachmentCount = 1;
rendering.pColorAttachments = &attachment;
commands->begin_rendering(&rendering);
// the clusters of the pass set of the colour pass, with the offsets it was drawn with
auto const offsets{std::array<std::uint32_t, 4>{draw.scene_offset, draw.light_offset, draw.cluster_offset, draw.weather_offset}};
start_draws(targets.output_formats, static_cast<int>(targets.extent.width), static_cast<int>(targets.extent.height), m_viewports[Index].main ? vk14::scene_state::set_colour : vk14::scene_state::set_plain, false);
draw.scene_offset = offsets[0];
draw.light_offset = offsets[1];
draw.cluster_offset = static_cast<std::uint32_t>(scene.clusters.params_offset);
draw.weather_offset = offsets[3];
if (bind_scene_pipeline(*scene.cluster_heatmap, targets.output_formats, vk14::blend_mode::scene_alpha, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP))
{
bind_pass_set();
std::array<VkDescriptorImageInfo, gl::MAX_TEXTURES> images;
images.fill(VkDescriptorImageInfo{ctx.sampling.clamp_nearest, targets.depth->view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL});
scene.material_set.push(commands, scene.scene_layout, images.data());
vk14::draw_constants constants;
push_constants(&constants);
commands->draw(4, 1, 0, 0);
}
commands->end_rendering();
end_draws();
}

View File

@@ -9,6 +9,7 @@ http://mozilla.org/MPL/2.0/.
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14descriptors.h"
#include "rendering/vulkan14/vulkan14commands.h"
#include "rendering/vulkan14/vulkan14device.h"
namespace vk14
@@ -20,14 +21,17 @@ bool samplers::create(device const &Device, float Anisotropy, std::string &Reaso
{
auto const handle{Device.handle()};
auto const maxanisotropy{Device.caps().limits.maxSamplerAnisotropy};
auto const make = [&](VkSampler &Sampler, VkFilter Filter, VkSamplerAddressMode Address, bool Mipmaps, float Aniso, bool Compare, char const *Name) {
VkFormatProperties depthformat{};
vkGetPhysicalDeviceFormatProperties(Device.physical(), VK_FORMAT_D32_SFLOAT, &depthformat);
auto const depthlinear{(depthformat.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT) != 0};
auto const make = [&](VkSampler &Sampler, VkFilter Filter, VkSamplerAddressMode Address, bool Mipmaps, float Aniso, bool Compare, char const *Name, VkSamplerAddressMode AddressV = VK_SAMPLER_ADDRESS_MODE_MAX_ENUM) {
VkSamplerCreateInfo info{};
info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
info.magFilter = Filter;
info.minFilter = Filter;
info.mipmapMode = (Filter == VK_FILTER_LINEAR ? VK_SAMPLER_MIPMAP_MODE_LINEAR : VK_SAMPLER_MIPMAP_MODE_NEAREST);
info.addressModeU = Address;
info.addressModeV = Address;
info.addressModeV = (AddressV != VK_SAMPLER_ADDRESS_MODE_MAX_ENUM ? AddressV : Address);
info.addressModeW = Address;
info.maxLod = (Mipmaps ? VK_LOD_CLAMP_NONE : 0.f);
if (Aniso > 1.f)
@@ -35,6 +39,8 @@ bool samplers::create(device const &Device, float Anisotropy, std::string &Reaso
info.anisotropyEnable = VK_TRUE;
info.maxAnisotropy = std::min(Aniso, maxanisotropy);
}
// the border of the clamped samplers: the farthest depth of the reverse z, black
info.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
if (Compare)
{
// as GL_LEQUAL of the opengl renderer: 1 (in the shadow) where the reference isn't nearer the light than the map (reverse
@@ -54,12 +60,16 @@ bool samplers::create(device const &Device, float Anisotropy, std::string &Reaso
};
return make(repeat_aniso, VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_REPEAT, true, Anisotropy, false, "repeat aniso") && make(clamp_aniso, VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, true, Anisotropy, false, "clamp aniso") &&
make(clamp_linear, VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, true, 1.f, false, "clamp linear") && make(clamp_nearest, VK_FILTER_NEAREST, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, false, 1.f, false, "clamp nearest") &&
make(shadow, VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, false, 1.f, true, "shadow");
make(shadow, VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, false, 1.f, true, "shadow") &&
make(repeat_linear, VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_REPEAT, true, 1.f, false, "repeat linear") &&
make(panorama, VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_REPEAT, false, 1.f, false, "panorama", VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE) &&
make(repeat_nearest, VK_FILTER_NEAREST, VK_SAMPLER_ADDRESS_MODE_REPEAT, false, 1.f, false, "repeat nearest") &&
make(depth, (depthlinear ? VK_FILTER_LINEAR : VK_FILTER_NEAREST), VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, false, 1.f, false, "depth");
}
void samplers::destroy(VkDevice Device)
{
for (auto *sampler : {&repeat_aniso, &clamp_aniso, &clamp_linear, &clamp_nearest, &shadow})
for (auto *sampler : {&repeat_aniso, &clamp_aniso, &clamp_linear, &clamp_nearest, &shadow, &repeat_linear, &panorama, &repeat_nearest, &depth})
{
if (*sampler != VK_NULL_HANDLE)
{
@@ -138,7 +148,7 @@ void push_set::destroy(VkDevice Device)
}
}
void push_set::push(VkCommandBuffer Commands, VkPipelineLayout Layout, void const *Data) const
void push_set::push(command_stream *Commands, VkPipelineLayout Layout, void const *Data) const
{
VkPushDescriptorSetWithTemplateInfo info{};
info.sType = VK_STRUCTURE_TYPE_PUSH_DESCRIPTOR_SET_WITH_TEMPLATE_INFO;
@@ -146,7 +156,7 @@ void push_set::push(VkCommandBuffer Commands, VkPipelineLayout Layout, void cons
info.layout = Layout;
info.set = set;
info.pData = Data;
vkCmdPushDescriptorSetWithTemplate2(Commands, &info);
Commands->push_descriptor_set_with_template2(&info, m_bindings.size() * m_stride, bind_point);
}
VkPipelineLayout make_pipeline_layout(VkDevice Device, std::vector<VkDescriptorSetLayout> const &Sets, std::vector<VkPushConstantRange> const &PushConstants, std::string const &Name)

View File

@@ -28,6 +28,12 @@ struct samplers
VkSampler clamp_linear{VK_NULL_HANDLE}; // images of the user interface, post-processing
VkSampler clamp_nearest{VK_NULL_HANDLE};
VkSampler shadow{VK_NULL_HANDLE}; // depth compared (shadow maps, reverse z)
VkSampler repeat_linear{VK_NULL_HANDLE}; // repeated, mipmapped, without anisotropy (the noises of the clouds)
VkSampler panorama{VK_NULL_HANDLE}; // repeated across, clamped up and down (the panorama of the clouds around the camera)
VkSampler repeat_nearest{VK_NULL_HANDLE}; // repeated, texel by texel (the noise of the ssao)
// the depth of the scene read by the post-processing, as the render targets of the depth of the opengl renderer: filtered where
// the device filters the depth format, the border the farthest depth (0)
VkSampler depth{VK_NULL_HANDLE};
bool create(device const &Device, float Anisotropy, std::string &Reason);
void destroy(VkDevice Device);
@@ -48,7 +54,7 @@ struct push_set
bool create_template(VkDevice Device, VkPipelineLayout Layout, std::string const &Name);
void destroy(VkDevice Device);
// pushes Data (laid out as create() was told)
void push(VkCommandBuffer Commands, VkPipelineLayout Layout, void const *Data) const;
void push(command_stream *Commands, VkPipelineLayout Layout, void const *Data) const;
private:
std::vector<VkDescriptorSetLayoutBinding> m_bindings;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,157 @@
/*
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/vulkan14grass.h"
#include "utilities/Logs.h"
#include <cstring>
namespace vk14
{
grass_field::~grass_field()
{
destroy();
}
void grass_field::init(resources const &Resources)
{
m_resources = &Resources;
}
void grass_field::destroy()
{
if (m_resources == nullptr)
{
return;
}
// the images go with the chunks; the device is idle
clear();
m_chunks.clear();
m_resources = nullptr;
}
std::shared_ptr<image> grass_field::make_image(VkFormat const Format, std::uint32_t const Width, std::uint32_t const Height, void const *Data, VkDeviceSize const Size, char const *Name)
{
image_desc desc;
desc.format = Format;
desc.extent = {Width, Height, 1};
desc.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
auto result{std::make_shared<image>()};
if (false == result->create(*m_resources->memory, desc, Name))
{
ErrorLog(std::string("heightmap terrain: no gpu image for the ") + Name);
return nullptr;
}
if (false == m_resources->uploads->upload(result, Data, Size, {{0, {Width, Height, 1}}}, false))
{
return nullptr;
}
return result;
}
void grass_field::gpu_upload(std::uint64_t const Id, gfx::grass_chunk const &Source)
{
if (m_resources == nullptr)
{
return;
}
auto &chunk{m_chunks[Id]};
auto const side{static_cast<std::uint32_t>(Source.cells + 1)};
chunk.images[0] = make_image(VK_FORMAT_R32_SFLOAT, side, side, Source.heights, static_cast<VkDeviceSize>(side) * side * sizeof(float), "grass heights");
chunk.samplers[0] = m_resources->sampling->clamp_nearest;
// the densities of the kinds side by side, as the channels of a texel
auto const samples{static_cast<std::uint32_t>(Source.samples + 1)};
auto const plane{static_cast<std::size_t>(samples) * samples};
std::size_t kinds{0};
while (kinds < Source.kinds.size() && Source.kinds[kinds] >= 0)
++kinds;
std::vector<std::uint8_t> texels(plane * 4, 0);
for (std::size_t kind = 0; kind < kinds; ++kind)
for (std::size_t i = 0; i < plane; ++i)
texels[i * 4 + kind] = Source.density[kind * plane + i];
chunk.images[1] = make_image(VK_FORMAT_R8G8B8A8_UNORM, samples, samples, texels.data(), texels.size(), "grass densities");
chunk.samplers[1] = m_resources->sampling->clamp_linear;
std::uint8_t const none{0};
auto const holeside{static_cast<std::uint32_t>((Source.holes > 0 && Source.hole_mask != nullptr) ? Source.holes + 1 : 1)};
chunk.images[2] = make_image(VK_FORMAT_R8_UNORM, holeside, holeside, holeside > 1 ? Source.hole_mask : &none, static_cast<VkDeviceSize>(holeside) * holeside, "grass holes");
chunk.samplers[2] = m_resources->sampling->clamp_linear;
// the table is made when the chunk is drawn
chunk.images[3].reset();
chunk.samplers[3] = m_resources->sampling->clamp_nearest;
chunk.table_made = false;
}
void grass_field::gpu_release(std::uint64_t const Id)
{
auto const lookup{m_chunks.find(Id)};
if (lookup == m_chunks.end())
{
return;
}
if (m_resources != nullptr)
{
// the frames in flight may still read them
m_resources->deletions->push(m_resources->uploads->current_value(), [images = std::move(lookup->second.images)]() {});
}
m_chunks.erase(lookup);
}
grass_field::chunk_images const *grass_field::images(chunk_draw const &Draw)
{
auto const lookup{m_chunks.find(Draw.id)};
if ((lookup == m_chunks.end()) || (m_resources == nullptr))
{
return nullptr;
}
auto &chunk{lookup->second};
for (std::size_t i = 0; i < 3; ++i)
{
if (chunk.images[i] == nullptr)
{
return nullptr;
}
}
// the kinds of the chunk, by columns: base, tip, shape, dry; the share and the chunk in the last row
std::array<glm::vec4, 20> table{};
for (std::size_t i = 0; i < 4; ++i)
{
table[0 * 4 + i] = Draw.base[i];
table[1 * 4 + i] = Draw.tip[i];
table[2 * 4 + i] = Draw.shape[i];
table[3 * 4 + i] = Draw.dry[i];
}
table[4 * 4 + 0] = Draw.share;
table[4 * 4 + 1] = Draw.chunk;
if ((false == chunk.table_made) || (std::memcmp(table.data(), chunk.table.data(), sizeof(table)) != 0) || (chunk.images[3] == nullptr))
{
auto map{chunk.images[3]};
if (map == nullptr)
{
map = make_image(VK_FORMAT_R32G32B32A32_SFLOAT, 4, 5, table.data(), sizeof(table), "grass kinds");
}
else if (false == m_resources->uploads->upload(map, table.data(), sizeof(table), {{0, {4, 5, 1}}}, false))
{
map.reset();
}
if (map == nullptr)
{
return nullptr;
}
chunk.images[3] = map;
chunk.table = table;
chunk.table_made = true;
}
return &chunk;
}
} // namespace vk14

View File

@@ -0,0 +1,60 @@
/*
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/.
*/
// the grass of the heightmap terrain in the vulkan renderer (the stage P6 of the plan, opengl46_grass): the images of the chunks
// of gfx::grass_field, uploaded when a chunk comes near. what the opengl renderer gives grass.vert in uniforms goes to the push
// constants (the tile and the frame) and to a table of the chunk (its kinds, an image the vertex shader reads), which is made
// again when what it holds changes
#pragma once
#include "rendering/common/grassfield.h"
#include "rendering/vulkan14/vulkan14texture.h"
#include <array>
#include <memory>
#include <unordered_map>
namespace vk14
{
class grass_field : public gfx::grass_field
{
public:
// the images a chunk is drawn with, in the order of the bindings of grass.vert: heights, densities, holes, the table
struct chunk_images
{
std::array<std::shared_ptr<image>, 4> images;
std::array<VkSampler, 4> samplers{};
std::array<glm::vec4, 20> table{}; // what the table holds
bool table_made{false};
};
grass_field() = default;
~grass_field() override;
// called once, before anything is made
void init(resources const &Resources);
// the images go (the device is idle)
void destroy();
// the images of a chunk drawn, its table made for the draw first; null: the chunk has none (yet)
chunk_images const *images(chunk_draw const &Draw);
protected:
void gpu_upload(std::uint64_t const Id, gfx::grass_chunk const &Source) override;
void gpu_release(std::uint64_t const Id) override;
private:
std::shared_ptr<image> make_image(VkFormat const Format, std::uint32_t const Width, std::uint32_t const Height, void const *Data, VkDeviceSize const Size, char const *Name);
resources const *m_resources{nullptr};
std::unordered_map<std::uint64_t, chunk_images> m_chunks;
};
} // namespace vk14

View File

@@ -9,6 +9,7 @@ http://mozilla.org/MPL/2.0/.
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14imgui.h"
#include "rendering/vulkan14/vulkan14commands.h"
#include "rendering/vulkan14/vulkan14bindings.h"
#include "rendering/vulkan14/vulkan14device.h"
#include "imgui/imgui.h"
@@ -216,7 +217,7 @@ void imgui_backend::Release_Image(std::uint64_t const Image)
m_resources->images->remove(Image, *m_resources->deletions, m_resources->uploads->current_value());
}
void imgui_backend::record(VkCommandBuffer Commands, VkExtent2D Extent, VkFormat Format, bool Srgb)
void imgui_backend::record(command_stream *Commands, VkExtent2D Extent, VkFormat Format, bool Srgb)
{
auto const *drawdata{m_drawdata};
m_drawdata = nullptr;
@@ -270,10 +271,10 @@ void imgui_backend::record(VkCommandBuffer Commands, VkExtent2D Extent, VkFormat
indexdata += commands->IdxBuffer.Size * sizeof(ImDrawIdx);
}
vkCmdBindPipeline(Commands, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
Commands->bind_pipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
set_draw_state(Commands, Extent);
vkCmdBindVertexBuffers(Commands, 0, 1, &vertices.buffer, &vertices.offset);
vkCmdBindIndexBuffer(Commands, indices.buffer, indices.offset, sizeof(ImDrawIdx) == 2 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32);
Commands->bind_vertex_buffers(0, 1, &vertices.buffer, &vertices.offset);
Commands->bind_index_buffer(indices.buffer, indices.offset, sizeof(ImDrawIdx) == 2 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32);
// the pixels of the display (top left origin, y down) to the clip space of vulkan (y down too)
float transform[4];
@@ -288,7 +289,7 @@ void imgui_backend::record(VkCommandBuffer Commands, VkExtent2D Extent, VkFormat
push.offset = 0;
push.size = sizeof(transform);
push.pValues = transform;
vkCmdPushConstants2(Commands, &push);
Commands->push_constants2(&push);
auto const clipoffset{drawdata->DisplayPos};
std::uint32_t vertexoffset{0}, indexoffset{0};
@@ -324,14 +325,14 @@ void imgui_backend::record(VkCommandBuffer Commands, VkExtent2D Extent, VkFormat
continue;
}
VkRect2D scissor{{static_cast<std::int32_t>(minx), static_cast<std::int32_t>(miny)}, {static_cast<std::uint32_t>(maxx - minx), static_cast<std::uint32_t>(maxy - miny)}};
vkCmdSetScissor(Commands, 0, 1, &scissor);
Commands->set_scissor(0, 1, &scissor);
if (pushed != command.GetTexID())
{
VkDescriptorImageInfo info{entry->sampler, entry->target->view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL};
m_set.push(Commands, m_layout, &info);
pushed = command.GetTexID();
}
vkCmdDrawIndexed(Commands, command.ElemCount, 1, command.IdxOffset + indexoffset, static_cast<std::int32_t>(command.VtxOffset + vertexoffset), 0);
Commands->draw_indexed(command.ElemCount, 1, command.IdxOffset + indexoffset, static_cast<std::int32_t>(command.VtxOffset + vertexoffset), 0);
}
vertexoffset += static_cast<std::uint32_t>(commands->VtxBuffer.Size);
indexoffset += static_cast<std::uint32_t>(commands->IdxBuffer.Size);

View File

@@ -50,7 +50,7 @@ class imgui_backend : public imgui_renderer
}
// records the frame of imgui into Commands, in a rendering to an image of Extent and Format begun by the renderer. Srgb: the
// image encodes the colour itself
void record(VkCommandBuffer Commands, VkExtent2D Extent, VkFormat Format, bool Srgb);
void record(command_stream *Commands, VkExtent2D Extent, VkFormat Format, bool Srgb);
// the frame of imgui is dropped (nothing to draw it to)
void skip()
{

View File

@@ -0,0 +1,822 @@
/*
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/.
*/
// the instances drawn from the gpu culling in the vulkan renderer (the stage P5 of the plan; opengl46_renderer::Render_Instances_Indirect()
// and the rest): the cullings of the passes picked as the opengl renderer picks them, the rendering of the pass ended for the compute
// passes and begun again where it was, the walks of the culled groups shared with the opengl renderer (gfx::scene_walk), and the indirect
// draws of the recorded draws, with multi draws where they follow one another with the same state
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14context.h"
#include "rendering/vulkan14/vulkan14commands.h"
#include "model/AnimModel.h"
#include "model/Model3d.h"
#include "utilities/Globals.h"
#include "utilities/Logs.h"
#include <chrono>
namespace
{
VkPipelineStageFlags2 const compute_stage{VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT};
} // namespace
// the cullings
bool vulkan14_renderer::init_cull()
{
auto &ctx{*m_context};
auto &scene{ctx.scene};
auto &cull{scene.cull};
auto const device{ctx.device.handle()};
vk14::cull_environment environment;
environment.memory = &ctx.memory;
environment.uploads = &ctx.uploads;
environment.deletions = &ctx.deletions;
environment.ring = &ctx.ring;
environment.uniform_alignment = ctx.device.caps().limits.minUniformBufferOffsetAlignment;
environment.storage_alignment = ctx.device.caps().limits.minStorageBufferOffsetAlignment;
environment.frames = static_cast<std::uint32_t>(ctx.frames.size());
environment.no_pyramid = vk14::image_descriptor(ctx.sampling.clamp_nearest, scene.depth_empty->view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
if (false == cull.main.init(environment, device, ctx.pipelines, ctx.shaders, scene.pass_layout))
{
return false;
}
for (auto &cascade : cull.cascades)
{
if (false == cascade.init(environment, device, ctx.pipelines, ctx.shaders, scene.pass_layout, &cull.main))
{
return false;
}
}
if ((false == cull.reflections.init(environment, device, ctx.pipelines, ctx.shaders, scene.pass_layout, &cull.main)) || (false == cull.hiz.init(device, ctx.pipelines, ctx.shaders, scene.pass_layout)))
{
return false;
}
cull.main.begin_frame(ctx.slot, ctx.timelinevalue + 1, ctx.completed());
cull.ready = true;
return true;
}
void vulkan14_renderer::destroy_cull()
{
if (!m_context)
{
return;
}
auto &cull{m_context->scene.cull};
auto const device{m_context->device.handle()};
// the ones which share the kernels of the main view first
for (auto &cascade : cull.cascades)
{
cascade.destroy(device);
}
for (auto *culls : {&cull.headlights, &cull.lamps})
{
for (auto &culling : *culls)
{
if (culling)
{
culling->destroy(device);
}
}
culls->clear();
}
cull.reflections.destroy(device);
cull.main.destroy(device);
cull.hiz.destroy(device);
cull.shadowfit.destroy(device);
cull.ready = false;
}
vk14::instance_cull &vulkan14_renderer::pass_cull()
{
return static_cast<vk14::instance_cull &>(*m_passcull);
}
// the gpu culling runs along the first color pass of the main viewport of a frame, and the passes of the cascades of the sun
// shadows and of the faces of the environment map drawn for it, each with a culling of its own (opengl46_renderer::walk_region_begin())
void vulkan14_renderer::walk_region_begin()
{
vk14::instance_cull *passcull{nullptr};
auto &cull{m_context->scene.cull};
if ((Global.gfx_gpucull != 0) && (m_current_viewport != nullptr) && (m_current_viewport->main) && m_draw->rendering)
{
static bool tried{false};
if ((false == cull.ready) && (false == tried))
{
tried = true;
if (false == init_cull())
{
ErrorLog("vulkan: the gpu culling of the instances can't be used, the cpu culls them (gfx.gpucull)");
destroy_cull();
}
}
if (cull.ready)
{
if ((m_renderpass.draw_mode == rendermode::color) && (m_instancecullframe != m_framestamp))
{
m_instancecullframe = m_framestamp;
passcull = &cull.main;
}
else if ((m_renderpass.draw_mode == rendermode::shadows) && (m_lightshadowcull != nullptr))
{
// the map of a set of headlights or the scenery part of the map of a lamp, which picked its culling
passcull = m_lightshadowcull;
m_shadowcasters.clear();
}
else if ((m_renderpass.draw_mode == rendermode::shadows) && Global.gfx_gpucull_shadows && (m_shadowcascade >= 0) && (m_shadowcascade < static_cast<int>(cull.cascades.size())) &&
(false == m_cubeshadows))
{
// the cascades of the sun
passcull = &cull.cascades[m_shadowcascade];
m_shadowcasters.clear();
}
else if ((m_renderpass.draw_mode == rendermode::reflections) && Global.gfx_gpucull_reflections && (Global.reflectiontune.fidelity >= 1))
{
passcull = &cull.reflections;
}
}
}
Begin_Pass_Cull(passcull, (passcull == &cull.main));
}
bool vulkan14_renderer::walk_instances_begin()
{
if (m_indirectcolourpending)
{
// the instances culled and recorded by the depth pass of the whole scene, the buckets weren't queued for the cpu
render_instances_indirect_pending();
}
if (m_instancecullpass)
{
if (m_passcull->drawing())
{
// the buckets of the pass weren't queued for the cpu, which leaves the loop of the walk empty
if (m_passcull == &m_context->scene.cull.main)
{
render_instances_indirect();
}
else
{
render_instances_indirect_pass();
}
}
else
{
// the view of the pass is in place here, the one Render_Instanced() takes
suspend_rendering();
pass_cull().dispatch(m_context->commands, OpenGLMatrices.data(GL_MODELVIEW));
resume_rendering();
// what the cpu drew of the sleepers and the models drawn on their own
Compare_Pass_Cull();
}
}
return m_instancecullpass;
}
void vulkan14_renderer::walk_instances_end()
{
End_Pass_Cull();
}
// the rendering ended and begun again around the compute passes
void vulkan14_renderer::remember_rendering(VkRenderingInfo const &Info, std::vector<vk14::image_use> const &Uses)
{
auto &resume{m_draw->resume};
resume.info = Info;
resume.info.pNext = nullptr;
resume.info.flags = 0;
auto const colours{std::min<std::uint32_t>(Info.colorAttachmentCount, static_cast<std::uint32_t>(resume.colours.size()))};
for (std::uint32_t i = 0; i < colours; ++i)
{
resume.colours[i] = Info.pColorAttachments[i];
// what was drawn is drawn over
resume.colours[i].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
}
resume.info.colorAttachmentCount = colours;
resume.info.pColorAttachments = (colours > 0 ? resume.colours.data() : nullptr);
resume.has_depth = (Info.pDepthAttachment != nullptr);
if (resume.has_depth)
{
resume.depth = *Info.pDepthAttachment;
resume.depth.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
}
resume.info.pDepthAttachment = (resume.has_depth ? &resume.depth : nullptr);
resume.info.pStencilAttachment = nullptr;
resume.uses = Uses;
resume.valid = true;
}
void vulkan14_renderer::suspend_rendering()
{
auto &draw{*m_draw};
if (false == draw.rendering)
{
return;
}
m_context->commands->end_rendering();
draw.rendering = false;
}
void vulkan14_renderer::resume_rendering()
{
auto &draw{*m_draw};
auto &resume{draw.resume};
if (draw.rendering || (false == resume.valid) || (draw.targets == nullptr))
{
return;
}
auto const commands{m_context->commands};
// what the rendering stored before is loaded again
vk14::barriers(commands, resume.uses);
commands->begin_rendering(&resume.info);
// the dynamic state starts anew with the rendering, the state of the walk goes on
auto const set{draw.set};
auto const shadows{draw.shadows};
auto const blending{draw.blending};
auto const cullface{draw.cullface};
auto const glare{draw.glare};
auto const depthmask_off{draw.depthmask_off};
auto *program{draw.program};
auto const textures{draw.textures};
start_draws(*draw.targets, static_cast<int>(draw.extent.width), static_cast<int>(draw.extent.height), set, shadows);
draw.blending = blending;
draw.cullface = cullface;
draw.glare = glare;
draw.depthmask_off = depthmask_off;
draw.program = program;
draw.textures = textures;
if (draw.depthpass)
{
// the bias of the depth pass of the scene (render_scene_depth())
commands->set_depth_bias_enable(VK_TRUE);
commands->set_depth_bias(-1.f, 0.f, -1.f);
}
}
// the draws
vk14::scene_program *vulkan14_renderer::multidraw_program(vk14::scene_program &Program)
{
if (Program.multidraw_tried)
{
return Program.multidraw.get();
}
Program.multidraw_tried = true;
if (Program.vertex_name.empty() || Program.fragment_name.empty() || (Program.vertices != vk14::vertex_kind::scene))
{
return nullptr;
}
auto &ctx{*m_context};
try
{
auto variant{std::make_shared<vk14::scene_program>()};
variant->defines = Program.defines + "#define MODEL_DRAWS\n";
variant->vertex = ctx.shaders.get(Program.vertex_name, variant->defines);
variant->fragment = ctx.shaders.get(Program.fragment_name, variant->defines);
variant->vertex_name = Program.vertex_name;
variant->fragment_name = Program.fragment_name;
variant->vertices = Program.vertices;
variant->name = Program.name + " (multi draws)";
variant->multidraw_tried = true;
Program.multidraw = std::move(variant);
}
catch (gfx::shader_exception const &Exception)
{
vk14::log_error(Exception.what());
}
return Program.multidraw.get();
}
bool vulkan14_renderer::draw_indirect(vk14::instance_cull &Cull, command_set const Set, std::size_t const First, std::uint32_t const Count, vk14::scene_program &Program, vk14::blend_mode const Blend)
{
auto &ctx{*m_context};
auto &scene{ctx.scene};
auto &draw{*m_draw};
auto const &first{m_indirectdraws[First]};
if ((false == draw.rendering) || (Count == 0) || (Program.vertices != vk14::vertex_kind::scene) || (Cull.command_buffer() == VK_NULL_HANDLE))
{
return false;
}
auto const &geometry{first.geometry};
if ((geometry.bank == 0) || (geometry.bank > m_banks.size()) || (m_banks[geometry.bank - 1] == nullptr))
{
return false;
}
// the buffers of the bank of the chunks, with their data on the gpu (the chunks of a multi draw are of one bank)
gfx::indirect_source source;
if (false == m_geometry.indirect_source(geometry, source))
{
return false;
}
auto const &chunk{static_cast<vk14::vulkan14_geometrybank *>(m_banks[geometry.bank - 1])->last()};
if (false == bind_scene_pipeline(Program, *draw.targets, Blend, static_cast<int>(chunk.topology)))
{
return false;
}
apply_draw_state();
// the blocks of the draws, one after another in the window of the blocks: the multi draw picks them by the number of the draw
std::uint32_t block{~0u};
if (Count == 1)
{
model_ubs = first.ubs;
block = push_draw_block();
}
else
{
auto &draws{scene.draws};
auto const size{static_cast<VkDeviceSize>(Count) * draws.stride};
auto const slice{size <= draws.window ? draws.ring.allocate(size, draws.stride) : vk14::ring_slice{}};
if (slice)
{
if ((false == draws.open) || (slice.offset + size > draws.base + draws.window))
{
draws.base = slice.offset;
draws.open = true;
draw.set_dirty = true;
}
for (std::uint32_t i = 0; i < Count; ++i)
{
std::memcpy(slice.data + i * draws.stride, &m_indirectdraws[First + i].ubs, sizeof(gl::model_ubs));
}
block = static_cast<std::uint32_t>((slice.offset - draws.base) / draws.stride);
}
}
// the next draw of the walk pushes a block of its own
draw.block = ~0u;
if (block == ~0u)
{
return false;
}
if (draw.set_dirty)
{
bind_pass_set();
}
if (draw.textures_dirty)
{
push_textures();
}
auto const commands{draw.commands};
if (draw.pushed_draw != block)
{
vk14::draw_constants constants;
constants.draw = block;
VkPushConstantsInfo info{};
info.sType = VK_STRUCTURE_TYPE_PUSH_CONSTANTS_INFO;
info.layout = scene.scene_layout;
info.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
info.offset = 0;
info.size = sizeof(constants);
info.pValues = &constants;
commands->push_constants2(&info);
draw.pushed_draw = block;
}
if ((chunk.vertices != draw.vertices) || (chunk.userdata != draw.userdata))
{
VkBuffer const buffers[2]{chunk.vertices, chunk.userdata};
VkDeviceSize const offsets[2]{0, 0};
commands->bind_vertex_buffers(0, 2, buffers, offsets);
draw.vertices = chunk.vertices;
draw.userdata = chunk.userdata;
}
auto const offset{static_cast<VkDeviceSize>(Cull.draw_command_offset(Set, First))};
if (chunk.indices != VK_NULL_HANDLE)
{
if (chunk.indices != draw.indices)
{
commands->bind_index_buffer(chunk.indices, 0, VK_INDEX_TYPE_UINT32);
draw.indices = chunk.indices;
}
commands->draw_indexed_indirect(Cull.command_buffer(), offset, Count, sizeof(gl::instance_draw_command));
}
else
{
commands->draw_indirect(Cull.command_buffer(), offset, Count, sizeof(gl::instance_draw_command));
}
draw.draws += Count;
return true;
}
vulkan14_renderer::indirect_counts vulkan14_renderer::draw_indirect_colour(vk14::instance_cull &Cull, command_set const Set)
{
auto &draw{*m_draw};
auto const &scene{m_context->scene};
// the draws were recorded with the seed of scenery models and sleepers, 0 (TAnimModel::RaPrepare()); vehicles drawn since
// leave their own, which would pick other texture variants than those the draws were recorded and ordered with
TSubModel::iVariantSeed = 0;
indirect_counts counts;
auto const count{m_indirectdraws.size()};
auto const longest{static_cast<std::size_t>(scene.draws.window / scene.draws.stride)};
for (std::size_t index = 0; index < count;)
{
auto const blending{m_indirectdraws[index].blending};
if (blending)
{
walk_blending(true);
}
walk_material(m_indirectdraws[index].material, m_indirectdraws[index].submodel);
auto *program{draw.program};
auto *multidraw{(m_indirectmultidraw && (program != nullptr)) ? multidraw_program(*program) : nullptr};
auto last{index + 1};
if (multidraw != nullptr)
{
// the draws which follow with the same state, in one call
while ((last < count) && (last - index < longest) && Indirect_Mergeable(index, last))
{
++last;
}
}
if (program != nullptr)
{
auto const drawcount{static_cast<std::uint32_t>(last - index)};
if (draw_indirect(Cull, Set, index, drawcount, (multidraw != nullptr ? *multidraw : *program), (draw.blending ? vk14::blend_mode::scene_alpha : vk14::blend_mode::none)))
{
counts.draws += drawcount;
}
++counts.calls;
}
index = last;
if (blending)
{
walk_blending(false);
}
}
if (count > 0)
{
// the model block the draws made one by one leave behind
model_ubs = m_indirectdraws.back().ubs;
}
model_ubs.emission = 0.0f;
return counts;
}
vulkan14_renderer::indirect_counts vulkan14_renderer::draw_indirect_depth(vk14::instance_cull &Cull, command_set const Set)
{
auto &draw{*m_draw};
auto &scene{m_context->scene};
auto const commands{m_context->commands};
// as in draw_indirect_colour(): the seed the draws were recorded with
TSubModel::iVariantSeed = 0;
indirect_counts counts;
auto const count{m_indirectdraws.size()};
auto const longest{static_cast<std::size_t>(scene.draws.window / scene.draws.stride)};
// whether the prepass of the class draws the draw, and with which diffuse texture (class 2)
auto const prepass_texture = [this](std::size_t const Index, int const Prepassclass, texture_handle &Texture) -> bool {
auto const &entry{m_indirectdraws[Index]};
if ((entry.blending) || (entry.ubs.opacity < 0.f))
{
return false;
}
if ((entry.type != GL_TRIANGLES) && (entry.type != GL_TRIANGLE_STRIP) && (entry.type != GL_TRIANGLE_FAN))
{
return false;
}
if (prepass_class(entry.material) != Prepassclass)
{
return false;
}
Texture = null_handle;
if (Prepassclass == 2)
{
// the diffuse texture of the colour pass, the same choice as walk_material() makes
auto const &material{m_materials.material(entry.material)};
auto const &textures{material.texture_variants.empty() ? material.textures : material.GetTextures(TSubModel::VariantId(entry.submodel))};
if (textures[0] == null_handle)
{
return false;
}
Texture = textures[0];
}
return true;
};
// inside the colour pass the depth goes a little farther than the surface, without the colour (the depth pass of the scene has
// its own state)
auto const insidecolour{false == draw.depthpass};
if (insidecolour)
{
commands->set_depth_bias_enable(VK_TRUE);
commands->set_depth_bias(-1.f, 0.f, -1.f);
}
walk_blending(false);
// the draws without the alpha test first, then the ones with it, one program each
for (auto prepassclass = 1; prepassclass <= 2; ++prepassclass)
{
auto &program{prepassclass == 1 ? *scene.prepass : *scene.prepass_alpha};
auto *multidraw{m_indirectmultidraw ? multidraw_program(program) : nullptr};
for (std::size_t index = 0; index < count;)
{
texture_handle texture;
if (false == prepass_texture(index, prepassclass, texture))
{
++index;
continue;
}
if (prepassclass == 2)
{
walk_texture(0, texture);
}
auto const &entry{m_indirectdraws[index]};
auto last{index + 1};
if (multidraw != nullptr)
{
// the draws which follow in the same bank and of the same primitive, with the same texture for the alpha test. the
// material doesn't matter to the depth
texture_handle othertexture;
while ((last < count) && (last - index < longest) && (m_indirectdraws[last].group == entry.group) && (m_indirectdraws[last].type == entry.type) &&
(m_indirectcommands[last].indexed == m_indirectcommands[index].indexed) && (prepass_texture(last, prepassclass, othertexture)) && (othertexture == texture))
{
++last;
}
}
auto const drawcount{static_cast<std::uint32_t>(last - index)};
if (draw_indirect(Cull, Set, index, drawcount, (multidraw != nullptr ? *multidraw : program), (insidecolour ? vk14::blend_mode::no_colour : vk14::blend_mode::none)))
{
counts.draws += drawcount;
}
++counts.calls;
index = last;
}
}
if (insidecolour)
{
commands->set_depth_bias_enable(VK_FALSE);
}
return counts;
}
vulkan14_renderer::indirect_counts vulkan14_renderer::draw_indirect_shadow(vk14::instance_cull &Cull, command_set const Set)
{
auto &scene{m_context->scene};
indirect_counts counts;
auto const count{m_indirectdraws.size()};
auto const longest{static_cast<std::size_t>(scene.draws.window / scene.draws.stride)};
for (std::size_t index = 0; index < count;)
{
auto const &entry{m_indirectdraws[index]};
auto const texture{Indirect_Shadow_Texture(entry.material)};
auto &program{m_cubeshadows ? (texture != null_handle ? *scene.cube_alpha_shadow : *scene.cube_shadow) : (texture != null_handle ? *scene.alpha_shadow : *scene.shadow)};
auto *multidraw{m_indirectmultidraw ? multidraw_program(program) : nullptr};
auto last{index + 1};
if (multidraw != nullptr)
{
// the draws which follow in the same bank and of the same primitive, with the same texture for the alpha test
while ((last < count) && (last - index < longest) && (m_indirectdraws[last].group == entry.group) && (m_indirectdraws[last].type == entry.type) &&
(m_indirectcommands[last].indexed == m_indirectcommands[index].indexed) && (Indirect_Shadow_Texture(m_indirectdraws[last].material) == texture))
{
++last;
}
}
if (texture != null_handle)
{
walk_texture(0, texture);
}
auto const drawcount{static_cast<std::uint32_t>(last - index)};
if (draw_indirect(Cull, Set, index, drawcount, (multidraw != nullptr ? *multidraw : program), vk14::blend_mode::none))
{
counts.draws += drawcount;
}
++counts.calls;
index = last;
}
if (count > 0)
{
model_ubs = m_indirectdraws.back().ubs;
}
return counts;
}
void vulkan14_renderer::record_indirect_draws(vk14::instance_cull &Cull, bool const Cascade)
{
m_indirectcull = &Cull;
Walk_Indirect_Draws(Cull, Cascade);
auto const orderstart{std::chrono::steady_clock::now()};
// with multi draws the draws with the same state follow one another; the order goes into the commands, so it comes first
m_indirectmultidraw = Global.gfx_gpucull_multidraw;
if (m_indirectmultidraw && (false == m_indirectdraws.empty()))
{
Order_Indirect_Draws();
}
Cull.fill_commands(m_context->commands, m_indirectcommands);
m_walkcounts.ordertime += std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - orderstart).count();
}
// the instances of the main view: the depth pyramid of what the pass drew so far, the culling, the walks, the depth first (the
// depth pass of the scene, gfx.depthprepass), the colour; in two phases the instances seen in the previous frame first
void vulkan14_renderer::render_instances_indirect()
{
auto const cpustart{std::chrono::steady_clock::now()};
auto &ctx{*m_context};
auto &scene{ctx.scene};
auto &cull{scene.cull.main};
auto &draw{*m_draw};
auto const commands{ctx.commands};
auto const viewport{static_cast<std::size_t>(m_current_viewport - m_viewports.data())};
auto *targets{viewport < scene.targets.size() ? &scene.targets[viewport] : nullptr};
auto const projection{OpenGLMatrices.data(GL_PROJECTION)};
suspend_rendering();
// the occlusion test against the depth drawn so far in the pass: cab, terrain, shapes, tracks, vehicles, other models
vk14::cull_occlusion occlusion;
auto const build = [&]() {
vk14::barriers(commands, {vk14::use_of(*targets->depth, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, compute_stage, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT)});
if (scene.cull.hiz.build(commands, ctx.device.handle(), ctx.memory, ctx.deletions, ctx.uploads.current_value(), targets->depth->view(), ctx.sampling.clamp_nearest, static_cast<int>(targets->extent.width),
static_cast<int>(targets->extent.height)))
{
occlusion = scene.cull.hiz.occlusion(ctx.sampling.clamp_nearest, projection);
}
};
auto const useocclusion{Global.gfx_gpucull_occlusion && (false == Global.gfx_skippipeline) && (targets != nullptr) && targets->depth};
if (useocclusion)
{
build();
}
// in two phases the instances seen in the previous frame are drawn first, and the others are tested against a pyramid made
// again with their depth in it (not in wireframe, which doesn't draw surfaces)
cull.dispatch(commands, OpenGLMatrices.data(GL_MODELVIEW), (occlusion.pyramid != VK_NULL_HANDLE ? &occlusion : nullptr), (Global.gfx_gpucull_occlusion_instances && (false == Global.bWireFrame)));
// the draws, recorded while the gpu culls
record_indirect_draws(cull, false);
auto const twophase{cull.two_phase()};
auto const draws{false == m_indirectdraws.empty()};
// the depth comes first in the depth pass of the whole scene, which leaves the colour to the colour pass, and with the depth
// prepass of the instances
auto const depthfirst{draw.depthpass || (Global.gfx_depthprepass && (false == Global.bWireFrame))};
indirect_counts prepasscounts;
indirect_counts colourcounts;
auto colourset{command_set::first};
resume_rendering();
if (twophase)
{
if (draws)
{
// the instances seen in the previous frame, then the pyramid again, with them in it
use_cull_pass_sets(&cull);
if (depthfirst)
{
prepasscounts += draw_indirect_depth(cull, command_set::first);
}
else
{
colourcounts += draw_indirect_colour(cull, command_set::first);
}
use_cull_pass_sets(nullptr);
}
suspend_rendering();
if (draws)
{
build();
}
// the others are tested against it; with no draws the instances are only marked for the next frame
cull.dispatch_second(commands, occlusion);
resume_rendering();
colourset = (depthfirst ? command_set::all : command_set::second);
}
if (draws)
{
use_cull_pass_sets(&cull);
if (depthfirst)
{
prepasscounts += draw_indirect_depth(cull, (twophase ? command_set::second : command_set::first));
}
if (false == draw.depthpass)
{
colourcounts += draw_indirect_colour(cull, colourset);
}
use_cull_pass_sets(nullptr);
}
if (draw.depthpass)
{
// the colour pass takes the draws from here, and counts them
m_indirectcolourpending = true;
m_indirectcolourset = colourset;
m_indirectcoloursleepers = m_sleeperspass;
m_indirectcolourmodels = m_modelspass;
m_renderpass.draw_stats.drawcalls += static_cast<int>(prepasscounts.calls);
colourcounts.draws = static_cast<std::uint32_t>(m_indirectdraws.size());
}
else
{
m_renderpass.draw_stats.instanced_drawcalls += static_cast<int>(colourcounts.calls);
m_renderpass.draw_stats.drawcalls += static_cast<int>(prepasscounts.calls);
// the count of the frame the gpu reported last
m_renderpass.draw_stats.instances += static_cast<int>(cull.last_count());
m_renderpass.draw_stats.models += static_cast<int>(cull.last_count());
}
cull.draw_result(colourcounts.draws, prepasscounts.draws, colourcounts.calls, prepasscounts.calls, std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - cpustart).count());
}
// the instances of a pass other than the main view: the culling of the pass with its own camera, no occlusion test, the draws
// recorded the same way, then drawn once with the programs the pass draws with on the cpu
void vulkan14_renderer::render_instances_indirect_pass()
{
auto const cpustart{std::chrono::steady_clock::now()};
auto &cull{pass_cull()};
auto &cascades{m_context->scene.cull.cascades};
suspend_rendering();
cull.dispatch(m_context->commands, OpenGLMatrices.data(GL_MODELVIEW));
record_indirect_draws(cull, (&cull >= cascades.data()) && (&cull < cascades.data() + cascades.size()));
resume_rendering();
indirect_counts counts;
if (false == m_indirectdraws.empty())
{
use_cull_pass_sets(&cull);
counts = (m_renderpass.draw_mode == rendermode::shadows ? draw_indirect_shadow(cull, command_set::first) : draw_indirect_colour(cull, command_set::first));
use_cull_pass_sets(nullptr);
}
m_renderpass.draw_stats.instanced_drawcalls += static_cast<int>(counts.calls);
// the count of the frame the gpu reported last
m_renderpass.draw_stats.instances += static_cast<int>(cull.last_count());
m_renderpass.draw_stats.models += static_cast<int>(cull.last_count());
cull.draw_result(counts.draws, 0, counts.calls, 0, std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - cpustart).count());
}
// the colour of the instances culled and recorded by the depth pass of the whole scene
void vulkan14_renderer::render_instances_indirect_pending()
{
m_indirectcolourpending = false;
auto &cull{m_context->scene.cull.main};
indirect_counts colourcounts;
if (false == m_indirectdraws.empty())
{
auto const cpustart{std::chrono::steady_clock::now()};
use_cull_pass_sets(&cull);
colourcounts = draw_indirect_colour(cull, m_indirectcolourset);
use_cull_pass_sets(nullptr);
cull.draw_result(colourcounts.draws, cull.prepass_draws(), colourcounts.calls, cull.prepass_calls(), cull.cpu_time() + std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - cpustart).count());
}
m_renderpass.draw_stats.instanced_drawcalls += static_cast<int>(colourcounts.calls);
m_renderpass.draw_stats.instances += static_cast<int>(cull.last_count());
m_renderpass.draw_stats.models += static_cast<int>(cull.last_count());
}
// the cascades of the sun shadows fitted to the depth of the main view (opengl46_renderer::Shadow_Fit())
char const *vulkan14_renderer::shadow_fit_unavailable(viewport_config const &Viewport) const
{
auto const shadowviews{std::count_if(m_viewports.begin(), m_viewports.end(), [](viewport_config const &View) { return View.shadow; })};
return gfx::shadow_fit::unavailable(false == Global.gfx_skippipeline, Viewport.shadow, static_cast<std::size_t>(shadowviews));
}
bool vulkan14_renderer::shadow_fit(viewport_config const &Viewport, std::array<float, gl::MAX_CASCADES + 1> &Splits, gfx::shadow_fit::areas &Areas)
{
auto &ctx{*m_context};
auto &cull{ctx.scene.cull};
auto const *unavailable{Global.gfx_shadow_sdsm ? shadow_fit_unavailable(Viewport) : nullptr};
if (Global.gfx_shadow_sdsm && (unavailable == nullptr))
{
if (cull.shadowfit)
{
// takes in what came back
cull.shadowfit.latest(ctx.completed());
}
else
{
unavailable = "no compute passes";
}
}
return cull.shadowfit.fit(m_framestamp, m_colorpass.pass_camera, unavailable, Splits, Areas, m_shadowfitstate);
}
void vulkan14_renderer::measure_shadow_fit(std::size_t const Viewport)
{
auto &ctx{*m_context};
auto &scene{ctx.scene};
auto &cull{scene.cull};
auto const &vp{m_viewports[Viewport]};
if ((false == vp.main) || (false == Global.gfx_shadow_sdsm) || (shadow_fit_unavailable(vp) != nullptr) || (Viewport >= scene.targets.size()) || (false == bool(scene.targets[Viewport].depth)))
{
return;
}
if ((false == bool(cull.shadowfit)) && (false == cull.shadowfit_tried))
{
cull.shadowfit_tried = true;
if (false == cull.shadowfit.init(ctx.device.handle(), ctx.pipelines, ctx.shaders, scene.pass_layout, ctx.memory, static_cast<std::uint32_t>(ctx.frames.size())))
{
ErrorLog("vulkan: the cascades of the sun shadows can't be fitted to the depth of the view (gfx.shadow.sdsm)");
cull.shadowfit.destroy(ctx.device.handle());
}
}
if (false == bool(cull.shadowfit))
{
return;
}
auto &targets{scene.targets[Viewport]};
auto const commands{ctx.commands};
vk14::barriers(commands, {vk14::use_of(*targets.depth, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, compute_stage, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT)});
gfx::shadow_fit::view const view{m_colorpass.pass_camera.position(), m_colorpass.pass_camera.projection(), glm::mat3{m_colorpass.pass_camera.modelview()},
gfx::shadow_fit::light_rotation(m_sunlight.direction), gfx::shadow_fit::fixed_splits().back()};
ctx.passes.begin_pass("sun shadows fitted to the depth");
cull.shadowfit.measure(commands, targets.depth->view(), ctx.sampling.clamp_nearest, static_cast<int>(targets.extent.width), static_cast<int>(targets.extent.height), view, m_framestamp, ctx.slot, ctx.timelinevalue + 1);
ctx.passes.end_pass();
}

View File

@@ -0,0 +1,359 @@
/*
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/.
*/
// the shadow maps of the lamps in the vulkan renderer (the stage P4 of the plan; opengl46_renderer::Render_local_shadows() and the
// rest): the lamp of the occupied cab, the lamps of the scenery and the headlights of the vehicles, which gfx::local_shadows picks,
// drawn by the shadow pass of the shared walk of the scene to the maps of vk14::local_shadow_maps
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14context.h"
#include "rendering/vulkan14/vulkan14commands.h"
#include "model/AnimModel.h"
#include "simulation/simulation.h"
#include "utilities/Globals.h"
#include "vehicle/DynObj.h"
namespace
{
VkPipelineStageFlags2 const depth_stage{VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT};
VkAccessFlags2 const depth_access{VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT};
VkPipelineStageFlags2 const sampled_stage{VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT};
} // namespace
// the rendering of the map the maps set up last (begin(), begin_lamp(), begin_headlight()), and the pass of the walk into it
void vulkan14_renderer::begin_local_shadow_rendering()
{
auto &ctx{*m_context};
auto &scene{ctx.scene};
auto const &target{scene.lamps.target()};
auto const commands{ctx.commands};
if ((target.map == nullptr) || (target.view == VK_NULL_HANDLE))
{
return;
}
auto const use{vk14::use_of(*target.map, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, depth_stage, depth_access)};
vk14::barriers(commands, {use});
VkRenderingAttachmentInfo depth{};
depth.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
depth.imageView = target.view;
depth.imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL;
depth.loadOp = (target.clear ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD);
depth.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
// the farthest depth
depth.clearValue.depthStencil = {0.f, 0};
VkRenderingInfo rendering{};
rendering.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
rendering.renderArea = {{0, 0}, {target.size, target.size}};
rendering.layerCount = 1;
rendering.viewMask = target.view_mask;
rendering.pDepthAttachment = &depth;
commands->begin_rendering(&rendering);
remember_rendering(rendering, {use});
// the cube maps: one sided walls (of cabs, of buildings) face the lamp, they are to cast shadows as well. the maps of the
// headlights: the back faces, as in the shadows of the sun
start_draws(target.cube ? scene.cube_formats : scene.shadow_formats, static_cast<int>(target.size), static_cast<int>(target.size), vk14::scene_state::set_plain, (false == target.cube));
m_draw->cullface = (false == target.cube);
walk_blending(false);
upload_scene_block();
upload_light_header();
}
void vulkan14_renderer::end_local_shadow_rendering()
{
if (m_draw->rendering)
{
m_context->commands->end_rendering();
}
end_draws();
}
// the maps in the layout the shading reads them in: the ones made or drawn since
void vulkan14_renderer::ready_lamp_maps()
{
auto &lamps{m_context->scene.lamps};
std::vector<vk14::image_use> uses;
for (auto *map : {lamps.cab_map(), lamps.lamp_maps(), lamps.headlight_maps()})
{
if (map != nullptr)
{
uses.push_back(vk14::use_of(*map, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT));
}
}
if (false == uses.empty())
{
vk14::barriers(m_context->commands, uses);
}
}
void vulkan14_renderer::render_local_shadows()
{
auto &lamps{m_context->scene.lamps};
// only the light buffers with the lamps read the maps
auto const *vehicle{Global.gfx_lights_clustered ? m_locallights.cab_lamp_vehicle() : nullptr};
if (false == lamps.update_wanted(vehicle, (vehicle != nullptr ? vehicle->mdKabina : nullptr), m_locallights.cab_lamp_local(), m_framestamp))
{
return;
}
m_context->passes.begin_pass("lamp shadows");
// the cab drawn by the shadow pass, with the programs of the cube map. the camera is put at the vehicle, which Render_cab()
// places the cab relative to, and the modelview takes the cab model to the space of the map
m_renderpass.draw_mode = rendermode::shadows;
m_renderpass.pass_camera.position() = vehicle->GetPosition();
m_renderpass.draw_stats = {};
OpenGLMatrices.mode(GL_MODELVIEW);
OpenGLMatrices.load_matrix(glm::mat4{glm::translate(glm::dmat4{1.0}, -glm::dvec3{lamps.lamp()}) * glm::inverse(vehicle->mMatrix)});
lamps.begin();
begin_local_shadow_rendering();
m_cubeshadows = true;
Render_cab(vehicle, 0.0f, false);
m_cubeshadows = false;
end_local_shadow_rendering();
lamps.end(static_cast<std::uint32_t>(m_renderpass.draw_stats.drawcalls));
m_renderpass = m_colorpass;
m_context->passes.end_pass();
}
void vulkan14_renderer::render_lamp_shadows()
{
auto &lamps{m_context->scene.lamps};
m_lampscenerydrawn = 0;
auto const &slots{lamps.lamps()};
if (slots.empty() || (false == Global.gfx_lights_clustered))
{
return;
}
// vehicles near the camera cast the shadows of the lamps, and the occupied cab
lamps.lamp_vehicles(Global.pCamera.Pos);
auto const vehiclesrange{static_cast<double>(Global.gfx_lights_shadows_vehicles_range)};
auto *cab{vehiclesrange > 0.0 ? lamps.shadow_cab() : nullptr};
auto timing{false};
for (auto const idx : slots)
{
auto const &slot{lamps.slot(idx)};
if (lamps.scenery_wanted(idx))
{
if (false == timing)
{
m_context->passes.begin_pass("scenery lamp shadows");
lamps.lamps_timer_begin();
timing = true;
}
render_lamp_shadow(idx, false, nullptr);
}
if (false == slot.scenery)
{
continue;
}
// the vehicles by the lamp, within its reach
auto const &slotvehicles{lamps.slot_vehicles(slot)};
auto *slotcab{(cab != nullptr) && (glm::length(cab->GetPosition() - slot.position) <= slot.range + cab->radius()) ? cab : nullptr};
if (slotvehicles.empty() && (slotcab == nullptr))
{
lamps.no_vehicles(idx);
continue;
}
if (false == lamps.vehicles_wanted(idx, m_framestamp))
{
continue;
}
if (false == timing)
{
m_context->passes.begin_pass("scenery lamp shadows");
lamps.lamps_timer_begin();
timing = true;
}
render_lamp_shadow(idx, true, slotcab);
}
if (timing)
{
lamps.lamps_timer_end();
m_context->passes.end_pass();
}
}
void vulkan14_renderer::render_lamp_shadow(std::size_t const Slot, bool const Vehicles, TDynamicObject *Cab)
{
auto &lamps{m_context->scene.lamps};
auto const &slot{lamps.slot(Slot)};
// the shadow pass from the lamp, in world axes: the camera of the pass is the lamp, its frustum the box of its reach. the
// distances which pick the details of the models are measured from the real camera, as in the shadows of the sun
m_renderpass = m_colorpass;
m_renderpass.draw_mode = rendermode::shadows;
m_renderpass.draw_range = slot.range;
m_renderpass.draw_stats = {};
auto &camera{m_renderpass.pass_camera};
camera.position() = slot.position;
camera.modelview() = glm::mat4{glm::translate(glm::dmat4{1.0}, -slot.position)};
auto const reach{slot.range};
camera.projection() = glm::ortho(-reach, reach, -reach, reach, -reach, reach);
camera.update_frustum();
OpenGLMatrices.mode(GL_MODELVIEW);
OpenGLMatrices.load_matrix(glm::mat4{1.f});
lamps.begin_lamp(Slot, Vehicles, m_instancerecordsversion, m_framestamp);
begin_local_shadow_rendering();
m_cubeshadows = true;
if (Vehicles)
{
for (auto *vehicle : lamps.slot_vehicles())
{
Render(vehicle);
}
if (Cab != nullptr)
{
Render_cab(Cab, 0.0f, false);
}
}
else
{
m_cubescenery = true;
m_cubeskipinstance = static_cast<TAnimModel const *>(slot.owner);
// the instances culled on the gpu, each scenery part drawn in a frame with a culling of its own
m_lightshadowcull = light_shadow_cull(m_context->scene.cull.lamps, m_lampscenerydrawn++, Global.gfx_gpucull_shadows_lamps);
Render(simulation::Region);
m_lightshadowcull = nullptr;
m_cubeskipinstance = nullptr;
m_cubescenery = false;
}
m_cubeshadows = false;
end_local_shadow_rendering();
lamps.end_lamp(static_cast<std::uint32_t>(m_renderpass.draw_stats.drawcalls));
m_renderpass = m_colorpass;
}
void vulkan14_renderer::render_headlight_shadows()
{
auto &lamps{m_context->scene.lamps};
// the headlights which shine, the most important first (in the order of update_lights()), whose beam may be seen within the
// reach of the shadows
auto const &picked{lamps.pick_headlights(simulation::Lights, m_colorpass.pass_camera)};
lamps.update_headlights(picked, m_framestamp);
auto const &slots{lamps.headlights()};
if (slots.empty())
{
return;
}
// the occupied cab, as in render_lamp_shadows()
auto *cab{lamps.shadow_cab()};
auto timing{false};
for (auto const idx : slots)
{
auto const &slot{lamps.headlight(idx)};
light_array::light_record const *light{nullptr};
for (auto const *candidate : picked)
{
if ((candidate->owner == slot.owner) && (candidate->index == slot.index))
{
light = candidate;
break;
}
}
if (light == nullptr)
{
continue;
}
glm::dvec3 projector;
glm::mat3 rotation;
glm::mat4 projection;
float nearplane, fieldofview;
headlight_projector(*light, projector, rotation, projection, nearplane, fieldofview);
// a vehicle on the move needs its map anew, the things around it and the vehicles passing by only now and then
auto const moved{(glm::length(projector - slot.projector) > 0.01) || (glm::dot(light->direction, slot.direction) < 0.99999f)};
if (false == lamps.headlight_wanted(idx, moved, m_framestamp))
{
continue;
}
if (false == timing)
{
m_context->passes.begin_pass("headlight shadows");
lamps.headlights_timer_begin();
timing = true;
}
render_headlight_shadow(idx, *light, cab);
}
if (timing)
{
lamps.headlights_timer_end();
m_context->passes.end_pass();
}
}
void vulkan14_renderer::render_headlight_shadow(std::size_t const Slot, light_array::light_record const &Light, TDynamicObject *Cab)
{
auto &lamps{m_context->scene.lamps};
glm::dvec3 projector;
glm::mat3 rotation;
glm::mat4 projection;
float nearplane, fieldofview;
headlight_projector(Light, projector, rotation, projection, nearplane, fieldofview);
auto const reach{nearplane + Global.gfx_lights_shadows_headlights_range};
// the shadow pass from the projector of the beam, with its projection. the distances which pick the details of the models
// are measured from the real camera, as in the shadows of the sun
m_renderpass = m_colorpass;
m_renderpass.draw_mode = rendermode::shadows;
m_renderpass.draw_range = reach;
m_renderpass.draw_stats = {};
auto &camera{m_renderpass.pass_camera};
camera.position() = projector;
camera.modelview() = glm::mat4{glm::dmat4{glm::dmat3{rotation}} * glm::translate(glm::dmat4{1.0}, -projector)};
camera.projection() = projection;
// the projection goes on to the infinity, what the shadows are cast by ends at their reach
camera.update_frustum(glm::perspective(fieldofview, 1.f, nearplane, reach));
setup_matrices();
auto const colorprojection{scene_ubs.projection};
scene_ubs.projection = projection;
lamps.begin_headlight(Slot, projector, rotation, projection, Light.direction, m_framestamp);
begin_local_shadow_rendering();
m_shadowskipvehicle = Light.owner;
// the instances culled on the gpu, each slot with a culling of its own
m_lightshadowcull = light_shadow_cull(m_context->scene.cull.headlights, Slot, Global.gfx_gpucull_shadows_headlights);
Render(simulation::Region);
m_lightshadowcull = nullptr;
if ((Cab != nullptr) && (Cab != Light.owner) && (glm::length(Cab->GetPosition() - projector) <= reach + Cab->radius()))
{
Render_cab(Cab, 0.0f, false);
}
m_shadowskipvehicle = nullptr;
end_local_shadow_rendering();
lamps.end_headlight(static_cast<std::uint32_t>(m_renderpass.draw_stats.drawcalls));
scene_ubs.projection = colorprojection;
m_renderpass = m_colorpass;
}
vk14::instance_cull *vulkan14_renderer::light_shadow_cull(std::vector<std::unique_ptr<vk14::instance_cull>> &Culls, std::size_t const Index, bool const Enabled)
{
auto &ctx{*m_context};
auto &cull{ctx.scene.cull};
// the same conditions as those of the other passes in walk_region_begin()
if ((Global.gfx_gpucull == 0) || (false == Enabled) || (m_current_viewport == nullptr) || (false == m_current_viewport->main) || (false == cull.ready))
{
return nullptr;
}
if (Culls.size() <= Index)
{
Culls.resize(Index + 1);
}
if (!Culls[Index])
{
// the buffers of its own, the records and the kernels of the main view
auto culling{std::make_unique<vk14::instance_cull>()};
vk14::cull_environment environment;
if (false == culling->init(environment, ctx.device.handle(), ctx.pipelines, ctx.shaders, ctx.scene.pass_layout, &cull.main))
{
culling->destroy(ctx.device.handle());
return nullptr;
}
Culls[Index] = std::move(culling);
}
return Culls[Index].get();
}

View File

@@ -9,6 +9,7 @@ http://mozilla.org/MPL/2.0/.
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14memory.h"
#include "rendering/vulkan14/vulkan14commands.h"
#include "rendering/vulkan14/vulkan14instance.h"
#include "rendering/vulkan14/vulkan14device.h"
@@ -278,14 +279,28 @@ void image::destroy()
state = image_state{};
}
void transition(VkCommandBuffer Commands, image &Image, VkImageLayout Layout, VkPipelineStageFlags2 Stage, VkAccessFlags2 Access)
void transition(command_stream *Commands, image &Image, VkImageLayout Layout, VkPipelineStageFlags2 Stage, VkAccessFlags2 Access)
{
auto const writes{VK_ACCESS_2_SHADER_WRITE_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT |
VK_ACCESS_2_HOST_WRITE_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT};
auto const &from{Image.state};
// reads after reads in the same layout need nothing
// reads after reads in the same layout need nothing, unless they are in stages the barrier after the last write didn't wait
// for: those wait for the stages which read the image already (which came after the write)
if ((from.layout == Layout) && ((from.access & writes) == 0) && ((Access & writes) == 0))
{
if ((Stage & ~from.stage) != 0)
{
VkMemoryBarrier2 chain{};
chain.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2;
chain.srcStageMask = (from.stage != VK_PIPELINE_STAGE_2_NONE ? from.stage : VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT);
chain.dstStageMask = Stage;
chain.dstAccessMask = Access;
VkDependencyInfo dependency{};
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
dependency.memoryBarrierCount = 1;
dependency.pMemoryBarriers = &chain;
Commands->pipeline_barrier2(&dependency);
}
Image.state.stage |= Stage;
Image.state.access |= Access;
return;
@@ -307,7 +322,7 @@ void transition(VkCommandBuffer Commands, image &Image, VkImageLayout Layout, Vk
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
dependency.imageMemoryBarrierCount = 1;
dependency.pImageMemoryBarriers = &barrier;
vkCmdPipelineBarrier2(Commands, &dependency);
Commands->pipeline_barrier2(&dependency);
Image.state = image_state{Layout, Stage, Access};
}

View File

@@ -177,7 +177,7 @@ class image
// a barrier of an image from its state to the given one, the state of the image updated. nothing when the image already is
// in the layout and was only read by the same stage
void transition(VkCommandBuffer Commands, image &Image, VkImageLayout Layout, VkPipelineStageFlags2 Stage, VkAccessFlags2 Access);
void transition(command_stream *Commands, image &Image, VkImageLayout Layout, VkPipelineStageFlags2 Stage, VkAccessFlags2 Access);
// objects destroyed once the gpu is done with the frame which used them last
class deletion_queue

View File

@@ -0,0 +1,342 @@
/*
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

View File

@@ -0,0 +1,105 @@
/*
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/.
*/
// the particles of the gpu in the vulkan renderer (the stage P6 of the plan, opengl46_gpu_particles): the storage buffers, the
// compute kernels and the indirect draws of gfx::gpu_particles. the compute passes run between two parts of the rendering of
// the colour pass (the host ends it and begins it again); the instances counted by a pass are read back once the gpu is done
// with the frame, as the opengl renderer reads them a few frames later
#pragma once
#include "rendering/common/gpuparticles.h"
#include "rendering/vulkan14/vulkan14compute.h"
#include "rendering/vulkan14/vulkan14texture.h"
#include <functional>
#include <memory>
namespace vk14
{
struct scene_program;
class gpu_particles : public gfx::gpu_particles
{
public:
// what the renderer does for the particles
struct host
{
// the rendering under way ended for the compute passes; the commands of the frame
std::function<command_stream *()> compute;
// the pass set of the pass bound for the kernel
std::function<void(compute_kernel const &Kernel)> bind;
// the rendering begun again and the draw of the instances recorded: Program with the blending of the scene, the instances
// from Output, the command from Command, Data0 and Data1 in the push constants (data[0], data[1])
std::function<void(scene_program &Program, VkBuffer Output, VkBuffer Command, glm::vec4 const &Data0, glm::vec4 const &Data1)> draw;
// the slot of the frame in flight, the value of the timeline the gpu reaches when it's done with the frame, the one it reached
std::function<std::uint32_t()> slot;
std::function<std::uint64_t()> value;
std::function<std::uint64_t()> completed;
};
gpu_particles() = default;
~gpu_particles() override;
// called once, before anything is made. Frames: the frames in flight
void init(VkDevice Device, pipeline_library &Pipelines, shader_library &Shaders, VkDescriptorSetLayout Passlayout, allocator &Memory, uploader &Uploads, deletion_queue &Deletions, frame_ring &Ring,
VkDeviceSize Alignment, std::uint32_t Frames, host Host);
// everything goes (the device is idle)
void destroy();
protected:
bool gpu_init() override;
void gpu_layout(std::uint32_t const Places, std::uint32_t const Kept, std::vector<std::uint32_t> const &Owners, std::size_t const Sources) override;
void gpu_simulate(smoke_simulation const &Simulation, std::vector<gl::particle_smoke_source> const &Records) override;
void gpu_render_smoke(smoke_draw const &Draw, std::size_t &Drawn) override;
void gpu_collect_precipitation(std::size_t &Drawn) override;
void gpu_precipitation_storage(std::uint32_t const Capacity) override;
void gpu_render_precipitation(precipitation_draw const &Draw) override;
void gpu_clear() override;
private:
std::shared_ptr<buffer> make_buffer(VkDeviceSize const Size, VkBufferUsageFlags const Usage, char const *Name);
void let_go(std::shared_ptr<buffer> &Buffer);
// the command of a draw reset before the compute pass counts its instances
void reset_command(command_stream *Commands, buffer const &Command);
static void collect(readback_ring &Readback, std::uint64_t Completed, std::size_t &Drawn);
VkDevice m_device{VK_NULL_HANDLE};
pipeline_library *m_pipelines{nullptr};
shader_library *m_shaders{nullptr};
VkDescriptorSetLayout m_passlayout{VK_NULL_HANDLE};
allocator *m_memory{nullptr};
uploader *m_uploads{nullptr};
deletion_queue *m_deletions{nullptr};
frame_ring *m_ring{nullptr};
VkDeviceSize m_alignment{256};
std::uint32_t m_frames{0};
host m_host;
compute_kernel m_smokesimulate;
compute_kernel m_smokeprepare;
compute_kernel m_precipitationprepare;
std::shared_ptr<scene_program> m_smokedraw;
std::shared_ptr<scene_program> m_precipitationdraw;
// smoke
std::shared_ptr<buffer> m_particles;
std::shared_ptr<buffer> m_sources;
std::shared_ptr<buffer> m_owners;
std::shared_ptr<buffer> m_smokeoutput;
std::shared_ptr<buffer> m_smokecommand;
readback_ring m_smokereadback;
// rain and snow
std::shared_ptr<buffer> m_precipitationoutput;
std::shared_ptr<buffer> m_precipitationcommand;
readback_ring m_precipitationreadback;
};
} // namespace vk14

View File

@@ -9,6 +9,7 @@ http://mozilla.org/MPL/2.0/.
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14passes.h"
#include "rendering/vulkan14/vulkan14commands.h"
#include "rendering/vulkan14/vulkan14device.h"
namespace vk14
@@ -19,7 +20,7 @@ image_use use_of(image &Image, VkImageLayout Layout, VkPipelineStageFlags2 Stage
return image_use{Image.handle(), Image.aspect(), &Image.state, Layout, Stage, Access};
}
void barriers(VkCommandBuffer Commands, std::vector<image_use> const &Uses)
void barriers(command_stream *Commands, std::vector<image_use> const &Uses)
{
auto const writes{VK_ACCESS_2_SHADER_WRITE_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT |
VK_ACCESS_2_HOST_WRITE_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT};
@@ -31,9 +32,23 @@ void barriers(VkCommandBuffer Commands, std::vector<image_use> const &Uses)
continue;
}
auto &state{*use.state};
// reads after reads in the same layout need nothing
// reads after reads in the same layout need nothing, unless they are in stages the barrier after the last write didn't
// wait for: those wait for the stages which read the image already (which came after the write)
if ((state.layout == use.layout) && ((state.access & writes) == 0) && ((use.access & writes) == 0))
{
if ((use.stage & ~state.stage) != 0)
{
VkMemoryBarrier2 chain{};
chain.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2;
chain.srcStageMask = (state.stage != VK_PIPELINE_STAGE_2_NONE ? state.stage : VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT);
chain.dstStageMask = use.stage;
chain.dstAccessMask = use.access;
VkDependencyInfo dependency{};
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
dependency.memoryBarrierCount = 1;
dependency.pMemoryBarriers = &chain;
Commands->pipeline_barrier2(&dependency);
}
state.stage |= use.stage;
state.access |= use.access;
continue;
@@ -61,7 +76,7 @@ void barriers(VkCommandBuffer Commands, std::vector<image_use> const &Uses)
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
dependency.imageMemoryBarrierCount = static_cast<std::uint32_t>(list.size());
dependency.pImageMemoryBarriers = list.data();
vkCmdPipelineBarrier2(Commands, &dependency);
Commands->pipeline_barrier2(&dependency);
}
pass_list::~pass_list()
@@ -114,7 +129,7 @@ void pass_list::destroy()
m_device = VK_NULL_HANDLE;
}
void pass_list::begin_frame(VkCommandBuffer Commands, std::uint32_t Frame, std::uint64_t Number)
void pass_list::begin_frame(command_stream *Commands, std::uint32_t Frame, std::uint64_t Number)
{
m_commands = Commands;
m_frame = Frame;
@@ -124,7 +139,7 @@ void pass_list::begin_frame(VkCommandBuffer Commands, std::uint32_t Frame, std::
frame.used = 0;
frame.number = Number;
frame.recorded = true;
vkCmdResetQueryPool(Commands, frame.pool, 0, max_passes * 2);
Commands->reset_query_pool(frame.pool, 0, max_passes * 2);
}
void pass_list::begin_pass(char const *Name, std::vector<image_use> const &Uses)
@@ -139,7 +154,7 @@ void pass_list::begin_pass(char const *Name, std::vector<image_use> const &Uses)
// (no timestamps on a queue without valid bits; the device is chosen with them, this is for safety)
if ((frame.used < max_passes) && (m_mask != 0))
{
vkCmdWriteTimestamp2(m_commands, VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT, frame.pool, frame.used * 2);
m_commands->write_timestamp2(VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT, frame.pool, frame.used * 2);
frame.names.emplace_back(Name);
m_open = static_cast<int>(frame.used);
++frame.used;
@@ -158,7 +173,7 @@ void pass_list::end_pass()
}
if (m_open < static_cast<int>(max_passes))
{
vkCmdWriteTimestamp2(m_commands, VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT, m_frames[m_frame].pool, static_cast<std::uint32_t>(m_open) * 2 + 1);
m_commands->write_timestamp2(VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT, m_frames[m_frame].pool, static_cast<std::uint32_t>(m_open) * 2 + 1);
}
end_label(m_commands);
m_open = -1;
@@ -167,7 +182,7 @@ void pass_list::end_pass()
void pass_list::end_frame()
{
end_pass();
m_commands = VK_NULL_HANDLE;
m_commands = nullptr;
}
void pass_list::collect(std::uint32_t Frame)

View File

@@ -39,7 +39,7 @@ struct image_use
image_use use_of(image &Image, VkImageLayout Layout, VkPipelineStageFlags2 Stage, VkAccessFlags2 Access);
// the barriers which bring the images from their states to the uses, the states updated
void barriers(VkCommandBuffer Commands, std::vector<image_use> const &Uses);
void barriers(command_stream *Commands, std::vector<image_use> const &Uses);
class pass_list
{
@@ -53,7 +53,7 @@ class pass_list
void destroy();
// the frame Frame (index of the frame in flight) is recorded to Commands from now on; Number counts the frames
void begin_frame(VkCommandBuffer Commands, std::uint32_t Frame, std::uint64_t Number);
void begin_frame(command_stream *Commands, std::uint32_t Frame, std::uint64_t Number);
// a pass: the barriers of its images, its label and the timestamp of its start
void begin_pass(char const *Name, std::vector<image_use> const &Uses = {});
void end_pass();
@@ -85,7 +85,7 @@ class pass_list
float m_period{1.f}; // ns per tick
std::uint64_t m_mask{~0ull}; // the valid bits of the timestamps
std::vector<frame_queries> m_frames;
VkCommandBuffer m_commands{VK_NULL_HANDLE};
command_stream *m_commands{nullptr};
std::uint32_t m_frame{0};
int m_open{-1};
// averaged times of the parts, ms

View File

@@ -37,6 +37,17 @@ float srgb_to_linear(float const Value)
return (Value <= 0.04045f ? Value / 12.92f : std::pow((Value + 0.055f) / 1.055f, 2.4f));
}
// the threads of the renderer (gfx.threads), the calling one with them: 0, the cores less two
std::size_t render_threads()
{
if (Global.gfx_threads > 0)
{
return static_cast<std::size_t>(Global.gfx_threads);
}
auto const cores{static_cast<std::size_t>(std::thread::hardware_concurrency())};
return (cores > 3 ? cores - 2 : 1);
}
} // namespace
bool vulkan14_renderer::context::create_frames(std::uint32_t const Count, std::string &Reason)
@@ -69,25 +80,250 @@ bool vulkan14_renderer::context::create_frames(std::uint32_t const Count, std::s
Reason = "no command pool";
return false;
}
VkCommandBuffer buffers[2] = {VK_NULL_HANDLE, VK_NULL_HANDLE};
VkCommandBufferAllocateInfo bufferinfo{};
bufferinfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
bufferinfo.commandPool = frames[i].pool;
bufferinfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
bufferinfo.commandBufferCount = 2;
if (!VK14_VERIFY(vkAllocateCommandBuffers(handle, &bufferinfo, buffers)))
bufferinfo.commandBufferCount = 1;
if (!VK14_VERIFY(vkAllocateCommandBuffers(handle, &bufferinfo, &frames[i].uploads)))
{
Reason = "no command buffer";
return false;
}
frames[i].uploads = buffers[0];
frames[i].commands = buffers[1];
instance.set_object_name(handle, VK_OBJECT_TYPE_COMMAND_BUFFER, reinterpret_cast<std::uint64_t>(frames[i].uploads), "uploads " + std::to_string(i));
instance.set_object_name(handle, VK_OBJECT_TYPE_COMMAND_BUFFER, reinterpret_cast<std::uint64_t>(frames[i].commands), "frame " + std::to_string(i));
}
if (Global.gfx_vulkan_renderthread)
{
start_render_thread();
}
return true;
}
bool vulkan14_renderer::context::record(frame &Frame, bool const Uploads)
{
auto const handle{device.handle()};
auto const start{std::chrono::steady_clock::now()};
Frame.upload_stream.finish();
Frame.commands.finish();
VkCommandBufferBeginInfo begin{};
begin.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
if (Uploads)
{
VK14_VERIFY(vkBeginCommandBuffer(Frame.uploads, &begin));
Frame.upload_stream.replay(Frame.uploads);
VK14_VERIFY(vkEndCommandBuffer(Frame.uploads));
}
// the chunks split evenly between the workers, each with a command pool of its own (a pool is used by one thread at a time);
// which worker records a chunk doesn't matter: each chunk has a command buffer of its own, they go to the queue in their order
auto const chunks{Frame.commands.chunks()};
auto const threads{std::max<std::size_t>(1, std::min<std::size_t>(render_threads(), chunks))};
while (Frame.workers.size() < threads)
{
VkCommandPoolCreateInfo poolinfo{};
poolinfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
poolinfo.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT;
poolinfo.queueFamilyIndex = device.families().graphics;
VkCommandPool pool{VK_NULL_HANDLE};
if (!VK14_VERIFY(vkCreateCommandPool(handle, &poolinfo, nullptr, &pool)))
{
return false;
}
Frame.workers.emplace_back().pool = pool;
}
Frame.chunks.assign(chunks, VK_NULL_HANDLE);
workers.run(threads, threads, [&](std::size_t const Worker) {
auto &commands{Frame.workers[Worker]};
for (auto chunk = chunks * Worker / threads; chunk < chunks * (Worker + 1) / threads; ++chunk)
{
if (commands.used == commands.buffers.size())
{
VkCommandBufferAllocateInfo bufferinfo{};
bufferinfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
bufferinfo.commandPool = commands.pool;
bufferinfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
bufferinfo.commandBufferCount = 1;
VkCommandBuffer buffer{VK_NULL_HANDLE};
if (!VK14_VERIFY(vkAllocateCommandBuffers(handle, &bufferinfo, &buffer)))
{
return;
}
commands.buffers.push_back(buffer);
}
auto const buffer{commands.buffers[commands.used++]};
VK14_VERIFY(vkBeginCommandBuffer(buffer, &begin));
Frame.commands.replay(chunk, buffer);
VK14_VERIFY(vkEndCommandBuffer(buffer));
Frame.chunks[chunk] = buffer;
}
});
record_threads = threads;
record_time = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();
record_calls = Frame.commands.calls();
record_chunks = chunks;
return std::find(Frame.chunks.begin(), Frame.chunks.end(), VK_NULL_HANDLE) == Frame.chunks.end();
}
bool vulkan14_renderer::context::execute(render_job &Job, char const *&Failure)
{
auto &frame{*Job.target};
if (false == record(frame, Job.uploaded))
{
Failure = "recording a frame";
return false;
}
// the uploads first, in the same submit: their barriers make them done before the frame uses what they copied
std::vector<VkCommandBufferSubmitInfo> commands;
if (Job.uploaded)
{
VkCommandBufferSubmitInfo info{};
info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO;
info.commandBuffer = frame.uploads;
commands.push_back(info);
}
// the chunks of the frame in their order: a rendering suspended at the end of one is resumed at the start of the next
for (auto const chunk : frame.chunks)
{
VkCommandBufferSubmitInfo info{};
info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO;
info.commandBuffer = chunk;
commands.push_back(info);
}
VkSubmitInfo2 submit{};
submit.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2;
submit.waitSemaphoreInfoCount = static_cast<std::uint32_t>(Job.waits.size());
submit.pWaitSemaphoreInfos = Job.waits.data();
submit.commandBufferInfoCount = static_cast<std::uint32_t>(commands.size());
submit.pCommandBufferInfos = commands.data();
submit.signalSemaphoreInfoCount = static_cast<std::uint32_t>(Job.signals.size());
submit.pSignalSemaphoreInfos = Job.signals.data();
if (!VK14_VERIFY(vkQueueSubmit2(device.graphics_queue(), 1, &submit, VK_NULL_HANDLE)))
{
Failure = "submitting a frame";
return false;
}
if ((false == Job.windows.empty()) && (false == present(Job.windows)))
{
Failure = "presenting";
return false;
}
return true;
}
bool vulkan14_renderer::context::present(std::vector<vk14::swapchain *> const &Windows)
{
std::vector<VkSemaphore> waits;
std::vector<VkSwapchainKHR> swapchains;
std::vector<std::uint32_t> images;
std::vector<VkResult> results(Windows.size(), VK_SUCCESS);
for (auto *window : Windows)
{
waits.push_back(window->present_semaphore());
swapchains.push_back(window->handle());
images.push_back(window->image_index());
}
VkPresentInfoKHR info{};
info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
info.waitSemaphoreCount = static_cast<std::uint32_t>(waits.size());
info.pWaitSemaphores = waits.data();
info.swapchainCount = static_cast<std::uint32_t>(swapchains.size());
info.pSwapchains = swapchains.data();
info.pImageIndices = images.data();
info.pResults = results.data();
auto const result{vkQueuePresentKHR(device.graphics_queue(), &info)};
for (std::size_t i = 0; i < Windows.size(); ++i)
{
Windows[i]->presented(results[i]);
}
if ((result == VK_ERROR_DEVICE_LOST) || (result == VK_ERROR_SURFACE_LOST_KHR))
{
vk14::log_error(std::string("vkQueuePresentKHR failed with ") + vk14::result_string(result));
return false;
}
return true;
}
void vulkan14_renderer::context::start_render_thread()
{
render_stop = false;
render_thread = std::thread([this]() {
std::unique_lock<std::mutex> lock(render_mutex);
while (true)
{
render_wake.wait(lock, [this]() { return render_stop || render_queued; });
if (false == render_queued)
{
// stopped, with nothing left to do
break;
}
lock.unlock();
char const *failure{nullptr};
auto done{false};
try
{
done = execute(render_pending, failure);
}
catch (std::exception const &Error)
{
vk14::log_error(std::string("the render thread failed: ") + Error.what());
failure = "recording a frame";
}
catch (...)
{
vk14::log_error("the render thread failed");
failure = "recording a frame";
}
lock.lock();
if ((false == done) && (render_failure == nullptr))
{
render_failure = (failure != nullptr ? failure : "recording a frame");
}
render_queued = false;
render_idle.notify_all();
}
});
}
void vulkan14_renderer::context::stop_render_thread()
{
if (false == render_thread.joinable())
{
return;
}
{
std::lock_guard<std::mutex> lock(render_mutex);
render_stop = true;
}
render_wake.notify_all();
render_thread.join();
}
void vulkan14_renderer::context::hand_over(render_job &&Job)
{
{
std::unique_lock<std::mutex> lock(render_mutex);
render_idle.wait(lock, [this]() { return false == render_queued; });
render_pending = std::move(Job);
render_queued = true;
}
render_wake.notify_one();
}
char const *vulkan14_renderer::context::drain()
{
if (false == render_threaded())
{
return nullptr;
}
auto const start{std::chrono::steady_clock::now()};
std::unique_lock<std::mutex> lock(render_mutex);
render_idle.wait(lock, [this]() { return false == render_queued; });
render_wait = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();
auto const failure{render_failure};
render_failure = nullptr;
return failure;
}
// the parts of the renderer made once the device is there
bool vulkan14_renderer::context::create_core(std::uint32_t const Frames, std::string &Reason)
{
@@ -102,7 +338,7 @@ bool vulkan14_renderer::context::create_core(std::uint32_t const Frames, std::st
}
auto const &limits{device.caps().limits};
(void)limits;
if (false == ring.create(memory, ring_size, Frames, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, "frame ring", ring_tail))
if (false == ring.create(memory, ring_size, Frames, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, "frame ring", ring_tail))
{
Reason = "no ring for the data of the frames";
return false;
@@ -151,6 +387,8 @@ bool vulkan14_renderer::context::wait(std::uint64_t const Value) const
void vulkan14_renderer::context::destroy()
{
auto const handle{device.handle()};
// the frame given to the render thread goes to the queue first
stop_render_thread();
if (handle != VK_NULL_HANDLE)
{
// the end of the renderer, not a frame: everything on the gpu has to be done
@@ -178,6 +416,10 @@ void vulkan14_renderer::context::destroy()
{
vkDestroyCommandPool(handle, frame.pool, nullptr);
}
for (auto &worker : frame.workers)
{
vkDestroyCommandPool(handle, worker.pool, nullptr);
}
}
if (timeline != VK_NULL_HANDLE)
{
@@ -288,9 +530,11 @@ bool vulkan14_renderer::Init(GLFWwindow *Window)
vk14::log_info(std::to_string(framesinflight) + " frames in flight");
// until the stages of the plan bring them, these parts of the simulator aren't there with vulkan
vk14::log_info("not available with vulkan yet: the atmosphere, the sky ambient light, automatic exposure and bloom, stars (P1), the weather fog and clouds (P2), "
"ambient occlusion and contact shadows (P3), shadows of lamps and headlights (P4), the culling on the gpu (P5), the heightmap terrain, grass and the particles "
"of the gpu (P6), the map panel, python screens in their own windows (N5), panels in their own windows (R5b), VR (X4)");
vk14::log_info("not available with vulkan yet: the map panel, python screens in their own windows (N5), panels in their own windows (R5b), VR (X4)");
// the settings of eu07.ini which only the opengl renderer takes; everything else of gfx.* is taken here as there
vk14::log_info("settings of the opengl renderer without effect with vulkan: compresstex (the textures go to the gpu as the files give them), gfx.depthprepass.replay (the scene "
"is walked again for its depth), gfx.bufferstreaming, gfx.geometry.shared, gfx.format.color, gfx.format.depth, gfx.postfx.motionblur.format (the formats of the "
"targets are fixed), gfx.gldebug (gfx.vulkan.validation instead)");
WriteLog("Gfx Renderer: setup complete");
update_info();
return true;
@@ -318,6 +562,12 @@ bool vulkan14_renderer::AddViewport(global_settings::extraviewport_config const
return false;
}
auto &ctx{*m_context};
// the windows the render thread presents are left as they are until it's done
if (auto const *failure{ctx.drain()})
{
report_device_lost(failure);
return false;
}
auto swapchain{std::make_unique<vk14::swapchain>()};
std::string reason;
if (false == swapchain->create(ctx.instance, ctx.device, window, "viewport " + conf.monitor, static_cast<std::uint32_t>(ctx.frames.size()), reason))
@@ -343,7 +593,8 @@ void vulkan14_renderer::Shutdown()
{
if (m_context && (m_context->device.handle() != VK_NULL_HANDLE))
{
// the end of the renderer, not a frame: everything on the gpu has to be done
// the end of the renderer, not a frame: everything on the gpu has to be done, the frame of the render thread with it
m_context->stop_render_thread();
vkDeviceWaitIdle(m_context->device.handle());
}
// the geometry on the gpu goes with the context; the data of the banks stays for the simulation
@@ -389,34 +640,11 @@ void vulkan14_renderer::SwapBuffers()
auto &ctx{*m_context};
Timer::subsystem.gfx_swap.start();
// with the render thread the windows are presented by it (ctx.acquired was handed over to it)
if (false == ctx.acquired.empty())
{
std::vector<VkSemaphore> waits;
std::vector<VkSwapchainKHR> swapchains;
std::vector<std::uint32_t> images;
std::vector<VkResult> results(ctx.acquired.size(), VK_SUCCESS);
for (auto *window : ctx.acquired)
if (false == ctx.present(ctx.acquired))
{
waits.push_back(window->present_semaphore());
swapchains.push_back(window->handle());
images.push_back(window->image_index());
}
VkPresentInfoKHR present{};
present.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
present.waitSemaphoreCount = static_cast<std::uint32_t>(waits.size());
present.pWaitSemaphores = waits.data();
present.swapchainCount = static_cast<std::uint32_t>(swapchains.size());
present.pSwapchains = swapchains.data();
present.pImageIndices = images.data();
present.pResults = results.data();
auto const result{vkQueuePresentKHR(ctx.device.graphics_queue(), &present)};
for (std::size_t i = 0; i < ctx.acquired.size(); ++i)
{
ctx.acquired[i]->presented(results[i]);
}
if ((result == VK_ERROR_DEVICE_LOST) || (result == VK_ERROR_SURFACE_LOST_KHR))
{
vk14::log_error(std::string("vkQueuePresentKHR failed with ") + vk14::result_string(result));
report_device_lost("presenting");
}
ctx.acquired.clear();
@@ -484,6 +712,17 @@ void vulkan14_renderer::Update(double const Deltatime)
{
m_materials.on_weather_change();
}
if (((m_simulationstate.season != simulationstate.season) && (false == simulationstate.season.empty())) ||
((m_simulationstate.weather != simulationstate.weather) && (false == simulationstate.weather.empty())))
{
// the heightmap terrain keeps copies of the textures of its layer materials
if (m_context && m_context->scene.terrain)
{
m_context->scene.terrain->on_environment_change();
}
// the seasonal variants of the models are shown and hidden anew: the walks kept by the gpu culling go
m_walkrecords.clear();
}
}
if (m_context && m_context->textures && (true == Global.ResourceSweep) && (true == simulation::is_ready))
@@ -511,7 +750,9 @@ void vulkan14_renderer::update_info()
auto const &caps{ctx.device.caps()};
m_infotimes = "vulkan " + vk14::version_string(caps.api_version) + ": " + caps.device_name + " (" + caps.driver_name + ")\n" + "frames: " + std::to_string(ctx.framecount) + ", in flight " + std::to_string(ctx.frames.size()) + "\n" +
"mainloop total: " + to_string(Timer::subsystem.mainloop_total.average(), 2) + " ms, frame cpu: " + to_string(Timer::subsystem.gfx_total.average(), 2) + " ms, ui: " + to_string(Timer::subsystem.gfx_gui.average(), 2) +
" ms, present: " + to_string(Timer::subsystem.gfx_swap.average(), 2) + " ms\n" + "gpu frame parts:\n" + ctx.passes.info();
" ms, present: " + to_string(Timer::subsystem.gfx_swap.average(), 2) + " ms\n" + "command buffers recorded: " + to_string(ctx.record_time, 2) + " ms, " + std::to_string(ctx.record_calls) + " calls in " +
std::to_string(ctx.record_chunks) + " chunks, " + std::to_string(ctx.record_threads) + (ctx.record_threads == 1 ? " thread" : " threads") +
(ctx.render_threaded() ? ", on the render thread; waited for it: " + to_string(ctx.render_wait, 2) + " ms\n" : "\n") + "gpu frame parts:\n" + ctx.passes.info();
VkDeviceSize usage{0}, budget{0};
ctx.memory.budget(usage, budget);
m_infostats = "device memory: " + vk14::memory_size_string(usage) + " of " + vk14::memory_size_string(budget) + (ctx.device.memory_budget_enabled() ? "" : " (no budget from the driver)") + "\n" + "images: " + std::to_string(ctx.images.size()) +
@@ -522,6 +763,9 @@ void vulkan14_renderer::update_info()
auto const extent{window->extent()};
m_infostats += "window: " + std::to_string(extent.width) + "x" + std::to_string(extent.height) + (window->srgb() ? ", srgb" : ", unorm") + (window->present_mode() == VK_PRESENT_MODE_FIFO_KHR ? ", vsync" : ", no vsync") + "\n";
}
// the statistics of the scene and of the cullings, as the opengl renderer gives them
collect_cluster_stats();
m_infostats += "\n" + scene_info() + cull_info();
}
bool vulkan14_renderer::Debug_Ui_State(std::optional<bool> State)

View File

@@ -24,17 +24,27 @@ http://mozilla.org/MPL/2.0/.
#include "rendering/lightarray.h"
#include "rendering/common/scenewalk.h"
#include "rendering/common/locallights.h"
#include "rendering/common/skylight.h"
#include "rendering/common/weatherstate.h"
#include "rendering/common/shadowfit.h"
#include "rendering/common/gpuparticles.h"
#include "rendering/particles.h"
#include "model/material.h"
#include "vehicle/Camera.h"
#include "gl/ubo.h"
class world_environment;
struct VkRenderingInfo;
namespace vk14
{
struct scene_program;
struct target_formats;
class image;
class compute_kernel;
struct draw_constants;
enum class blend_mode;
class instance_cull;
struct image_use;
} // namespace vk14
class vulkan14_renderer : public gfx_renderer, public gfx::scene_walk
@@ -69,6 +79,10 @@ class vulkan14_renderer : public gfx_renderer, public gfx::scene_walk
bool Blendable_Material(material_handle const Material) override;
void Bind_Material(material_handle const Material, TSubModel const *sm = nullptr, lighting_data const *lighting = nullptr) override;
IMaterial const *Material(material_handle const Material) const override;
material_handle Terrain_Material(material_handle const Reuse, std::vector<gfx::terrain_layer> const &Layers, int const Samples, std::uint8_t const *Weights, glm::vec3 const &Placement,
int const HoleSamples = 0, std::uint8_t const *Holes = nullptr) override;
void Terrain_Release(material_handle const Material) override;
void Terrain_Grass(std::vector<gfx::grass_kind> const &Kinds, std::vector<gfx::grass_chunk> const &Chunks) override;
// shaders
std::shared_ptr<gfx::shader_program> Fetch_Shader(std::string const &name) override;
std::string Shader_Name(std::shared_ptr<gfx::shader_program> const &Program) const override;
@@ -158,6 +172,89 @@ class vulkan14_renderer : public gfx_renderer, public gfx::scene_walk
void walk_glare_draw(TSubModel *Submodel, gfx::geometry_handle const &Geometry) override;
void walk_light_query(TSubModel *Submodel, bool const Begin) override;
void walk_editor_cursor() override;
gfx::geometrybank_manager &walk_geometry() override;
bool walk_blending_enabled() const override;
void walk_region_begin() override;
bool walk_instances_begin() override;
void walk_instances_end() override;
// the gpu culling of the instances (vulkan14indirect.cpp, the stage P5; opengl46_renderer::Render_Instances_Indirect() and the
// rest): the cullings of the passes and the depth pyramid, made when the renderer starts
bool init_cull();
void destroy_cull();
// the rendering of the pass under way, kept to be begun again where it was: it's ended for the compute passes of the culling
// in the middle of the walk, and begun again with what it drew loaded, with the state of the draws as it was
void remember_rendering(VkRenderingInfo const &Info, std::vector<vk14::image_use> const &Uses);
void suspend_rendering();
void resume_rendering();
// the pass sets with the output of the culling as the instance buffer, made when the pass sets or the output changed; bound by
// the draws until they're dropped (nullptr)
void use_cull_pass_sets(vk14::instance_cull *Cull);
// writes the pass sets (Cull null) or those of the culling
void write_pass_sets(vk14::instance_cull *Cull);
// draws of the instances made, and the calls which made them
struct indirect_counts
{
std::uint32_t draws{0};
std::uint32_t calls{0};
indirect_counts &operator+=(indirect_counts const &Other)
{
draws += Other.draws;
calls += Other.calls;
return *this;
}
};
using command_set = gfx::instance_cull::command_set;
// the instances of the main view drawn from its culling: the depth pyramid, the culling, the walks, the depth and the colour,
// in two phases with the occlusion test of the instances drawn first; inside the depth pass of the scene the depth alone,
// the colour waits for the colour pass (render_instances_indirect_pending())
void render_instances_indirect();
// the same for the passes other than the main view: no occlusion test, no depth prepass, one phase
void render_instances_indirect_pass();
void render_instances_indirect_pending();
// the walks of the culled groups (gfx::scene_walk::Walk_Indirect_Draws()), the draws put in order for the multi draws, and the
// commands filled in by the culling
void record_indirect_draws(vk14::instance_cull &Cull, bool const Cascade);
// the recorded draws with the commands of the set: their colour with the programs of their materials, their depth with the
// programs of the depth prepass, the shadows with the programs of the shadows
indirect_counts draw_indirect_colour(vk14::instance_cull &Cull, command_set const Set);
indirect_counts draw_indirect_depth(vk14::instance_cull &Cull, command_set const Set);
indirect_counts draw_indirect_shadow(vk14::instance_cull &Cull, command_set const Set);
// Count recorded draws from First with the program (a multi draw with more than one) and the state of the draws
bool draw_indirect(vk14::instance_cull &Cull, command_set const Set, std::size_t const First, std::uint32_t const Count, vk14::scene_program &Program, vk14::blend_mode const Blend);
// the variant of a program for the multi draws, nullptr when there's none
vk14::scene_program *multidraw_program(vk14::scene_program &Program);
vk14::instance_cull &pass_cull();
// the shadow maps of the lamps (vulkan14lamps.cpp, the stage P4; opengl46_renderer::Render_local_shadows() and the rest): the cab
// lamp, the scenery lamps, one of them, the headlights, one set of them; the culling of a map (nullptr when its instances go
// through the cpu); the rendering of the map set up by the maps; the maps put in the layout the shading reads them in
void render_local_shadows();
void render_lamp_shadows();
void render_lamp_shadow(std::size_t const Slot, bool const Vehicles, TDynamicObject *Cab);
void render_headlight_shadows();
void render_headlight_shadow(std::size_t const Slot, light_array::light_record const &Light, TDynamicObject *Cab);
vk14::instance_cull *light_shadow_cull(std::vector<std::unique_ptr<vk14::instance_cull>> &Culls, std::size_t const Index, bool const Enabled);
void begin_local_shadow_rendering();
void end_local_shadow_rendering();
void ready_lamp_maps();
bool m_cubeshadows{false}; // the shadow pass draws to a cube map, with the programs of the cube maps
vk14::instance_cull *m_lightshadowcull{nullptr}; // the culling of the map of headlights or of a lamp being drawn
std::size_t m_lampscenerydrawn{0}; // scenery parts of the maps of lamps drawn in this frame so far
unsigned int m_instancecullframe{~0u}; // the frame the culling of the main view ran in last
int m_shadowcascade{-1}; // the cascade of the sun shadows being drawn
bool m_indirectmultidraw{false}; // the recorded draws are in order for the multi draws
vk14::instance_cull *m_indirectcull{nullptr}; // the culling the recorded draws were made for
command_set m_indirectcolourset{command_set::first}; // the commands the colour pass draws the pending instances with
// the cascades of the sun shadows fitted to the depth of the main view (gfx.shadow.sdsm): the cascades of the frame fitted to the
// latest measurement (opengl46_renderer::Shadow_Fit()), the measurement of the depth of the finished view, the state for the panel,
// the area of the cascade being set up and the sizes setup_pass() found for it
bool shadow_fit(viewport_config const &Viewport, std::array<float, gl::MAX_CASCADES + 1> &Splits, gfx::shadow_fit::areas &Areas);
char const *shadow_fit_unavailable(viewport_config const &Viewport) const;
void measure_shadow_fit(std::size_t const Viewport);
gfx::shadow_fit::fit_state m_shadowfitstate;
std::array<glm::vec3, 8> const *m_shadowfitarea{nullptr};
float m_shadowslicesize{0.f};
float m_shadowdrawnsize{0.f};
// the scene (vulkan14scene.cpp)
bool init_scene(std::string &Reason);
@@ -196,6 +293,14 @@ class vulkan14_renderer : public gfx_renderer, public gfx::scene_walk
void setup_shadow_matrices();
// the light clusters of the pass: the grid and the lists of the lights
void update_clusters(glm::ivec2 const &Targetsize);
// which program of the depth prepass stands for the program of the material: 0 none, 1 without the alpha test, 2 with it
int prepass_class(material_handle const Material);
// the depth of the whole opaque scene of the colour pass of the main viewport before its colour, for the contact shadows
// (Contactonly: with them alone) or for itself (gfx.depthprepass.scene), and the contact shadows set up to read it
void render_scene_depth(std::size_t const Viewport, glm::mat4 const &Future);
bool contact_shadows_wanted(viewport_config const &Viewport) const;
void setup_contact_shadows(std::size_t const Viewport);
bool m_contactshadows{false};
// the materials
material_handle invalid_material() const
{
@@ -215,6 +320,44 @@ class vulkan14_renderer : public gfx_renderer, public gfx::scene_walk
// the screenshot copied with a frame the gpu is done with, saved
void update_screenshot();
void bind_pass_set();
void update_pass_sets();
// the pass set of the colour of the main view bound for the compute kernels, before the dispatches of Kernel (the kernels share
// the layout of set 0 and the push constants, so the set stays bound for the ones which follow)
void bind_compute_set(vk14::compute_kernel const &Kernel);
// the light of the sky (vulkan14sky.cpp): the resources, their first state, the light of the frame worked out before its
// passes, the environment map prefiltered with the GGX lobe, the stars of the sky pass
bool init_sky(std::string &Reason);
void destroy_sky();
void clear_sky_images();
void update_sky();
void prefilter_environment();
void render_stars(glm::mat4 const &View);
// the automatic exposure of the image of the main view, and its bloom chain; the result of the chain, null without it
void measure_exposure(vk14::image &Image);
vk14::image *render_bloom(std::size_t const Viewport, vk14::image &Image);
// the weather (vulkan14weather.cpp): the resources, the noises of the clouds, the block of the pass, what the weather is for the
// frame (before its passes), the clouds and the fog volume of the colour pass of the main view, the clouds and the fog over the sky
bool init_weather(std::string &Reason);
void destroy_weather();
void make_cloud_noise();
void upload_weather_block();
void update_weather();
void render_weather_view(glm::mat4 const &Projection, glm::mat4 const &View, glm::dvec3 const &Camera, glm::ivec2 const &Size, int const Cablamp);
void render_sky_weather(glm::mat4 const &Projection, bool const Mainview);
// the ambient occlusion (vulkan14ao.cpp): the resources, and the occlusion of a viewport multiplied into its colour
bool init_ao(std::string &Reason);
void destroy_ao();
void render_ao(std::size_t const Viewport, int const Mode);
// the particles of the gpu: what the colour pass under way draws them with, and the depth of the scene drawn so far copied for
// the soft particles (false: they aren't soft)
gfx::gpu_particles::view particles_view() const;
bool setup_particles_depth(std::size_t const Viewport);
bool m_particlesoft{false};
// the grass of the heightmap terrain seen from the main view, over the opaque scene (opengl46_renderer::Render_grass())
void render_grass(glm::mat4 const &Future);
// a pass of the post-processing: the fragment shader drawn over the whole of the target, reading the images (Pass: a pass of
// its own, for the gpu times). Repeated: a bit for each input read texel by texel and repeated (the noise of the ssao)
void post_pass(char const *Fragment, vk14::image &Target, std::vector<vk14::image *> const &Inputs, vk14::draw_constants const &Constants, char const *Pass = nullptr, unsigned const Repeated = 0);
bool bind_scene_pipeline(vk14::scene_program &Program, vk14::target_formats const &Targets, vk14::blend_mode const Blend, int const Topology);
void apply_draw_state();
void push_textures();
@@ -223,6 +366,11 @@ class vulkan14_renderer : public gfx_renderer, public gfx::scene_walk
void update_texture_release();
std::unique_ptr<context> m_context;
// the light of the sky and the weather of the frame, shared with the opengl renderer
gfx::sky_light m_skylight;
gfx::weather_state m_weatherstate;
// the environment map prefiltered with the GGX lobe is bound for the pass (light_data.env_ggx)
bool m_envmap_bound{false};
// the data of the geometry, which the simulation reads back, and the banks with it on the gpu
gfx::geometrybank_manager m_geometry;
std::vector<gfx::geometry_bank *> m_banks;
@@ -295,6 +443,24 @@ class vulkan14_renderer : public gfx_renderer, public gfx::scene_walk
debug_stats m_laststats;
void update_info();
// the debug windows (vulkan14debug.cpp, opengl46_renderer::draw_debug_ui()), the lines of the cullings and of the scene for them
// and for the statistics, the counters of the light clusters read back, and the heatmap of the clusters over the output of a
// viewport (opengl46_renderer::Render_cluster_heatmap())
void draw_debug_ui();
std::string cull_info();
std::string scene_info();
void collect_cluster_stats();
void render_cluster_heatmap(std::size_t const Viewport);
// the headlights (opengl46_renderer::headlight_config_s), set by the debug window
struct headlight_config
{
float in_cutoff{1.005f};
float out_cutoff{0.993f};
float falloff_linear{0.15f};
float falloff_quadratic{0.15f};
float intensity{1.0f};
float ambient{0.0f};
} m_headlightconfig;
// the device was lost: what was on the gpu goes to the log
void report_device_lost(char const *Where);

File diff suppressed because it is too large Load Diff

View File

@@ -9,8 +9,10 @@ http://mozilla.org/MPL/2.0/.
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14shaders.h"
#include "rendering/vulkan14/vulkan14commands.h"
#include "rendering/vulkan14/vulkan14device.h"
#include "rendering/vulkan14/vulkan14bindings.h"
#include "rendering/common/localshadows.h"
#include "gl/ubo.h"
#include "utilities/Globals.h"
#include "utilities/Logs.h"
@@ -439,6 +441,7 @@ std::uint64_t pipeline_library::key_of(pipeline_desc const &Desc) const
}
hash_value(hash, Desc.depth_format);
hash_value(hash, Desc.samples);
hash_value(hash, Desc.view_mask);
for (auto const value : Desc.specialization)
{
hash_value(hash, value);
@@ -546,6 +549,9 @@ VkPipeline pipeline_library::get(pipeline_desc const &Desc)
{
case blend_mode::none:
break;
case blend_mode::no_colour:
attachment.colorWriteMask = 0;
break;
case blend_mode::alpha:
attachment.blendEnable = VK_TRUE;
attachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
@@ -582,6 +588,24 @@ VkPipeline pipeline_library::get(pipeline_desc const &Desc)
attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
attachment.alphaBlendOp = VK_BLEND_OP_ADD;
break;
case blend_mode::under_transmittance:
attachment.blendEnable = VK_TRUE;
attachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
attachment.dstColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
attachment.colorBlendOp = VK_BLEND_OP_ADD;
attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
attachment.alphaBlendOp = VK_BLEND_OP_ADD;
break;
case blend_mode::multiply:
attachment.blendEnable = VK_TRUE;
attachment.srcColorBlendFactor = VK_BLEND_FACTOR_ZERO;
attachment.dstColorBlendFactor = VK_BLEND_FACTOR_SRC_COLOR;
attachment.colorBlendOp = VK_BLEND_OP_ADD;
attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
attachment.alphaBlendOp = VK_BLEND_OP_ADD;
break;
case blend_mode::additive:
attachment.blendEnable = VK_TRUE;
attachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
@@ -609,6 +633,7 @@ VkPipeline pipeline_library::get(pipeline_desc const &Desc)
rendering.colorAttachmentCount = static_cast<std::uint32_t>(Desc.colour_formats.size());
rendering.pColorAttachmentFormats = Desc.colour_formats.data();
rendering.depthAttachmentFormat = Desc.depth_format;
rendering.viewMask = Desc.view_mask;
VkGraphicsPipelineCreateInfo info{};
info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
@@ -703,23 +728,23 @@ std::string pipeline_library::info() const
return "pipelines: " + std::to_string(m_pipelines.size()) + " (made in " + buffer + " ms)";
}
void set_draw_state(VkCommandBuffer Commands, VkExtent2D Extent)
void set_draw_state(command_stream *Commands, VkExtent2D Extent)
{
VkViewport viewport{0.f, 0.f, static_cast<float>(Extent.width), static_cast<float>(Extent.height), 0.f, 1.f};
vkCmdSetViewport(Commands, 0, 1, &viewport);
Commands->set_viewport(0, 1, &viewport);
VkRect2D scissor{{0, 0}, Extent};
vkCmdSetScissor(Commands, 0, 1, &scissor);
vkCmdSetCullMode(Commands, VK_CULL_MODE_NONE);
vkCmdSetFrontFace(Commands, VK_FRONT_FACE_COUNTER_CLOCKWISE);
vkCmdSetPrimitiveTopology(Commands, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST);
vkCmdSetDepthTestEnable(Commands, VK_FALSE);
vkCmdSetDepthWriteEnable(Commands, VK_FALSE);
Commands->set_scissor(0, 1, &scissor);
Commands->set_cull_mode(VK_CULL_MODE_NONE);
Commands->set_front_face(VK_FRONT_FACE_COUNTER_CLOCKWISE);
Commands->set_primitive_topology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST);
Commands->set_depth_test_enable(VK_FALSE);
Commands->set_depth_write_enable(VK_FALSE);
// reverse z, as the opengl renderer
vkCmdSetDepthCompareOp(Commands, VK_COMPARE_OP_GREATER_OR_EQUAL);
vkCmdSetDepthBiasEnable(Commands, VK_FALSE);
vkCmdSetDepthBias(Commands, 0.f, 0.f, 0.f);
vkCmdSetPrimitiveRestartEnable(Commands, VK_FALSE);
vkCmdSetStencilTestEnable(Commands, VK_FALSE);
Commands->set_depth_compare_op(VK_COMPARE_OP_GREATER_OR_EQUAL);
Commands->set_depth_bias_enable(VK_FALSE);
Commands->set_depth_bias(0.f, 0.f, 0.f);
Commands->set_primitive_restart_enable(VK_FALSE);
Commands->set_stencil_test_enable(VK_FALSE);
}
} // namespace vk14
@@ -746,6 +771,21 @@ std::string binding_preamble()
define("SHADOW_TEX", SHADOW_TEX);
define("ENV_TEX", ENV_TEX);
define("HEADLIGHT_TEX", HEADLIGHT_TEX);
define("SKY_SSBO", SKY_SSBO);
define("ATMOSPHERE_UBO", ATMOSPHERE_UBO);
define("SKY_VIEW_TEX", SKY_VIEW_TEX);
define("ENV_GGX_TEX", ENV_GGX_TEX);
define("WEATHER_UBO", WEATHER_UBO);
define("WEATHER_HORIZON_UBO", WEATHER_HORIZON_UBO);
define("FOG_VOLUME_TEX", FOG_VOLUME_TEX);
define("CLOUD_SHADOW_TEX", CLOUD_SHADOW_TEX);
define("CLOUD_SHAPE_TEX", CLOUD_SHAPE_TEX);
define("CLOUD_DETAIL_TEX", CLOUD_DETAIL_TEX);
define("CLOUD_COVERAGE_TEX", CLOUD_COVERAGE_TEX);
define("LOCAL_SHADOW_TEX", LOCAL_SHADOW_TEX);
define("LOCAL_SHADOW_LAMPS_TEX", LOCAL_SHADOW_LAMPS_TEX);
define("HEADLIGHT_SHADOW_TEX", HEADLIGHT_SHADOW_TEX);
define("SCENE_DEPTH_TEX", SCENE_DEPTH_TEX);
define("PASS_BINDING_COUNT", PASS_BINDING_COUNT);
define("MATERIAL_TEXTURE_FIRST", MATERIAL_TEXTURE_FIRST);
define("CL_CLUSTER_UBO", CL_CLUSTER_UBO);
@@ -756,6 +796,9 @@ std::string binding_preamble()
define("CL_LIGHT_SSBO", CL_LIGHT_SSBO);
define("POST_SET", POST_SET);
define("POST_TEXTURE_FIRST", POST_TEXTURE_FIRST);
define("POST_TEXTURES", POST_TEXTURES);
define("POST_EXPOSURE_SSBO", POST_EXPOSURE_SSBO);
define("COMPUTE_SET", COMPUTE_SET);
define("PUSH_CONSTANTS_SIZE", PUSH_CONSTANTS_SIZE);
define("SPEC_OUTPUT_GAMMA", SPEC_OUTPUT_GAMMA);
define("SPEC_SHADOWMAP_ENABLED", SPEC_SHADOWMAP_ENABLED);
@@ -776,6 +819,31 @@ std::string binding_preamble()
define("MAX_LIGHTS_PER_CLUSTER", gl::MAX_LIGHTS_PER_CLUSTER);
define("CLUSTER_BUILD_GROUP_SIZE", gl::CLUSTER_BUILD_GROUP_SIZE);
define("CLUSTER_CULL_GROUP_SIZE", gl::CLUSTER_CULL_GROUP_SIZE);
// the sizes of the stages P: the light of the sky, the exposure, the weather, the culling on the gpu, the shadow fit, the particles
define("ENVMAP_GGX_LEVELS", static_cast<unsigned int>(gl::ENVMAP_GGX_LEVELS), "U");
preamble += "#define ENVMAP_GGX_ROUGHNESS_MIN " + std::to_string(gl::ENVMAP_GGX_ROUGHNESS_MIN) + "\n";
define("ENVMAP_GGX_GROUP_SIZE", static_cast<unsigned int>(gl::ENVMAP_GGX_GROUP_SIZE));
define("EXPOSURE_HISTOGRAM_BINS", gl::EXPOSURE_HISTOGRAM_BINS);
define("EXPOSURE_HISTOGRAM_GROUP_SIZE", gl::EXPOSURE_HISTOGRAM_GROUP_SIZE);
define("ATMOSPHERE_TRANSMITTANCE_WIDTH", gl::ATMOSPHERE_TRANSMITTANCE_WIDTH);
define("ATMOSPHERE_TRANSMITTANCE_HEIGHT", gl::ATMOSPHERE_TRANSMITTANCE_HEIGHT);
define("ATMOSPHERE_MULTISCATTERING_SIZE", gl::ATMOSPHERE_MULTISCATTERING_SIZE);
define("ATMOSPHERE_SKYVIEW_WIDTH", gl::ATMOSPHERE_SKYVIEW_WIDTH);
define("ATMOSPHERE_SKYVIEW_HEIGHT", gl::ATMOSPHERE_SKYVIEW_HEIGHT);
define("ATMOSPHERE_AMBIENT_GROUP_SIZE", gl::ATMOSPHERE_AMBIENT_GROUP_SIZE);
define("MAX_CLOUD_LAYERS", static_cast<unsigned int>(gl::MAX_CLOUD_LAYERS));
define("FOG_HORIZON_SAMPLES", static_cast<unsigned int>(gl::FOG_HORIZON_SAMPLES));
define("WEATHER_GROUP_SIZE", gl::WEATHER_GROUP_SIZE);
preamble += "#define FOG_VOLUME_BIAS " + std::to_string(gl::FOG_VOLUME_BIAS) + "\n";
define("INSTANCE_CULL_GROUP_SIZE", gl::INSTANCE_CULL_GROUP_SIZE);
define("INSTANCE_OFFSETS_GROUP_SIZE", gl::INSTANCE_OFFSETS_GROUP_SIZE);
define("INSTANCE_COMMAND_GROUP_SIZE", gl::INSTANCE_COMMAND_GROUP_SIZE);
define("INSTANCE_COUNTER_HEADER", gl::INSTANCE_COUNTER_HEADER);
define("INSTANCE_JOB_SLEEPERS", static_cast<unsigned int>(gl::INSTANCE_JOB_SLEEPERS), "U");
define("HIZ_GROUP_SIZE", gl::HIZ_GROUP_SIZE);
define("SHADOW_FIT_GROUP_SIZE", gl::SHADOW_FIT_GROUP_SIZE);
preamble += "#define LOCAL_SHADOW_NEAR " + std::to_string(gfx::local_shadows::shadow_near) + "\n";
define("PARTICLE_GROUP_SIZE", gl::PARTICLE_GROUP_SIZE);
return preamble;
}

View File

@@ -79,7 +79,10 @@ enum class blend_mode
additive,
// the blending of the scene, as glBlendFunc() of the opengl renderer gives it to the colour and the alpha alike
scene_alpha, // GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA
scene_additive // GL_SRC_ALPHA, GL_ONE (the glare of the lamps)
scene_additive, // GL_SRC_ALPHA, GL_ONE (the glare of the lamps)
under_transmittance, // GL_ONE, GL_SRC_ALPHA: the colour added, what is there kept by the alpha (the clouds and the fog over the sky)
multiply, // GL_ZERO, GL_SRC_COLOR: what is there times the colour (the ambient occlusion)
no_colour // the colour isn't written, only the depth (the depth prepass of the instances inside the colour pass)
};
struct vertex_layout
@@ -100,6 +103,8 @@ struct pipeline_desc
std::vector<VkFormat> colour_formats;
VkFormat depth_format{VK_FORMAT_UNDEFINED};
VkSampleCountFlagBits samples{VK_SAMPLE_COUNT_1_BIT};
// the views drawn at once (multiview, the cube shadow maps of the lamps), 0: none
std::uint32_t view_mask{0};
// the values of the specialization constants 0, 1, 2 ... of all the stages
std::vector<std::uint32_t> specialization;
std::string name;
@@ -132,6 +137,6 @@ class pipeline_library
};
// the dynamic state of a pipeline in its plain form: the whole of Extent, no culling, no depth, triangles
void set_draw_state(VkCommandBuffer Commands, VkExtent2D Extent);
void set_draw_state(command_stream *Commands, VkExtent2D Extent);
} // namespace vk14

View File

@@ -0,0 +1,167 @@
/*
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/vulkan14shadows.h"
#include "rendering/vulkan14/vulkan14commands.h"
namespace vk14
{
local_shadow_maps::~local_shadow_maps()
{
destroy();
}
void local_shadow_maps::init(VkDevice const Device, allocator &Memory, deletion_queue &Deletions)
{
m_device = Device;
m_memory = &Memory;
m_deletions = &Deletions;
}
void local_shadow_maps::destroy()
{
// the device is idle: the maps go at once
m_deletions = nullptr;
let_go(m_cab, m_cabviews);
let_go(m_lamps, m_lampviews);
let_go(m_headlights, m_headlightviews);
}
bool local_shadow_maps::make(std::shared_ptr<image> &Map, std::vector<VkImageView> &Views, int const Size, int const Layers, bool const Cube, char const *Name)
{
if ((m_memory == nullptr) || (Size <= 0) || (Layers <= 0))
{
return false;
}
image_desc desc;
desc.format = VK_FORMAT_D32_SFLOAT;
desc.extent = {static_cast<std::uint32_t>(Size), static_cast<std::uint32_t>(Size), 1};
desc.layers = static_cast<std::uint32_t>(Layers);
desc.view_type = (Cube ? VK_IMAGE_VIEW_TYPE_CUBE_ARRAY : VK_IMAGE_VIEW_TYPE_2D_ARRAY);
desc.flags = (Cube ? VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT : 0);
desc.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
Map = std::make_shared<image>();
if (false == Map->create(*m_memory, desc, Name))
{
Map.reset();
return false;
}
// the cubes drawn with multiview, six layers each, or the layers drawn one by one
auto const step{Cube ? 6 : 1};
for (int first = 0; first < Layers; first += step)
{
VkImageViewCreateInfo info{};
info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
info.image = Map->handle();
info.viewType = (Cube ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D);
info.format = desc.format;
info.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 1, static_cast<std::uint32_t>(first), static_cast<std::uint32_t>(step)};
VkImageView view{VK_NULL_HANDLE};
if (vkCreateImageView(m_device, &info, nullptr, &view) != VK_SUCCESS)
{
let_go(Map, Views);
return false;
}
Views.push_back(view);
}
return true;
}
void local_shadow_maps::let_go(std::shared_ptr<image> &Map, std::vector<VkImageView> &Views)
{
if (m_deletions != nullptr)
{
// the frames in flight may still read them
m_deletions->push(m_value, [device = m_device, views = std::move(Views), map = std::move(Map)]() {
for (auto *view : views)
{
vkDestroyImageView(device, view, nullptr);
}
});
}
else
{
for (auto *view : Views)
{
vkDestroyImageView(m_device, view, nullptr);
}
}
Views.clear();
Map.reset();
}
bool local_shadow_maps::gpu_allocate(int const Size)
{
return make(m_cab, m_cabviews, Size, 6 * (cab_slot + 1), true, "lamp shadow map");
}
void local_shadow_maps::gpu_release()
{
let_go(m_cab, m_cabviews);
}
bool local_shadow_maps::gpu_allocate_lamps(int const Count, int const Size)
{
// the scenery parts and the working copies
return make(m_lamps, m_lampviews, Size, 6 * 2 * Count, true, "scenery lamp shadow maps");
}
void local_shadow_maps::gpu_release_lamps()
{
let_go(m_lamps, m_lampviews);
}
bool local_shadow_maps::gpu_allocate_headlights(int const Count, int const Size)
{
return make(m_headlights, m_headlightviews, Size, Count, false, "headlight shadow maps");
}
void local_shadow_maps::gpu_release_headlights()
{
let_go(m_headlights, m_headlightviews);
}
void local_shadow_maps::gpu_begin_cab()
{
m_target = draw_target{m_cab.get(), m_cabviews.empty() ? VK_NULL_HANDLE : m_cabviews[cab_slot], static_cast<std::uint32_t>(m_size), 0x3f, true, true};
}
void local_shadow_maps::gpu_begin_lamp(int const Scenerycube, int const Workingcube, bool const Vehicles)
{
auto const cube{Vehicles ? Workingcube : Scenerycube};
m_target = draw_target{m_lamps.get(), (static_cast<std::size_t>(cube) < m_lampviews.size()) ? m_lampviews[cube] : VK_NULL_HANDLE, static_cast<std::uint32_t>(m_lampssize), 0x3f, (false == Vehicles), true};
if (Vehicles && m_lamps && (m_commands != nullptr))
{
// the vehicles go over the scenery: the copy of the scenery part
transition(m_commands, *m_lamps, VK_IMAGE_LAYOUT_GENERAL, VK_PIPELINE_STAGE_2_COPY_BIT, VK_ACCESS_2_TRANSFER_READ_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT);
VkImageCopy2 region{};
region.sType = VK_STRUCTURE_TYPE_IMAGE_COPY_2;
region.srcSubresource = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, static_cast<std::uint32_t>(Scenerycube * 6), 6};
region.dstSubresource = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, static_cast<std::uint32_t>(Workingcube * 6), 6};
region.extent = {static_cast<std::uint32_t>(m_lampssize), static_cast<std::uint32_t>(m_lampssize), 1};
VkCopyImageInfo2 copy{};
copy.sType = VK_STRUCTURE_TYPE_COPY_IMAGE_INFO_2;
copy.srcImage = m_lamps->handle();
copy.srcImageLayout = VK_IMAGE_LAYOUT_GENERAL;
copy.dstImage = m_lamps->handle();
copy.dstImageLayout = VK_IMAGE_LAYOUT_GENERAL;
copy.regionCount = 1;
copy.pRegions = &region;
m_commands->copy_image2(&copy);
}
}
void local_shadow_maps::gpu_begin_headlight(std::size_t const Slot)
{
m_target = draw_target{m_headlights.get(), (Slot < m_headlightviews.size()) ? m_headlightviews[Slot] : VK_NULL_HANDLE, static_cast<std::uint32_t>(m_headlightssize), 0, true, false};
}
} // namespace vk14

View File

@@ -0,0 +1,113 @@
/*
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/.
*/
// the shadow maps of the lamps in the vulkan renderer (the stage P4 of the plan, opengl46_local_shadows): the maps of gfx::local_shadows,
// which tells what they hold. the six faces of a cube map are drawn at once by multiview (shadowcube.vert reads the face from
// gl_ViewIndex) instead of the geometry shader of the opengl renderer. the renderer draws to the target the class sets up
// (target()), the copies and the clears of the cubes of the lamps are recorded into the commands of the frame
#pragma once
#include "rendering/common/localshadows.h"
#include "rendering/vulkan14/vulkan14memory.h"
#include <memory>
#include <vector>
namespace vk14
{
class local_shadow_maps : public gfx::local_shadows
{
public:
// what the drawing begun last draws to
struct draw_target
{
image *map{nullptr};
VkImageView view{VK_NULL_HANDLE}; // the layers of the cube (6, multiview) or of the headlights (1)
std::uint32_t size{0};
std::uint32_t view_mask{0}; // 0x3f for a cube
bool clear{true}; // the cube or the layer starts cleared, the working copy of a lamp from the copy of its scenery part
bool cube{true};
};
local_shadow_maps() = default;
~local_shadow_maps() override;
// Device, Memory, Deletions: what the maps are made and let go with; called once
void init(VkDevice Device, allocator &Memory, deletion_queue &Deletions);
// the maps go (the device is about to)
void destroy();
// the commands of the frame being recorded and the value of the timeline the gpu reaches when it's done with it, before the drawing
void begin_frame(command_stream *Commands, std::uint64_t Value)
{
m_commands = Commands;
m_value = Value;
}
// the frame was recorded: no commands go to it any more
void end_frame()
{
m_commands = nullptr;
}
draw_target const &target() const
{
return m_target;
}
// the maps, for the pass sets (null: none, the stand-ins are read)
image *cab_map() const
{
return m_cab.get();
}
image *lamp_maps() const
{
return m_lamps.get();
}
image *headlight_maps() const
{
return m_headlights.get();
}
protected:
bool gpu_allocate(int const Size) override;
void gpu_release() override;
bool gpu_allocate_lamps(int const Count, int const Size) override;
void gpu_release_lamps() override;
bool gpu_allocate_headlights(int const Count, int const Size) override;
void gpu_release_headlights() override;
void gpu_begin_cab() override;
void gpu_end_cab() override {}
void gpu_begin_lamp(int const Scenerycube, int const Workingcube, bool const Vehicles) override;
void gpu_end_lamp() override {}
void gpu_begin_headlight(std::size_t const Slot) override;
void gpu_end_headlight() override {}
// the gpu times are those of the pass list
void gpu_lamps_timer(bool const Begin) override {}
void gpu_headlights_timer(bool const Begin) override {}
private:
// a depth map array of Layers layers of Size x Size, its default view a cube array (Cube) or a 2d array, and a view of each
// cube (6 layers) or of each layer
bool make(std::shared_ptr<image> &Map, std::vector<VkImageView> &Views, int const Size, int const Layers, bool const Cube, char const *Name);
void let_go(std::shared_ptr<image> &Map, std::vector<VkImageView> &Views);
VkDevice m_device{VK_NULL_HANDLE};
allocator *m_memory{nullptr};
deletion_queue *m_deletions{nullptr};
command_stream *m_commands{nullptr};
std::uint64_t m_value{0};
std::shared_ptr<image> m_cab;
std::vector<VkImageView> m_cabviews;
std::shared_ptr<image> m_lamps;
std::vector<VkImageView> m_lampviews;
std::shared_ptr<image> m_headlights;
std::vector<VkImageView> m_headlightviews;
draw_target m_target;
};
} // namespace vk14

View File

@@ -0,0 +1,482 @@
/*
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/.
*/
// the light of the sky of the vulkan renderer (the stage P1 of the plan, opengl46_sky and opengl46_exposure of the opengl
// renderer): the look-up tables of the physical atmosphere made by compute kernels, the light of the frame worked out from
// them and the directional part of the ambient light (gfx::sky_light, shared with the opengl renderer), the environment map
// prefiltered with the GGX lobe, the stars of the catalogue; the automatic exposure and the bloom of the post-processing
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14context.h"
#include "rendering/vulkan14/vulkan14commands.h"
#include "rendering/common/exposure.h"
#include "simulation/simulation.h"
#include "simulation/simulationenvironment.h"
#include "utilities/Globals.h"
#include "utilities/Timer.h"
namespace
{
VkPipelineStageFlags2 const shader_stages{VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT};
VkPipelineStageFlags2 const sampled_stage{VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT};
VkAccessFlags2 const sampled_access{VK_ACCESS_2_SHADER_SAMPLED_READ_BIT};
VkPipelineStageFlags2 const compute_stage{VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT};
VkAccessFlags2 const storage_write{VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT};
std::uint32_t groups(std::uint32_t const Size, std::uint32_t const Group)
{
return (Size + Group - 1) / Group;
}
// a view of one level of a cube image as a cube, for a kernel writing the level
VkImageView level_view(VkDevice Device, vk14::image const &Image, std::uint32_t const Level, std::string const &Name)
{
VkImageViewCreateInfo info{};
info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
info.image = Image.handle();
info.viewType = VK_IMAGE_VIEW_TYPE_CUBE;
info.format = Image.desc().format;
info.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, Level, 1, 0, 6};
VkImageView view{VK_NULL_HANDLE};
if (!VK14_VERIFY(vkCreateImageView(Device, &info, nullptr, &view)))
{
return VK_NULL_HANDLE;
}
vk14::name_object(Device, VK_OBJECT_TYPE_IMAGE_VIEW, vk14::handle_value(view), Name);
return view;
}
// the push constants of the kernels, laid out as the blocks of the shaders (std430)
struct prefilter_constants
{
float roughness;
std::int32_t target_size;
float source_size;
std::int32_t sample_count;
};
struct histogram_constants
{
glm::vec2 log_range;
};
struct average_constants
{
glm::vec2 log_range;
glm::vec2 pad;
glm::vec4 adaptation;
glm::vec2 exposure_range;
};
} // namespace
bool vulkan14_renderer::init_sky(std::string &Reason)
{
auto &ctx{*m_context};
auto &scene{ctx.scene};
auto &sky{scene.sky};
auto &exposure{scene.exposure};
auto const device{ctx.device.handle()};
auto const frames{static_cast<std::uint32_t>(ctx.frames.size())};
// the buffers the shading reads
if ((false == sky.sky.create(ctx.memory, sizeof(gl::sky_data), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, vk14::memory_kind::device, "sky ambient")) ||
(false == sky.atmosphere.create(ctx.memory, sizeof(gl::atmosphere_ubs), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, vk14::memory_kind::device, "atmosphere")) ||
(false == sky.result.create(ctx.memory, sizeof(gl::atmosphere_result), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, vk14::memory_kind::device, "atmosphere result")) ||
(false == sky.result_readback.create(ctx.memory, sizeof(gl::atmosphere_result), frames, "atmosphere result readback")) ||
(false == exposure.data.create(ctx.memory, sizeof(gl::exposure_data), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, vk14::memory_kind::device, "exposure")) ||
(false == exposure.readback.create(ctx.memory, 4 * sizeof(float), frames, "exposure readback")))
{
Reason = "no buffers of the light of the sky";
return false;
}
// the tables of the atmosphere, black until they are first made
auto make_image = [&](std::shared_ptr<vk14::image> &Image, vk14::image_desc const &Desc, char const *Name) {
Image = std::make_shared<vk14::image>();
if (false == Image->create(ctx.memory, Desc, Name))
{
Image.reset();
Reason = std::string("no image for ") + Name;
return false;
}
return true;
};
{
vk14::image_desc desc;
desc.format = VK_FORMAT_R16G16B16A16_SFLOAT;
desc.usage = VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
desc.extent = {gl::ATMOSPHERE_TRANSMITTANCE_WIDTH, gl::ATMOSPHERE_TRANSMITTANCE_HEIGHT, 1};
if (false == make_image(sky.transmittance, desc, "atmosphere transmittance"))
{
return false;
}
desc.extent = {gl::ATMOSPHERE_MULTISCATTERING_SIZE, gl::ATMOSPHERE_MULTISCATTERING_SIZE, 1};
if (false == make_image(sky.multiscattering, desc, "atmosphere multiple scattering"))
{
return false;
}
desc.extent = {gl::ATMOSPHERE_SKYVIEW_WIDTH, gl::ATMOSPHERE_SKYVIEW_HEIGHT, 1};
if (false == make_image(sky.skyview, desc, "atmosphere sky-view"))
{
return false;
}
}
// the environment map prefiltered with the GGX lobe, made with the environment map
if (Global.gfx_envmap_enabled)
{
vk14::image_desc desc;
desc.format = VK_FORMAT_R16G16B16A16_SFLOAT;
desc.view_type = VK_IMAGE_VIEW_TYPE_CUBE;
desc.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
desc.layers = 6;
desc.extent = {static_cast<std::uint32_t>(gl::ENVMAP_GGX_SIZE), static_cast<std::uint32_t>(gl::ENVMAP_GGX_SIZE), 1};
desc.mips = static_cast<std::uint32_t>(gl::ENVMAP_GGX_LEVELS);
desc.usage = VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
if (false == make_image(sky.ggx, desc, "environment map ggx"))
{
return false;
}
for (std::uint32_t level = 0; level < desc.mips; ++level)
{
sky.ggx_levels.push_back(level_view(device, *sky.ggx, level, "environment map ggx " + std::to_string(level)));
}
}
// the stars of the catalogue, in the equatorial frame
{
auto const stars{gfx::weather_state::star_vertices()};
sky.star_count = static_cast<std::uint32_t>(stars.size() / 2);
if (sky.star_count > 0)
{
auto vertices{std::make_shared<vk14::buffer>()};
auto const size{static_cast<VkDeviceSize>(stars.size() * sizeof(glm::vec4))};
if ((false == vertices->create(ctx.memory, size, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, vk14::memory_kind::device, "stars")) || (false == ctx.uploads.upload_buffer(vertices, stars.data(), size)))
{
Reason = "no buffer of the stars";
return false;
}
sky.star_vertices = vertices;
}
}
// the kernels and the programs
try
{
auto kernel = [&](vk14::compute_kernel &Kernel, char const *Shader, char const *Name) {
if (false == Kernel.create(device, ctx.pipelines, ctx.shaders.get(Shader), scene.pass_layout, {}, Name))
{
Reason = std::string("no compute pipeline of ") + Name;
return false;
}
return true;
};
if ((false == kernel(sky.transmittance_kernel, "atmosphere_transmittance.comp", "atmosphere transmittance")) || (false == kernel(sky.multiscattering_kernel, "atmosphere_multiscattering.comp", "atmosphere multiple scattering")) ||
(false == kernel(sky.skyview_kernel, "atmosphere_skyview.comp", "atmosphere sky-view")) || (false == kernel(sky.ambient_kernel, "atmosphere_ambient.comp", "atmosphere ambient")) ||
(false == kernel(sky.prefilter_kernel, "envmap_prefilter.comp", "environment map ggx")) || (false == kernel(exposure.histogram, "exposure_histogram.comp", "exposure histogram")) ||
(false == kernel(exposure.average, "exposure_average.comp", "exposure average")))
{
return false;
}
auto program = [&](char const *Vertex, char const *Fragment, vk14::vertex_kind const Kind) {
auto result{std::make_shared<vk14::scene_program>()};
result->vertex = ctx.shaders.get(Vertex);
result->fragment = ctx.shaders.get(Fragment);
result->fragment_name = Fragment;
result->vertices = Kind;
result->name = std::string(Vertex) + " + " + Fragment;
return result;
};
sky.atmosphere_dome = program("vbocolor.vert", "sky_atmosphere.frag", vk14::vertex_kind::colour);
sky.stars = program("stars.vert", "stars.frag", vk14::vertex_kind::star);
for (auto const *name : {"postfx_bloom_down.frag", "postfx_bloom_up.frag"})
{
scene.post_fragments.emplace(name, ctx.shaders.get(name));
}
}
catch (gfx::shader_exception const &e)
{
Reason = std::string("invalid shader: ") + e.what();
return false;
}
return true;
}
void vulkan14_renderer::destroy_sky()
{
auto &ctx{*m_context};
auto &scene{ctx.scene};
auto &sky{scene.sky};
auto &exposure{scene.exposure};
auto const device{ctx.device.handle()};
for (auto *kernel : {&sky.transmittance_kernel, &sky.multiscattering_kernel, &sky.skyview_kernel, &sky.ambient_kernel, &sky.prefilter_kernel, &exposure.histogram, &exposure.average})
{
kernel->destroy(device);
}
for (auto *view : sky.ggx_levels)
{
if (view != VK_NULL_HANDLE)
{
vkDestroyImageView(device, view, nullptr);
}
}
sky.ggx_levels.clear();
sky.ggx.reset();
sky.transmittance.reset();
sky.multiscattering.reset();
sky.skyview.reset();
sky.sky.destroy();
sky.atmosphere.destroy();
sky.result.destroy();
sky.result_readback.destroy();
sky.atmosphere_dome.reset();
sky.stars.reset();
sky.star_vertices.reset();
exposure.data.destroy();
exposure.readback.destroy();
}
// the images of the sky put in their layouts the first time: black, ready to be read
void vulkan14_renderer::clear_sky_images()
{
auto &ctx{*m_context};
auto &scene{ctx.scene};
auto &sky{scene.sky};
auto const commands{ctx.commands};
std::vector<vk14::image_use> uses;
for (auto *image : {sky.transmittance.get(), sky.multiscattering.get(), sky.skyview.get(), sky.ggx.get()})
{
if (image != nullptr)
{
uses.push_back(vk14::use_of(*image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_2_CLEAR_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT));
}
}
vk14::barriers(commands, uses);
VkClearColorValue const black{{0.f, 0.f, 0.f, 0.f}};
for (auto *image : {sky.transmittance.get(), sky.multiscattering.get(), sky.skyview.get(), sky.ggx.get()})
{
if (image != nullptr)
{
VkImageSubresourceRange const range{VK_IMAGE_ASPECT_COLOR_BIT, 0, image->desc().mips, 0, image->desc().layers};
commands->clear_color_image(image->handle(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &black, 1, &range);
}
}
uses.clear();
for (auto *image : {sky.transmittance.get(), sky.multiscattering.get(), sky.skyview.get(), sky.ggx.get()})
{
if (image != nullptr)
{
uses.push_back(vk14::use_of(*image, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access));
}
}
vk14::barriers(commands, uses);
// the exposure starts from the key, not adapted yet
gl::exposure_data data{};
data.adapted_luminance = Global.gfx_postfx_autoexposure_key;
data.exposure = 1.f;
data.average_luminance = Global.gfx_postfx_autoexposure_key;
data.valid = 0;
vk14::update_buffer(commands, scene.exposure.data, &data, sizeof(data), 0);
// the atmosphere valid from the start, and no directional ambient light
vk14::update_buffer(commands, sky.atmosphere, &m_skylight.atmosphere(), sizeof(gl::atmosphere_ubs), 0);
vk14::update_buffer(commands, sky.sky, &sky.uploaded, sizeof(gl::sky_data), 0);
sky.sky_uploaded = true;
}
// the light of the sky of the frame (opengl46_sky::update()): the tables of the atmosphere made where their input changed, the
// result of an earlier frame read back, the sunlight, the ambient light and the fog colour of the frame worked out from it and
// the directional ambient light written for the shading. before the passes of the frame
void vulkan14_renderer::update_sky()
{
auto &ctx{*m_context};
auto &scene{ctx.scene};
auto &sky{scene.sky};
auto const commands{ctx.commands};
auto const &sun{simulation::Environment.sun()};
auto const sundirection{sun.getDirection()};
m_skylight.begin_update();
if (Global.gfx_atmosphere)
{
bool medium{false};
if (m_skylight.atmosphere_changed(glm::normalize(sundirection), medium))
{
ctx.passes.begin_pass("atmosphere");
vk14::update_buffer(commands, sky.atmosphere, &m_skylight.atmosphere(), sizeof(gl::atmosphere_ubs), 0);
bind_compute_set(sky.transmittance_kernel);
auto const sampled = [&](vk14::image &Image) { return vk14::image_descriptor(ctx.sampling.clamp_linear, Image.view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); };
if (medium)
{
vk14::barriers(commands, {vk14::use_of(*sky.transmittance, VK_IMAGE_LAYOUT_GENERAL, compute_stage, storage_write)});
sky.transmittance_kernel.run(commands, {vk14::storage_image(sky.transmittance->view())}, groups(gl::ATMOSPHERE_TRANSMITTANCE_WIDTH, 8), groups(gl::ATMOSPHERE_TRANSMITTANCE_HEIGHT, 8));
vk14::barriers(commands, {vk14::use_of(*sky.transmittance, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access),
vk14::use_of(*sky.multiscattering, VK_IMAGE_LAYOUT_GENERAL, compute_stage, storage_write)});
sky.multiscattering_kernel.run(commands, {sampled(*sky.transmittance), vk14::storage_image(sky.multiscattering->view())}, gl::ATMOSPHERE_MULTISCATTERING_SIZE, gl::ATMOSPHERE_MULTISCATTERING_SIZE);
vk14::barriers(commands, {vk14::use_of(*sky.multiscattering, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access)});
}
vk14::barriers(commands, {vk14::use_of(*sky.skyview, VK_IMAGE_LAYOUT_GENERAL, compute_stage, storage_write)});
sky.skyview_kernel.run(commands, {sampled(*sky.transmittance), sampled(*sky.multiscattering), vk14::storage_image(sky.skyview->view())}, groups(gl::ATMOSPHERE_SKYVIEW_WIDTH, 8), groups(gl::ATMOSPHERE_SKYVIEW_HEIGHT, 8));
vk14::barriers(commands, {vk14::use_of(*sky.skyview, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access)});
vk14::memory_barrier(commands, VK_PIPELINE_STAGE_2_COPY_BIT, VK_ACCESS_2_TRANSFER_READ_BIT, compute_stage, storage_write);
sky.ambient_kernel.run(commands, {sampled(*sky.transmittance), sampled(*sky.skyview), vk14::buffer_descriptor(sky.result.handle(), 0, sizeof(gl::atmosphere_result))}, 1);
sky.result_readback.push(commands, ctx.slot, ctx.timelinevalue + 1, sky.result.handle(), 0, sizeof(gl::atmosphere_result));
ctx.passes.end_pass();
}
gl::atmosphere_result result;
if (sky.result_readback.pop(ctx.completed(), &result, sizeof(result)))
{
m_skylight.set_result(result);
}
}
m_skylight.end_update(m_sunlight, m_fogcolor, simulation::Environment.skydome(), sundirection, sun.getAngle());
// the directional part of the ambient light for the shading
auto const &skydata{m_skylight.sky_data()};
if ((false == sky.sky_uploaded) || (0 != std::memcmp(&skydata, &sky.uploaded, sizeof(skydata))))
{
sky.uploaded = skydata;
vk14::update_buffer(commands, sky.sky, &sky.uploaded, sizeof(gl::sky_data), 0);
sky.sky_uploaded = true;
}
}
// the levels of the GGX map prefiltered from the environment map with its mipmaps (opengl46_sky::prefilter())
void vulkan14_renderer::prefilter_environment()
{
auto &ctx{*m_context};
auto &scene{ctx.scene};
auto &sky{scene.sky};
if ((false == bool(sky.ggx)) || (false == bool(scene.env_map)))
{
return;
}
auto const commands{ctx.commands};
ctx.passes.begin_pass("envmap ggx", {vk14::use_of(*scene.env_map, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, compute_stage, sampled_access),
vk14::use_of(*sky.ggx, VK_IMAGE_LAYOUT_GENERAL, compute_stage, storage_write)});
for (std::size_t level = 0; level < gl::ENVMAP_GGX_LEVELS; ++level)
{
auto const size{static_cast<std::int32_t>(gl::ENVMAP_GGX_SIZE >> level)};
prefilter_constants constants;
constants.roughness = gl::ENVMAP_GGX_ROUGHNESS_MIN + (1.f - gl::ENVMAP_GGX_ROUGHNESS_MIN) * static_cast<float>(level) / static_cast<float>(gl::ENVMAP_GGX_LEVELS - 1);
constants.target_size = size;
constants.source_size = static_cast<float>(gl::ENVMAP_SIZE);
constants.sample_count = 64;
auto const count{groups(static_cast<std::uint32_t>(size), static_cast<std::uint32_t>(gl::ENVMAP_GGX_GROUP_SIZE))};
sky.prefilter_kernel.dispatch(commands, {vk14::storage_image(sky.ggx_levels[level]), vk14::image_descriptor(ctx.sampling.clamp_linear, scene.env_map->view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)}, constants, count, count, 6);
}
vk14::barriers(commands, {vk14::use_of(*sky.ggx, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access), vk14::use_of(*scene.env_map, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access)});
ctx.passes.end_pass();
sky.ggx_ready = true;
}
// the stars, in the sky pass (opengl46_weather::draw_stars()). View: the rotation of the camera of the pass
void vulkan14_renderer::render_stars(glm::mat4 const &View)
{
auto &ctx{*m_context};
auto &scene{ctx.scene};
auto &sky{scene.sky};
auto &draw{*m_draw};
if ((sky.star_count == 0) || (false == bool(sky.star_vertices)) || (false == bool(sky.stars)))
{
return;
}
auto const &sun{simulation::Environment.sun()};
glm::mat4 toworld, toview;
glm::vec4 params;
if (false == m_weatherstate.stars(sun.getAngle(), sun.getSiderealTime(), sun.getLatitude(), View, toworld, toview, params))
{
return;
}
if (false == bind_scene_pipeline(*sky.stars, *draw.targets, vk14::blend_mode::additive, VK_PRIMITIVE_TOPOLOGY_POINT_LIST))
{
return;
}
apply_draw_state();
model_ubs.param[0] = params;
draw.block = push_draw_block();
if (draw.block == ~0u)
{
return;
}
if (draw.set_dirty)
{
bind_pass_set();
}
if (draw.textures_dirty)
{
push_textures();
}
vk14::draw_constants constants;
constants.draw = draw.block;
for (int column = 0; column < 3; ++column)
{
constants.data[column] = toworld[column];
constants.data[3 + column] = toview[column];
}
push_constants(&constants);
VkDeviceSize const offset{0};
auto const buffer{sky.star_vertices->handle()};
draw.commands->bind_vertex_buffers(0, 1, &buffer, &offset);
draw.commands->draw(sky.star_count, 1, 0, 0);
draw.vertices = VK_NULL_HANDLE;
draw.userdata = VK_NULL_HANDLE;
}
// the luminance of the image measured and the exposure adapted to it (opengl46_exposure::measure()), outside a rendering
void vulkan14_renderer::measure_exposure(vk14::image &Image)
{
auto &ctx{*m_context};
auto &exposure{ctx.scene.exposure};
auto const commands{ctx.commands};
float data[4];
if (exposure.readback.pop(ctx.completed(), data, sizeof(data)))
{
std::copy(std::begin(data), std::end(data), std::begin(exposure.values));
}
vk14::barriers(commands, {vk14::use_of(Image, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, compute_stage, sampled_access)});
// the reads of the tonemapping of the frame before
vk14::memory_barrier(commands, VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_2_COPY_BIT, VK_ACCESS_2_SHADER_STORAGE_READ_BIT | VK_ACCESS_2_TRANSFER_READ_BIT, compute_stage, VK_ACCESS_2_SHADER_STORAGE_READ_BIT | storage_write);
auto const buffer{vk14::buffer_descriptor(exposure.data.handle(), 0, sizeof(gl::exposure_data))};
histogram_constants histogram{gfx::exposure_settings::histogram_range()};
auto const groupsize{2 * gl::EXPOSURE_HISTOGRAM_GROUP_SIZE};
exposure.histogram.dispatch(commands, {vk14::image_descriptor(ctx.sampling.clamp_nearest, Image.view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL), buffer}, histogram, groups(Image.desc().extent.width, groupsize),
groups(Image.desc().extent.height, groupsize));
vk14::memory_barrier(commands, compute_stage, storage_write, compute_stage, VK_ACCESS_2_SHADER_STORAGE_READ_BIT | storage_write);
average_constants average{};
average.log_range = gfx::exposure_settings::average_range();
average.adaptation = gfx::exposure_settings::adaptation(Timer::GetDeltaRenderTime());
average.exposure_range = gfx::exposure_settings::exposure_range();
exposure.average.dispatch(commands, {buffer}, average, 1);
// read by the tonemapping, and copied for the debug window
vk14::memory_barrier(commands, compute_stage, storage_write, VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_2_COPY_BIT, VK_ACCESS_2_SHADER_STORAGE_READ_BIT | VK_ACCESS_2_TRANSFER_READ_BIT);
exposure.readback.push(commands, ctx.slot, ctx.timelinevalue + 1, exposure.data.handle(), offsetof(gl::exposure_data, adapted_luminance), 4 * sizeof(float));
}
// the image taken down and up the bloom chain of the viewport (opengl46_exposure::bloom()); the result, which the tonemapping
// blends in, or null without the chain
vk14::image *vulkan14_renderer::render_bloom(std::size_t const Index, vk14::image &Image)
{
auto &ctx{*m_context};
auto &targets{ctx.scene.targets[Index]};
if (targets.bloom_sizes.empty())
{
return nullptr;
}
// down, the first step from the image itself
for (std::size_t level = 0; level < targets.bloom_sizes.size(); ++level)
{
vk14::draw_constants constants;
constants.data[0] = gfx::exposure_settings::bloom_params(level);
post_pass("postfx_bloom_down.frag", *targets.bloom_down[level], {level == 0 ? &Image : targets.bloom_down[level - 1].get()}, constants);
}
// up, from the lowest level
for (std::size_t level = targets.bloom_up.size(); level-- > 0;)
{
auto *lower{(level + 1 < targets.bloom_up.size()) ? targets.bloom_up[level + 1].get() : targets.bloom_down[level + 1].get()};
post_pass("postfx_bloom_up.frag", *targets.bloom_up[level], {lower, targets.bloom_down[level].get()}, vk14::draw_constants{});
}
return targets.bloom_up.empty() ? targets.bloom_down.front().get() : targets.bloom_up.front().get();
}

View File

@@ -9,6 +9,7 @@ http://mozilla.org/MPL/2.0/.
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14swapchain.h"
#include "rendering/vulkan14/vulkan14commands.h"
#include "rendering/vulkan14/vulkan14instance.h"
#include "rendering/vulkan14/vulkan14device.h"
#include "utilities/Globals.h"
@@ -360,7 +361,7 @@ void swapchain::presented(VkResult const Result)
}
}
void swapchain::record_clear(VkCommandBuffer Commands, glm::vec4 const &Colour) const
void swapchain::record_clear(command_stream *Commands, glm::vec4 const &Colour) const
{
VkImageMemoryBarrier2 barrier{};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
@@ -380,7 +381,7 @@ void swapchain::record_clear(VkCommandBuffer Commands, glm::vec4 const &Colour)
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
dependency.imageMemoryBarrierCount = 1;
dependency.pImageMemoryBarriers = &barrier;
vkCmdPipelineBarrier2(Commands, &dependency);
Commands->pipeline_barrier2(&dependency);
VkRenderingAttachmentInfo colour{};
colour.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
@@ -396,8 +397,8 @@ void swapchain::record_clear(VkCommandBuffer Commands, glm::vec4 const &Colour)
rendering.layerCount = 1;
rendering.colorAttachmentCount = 1;
rendering.pColorAttachments = &colour;
vkCmdBeginRendering(Commands, &rendering);
vkCmdEndRendering(Commands);
Commands->begin_rendering(&rendering);
Commands->end_rendering();
barrier.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
barrier.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
@@ -405,7 +406,7 @@ void swapchain::record_clear(VkCommandBuffer Commands, glm::vec4 const &Colour)
barrier.dstAccessMask = VK_ACCESS_2_NONE;
barrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
barrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
vkCmdPipelineBarrier2(Commands, &dependency);
Commands->pipeline_barrier2(&dependency);
}
void swapchain::release_images()

View File

@@ -44,7 +44,7 @@ class swapchain
// the image of the frame Frame (index of the frame in flight)
acquire_result acquire(std::uint32_t const Frame);
// the image acquired, from undefined to cleared with Colour (linear) and ready to be shown
void record_clear(VkCommandBuffer Commands, glm::vec4 const &Colour) const;
void record_clear(command_stream *Commands, glm::vec4 const &Colour) const;
// the image acquired and its view
VkImage image() const
{

View File

@@ -0,0 +1,361 @@
/*
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/vulkan14terrain.h"
#include "rendering/vulkan14/vulkan14commands.h"
#include "rendering/vulkan14/vulkan14device.h"
#include "utilities/Logs.h"
namespace vk14
{
namespace
{
std::uint32_t mip_count(std::uint32_t const Size)
{
return static_cast<std::uint32_t>(std::floor(std::log2(std::max(Size, 1u)))) + 1;
}
} // namespace
terrain_materials::~terrain_materials()
{
destroy();
}
void terrain_materials::init(resources const &Resources, copy_draw Draw)
{
m_resources = &Resources;
m_draw = std::move(Draw);
}
void terrain_materials::destroy()
{
if (m_resources == nullptr)
{
return;
}
auto const device{m_resources->gpu->handle()};
for (auto &images : m_images)
{
for (auto *view : images.layers)
{
vkDestroyImageView(device, view, nullptr);
}
images = array_images{};
}
m_resources = nullptr;
}
bool terrain_materials::gpu_init()
{
return (m_resources != nullptr) && bool(m_draw);
}
vulkan14_texture &terrain_materials::texture(texture_handle const Handle)
{
return static_cast<vulkan14_texture &>(m_textures.texture(Handle));
}
std::shared_ptr<image> terrain_materials::make_image(VkFormat const Format, std::uint32_t const Width, std::uint32_t const Height, std::uint32_t const Mips, char const *Name)
{
image_desc desc;
desc.format = Format;
desc.extent = {Width, Height, 1};
desc.mips = Mips;
desc.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
auto result{std::make_shared<image>()};
if (false == result->create(*m_resources->memory, desc, Name))
{
ErrorLog(std::string("heightmap terrain: no gpu image for ") + Name);
return nullptr;
}
return result;
}
void terrain_materials::upload(texture_handle const Texture, VkFormat const Format, std::uint32_t const Width, std::uint32_t const Height, void const *Data, VkDeviceSize const Size, bool const Mips, VkSampler const Sampler)
{
auto &target{texture(Texture)};
auto map{target.adopted()};
auto const generate{Mips && m_resources->uploads->can_blit(Format)};
auto const mips{generate ? mip_count(std::max(Width, Height)) : 1u};
if ((map == nullptr) || (map->desc().format != Format) || (map->desc().extent.width != Width) || (map->desc().extent.height != Height) || (map->desc().mips != mips))
{
map = make_image(Format, Width, Height, mips, target.name.c_str());
if (map == nullptr)
{
return;
}
}
// the frames sent before are done with the content the upload replaces before it's written (the barrier of the upload)
if (m_resources->uploads->upload(map, Data, Size, {{0, {Width, Height, 1}}}, generate))
{
target.adopt(map, Sampler);
}
}
void terrain_materials::gpu_white(texture_source &Texture)
{
std::uint8_t const texel[4]{255, 255, 255, 255};
auto &target{static_cast<vulkan14_texture &>(Texture)};
auto map{make_image(VK_FORMAT_R8G8B8A8_UNORM, 1, 1, 1, target.name.c_str())};
if ((map != nullptr) && m_resources->uploads->upload(map, texel, sizeof(texel), {{0, {1, 1, 1}}}, false))
{
target.adopt(map, m_resources->sampling->repeat_nearest);
}
}
void terrain_materials::gpu_storage(texture_array const &Array, int const Layers, bool const Fresh)
{
auto const index{static_cast<std::size_t>(&Array - arrays().data())};
auto &images{m_images[index]};
let_go(images);
image_desc desc;
desc.format = (Array.srgb ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM);
desc.extent = {static_cast<std::uint32_t>(Array.size), static_cast<std::uint32_t>(Array.size), 1};
desc.mips = mip_count(static_cast<std::uint32_t>(Array.size));
desc.layers = static_cast<std::uint32_t>(Layers);
desc.view_type = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
desc.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
auto &target{texture(Array.handle)};
auto map{std::make_shared<image>()};
if (false == map->create(*m_resources->memory, desc, target.name))
{
ErrorLog("heightmap terrain: no gpu image for the layers of \"" + target.name + "\"");
return;
}
auto const device{m_resources->gpu->handle()};
for (int layer = 0; layer < Layers; ++layer)
{
VkImageViewCreateInfo info{};
info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
info.image = map->handle();
info.viewType = VK_IMAGE_VIEW_TYPE_2D;
info.format = desc.format;
info.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, static_cast<std::uint32_t>(layer), 1};
VkImageView view{VK_NULL_HANDLE};
if (vkCreateImageView(device, &info, nullptr, &view) != VK_SUCCESS)
{
ErrorLog("heightmap terrain: no view of a layer of \"" + target.name + "\"");
images.layers.clear();
break;
}
images.layers.push_back(view);
}
images.map = map;
// cleared by the next frame, before anything reads it
images.clear = true;
target.adopt(map, m_resources->sampling->repeat_aniso);
}
void terrain_materials::let_go(array_images &Images)
{
if (Images.layers.empty() && (Images.map == nullptr))
{
return;
}
// the frames in flight may still read them
m_resources->deletions->push(m_resources->uploads->current_value(), [device = m_resources->gpu->handle(), views = std::move(Images.layers), map = std::move(Images.map)]() {
for (auto *view : views)
{
vkDestroyImageView(device, view, nullptr);
}
});
Images = array_images{};
}
void terrain_materials::gpu_blank(slot const &Slot)
{
std::uint8_t const blank[4]{0, 0, 0, 0};
for (auto const handle : Slot.weights)
{
upload(handle, VK_FORMAT_R8G8B8A8_UNORM, 1, 1, blank, sizeof(blank), true, m_resources->sampling->clamp_linear);
}
std::array<float, max_layers * 2 * 4> const zeros{};
upload(Slot.table, VK_FORMAT_R32G32B32A32_SFLOAT, static_cast<std::uint32_t>(max_layers), 2, zeros.data(), sizeof(zeros), false, m_resources->sampling->clamp_nearest);
// led between the samples, so the edges of the holes run smoothly across the cells
std::uint8_t const none{0};
upload(Slot.holes, VK_FORMAT_R8_UNORM, 1, 1, &none, 1, false, m_resources->sampling->clamp_linear);
}
void terrain_materials::gpu_weights(texture_handle const Texture, int const Side, std::uint8_t const *Texels)
{
auto const side{static_cast<std::uint32_t>(Side)};
upload(Texture, VK_FORMAT_R8G8B8A8_UNORM, side, side, Texels, static_cast<VkDeviceSize>(side) * side * 4, true, m_resources->sampling->clamp_linear);
}
void terrain_materials::gpu_table(texture_handle const Texture, float const *Table)
{
upload(Texture, VK_FORMAT_R32G32B32A32_SFLOAT, static_cast<std::uint32_t>(max_layers), 2, Table, max_layers * 2 * 4 * sizeof(float), false, m_resources->sampling->clamp_nearest);
}
void terrain_materials::gpu_holes(texture_handle const Texture, int const Side, std::uint8_t const *Samples)
{
auto const side{static_cast<std::uint32_t>(Side)};
upload(Texture, VK_FORMAT_R8_UNORM, side, side, Samples, static_cast<VkDeviceSize>(side) * side, false, m_resources->sampling->clamp_linear);
}
void terrain_materials::begin_frame(command_stream *const Commands, std::uint64_t const Value)
{
m_commands = Commands;
m_value = Value;
for (auto &images : m_images)
{
if ((false == images.clear) || (images.map == nullptr))
{
continue;
}
images.clear = false;
// the layers not copied yet read black, as the storage of opengl starts out on most drivers
transition(Commands, *images.map, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_2_CLEAR_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT);
VkClearColorValue const zero{};
VkImageSubresourceRange const range{VK_IMAGE_ASPECT_COLOR_BIT, 0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS};
Commands->clear_color_image(images.map->handle(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &zero, 1, &range);
transition(Commands, *images.map, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT);
}
}
void terrain_materials::gpu_begin_copies()
{
for (auto &images : m_images)
{
images.drawn = false;
}
}
void terrain_materials::gpu_copy(texture_array const &Array, int const Layer, texture_source *Source, std::array<float, 4> const &Default)
{
auto const index{static_cast<std::size_t>(&Array - arrays().data())};
auto &images{m_images[index]};
if ((m_commands == nullptr) || (images.map == nullptr) || (static_cast<std::size_t>(Layer) >= images.layers.size()))
{
return;
}
if (false == images.drawn)
{
images.drawn = true;
transition(m_commands, *images.map, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT);
}
auto const view{images.layers[Layer]};
auto const format{images.map->desc().format};
VkExtent2D const extent{images.map->desc().extent.width, images.map->desc().extent.height};
if (Source != nullptr)
{
auto const *entry{m_resources->images->find(static_cast<vulkan14_texture &>(*Source).get_id())};
if (entry != nullptr)
{
m_draw(m_commands, view, format, extent, entry->target->view(), entry->sampler);
return;
}
}
// a map the layer doesn't have; one which failed reads as the texture 0 of opengl
VkRenderingAttachmentInfo attachment{};
attachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
attachment.imageView = view;
attachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
if (Source == nullptr)
{
std::copy(Default.begin(), Default.end(), attachment.clearValue.color.float32);
}
else
{
attachment.clearValue.color = VkClearColorValue{{0.f, 0.f, 0.f, 1.f}};
}
VkRenderingInfo rendering{};
rendering.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
rendering.renderArea = {{0, 0}, extent};
rendering.layerCount = 1;
rendering.colorAttachmentCount = 1;
rendering.pColorAttachments = &attachment;
m_commands->begin_rendering(&rendering);
m_commands->end_rendering();
}
void terrain_materials::gpu_end_copies(bool const Copied)
{
for (auto &images : m_images)
{
if ((false == images.drawn) || (images.map == nullptr))
{
continue;
}
images.drawn = false;
if (Copied && (images.map->desc().mips > 1) && m_resources->uploads->can_blit(images.map->desc().format))
{
make_mipmaps(*images.map);
}
else
{
transition(m_commands, *images.map, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT);
}
}
}
// the other levels of every layer made by blits, each from the one before it (glGenerateMipmap of the opengl renderer)
void terrain_materials::make_mipmaps(image &Map)
{
auto const commands{m_commands};
auto const layers{Map.desc().layers};
auto const mips{Map.desc().mips};
auto const extent{Map.desc().extent};
auto barrier = [&](std::uint32_t Level, std::uint32_t Count, VkImageLayout From, VkImageLayout To, VkPipelineStageFlags2 SrcStage, VkAccessFlags2 SrcAccess, VkPipelineStageFlags2 DstStage, VkAccessFlags2 DstAccess) {
VkImageMemoryBarrier2 entry{};
entry.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
entry.srcStageMask = SrcStage;
entry.srcAccessMask = SrcAccess;
entry.dstStageMask = DstStage;
entry.dstAccessMask = DstAccess;
entry.oldLayout = From;
entry.newLayout = To;
entry.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
entry.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
entry.image = Map.handle();
entry.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, Level, Count, 0, layers};
VkDependencyInfo dependency{};
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
dependency.imageMemoryBarrierCount = 1;
dependency.pImageMemoryBarriers = &entry;
commands->pipeline_barrier2(&dependency);
};
// the first level drawn, the others written by the blits
barrier(0, 1, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_READ_BIT);
barrier(1, mips - 1, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_2_NONE, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT);
for (std::uint32_t level = 1; level < mips; ++level)
{
if (level > 1)
{
barrier(level - 1, 1, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_READ_BIT);
}
VkImageBlit2 blit{};
blit.sType = VK_STRUCTURE_TYPE_IMAGE_BLIT_2;
blit.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, level - 1, 0, layers};
blit.srcOffsets[1] = {static_cast<std::int32_t>(std::max(extent.width >> (level - 1), 1u)), static_cast<std::int32_t>(std::max(extent.height >> (level - 1), 1u)), 1};
blit.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, level, 0, layers};
blit.dstOffsets[1] = {static_cast<std::int32_t>(std::max(extent.width >> level, 1u)), static_cast<std::int32_t>(std::max(extent.height >> level, 1u)), 1};
VkBlitImageInfo2 info{};
info.sType = VK_STRUCTURE_TYPE_BLIT_IMAGE_INFO_2;
info.srcImage = Map.handle();
info.srcImageLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
info.dstImage = Map.handle();
info.dstImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
info.regionCount = 1;
info.pRegions = &blit;
info.filter = VK_FILTER_LINEAR;
commands->blit_image2(&info);
}
barrier(0, mips - 1, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_NONE, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT);
barrier(mips - 1, 1, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT);
Map.state = image_state{VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT};
}
} // namespace vk14

View File

@@ -0,0 +1,85 @@
/*
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/.
*/
// the materials of the heightmap terrain in the vulkan renderer (the stage P6 of the plan, opengl46_terrain_materials): the
// textures of gfx::terrain_materials as images of the textures of the bank (vulkan14_texture::adopt()). the weights, the tables
// and the holes of the chunks go through the uploads; the layer maps are drawn into the layers of the arrays by the copy shader
// (Draw, given by the renderer) in the commands of the frame, the mipmaps of the arrays made by blits after them
#pragma once
#include "rendering/common/terrainmaterials.h"
#include "rendering/vulkan14/vulkan14texture.h"
#include <functional>
namespace vk14
{
class terrain_materials : public gfx::terrain_materials
{
public:
// the copy shader drawn into Target (a layer of an array in the layout of a colour attachment, of Format and Extent) from the
// image Source sampled with Sampler
using copy_draw = std::function<void(command_stream *Commands, VkImageView Target, VkFormat Format, VkExtent2D Extent, VkImageView Source, VkSampler Sampler)>;
terrain_materials(material_manager &Materials, texture_bank &Textures) : gfx::terrain_materials(Materials, Textures) {}
~terrain_materials() override;
// called once, before anything is made
void init(resources const &Resources, copy_draw Draw);
// the views go (the device is idle)
void destroy();
// the commands of the frame and the value of the timeline the gpu reaches when it's done with it: the arrays made since the last
// frame are cleared. before update()
void begin_frame(command_stream *Commands, std::uint64_t Value);
// the frame was recorded: no commands go to it any more
void end_frame()
{
m_commands = nullptr;
}
protected:
bool gpu_init() override;
void gpu_texture(texture_source &Texture, texture_kind const Kind) override {}
void gpu_white(texture_source &Texture) override;
void gpu_storage(texture_array const &Array, int const Layers, bool const Fresh) override;
void gpu_blank(slot const &Slot) override;
void gpu_weights(texture_handle const Texture, int const Side, std::uint8_t const *Texels) override;
void gpu_table(texture_handle const Texture, float const *Table) override;
void gpu_holes(texture_handle const Texture, int const Side, std::uint8_t const *Samples) override;
void gpu_begin_copies() override;
void gpu_copy(texture_array const &Array, int const Layer, texture_source *Source, std::array<float, 4> const &Default) override;
void gpu_end_copies(bool const Copied) override;
private:
struct array_images
{
std::shared_ptr<image> map;
std::vector<VkImageView> layers; // the first level of each layer, drawn to
bool clear{false}; // made since the last frame, its content not defined yet
bool drawn{false}; // a layer was drawn in the copies being made
};
vulkan14_texture &texture(texture_handle const Handle);
// an image of Format, Width x Height, a single layer, with Mips levels, sampled and uploaded to
std::shared_ptr<image> make_image(VkFormat const Format, std::uint32_t const Width, std::uint32_t const Height, std::uint32_t const Mips, char const *Name);
// Data into the image of the texture, made anew of Format when there is none of the size; mipmaps made of the first level when Mips
void upload(texture_handle const Texture, VkFormat const Format, std::uint32_t const Width, std::uint32_t const Height, void const *Data, VkDeviceSize const Size, bool const Mips, VkSampler const Sampler);
void let_go(array_images &Images);
void make_mipmaps(image &Map);
resources const *m_resources{nullptr};
copy_draw m_draw;
command_stream *m_commands{nullptr};
std::uint64_t m_value{0};
std::array<array_images, 3> m_images; // of the arrays: diffuse, normal maps, specular/gloss
};
} // namespace vk14

View File

@@ -9,6 +9,7 @@ http://mozilla.org/MPL/2.0/.
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14texture.h"
#include "rendering/vulkan14/vulkan14commands.h"
#include "rendering/vulkan14/vulkan14device.h"
#include "utilities/Globals.h"
#include "utilities/Logs.h"
@@ -42,7 +43,7 @@ bool uploader::can_blit(VkFormat Format) const
return result;
}
void uploader::begin_frame(VkCommandBuffer Commands, std::uint64_t Value)
void uploader::begin_frame(command_stream *Commands, std::uint64_t Value)
{
m_commands = Commands;
m_value = Value;
@@ -57,7 +58,7 @@ void uploader::begin_frame(VkCommandBuffer Commands, std::uint64_t Value)
bool uploader::end_frame()
{
if (m_buffercopies && (m_commands != VK_NULL_HANDLE))
if (m_buffercopies && (m_commands != nullptr))
{
// the draws of the frame read what was copied
VkMemoryBarrier2 barrier{};
@@ -70,10 +71,10 @@ bool uploader::end_frame()
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
dependency.memoryBarrierCount = 1;
dependency.pMemoryBarriers = &barrier;
vkCmdPipelineBarrier2(m_commands, &dependency);
m_commands->pipeline_barrier2(&dependency);
}
m_buffercopies = false;
m_commands = VK_NULL_HANDLE;
m_commands = nullptr;
return m_recorded;
}
@@ -99,7 +100,7 @@ bool uploader::upload_buffer(std::shared_ptr<buffer> const &Target, void const *
std::memcpy(upload.staging->mapped(), Data, static_cast<std::size_t>(Size));
upload.staging->flush(0, Size);
m_uploaded += Size;
if (m_commands != VK_NULL_HANDLE)
if (m_commands != nullptr)
{
record(upload);
}
@@ -125,7 +126,7 @@ bool uploader::upload(std::shared_ptr<image> const &Image, void const *Data, VkD
std::memcpy(upload.staging->mapped(), Data, static_cast<std::size_t>(Size));
upload.staging->flush(0, Size);
m_uploaded += Size;
if (m_commands != VK_NULL_HANDLE)
if (m_commands != nullptr)
{
record(upload);
}
@@ -153,7 +154,7 @@ void uploader::record(job &Job)
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
dependency.memoryBarrierCount = 1;
dependency.pMemoryBarriers = &barrier;
vkCmdPipelineBarrier2(m_commands, &dependency);
m_commands->pipeline_barrier2(&dependency);
m_buffercopies = true;
}
std::vector<VkBufferCopy2> regions;
@@ -174,7 +175,7 @@ void uploader::record(job &Job)
copy.dstBuffer = Job.buffertarget->handle();
copy.regionCount = static_cast<std::uint32_t>(regions.size());
copy.pRegions = regions.data();
vkCmdCopyBuffer2(m_commands, &copy);
m_commands->copy_buffer2(&copy);
m_recorded = true;
m_deletions->push(m_value, [staging = Job.staging, target = Job.buffertarget]() {});
return;
@@ -204,7 +205,7 @@ void uploader::record(job &Job)
copy.dstImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
copy.regionCount = static_cast<std::uint32_t>(regions.size());
copy.pRegions = regions.data();
vkCmdCopyBufferToImage2(commands, &copy);
commands->copy_buffer_to_image2(&copy);
auto const given{static_cast<std::uint32_t>(Job.levels.size())};
if (Job.mipmaps && (mips > given) && (given > 0))
@@ -227,7 +228,7 @@ void uploader::record(job &Job)
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
dependency.imageMemoryBarrierCount = 1;
dependency.pImageMemoryBarriers = &entry;
vkCmdPipelineBarrier2(commands, &dependency);
commands->pipeline_barrier2(&dependency);
};
auto const extent{target.desc().extent};
for (std::uint32_t level = given; level < mips; ++level)
@@ -249,7 +250,7 @@ void uploader::record(job &Job)
info.regionCount = 1;
info.pRegions = &blit;
info.filter = VK_FILTER_LINEAR;
vkCmdBlitImage2(commands, &info);
commands->blit_image2(&info);
}
barrier(0, mips - 1, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_NONE, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT);
barrier(mips - 1, 1, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
@@ -628,6 +629,31 @@ void vulkan14_texture::apply_pending()
}
}
void vulkan14_texture::adopt(std::shared_ptr<image> const &Image, VkSampler const Sampler)
{
if (m_id != 0)
{
if (m_image != Image)
{
m_resources.deletions->push(m_resources.uploads->current_value(), [old = m_image]() {});
}
m_resources.images->replace(m_id, Image, Sampler);
}
else
{
m_id = m_resources.images->add(Image, Sampler);
}
m_image = Image;
m_made = true;
if (Image)
{
data_width = Image->desc().extent.width;
data_height = Image->desc().extent.height;
}
is_ready = true;
is_static = true;
}
void vulkan14_texture::upload_rgba(std::uint8_t const *Data, std::size_t Width, std::size_t Height)
{
// as the opengl renderer: srgb rgba with mipmaps, the image made anew when the size changes

View File

@@ -43,7 +43,7 @@ class uploader
void init(allocator const &Allocator, deletion_queue &Deletions, VkPhysicalDevice Physical);
// the commands of the uploads go to Commands until end_frame(); Value: the value of the timeline the gpu reaches when it's done
// with the frame. the uploads waiting from between the frames are recorded first
void begin_frame(VkCommandBuffer Commands, std::uint64_t Value);
void begin_frame(command_stream *Commands, std::uint64_t Value);
// whether anything was recorded to the commands of the frame
bool end_frame();
// the last value of the timeline the gpu may use the objects with: the frame being made, or the last one sent
@@ -82,7 +82,7 @@ class uploader
allocator const *m_allocator{nullptr};
deletion_queue *m_deletions{nullptr};
VkPhysicalDevice m_physical{VK_NULL_HANDLE};
VkCommandBuffer m_commands{VK_NULL_HANDLE};
command_stream *m_commands{nullptr};
std::uint64_t m_value{0};
bool m_recorded{false};
// buffers were copied to in the frame: the barriers before the first copy and after the last one
@@ -176,6 +176,13 @@ struct vulkan14_texture : public texture_source
// the content given by update_from_memory() from another thread, uploaded by the thread of the renderer
void apply_pending();
// the image of a texture the renderer fills itself (the textures of the heightmap terrain): sampled with Sampler from now on,
// the image it had before goes once the gpu is done with it
void adopt(std::shared_ptr<image> const &Image, VkSampler Sampler);
std::shared_ptr<image> const &adopted() const
{
return m_image;
}
protected:
void on_failed() override;

View File

@@ -0,0 +1,665 @@
/*
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/.
*/
// the weather of the vulkan renderer (the stage P2 of the plan, opengl46_weather of the opengl renderer), drawn from the weather of
// the simulation by gfx::weather_state, which it shares with the opengl renderer: the block of the weather of every pass, the
// colours of the fog towards the horizon from the sky-view table of the atmosphere, the noises of the clouds, the shadows of the
// clouds, the clouds of the main view (blended over frames) and the panorama of the clouds for the other views, the fog volume
// of the main view, and the clouds and the fog laid over the sky
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14context.h"
#include "rendering/vulkan14/vulkan14commands.h"
#include "simulation/simulation.h"
#include "simulation/simulationenvironment.h"
#include "utilities/Globals.h"
namespace
{
// sizes of the noises (clouds_noise.comp)
int const SHAPE_NOISE_SIZE{128};
int const DETAIL_NOISE_SIZE{32};
int const COVERAGE_NOISE_SIZE{512};
VkPipelineStageFlags2 const compute_stage{VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT};
VkPipelineStageFlags2 const sampled_stage{VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT};
VkAccessFlags2 const sampled_access{VK_ACCESS_2_SHADER_SAMPLED_READ_BIT};
VkAccessFlags2 const storage_write{VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT};
std::uint32_t groups(int const Size)
{
return static_cast<std::uint32_t>((Size + static_cast<int>(gl::WEATHER_GROUP_SIZE) - 1) / static_cast<int>(gl::WEATHER_GROUP_SIZE));
}
// a view of the first level of an image, for a kernel writing it
VkImageView first_level_view(VkDevice Device, vk14::image const &Image, std::string const &Name)
{
VkImageViewCreateInfo info{};
info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
info.image = Image.handle();
info.viewType = Image.desc().view_type;
info.format = Image.desc().format;
info.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, Image.desc().layers};
VkImageView view{VK_NULL_HANDLE};
if (!VK14_VERIFY(vkCreateImageView(Device, &info, nullptr, &view)))
{
return VK_NULL_HANDLE;
}
vk14::name_object(Device, VK_OBJECT_TYPE_IMAGE_VIEW, vk14::handle_value(view), Name);
return view;
}
// the push constants and the blocks of the kernels, laid out as those of the shaders
struct noise_constants
{
std::int32_t kind;
std::int32_t size;
};
struct horizon_constants
{
std::int32_t offset;
std::int32_t pad[3];
glm::vec4 params;
glm::vec3 white_balance;
float pad1;
glm::vec3 old_color;
float pad2;
};
struct shadow_constants
{
std::int32_t size;
std::int32_t pad[3];
glm::vec3 placement;
std::int32_t steps;
};
struct march_constants
{
std::int32_t mode;
std::int32_t pad;
glm::ivec2 size;
glm::mat4 inverse_projection;
glm::mat4 view_to_world;
std::int32_t frame;
std::int32_t pad1[3];
glm::vec4 quality;
glm::ivec2 rows;
};
static_assert(offsetof(march_constants, quality) == 160, "the layout of the constants of clouds_march.comp");
struct temporal_constants
{
glm::ivec2 size;
glm::ivec2 pad;
glm::mat4 inverse_projection;
glm::mat4 view_to_world;
glm::mat4 previous_projection_view;
glm::vec3 previous_camera;
float blend;
};
static_assert(sizeof(temporal_constants) == 224, "the layout of the constants of clouds_temporal.comp");
struct inject_block
{
glm::ivec3 grid;
std::int32_t pad;
glm::mat4 inverse_projection;
glm::mat4 view_to_world;
glm::vec4 slicing;
glm::mat4 previous_projection_view;
glm::vec3 previous_camera;
float pad1;
glm::vec4 options;
std::int32_t cablamp;
std::int32_t pad2;
glm::vec2 previous_slicing;
glm::vec2 jitter;
};
static_assert(offsetof(inject_block, cablamp) == 256, "the layout of the block of fog_inject.comp");
struct integrate_constants
{
glm::ivec3 grid;
std::int32_t pad;
glm::mat4 inverse_projection;
glm::mat4 view_to_world;
glm::vec4 slicing;
};
} // namespace
bool vulkan14_renderer::init_weather(std::string &Reason)
{
auto &ctx{*m_context};
auto &scene{ctx.scene};
auto &weather{scene.weather};
auto const device{ctx.device.handle()};
// the noises, mipmapped and repeated
auto mipmapped = [&](std::shared_ptr<vk14::image> &Image, bool const Volume, int const Size, char const *Name) {
vk14::image_desc desc;
desc.type = (Volume ? VK_IMAGE_TYPE_3D : VK_IMAGE_TYPE_2D);
desc.view_type = (Volume ? VK_IMAGE_VIEW_TYPE_3D : VK_IMAGE_VIEW_TYPE_2D);
desc.format = VK_FORMAT_R8G8B8A8_UNORM;
desc.extent = {static_cast<std::uint32_t>(Size), static_cast<std::uint32_t>(Size), Volume ? static_cast<std::uint32_t>(Size) : 1u};
desc.mips = static_cast<std::uint32_t>(std::floor(std::log2(static_cast<float>(Size)))) + 1;
desc.usage = VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
Image = std::make_shared<vk14::image>();
if (false == Image->create(ctx.memory, desc, Name))
{
Image.reset();
Reason = std::string("no image for ") + Name;
return false;
}
return true;
};
if ((false == mipmapped(weather.shape_noise, true, SHAPE_NOISE_SIZE, "cloud shape noise")) || (false == mipmapped(weather.detail_noise, true, DETAIL_NOISE_SIZE, "cloud detail noise")) ||
(false == mipmapped(weather.coverage_noise, false, COVERAGE_NOISE_SIZE, "cloud coverage noise")))
{
return false;
}
{
vk14::image_desc desc;
desc.format = VK_FORMAT_R32_SFLOAT;
desc.usage = VK_IMAGE_USAGE_STORAGE_BIT;
weather.depth_dummy = std::make_shared<vk14::image>();
if (false == weather.depth_dummy->create(ctx.memory, desc, "clouds panorama depth"))
{
weather.depth_dummy.reset();
Reason = "no image for the depth of the panorama";
return false;
}
}
try
{
auto kernel = [&](vk14::compute_kernel &Kernel, char const *Shader, char const *Name) {
if (false == Kernel.create(device, ctx.pipelines, ctx.shaders.get(Shader), scene.pass_layout, {}, Name))
{
Reason = std::string("no compute pipeline of ") + Name;
return false;
}
return true;
};
if ((false == kernel(weather.noise, "clouds_noise.comp", "clouds noise")) || (false == kernel(weather.march, "clouds_march.comp", "clouds march")) ||
(false == kernel(weather.temporal, "clouds_temporal.comp", "clouds temporal")) || (false == kernel(weather.shadow, "clouds_shadow.comp", "clouds shadow")) ||
(false == kernel(weather.horizon, "fog_horizon.comp", "fog horizon")) || (false == kernel(weather.inject, "fog_inject.comp", "fog inject")) || (false == kernel(weather.integrate, "fog_integrate.comp", "fog integrate")))
{
return false;
}
weather.sky = std::make_shared<vk14::scene_program>();
weather.sky->vertex = ctx.shaders.get("quad.vert", "#define QUAD_SCENE 1\n");
weather.sky->fragment = ctx.shaders.get("sky_weather.frag");
weather.sky->fragment_name = "sky_weather.frag";
weather.sky->vertices = vk14::vertex_kind::none;
weather.sky->name = "quad.vert + sky_weather.frag";
}
catch (gfx::shader_exception const &e)
{
Reason = std::string("invalid shader: ") + e.what();
return false;
}
return true;
}
void vulkan14_renderer::destroy_weather()
{
auto &ctx{*m_context};
auto &weather{ctx.scene.weather};
auto const device{ctx.device.handle()};
for (auto *kernel : {&weather.noise, &weather.march, &weather.temporal, &weather.shadow, &weather.horizon, &weather.inject, &weather.integrate})
{
kernel->destroy(device);
}
for (auto *image : {&weather.shape_noise, &weather.detail_noise, &weather.coverage_noise, &weather.depth_dummy, &weather.shadow_map, &weather.clouds_color, &weather.clouds_depth, &weather.clouds_history[0], &weather.clouds_history[1], &weather.panorama,
&weather.fog_scattering[0], &weather.fog_scattering[1], &weather.fog_integrated})
{
image->reset();
}
weather.sky.reset();
}
namespace
{
// an image of the weather made anew: the old one goes once the gpu is done with the frames which used it (Value)
std::shared_ptr<vk14::image> weather_image(vk14::allocator &Memory, vk14::deletion_queue &Deletions, std::uint64_t const Value, std::shared_ptr<vk14::image> &Image, VkImageType const Type, VkFormat const Format, glm::ivec3 const &Size, char const *Name)
{
if (Image)
{
Deletions.push(Value, [old = std::move(Image)]() {});
}
vk14::image_desc desc;
desc.type = Type;
desc.view_type = (Type == VK_IMAGE_TYPE_3D ? VK_IMAGE_VIEW_TYPE_3D : VK_IMAGE_VIEW_TYPE_2D);
desc.format = Format;
desc.extent = {static_cast<std::uint32_t>(std::max(Size.x, 1)), static_cast<std::uint32_t>(std::max(Size.y, 1)), static_cast<std::uint32_t>(std::max(Size.z, 1))};
desc.usage = VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
Image = std::make_shared<vk14::image>();
if (false == Image->create(Memory, desc, Name))
{
Image.reset();
}
return Image;
}
} // namespace
// the three noises, once (opengl46_weather::make_noise())
void vulkan14_renderer::make_cloud_noise()
{
auto &ctx{*m_context};
auto &weather{ctx.scene.weather};
auto const commands{ctx.commands};
auto const device{ctx.device.handle()};
ctx.passes.begin_pass("clouds noise", {vk14::use_of(*weather.shape_noise, VK_IMAGE_LAYOUT_GENERAL, compute_stage, storage_write), vk14::use_of(*weather.detail_noise, VK_IMAGE_LAYOUT_GENERAL, compute_stage, storage_write),
vk14::use_of(*weather.coverage_noise, VK_IMAGE_LAYOUT_GENERAL, compute_stage, storage_write)});
// the kernel writes the first levels
auto const shape{first_level_view(device, *weather.shape_noise, "cloud shape noise level 0")};
auto const detail{first_level_view(device, *weather.detail_noise, "cloud detail noise level 0")};
auto const coverage{first_level_view(device, *weather.coverage_noise, "cloud coverage noise level 0")};
auto const make = [&](int const Kind, VkImageView Volume, int const Size) {
weather.noise.dispatch(commands, {vk14::storage_image(Volume), vk14::storage_image(coverage)}, noise_constants{Kind, Size}, groups(Size), groups(Size), Kind == 2 ? 1u : static_cast<std::uint32_t>(Size));
vk14::memory_barrier(commands, compute_stage, storage_write, compute_stage, storage_write);
};
make(0, shape, SHAPE_NOISE_SIZE);
make(1, detail, DETAIL_NOISE_SIZE);
make(2, shape, COVERAGE_NOISE_SIZE);
ctx.deletions.push(ctx.uploads.current_value(), [device, shape, detail, coverage]() {
vkDestroyImageView(device, shape, nullptr);
vkDestroyImageView(device, detail, nullptr);
vkDestroyImageView(device, coverage, nullptr);
});
// the mipmaps, each level from the one before it
for (auto *image : {weather.shape_noise.get(), weather.detail_noise.get(), weather.coverage_noise.get()})
{
auto const &desc{image->desc()};
auto barrier = [&](std::uint32_t Level, VkImageLayout From, VkImageLayout To, VkPipelineStageFlags2 SrcStage, VkAccessFlags2 SrcAccess, VkPipelineStageFlags2 DstStage, VkAccessFlags2 DstAccess) {
VkImageMemoryBarrier2 entry{};
entry.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
entry.srcStageMask = SrcStage;
entry.srcAccessMask = SrcAccess;
entry.dstStageMask = DstStage;
entry.dstAccessMask = DstAccess;
entry.oldLayout = From;
entry.newLayout = To;
entry.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
entry.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
entry.image = image->handle();
entry.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, Level, 1, 0, 1};
VkDependencyInfo dependency{};
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
dependency.imageMemoryBarrierCount = 1;
dependency.pImageMemoryBarriers = &entry;
commands->pipeline_barrier2(&dependency);
};
barrier(0, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, compute_stage, storage_write, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_READ_BIT);
for (std::uint32_t level = 1; level < desc.mips; ++level)
{
barrier(level, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_2_NONE, VK_ACCESS_2_NONE, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT);
auto const extent = [&](std::uint32_t Level) {
return VkOffset3D{std::max(static_cast<std::int32_t>(desc.extent.width >> Level), 1), std::max(static_cast<std::int32_t>(desc.extent.height >> Level), 1), std::max(static_cast<std::int32_t>(desc.extent.depth >> Level), 1)};
};
VkImageBlit2 blit{};
blit.sType = VK_STRUCTURE_TYPE_IMAGE_BLIT_2;
blit.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, level - 1, 0, 1};
blit.srcOffsets[1] = extent(level - 1);
blit.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, level, 0, 1};
blit.dstOffsets[1] = extent(level);
VkBlitImageInfo2 info{};
info.sType = VK_STRUCTURE_TYPE_BLIT_IMAGE_INFO_2;
info.srcImage = image->handle();
info.srcImageLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
info.dstImage = image->handle();
info.dstImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
info.regionCount = 1;
info.pRegions = &blit;
info.filter = VK_FILTER_LINEAR;
commands->blit_image2(&info);
barrier(level, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_READ_BIT);
}
VkImageMemoryBarrier2 entry{};
entry.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
entry.srcStageMask = VK_PIPELINE_STAGE_2_BLIT_BIT;
entry.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT;
entry.dstStageMask = sampled_stage;
entry.dstAccessMask = sampled_access;
entry.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
entry.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
entry.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
entry.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
entry.image = image->handle();
entry.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, desc.mips, 0, 1};
VkDependencyInfo dependency{};
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
dependency.imageMemoryBarrierCount = 1;
dependency.pImageMemoryBarriers = &entry;
commands->pipeline_barrier2(&dependency);
image->state = vk14::image_state{VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access};
}
ctx.passes.end_pass();
weather.noise_made = true;
}
// the block of the weather of the pass to come, from gfx::weather_state (without the colours of the horizon, a block of their own)
void vulkan14_renderer::upload_weather_block()
{
auto &ctx{*m_context};
auto constexpr size{offsetof(gl::weather_ubs, fog_horizon)};
auto const slice{ctx.ring.allocate(size, ctx.device.caps().limits.minUniformBufferOffsetAlignment)};
if (false == bool(slice))
{
return;
}
std::memcpy(slice.data, &m_weatherstate.block(), size);
m_draw->weather_offset = static_cast<std::uint32_t>(slice.offset);
m_draw->set_dirty = true;
}
// what the weather of the simulation is for the frame (opengl46_weather::update()): the block, the colours of the horizon, the
// shadows of the clouds, a band of the panorama. before the passes of the frame
void vulkan14_renderer::update_weather()
{
auto &ctx{*m_context};
auto &scene{ctx.scene};
auto &weather{scene.weather};
auto const commands{ctx.commands};
if (false == weather.noise_made)
{
make_cloud_noise();
}
m_weatherstate.update(m_skylight.atmosphere_weight(), true, -m_sunlight.direction, glm::vec3{m_sunlight.diffuse} * simulation::Environment.light_intensity(), glm::vec3{m_sunlight.ambient}, m_fogcolor, Global.pCamera.Pos);
auto const shadows{m_weatherstate.shadows_wanted()};
if (shadows)
{
if (weather.shadow_size != Global.gfx_clouds_shadows_size)
{
weather.shadow_size = Global.gfx_clouds_shadows_size;
weather_image(ctx.memory, ctx.deletions, ctx.uploads.current_value(), weather.shadow_map, VK_IMAGE_TYPE_2D, VK_FORMAT_R16_SFLOAT, glm::ivec3{weather.shadow_size, weather.shadow_size, 1}, "cloud shadow map");
if (weather.shadow_map)
{
// read before it's first drawn (the pass sets hold it)
vk14::barriers(commands, {vk14::use_of(*weather.shadow_map, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access)});
}
}
m_weatherstate.place_shadows(weather.shadow_size, Global.pCamera.Pos);
}
upload_weather_block();
ctx.passes.begin_pass("weather");
bind_compute_set(weather.horizon);
// the colours of the horizon: from the atmosphere on the gpu, the old fog colour otherwise
if (m_weatherstate.horizon_on_gpu())
{
horizon_constants constants{};
constants.params = glm::vec4{m_skylight.atmosphere_weight(), m_skylight.sky_scale(), 0.5f * m_skylight.overcast(), 64.f};
constants.white_balance = m_skylight.white_balance();
constants.old_color = m_fogcolor;
vk14::memory_barrier(commands, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_UNIFORM_READ_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT, compute_stage, storage_write);
weather.horizon.dispatch(commands, {vk14::buffer_descriptor(scene.weather_horizon.handle(), 0, sizeof(glm::vec4) * gl::FOG_HORIZON_SAMPLES)}, constants, 1);
vk14::memory_barrier(commands, compute_stage, storage_write, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_UNIFORM_READ_BIT | VK_ACCESS_2_SHADER_STORAGE_READ_BIT);
}
else
{
vk14::update_buffer(commands, scene.weather_horizon, &m_weatherstate.block().fog_horizon[0], sizeof(glm::vec4) * gl::FOG_HORIZON_SAMPLES, 0);
}
if (shadows && weather.shadow_map)
{
vk14::barriers(commands, {vk14::use_of(*weather.shadow_map, VK_IMAGE_LAYOUT_GENERAL, compute_stage, storage_write)});
shadow_constants constants{};
constants.size = weather.shadow_size;
constants.placement = glm::vec3{m_weatherstate.block().cloud_shadow_params.x, m_weatherstate.block().cloud_shadow_params.y, Global.gfx_clouds_shadows_range};
constants.steps = 8;
weather.shadow.dispatch(commands, {vk14::storage_image(weather.shadow_map->view())}, constants, groups(weather.shadow_size), groups(weather.shadow_size), 1);
vk14::barriers(commands, {vk14::use_of(*weather.shadow_map, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access)});
m_weatherstate.shadows_drawn(true);
upload_weather_block();
bind_compute_set(weather.march);
}
else
{
m_weatherstate.shadows_drawn(false);
}
// a band of the panorama of the clouds, all of it the first time
if (m_weatherstate.clouds_drawn())
{
auto const level{gfx::weather_state::quality(Global.gfx_clouds, Global.gfx_fog_quality)};
if ((weather.panorama_size != level.panorama) || (false == bool(weather.panorama)))
{
weather.panorama_size = level.panorama;
weather_image(ctx.memory, ctx.deletions, ctx.uploads.current_value(), weather.panorama, VK_IMAGE_TYPE_2D, VK_FORMAT_R16G16B16A16_SFLOAT, glm::ivec3{weather.panorama_size, 1}, "clouds panorama");
m_weatherstate.panorama_resized();
}
if (weather.panorama)
{
int first, rows;
m_weatherstate.panorama_band(weather.panorama_size, first, rows);
vk14::barriers(commands, {vk14::use_of(*weather.panorama, VK_IMAGE_LAYOUT_GENERAL, compute_stage, storage_write), vk14::use_of(*weather.depth_dummy, VK_IMAGE_LAYOUT_GENERAL, compute_stage, storage_write)});
march_constants constants{};
constants.mode = 1;
constants.size = weather.panorama_size;
constants.frame = m_weatherstate.frame();
// the panorama is seen blurred: fewer samples
constants.quality = glm::vec4{static_cast<float>(std::max(level.steps / 2, 24)), static_cast<float>(std::max(level.light_steps - 1, 2)), Global.gfx_clouds_distance, 0.f};
constants.rows = glm::ivec2{first, rows};
// the depth isn't written in this mode, but something has to be bound
weather.march.dispatch(commands, {vk14::storage_image(weather.panorama->view()), vk14::storage_image(weather.depth_dummy->view())}, constants, groups(weather.panorama_size.x), groups(rows), 1);
vk14::barriers(commands, {vk14::use_of(*weather.panorama, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access)});
}
}
ctx.passes.end_pass();
}
// the clouds and the fog volume of the colour pass of the main view (opengl46_weather::render_view()): Projection and View
// (rotation, world axes to the view) of its camera, Size of the target, Cablamp the light of the cab lamp or -1. the lights, the
// clusters and the block of the scene of the pass are written, the shadow map drawn. outside a rendering
void vulkan14_renderer::render_weather_view(glm::mat4 const &Projection, glm::mat4 const &View, glm::dvec3 const &Camera, glm::ivec2 const &Size, int const Cablamp)
{
auto &ctx{*m_context};
auto &weather{ctx.scene.weather};
auto const commands{ctx.commands};
auto const level{gfx::weather_state::quality(Global.gfx_clouds, Global.gfx_fog_quality)};
// the images of the clouds of the main view and of the fog volume, for the size and the quality
auto const cloudsize{glm::max((Size + level.divisor - 1) / level.divisor, glm::ivec2{1})};
if (m_weatherstate.clouds_drawn() && ((cloudsize != weather.view_size) || (false == bool(weather.clouds_color))))
{
weather.view_size = cloudsize;
weather_image(ctx.memory, ctx.deletions, ctx.uploads.current_value(), weather.clouds_color, VK_IMAGE_TYPE_2D, VK_FORMAT_R16G16B16A16_SFLOAT, glm::ivec3{cloudsize, 1}, "clouds colour");
weather_image(ctx.memory, ctx.deletions, ctx.uploads.current_value(), weather.clouds_depth, VK_IMAGE_TYPE_2D, VK_FORMAT_R32_SFLOAT, glm::ivec3{cloudsize, 1}, "clouds depth");
for (auto &history : weather.clouds_history)
{
weather_image(ctx.memory, ctx.deletions, ctx.uploads.current_value(), history, VK_IMAGE_TYPE_2D, VK_FORMAT_R16G16B16A16_SFLOAT, glm::ivec3{cloudsize, 1}, "clouds history");
}
m_weatherstate.previous_clouds.valid = false;
}
if ((Global.gfx_fog == 1) && ((level.fog_grid != weather.fog_grid) || (false == bool(weather.fog_integrated))))
{
weather.fog_grid = level.fog_grid;
for (auto &scattering : weather.fog_scattering)
{
weather_image(ctx.memory, ctx.deletions, ctx.uploads.current_value(), scattering, VK_IMAGE_TYPE_3D, VK_FORMAT_R16G16B16A16_SFLOAT, weather.fog_grid, "fog scattering");
}
// one slice more: the fog through the whole volume without the clouds, which the sky takes (fog_integrate.comp)
weather_image(ctx.memory, ctx.deletions, ctx.uploads.current_value(), weather.fog_integrated, VK_IMAGE_TYPE_3D, VK_FORMAT_R16G16B16A16_SFLOAT, weather.fog_grid + glm::ivec3{0, 0, 1}, "fog volume");
if (weather.fog_integrated)
{
// the pass sets hold it
vk14::barriers(commands, {vk14::use_of(*weather.fog_integrated, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access)});
}
m_weatherstate.previous_fog.valid = false;
}
auto const rotation{glm::mat4{glm::mat3{View}}};
auto const inverseprojection{glm::inverse(Projection)};
auto const viewtoworld{glm::transpose(rotation)};
auto const projectionview{Projection * rotation};
auto &previousclouds{m_weatherstate.previous_clouds};
auto &previousfog{m_weatherstate.previous_fog};
if (m_weatherstate.clouds_drawn() && weather.clouds_color && weather.clouds_depth && weather.clouds_history[0] && weather.clouds_history[1])
{
ctx.passes.begin_pass("clouds, fog volume", {vk14::use_of(*weather.clouds_color, VK_IMAGE_LAYOUT_GENERAL, compute_stage, storage_write), vk14::use_of(*weather.clouds_depth, VK_IMAGE_LAYOUT_GENERAL, compute_stage, storage_write)});
bind_compute_set(weather.march);
march_constants constants{};
constants.mode = 0;
constants.size = weather.view_size;
constants.inverse_projection = inverseprojection;
constants.view_to_world = viewtoworld;
constants.frame = m_weatherstate.frame();
constants.quality = glm::vec4{static_cast<float>(level.steps), static_cast<float>(level.light_steps), Global.gfx_clouds_distance, level.detail ? 1.f : 0.f};
constants.rows = glm::ivec2{0, weather.view_size.y};
weather.march.dispatch(commands, {vk14::storage_image(weather.clouds_color->view()), vk14::storage_image(weather.clouds_depth->view())}, constants, groups(weather.view_size.x), groups(weather.view_size.y), 1);
auto const next{1 - weather.clouds_current};
auto &history{*weather.clouds_history[weather.clouds_current]};
auto &result{*weather.clouds_history[next]};
vk14::barriers(commands, {vk14::use_of(*weather.clouds_color, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access),
vk14::use_of(*weather.clouds_depth, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access), vk14::use_of(history, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access),
vk14::use_of(result, VK_IMAGE_LAYOUT_GENERAL, compute_stage, storage_write)});
temporal_constants blend{};
blend.size = weather.view_size;
blend.inverse_projection = inverseprojection;
blend.view_to_world = viewtoworld;
blend.previous_projection_view = previousclouds.projection_view;
blend.previous_camera = glm::vec3{previousclouds.camera - Camera};
blend.blend = gfx::weather_state::continuity(previousclouds, Camera) ? 0.15f : 1.f;
auto const &linear{ctx.sampling.clamp_linear};
auto const &nearest{ctx.sampling.clamp_nearest};
weather.temporal.dispatch(commands,
{vk14::image_descriptor(linear, weather.clouds_color->view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL), vk14::image_descriptor(nearest, weather.clouds_depth->view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL),
vk14::image_descriptor(linear, history.view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL), vk14::storage_image(result.view())},
blend, groups(weather.view_size.x), groups(weather.view_size.y), 1);
vk14::barriers(commands, {vk14::use_of(result, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access)});
weather.clouds_current = next;
previousclouds = {projectionview, Camera, {}, true};
m_weatherstate.clouds_made();
}
else
{
ctx.passes.begin_pass("clouds, fog volume");
previousclouds.valid = false;
}
if ((Global.gfx_fog == 1) && weather.fog_integrated && weather.fog_scattering[0] && weather.fog_scattering[1])
{
bind_compute_set(weather.inject);
auto const temporal{Global.gfx_fog_temporal && gfx::weather_state::continuity(previousfog, Camera)};
auto const jitter{m_weatherstate.fog_jitter(temporal)};
auto const slicing{gfx::weather_state::fog_slicing(weather.fog_grid.z, jitter)};
auto const next{1 - weather.fog_current};
auto &history{*weather.fog_scattering[weather.fog_current]};
auto &result{*weather.fog_scattering[next]};
vk14::barriers(commands, {vk14::use_of(result, VK_IMAGE_LAYOUT_GENERAL, compute_stage, storage_write), vk14::use_of(history, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access)});
inject_block block{};
block.grid = weather.fog_grid;
block.inverse_projection = inverseprojection;
block.view_to_world = viewtoworld;
block.slicing = slicing;
block.previous_projection_view = previousfog.projection_view;
block.previous_camera = glm::vec3{previousfog.camera - Camera};
auto const sunshadows{Global.gfx_fog_shadows && Global.gfx_shadowmap_enabled};
auto const share{temporal ? std::clamp(Global.gfx_fog_temporal_share, 0.02f, 1.f) : 1.f};
block.options = glm::vec4{share, Global.gfx_fog_lights, sunshadows ? 1.f : 0.f, Global.gfx_fog_shadows ? 1.f : 0.f};
block.cablamp = Cablamp;
block.previous_slicing = glm::vec2{previousfog.slicing.x, previousfog.slicing.z};
block.jitter = glm::vec2{jitter.x, jitter.y};
auto const slice{ctx.ring.allocate(sizeof(block), ctx.device.caps().limits.minUniformBufferOffsetAlignment)};
if (slice)
{
std::memcpy(slice.data, &block, sizeof(block));
weather.inject.run(commands,
{vk14::storage_image(result.view()), vk14::image_descriptor(ctx.sampling.clamp_linear, history.view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL), vk14::buffer_descriptor(slice.buffer, slice.offset, sizeof(block))},
groups(weather.fog_grid.x), groups(weather.fog_grid.y), static_cast<std::uint32_t>(weather.fog_grid.z));
}
vk14::barriers(commands, {vk14::use_of(result, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access), vk14::use_of(*weather.fog_integrated, VK_IMAGE_LAYOUT_GENERAL, compute_stage, storage_write)});
integrate_constants integrate{};
integrate.grid = weather.fog_grid;
integrate.inverse_projection = inverseprojection;
integrate.view_to_world = viewtoworld;
integrate.slicing = slicing;
weather.integrate.dispatch(commands, {vk14::image_descriptor(ctx.sampling.clamp_linear, result.view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL), vk14::storage_image(weather.fog_integrated->view())}, integrate,
groups(weather.fog_grid.x), groups(weather.fog_grid.y), 1);
vk14::barriers(commands, {vk14::use_of(*weather.fog_integrated, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access)});
weather.fog_current = next;
previousfog = {projectionview, Camera, slicing, true};
m_weatherstate.fog_made(slicing, weather.fog_grid);
}
else
{
previousfog.valid = false;
}
ctx.passes.end_pass();
}
// the clouds and the fog over the sky, in the sky pass (opengl46_weather::draw_sky()). Mainview: the clouds of the main view, the
// panorama otherwise
void vulkan14_renderer::render_sky_weather(glm::mat4 const &Projection, bool const Mainview)
{
auto &ctx{*m_context};
auto &weather{ctx.scene.weather};
auto &draw{*m_draw};
if (false == bool(weather.sky))
{
return;
}
int mode{0};
vk14::image *image{nullptr};
if (m_weatherstate.clouds_drawn())
{
if (Mainview && m_weatherstate.clouds_view_valid() && weather.clouds_history[weather.clouds_current])
{
mode = 1;
image = weather.clouds_history[weather.clouds_current].get();
}
else if (weather.panorama)
{
mode = 2;
image = weather.panorama.get();
}
}
// over the sky as it is: (1, source alpha), without the depth
auto const glare{draw.glare};
draw.glare = true;
if (false == bind_scene_pipeline(*weather.sky, *draw.targets, vk14::blend_mode::under_transmittance, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP))
{
draw.glare = glare;
return;
}
auto const cull{draw.cullface};
draw.cullface = false;
apply_draw_state();
draw.cullface = cull;
draw.glare = glare;
draw.block = push_draw_block();
if (draw.block == ~0u)
{
return;
}
if (draw.set_dirty)
{
bind_pass_set();
}
// the clouds pushed as the first texture of the draw, the textures of the walk pushed again for the draws which follow
std::array<VkDescriptorImageInfo, gl::MAX_TEXTURES> images;
auto const &scene{ctx.scene};
images.fill(VkDescriptorImageInfo{ctx.sampling.clamp_linear, scene.white->view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL});
if (image != nullptr)
{
// the panorama repeats around the camera
images[0] = VkDescriptorImageInfo{(mode == 2 ? ctx.sampling.panorama : ctx.sampling.clamp_linear), image->view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL};
}
scene.material_set.push(draw.commands, scene.scene_layout, images.data());
draw.textures_dirty = true;
vk14::draw_constants constants;
constants.draw = draw.block;
auto const inverseprojection{glm::inverse(Projection)};
for (int column = 0; column < 4; ++column)
{
constants.data[column] = inverseprojection[column];
}
constants.data[4] = glm::vec4{static_cast<float>(mode), 0.f, 0.f, 0.f};
push_constants(&constants);
draw.commands->draw(4, 1, 0, 0);
}