17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-26 18:14:55 +01:00
Files
maszyna/rendering/vulkan14/vulkan14frame.cpp

3557 lines
143 KiB
C++

/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
// the scene of the vulkan renderer, part two (the stages D3-D7 of the plan): the frame and its passes, in the order of
// opengl46_renderer::Render_pass(): the cascades of the sun shadows, the faces of the environment map, the light clusters, the
// colour of the scene (the sky, the opaque scene with the cab, the translucent parts, the particles, the precipitation, the cab),
// the post-processing (the motion blur, the tonemapping, the chromatic aberration), the output to the windows with the user
// interface; the picking, the position of the cursor of the editor, the screenshots, the previews of the models
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14context.h"
#include "rendering/vulkan14/vulkan14commands.h"
#include "rendering/screenshot.h"
#include "rendering/editoroverlay.h"
#include "rendering/common/exposure.h"
#include "application/application.h"
#include "simulation/simulation.h"
#include "simulation/simulationenvironment.h"
#include "simulation/simulationtime.h"
#include "vehicle/Train.h"
#include "vehicle/DynObj.h"
#include "model/Model3d.h"
#include "model/AnimModel.h"
#include "utilities/Globals.h"
#include "utilities/Logs.h"
#include "utilities/Timer.h"
#include "utilities/utilities.h"
#include "utilities/color.h"
namespace
{
// the size of the (square) images of the picking passes
int constexpr pick_buffer_size{1024};
// the projections of the opengl renderer (opengl46_renderer::perspective_projection() and the others): reverse z with the
// range 1..0, an infinite far plane for the perspective ones
glm::mat4 perspective_projection(viewport_proj_config const &c, float n, float f, glm::mat4 &frustum)
{
glm::vec3 vr, vu, vn;
vr = glm::normalize(c.pb - c.pa);
vu = glm::normalize(c.pc - c.pa);
vn = glm::normalize(glm::cross(vr, vu));
glm::mat4 M;
M[0] = glm::vec4(vr, 0.0f);
M[1] = glm::vec4(vu, 0.0f);
M[2] = glm::vec4(vn, 0.0f);
M = glm::transpose(M);
glm::vec3 va, vb, vc;
va = c.pa - c.pe;
vb = c.pb - c.pe;
vc = c.pc - c.pe;
float l, r, b, t, d;
d = -glm::dot(va, vn);
l = glm::dot(vr, va) * n / d;
r = glm::dot(vr, vb) * n / d;
b = glm::dot(vu, va) * n / d;
t = glm::dot(vu, vc) * n / d;
frustum = glm::frustum(l, r, b, t, n, f);
glm::mat4 R = frustum;
frustum = glm::translate(frustum * M, -c.pe);
R[2][2] = -1.0f;
R[3][2] = -2.0f * n;
R = glm::mat4(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -0.5f, 0.0f, 0.0f, 0.0f, 0.5f, 1.0f) * R;
return glm::translate(R * M, -c.pe);
}
glm::mat4 perspective_projection_raw(float fovy, float aspect, float znear, float zfar)
{
const float f = 1.0f / std::tan(fovy / 2.0f);
return glm::mat4(f / aspect, 0.0f, 0.0f, 0.0f, 0.0f, f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, znear, 0.0f);
}
glm::mat4 ortho_projection(float l, float r, float b, float t, float znear, float zfar)
{
glm::mat4 proj = glm::ortho(l, r, b, t, znear, zfar);
return glm::mat4(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -0.5f, 0.0f, 0.0f, 0.0f, 0.5f, 1.0f) * proj;
}
glm::mat4 ortho_frustumtest_projection(float l, float r, float b, float t, float znear, float zfar)
{
return glm::ortho(l, r, b, t, znear, zfar);
}
// transform 1..-1 NDC xy coordinates to 1..0 (the z is 1..0 already)
glm::mat4 const coordmove{0.5, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.5, 0.5, 0.0, 1.0};
// the vertices of a particle of the smoke (opengl46_particles)
struct particle_vertex
{
glm::vec3 position;
glm::vec4 color;
glm::vec2 texture;
};
static_assert(sizeof(particle_vertex) == 36, "the layout of the vertices of the particles");
std::vector<std::pair<glm::vec3, glm::vec2>> const billboard_vertices{
{{-0.5f, -0.5f, 0.f}, {0.f, 0.f}}, {{0.5f, -0.5f, 0.f}, {1.f, 0.f}}, {{0.5f, 0.5f, 0.f}, {1.f, 1.f}}, {{-0.5f, -0.5f, 0.f}, {0.f, 0.f}}, {{0.5f, 0.5f, 0.f}, {1.f, 1.f}}, {{-0.5f, 0.5f, 0.f}, {0.f, 1.f}},
};
// the cone of the precipitation around the camera (opengl46_precipitation::create())
struct precipitation_mesh
{
std::vector<glm::vec3> vertices;
std::vector<glm::vec2> uvs;
std::vector<std::uint16_t> indices;
void create(int const Tesselation)
{
auto const heightfactor{10.f};
auto const verticaltexturestretchfactor{1.5f};
auto const latitudes{3};
auto const longitudes{Tesselation};
auto const longitudehalfstep{0.5f * static_cast<float>(2.0 * M_PI * 1.f / longitudes)};
std::uint16_t index = 0;
std::vector radii{25.f, 10.f, 5.f, 1.f};
for (auto radius : radii)
{
for (int i = 0; i <= latitudes; ++i)
{
auto const latitude{static_cast<float>(M_PI * (-0.5f + (float)(i) / latitudes))};
auto const z{std::sin(latitude)};
auto const zr{std::cos(latitude)};
for (int j = 0; j <= longitudes; ++j)
{
auto const longitude{static_cast<float>(2.0 * M_PI * (float)(j) / longitudes)};
auto const x{std::cos(longitude)};
auto const y{std::sin(longitude)};
vertices.emplace_back(glm::vec3(-x * zr, -z * heightfactor, -y * zr) * radius);
auto u = (i == 0 ? longitude + longitudehalfstep : i == latitudes ? longitude - longitudehalfstep : longitude);
uvs.emplace_back(u / (2.0 * M_PI) * radius, 1.f - (float)(i) / latitudes * radius * heightfactor * 0.5f / verticaltexturestretchfactor);
if ((i == 0) || (j == 0))
{
++index;
}
else
{
if (i < latitudes)
{
indices.emplace_back(index - 1 - (longitudes + 1));
indices.emplace_back(index - 1);
indices.emplace_back(index);
}
if (i > 1)
{
indices.emplace_back(index);
indices.emplace_back(index - (longitudes + 1));
indices.emplace_back(index - 1 - (longitudes + 1));
}
++index;
}
}
}
}
}
};
precipitation_mesh &precipitation()
{
static precipitation_mesh mesh;
if (mesh.vertices.empty())
{
mesh.create(18);
}
return mesh;
}
VkPipelineStageFlags2 const colour_stage{VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT};
VkAccessFlags2 const colour_access{VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT};
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};
// a depth resolved by a rendering: written in the stage of the colour output
VkPipelineStageFlags2 const resolve_depth_stage{VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT};
VkAccessFlags2 const resolve_depth_access{VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT | 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};
VkAccessFlags2 const sampled_access{VK_ACCESS_2_SHADER_SAMPLED_READ_BIT};
// the copies to the buffers the cpu reads back, made seen by the host once the gpu is done with the frame
void host_read_barrier(vk14::command_stream *Commands)
{
VkMemoryBarrier2 barrier{};
barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2;
barrier.srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT;
barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT;
barrier.dstStageMask = VK_PIPELINE_STAGE_2_HOST_BIT;
barrier.dstAccessMask = VK_ACCESS_2_HOST_READ_BIT;
VkDependencyInfo dependency{};
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
dependency.memoryBarrierCount = 1;
dependency.pMemoryBarriers = &barrier;
Commands->pipeline_barrier2(&dependency);
}
// the images the colour of a viewport is drawn to, as its rendering uses them
std::vector<vk14::image_use> colour_target_uses(vk14::viewport_targets &targets)
{
auto const msaa{targets.samples != VK_SAMPLE_COUNT_1_BIT};
std::vector<vk14::image_use> uses;
auto &colourimage{msaa ? *targets.msaa_colour : *targets.colour};
auto &depthimage{msaa ? *targets.msaa_depth : *targets.depth};
uses.push_back(vk14::use_of(colourimage, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, colour_stage, colour_access));
uses.push_back(vk14::use_of(depthimage, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, depth_stage, depth_access));
if (msaa)
{
uses.push_back(vk14::use_of(*targets.colour, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, colour_stage, colour_access));
uses.push_back(vk14::use_of(*targets.depth, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, resolve_depth_stage, resolve_depth_access));
}
if (targets.motion)
{
uses.push_back(vk14::use_of(msaa ? *targets.msaa_motion : *targets.motion_image, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, colour_stage, colour_access));
if (msaa)
{
uses.push_back(vk14::use_of(*targets.motion_image, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, colour_stage, colour_access));
}
}
return uses;
}
VkRenderingAttachmentInfo attachment_info(VkImageView View, VkImageLayout Layout, bool const Clear, VkClearValue const &Value)
{
VkRenderingAttachmentInfo info{};
info.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
info.imageView = View;
info.imageLayout = Layout;
info.loadOp = (Clear ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD);
info.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
info.clearValue = Value;
return info;
}
} // namespace
// the data of the frames, prepared once the renderer is there
void vulkan14_renderer::init_frame_data()
{
auto &ctx{*m_context};
// rgb value for 5780 kelvin
Global.DayLight.diffuse[0] = 1.0f;
Global.DayLight.diffuse[1] = 0.9490196f;
Global.DayLight.diffuse[2] = 0.9058824f;
Global.DayLight.is_directional = true;
auto const texturesize{static_cast<int>(ctx.device.caps().limits.maxImageDimension2D)};
Global.iMaxTextureSize = std::min(Global.iMaxTextureSize, texturesize);
Global.iMaxCabTextureSize = std::min(Global.iMaxCabTextureSize, texturesize);
Global.CurrentMaxTextureSize = Global.iMaxTextureSize;
WriteLog("texture sizes capped at " + std::to_string(Global.iMaxTextureSize) + "p (" + std::to_string(Global.iMaxCabTextureSize) + "p for cab textures)");
if (Global.iMultisampling)
{
WriteLog("using multisampling x" + std::to_string(1 << Global.iMultisampling));
}
if (Global.gfx_framebuffer_width == -1)
{
Global.gfx_framebuffer_width = Global.fb_size.x;
}
if (Global.gfx_framebuffer_height == -1)
{
Global.gfx_framebuffer_height = Global.fb_size.y;
}
WriteLog("main window size: " + std::to_string(Global.gfx_framebuffer_width) + "x" + std::to_string(Global.gfx_framebuffer_height));
m_viewports.clear();
auto &main{m_viewports.emplace_back()};
main.width = Global.gfx_framebuffer_width;
main.height = Global.gfx_framebuffer_height;
main.main = true;
main.shadow = true;
main.window = 0;
main.draw_range = 1.0f;
// preload some common textures
WriteLog("Loading common gfx data...");
m_glaretexture = Fetch_Texture("fx/lightglare");
m_suntexture = Fetch_Texture("fx/sun");
m_moontexture = Fetch_Texture("fx/moon");
m_smoketexture = Fetch_Texture("fx/smoke");
m_headlightstexture = Fetch_Texture("fx/headlights:st");
WriteLog("...gfx data pre-loading done");
// prepare basic geometry chunks
float const size = 2.5f / 2.0f;
auto const geometrybank = Create_Bank();
gfx::vertex_array billboard_array{
{{-size, size, 0.f}, glm::vec3(), {1.f, 1.f}}, {{size, size, 0.f}, glm::vec3(), {0.f, 1.f}}, {{-size, -size, 0.f}, glm::vec3(), {1.f, 0.f}}, {{size, -size, 0.f}, glm::vec3(), {0.f, 0.f}}};
gfx::userdata_array userdata{};
m_billboardgeometry = m_geometry.create_chunk(billboard_array, userdata, geometrybank, GL_TRIANGLE_STRIP);
m_invalid_material = Fetch_Material("invalid");
m_fogrange = 2000.f;
}
// the images of a viewport for its size and the settings
namespace
{
bool make_targets(vk14::allocator &Memory, vk14::deletion_queue &Deletions, std::uint64_t const Value, std::uint32_t &Formatscount, VkSampleCountFlags const Samplecounts, VkFormat const Bloomformat, vk14::viewport_targets &Targets,
int const Width, int const Height, bool const Srgb, bool const Taa)
{
// as many samples as asked for, as far as the device has them for the colour and the depth
auto samples{static_cast<VkSampleCountFlagBits>(std::clamp(1 << Global.iMultisampling, 1, 8))};
while ((samples > VK_SAMPLE_COUNT_1_BIT) && (0 == (Samplecounts & samples)))
{
samples = static_cast<VkSampleCountFlagBits>(samples >> 1);
}
auto const motion{Global.gfx_motion_vectors()};
auto const aberration{Global.gfx_postfx_chromaticaberration_enabled};
VkExtent2D const extent{static_cast<std::uint32_t>(std::max(Width, 1)), static_cast<std::uint32_t>(std::max(Height, 1))};
if ((Targets.extent.width == extent.width) && (Targets.extent.height == extent.height) && (Targets.samples == samples) && (Targets.motion == motion) && (Targets.taa == Taa) && (Targets.aberration == aberration) &&
(Targets.srgb == Srgb) && Targets.output)
{
return true;
}
Deletions.push(Value, [old = std::move(Targets)]() {});
auto const oldformats{Targets.formats};
auto const oldoutput{Targets.output_formats};
auto const oldpost{Targets.post_formats};
auto const olddepth{Targets.depth_formats};
Targets = vk14::viewport_targets{};
Targets.extent = extent;
Targets.samples = samples;
Targets.motion = motion;
Targets.taa = Taa;
Targets.aberration = aberration;
Targets.srgb = Srgb;
auto make = [&](std::shared_ptr<vk14::image> &Image, VkFormat Format, VkImageUsageFlags Usage, VkSampleCountFlagBits Samples, char const *Name) {
vk14::image_desc desc;
desc.format = Format;
desc.extent = {extent.width, extent.height, 1};
desc.samples = Samples;
desc.usage = Usage;
Image = std::make_shared<vk14::image>();
if (false == Image->create(Memory, desc, Name))
{
Image.reset();
return false;
}
return true;
};
auto const hdr{VK_FORMAT_R16G16B16A16_SFLOAT};
auto const velocity{VK_FORMAT_R16G16_SFLOAT};
auto const depth{VK_FORMAT_D32_SFLOAT};
auto const sampledcolour{VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT};
auto ok{true};
if (samples != VK_SAMPLE_COUNT_1_BIT)
{
ok = ok && make(Targets.msaa_colour, hdr, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, samples, "msaa colour") && make(Targets.msaa_depth, depth, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, samples, "msaa depth");
if (motion)
{
ok = ok && make(Targets.msaa_motion, velocity, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, samples, "msaa velocity");
}
}
ok = ok && make(Targets.colour, hdr, sampledcolour, VK_SAMPLE_COUNT_1_BIT, "scene colour") &&
make(Targets.depth, depth, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT, VK_SAMPLE_COUNT_1_BIT, "scene depth");
if (motion)
{
ok = ok && make(Targets.motion_image, velocity, sampledcolour, VK_SAMPLE_COUNT_1_BIT, "scene velocity");
}
if (motion && Global.gfx_postfx_motionblur_enabled)
{
ok = ok && make(Targets.blurred, hdr, sampledcolour, VK_SAMPLE_COUNT_1_BIT, "motion blurred");
}
if (Taa)
{
ok = ok && make(Targets.taa_history[0], hdr, sampledcolour, VK_SAMPLE_COUNT_1_BIT, "taa history") && make(Targets.taa_history[1], hdr, sampledcolour, VK_SAMPLE_COUNT_1_BIT, "taa history");
}
auto const output{Srgb ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM};
if (aberration)
{
ok = ok && make(Targets.tonemapped, output, sampledcolour, VK_SAMPLE_COUNT_1_BIT, "tonemapped");
}
ok = ok && make(Targets.output, output, sampledcolour | VK_IMAGE_USAGE_TRANSFER_SRC_BIT, VK_SAMPLE_COUNT_1_BIT, "output");
// the ambient occlusion, half the size, when it's on
if (ok && (Global.gfx_postfx_ao != 0))
{
auto makehalf = [&](std::shared_ptr<vk14::image> &Image, char const *Name) {
vk14::image_desc desc;
desc.format = VK_FORMAT_R8_UNORM;
desc.extent = {std::max(extent.width / 2, 1u), std::max(extent.height / 2, 1u), 1};
desc.usage = VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
Image = std::make_shared<vk14::image>();
if (false == Image->create(Memory, desc, Name))
{
Image.reset();
return false;
}
return true;
};
if ((false == makehalf(Targets.ssao, "ambient occlusion")) || (false == makehalf(Targets.ssao_blur, "ambient occlusion smoothed")))
{
ErrorLog("ambient occlusion framebuffer setup failed");
Targets.ssao.reset();
Targets.ssao_blur.reset();
}
}
// the bloom chain, made whether the bloom is on or not: it can be switched on in the debug window
if (ok)
{
auto const sizes{gfx::exposure_settings::bloom_sizes(static_cast<int>(extent.width), static_cast<int>(extent.height))};
auto makelevel = [&](std::vector<std::shared_ptr<vk14::image>> &Levels, glm::ivec2 const &Size, char const *Name) {
vk14::image_desc desc;
desc.format = Bloomformat;
desc.extent = {static_cast<std::uint32_t>(Size.x), static_cast<std::uint32_t>(Size.y), 1};
desc.usage = sampledcolour;
auto level{std::make_shared<vk14::image>()};
if (false == level->create(Memory, desc, Name))
{
return false;
}
Levels.push_back(level);
return true;
};
auto bloomok{true};
for (std::size_t level = 0; bloomok && (level < sizes.size()); ++level)
{
bloomok = makelevel(Targets.bloom_down, sizes[level], "bloom down");
}
for (std::size_t level = 0; bloomok && (level + 1 < sizes.size()); ++level)
{
bloomok = makelevel(Targets.bloom_up, sizes[level], "bloom up");
}
if (bloomok)
{
Targets.bloom_sizes = sizes;
}
else
{
Targets.bloom_down.clear();
Targets.bloom_up.clear();
ErrorLog("bloom framebuffer setup failed, no bloom");
}
}
if (false == ok)
{
vk14::log_error("the images of a viewport of " + std::to_string(extent.width) + "x" + std::to_string(extent.height) + " can't be made");
Targets.output.reset();
return false;
}
Targets.formats.colour = {hdr, motion ? velocity : VK_FORMAT_UNDEFINED};
Targets.formats.colours = (motion ? 2 : 1);
Targets.formats.depth = depth;
Targets.formats.samples = samples;
Targets.output_formats.colour = {output, VK_FORMAT_UNDEFINED};
Targets.output_formats.colours = 1;
Targets.post_formats.colour = {hdr, VK_FORMAT_UNDEFINED};
Targets.post_formats.colours = 1;
Targets.depth_formats.depth = depth;
Targets.depth_formats.samples = samples;
// the pipelines are made for the number of the formats: the same formats keep theirs
auto const same = [](vk14::target_formats const &Left, vk14::target_formats const &Right) {
return (Left.colour == Right.colour) && (Left.colours == Right.colours) && (Left.depth == Right.depth) && (Left.samples == Right.samples);
};
Targets.formats.id = ((oldformats.id != 0) && same(oldformats, Targets.formats) ? oldformats.id : ++Formatscount);
Targets.output_formats.id = ((oldoutput.id != 0) && same(oldoutput, Targets.output_formats) ? oldoutput.id : ++Formatscount);
Targets.post_formats.id = ((oldpost.id != 0) && same(oldpost, Targets.post_formats) ? oldpost.id : ++Formatscount);
Targets.depth_formats.id = ((olddepth.id != 0) && same(olddepth, Targets.depth_formats) ? olddepth.id : ++Formatscount);
return true;
}
} // namespace
// the frame
bool vulkan14_renderer::Render()
{
if (!m_context || m_context->devicelost)
{
return false;
}
auto &ctx{*m_context};
auto &scene{ctx.scene};
auto const device{ctx.device.handle()};
auto const slot{static_cast<std::uint32_t>(ctx.framecount % ctx.frames.size())};
auto &frame{ctx.frames[slot]};
Timer::subsystem.gfx_total.start();
// the render thread is done with the frame before: the queue, the swapchains and the frames are the main thread's again
if (auto const *failure{ctx.drain()})
{
report_device_lost(failure);
return false;
}
// the gpu has to be done with the frame which used the slot before
if (frame.done > 0)
{
if (false == ctx.wait(frame.done))
{
report_device_lost("waiting for a frame");
return false;
}
ctx.passes.collect(slot);
}
// what the frames done used last goes
ctx.deletions.collect(std::max(ctx.completed(), frame.done));
VK14_VERIFY(vkResetCommandPool(device, frame.pool, 0));
for (auto &worker : frame.workers)
{
VK14_VERIFY(vkResetCommandPool(device, worker.pool, 0));
worker.used = 0;
}
ctx.ring.begin_frame(slot);
scene.draws.ring.begin_frame(slot);
scene.instances.ring.begin_frame(slot);
scene.draws.open = false;
scene.instances.open = false;
// the draws which aren't instanced read the identity at the start of the window from the first draw of the frame on
open_instance_window();
// the occlusion queries of the lamps the slot made the last time
{
auto &submodels{scene.occlusion_submodels[slot]};
if (false == submodels.empty())
{
std::vector<std::uint32_t> results(submodels.size(), 1);
if (vkGetQueryPoolResults(device, scene.occlusion_pools[slot], 0, static_cast<std::uint32_t>(submodels.size()), results.size() * sizeof(std::uint32_t), results.data(), sizeof(std::uint32_t), 0) == VK_SUCCESS)
{
for (std::size_t i = 0; i < submodels.size(); ++i)
{
scene.glare_visible[submodels[i]] = (results[i] > 0);
}
}
vkResetQueryPool(device, scene.occlusion_pools[slot], 0, static_cast<std::uint32_t>(submodels.size()));
submodels.clear();
}
}
// the calls of the frame are kept, recorded into the command buffers once the frame is done
frame.upload_stream.clear();
frame.commands.clear();
auto const value{ctx.timelinevalue + 1};
ctx.uploads.begin_frame(&frame.upload_stream, value);
ctx.slot = slot;
ctx.commands = &frame.commands;
if (scene.cull.ready)
{
// the cullings share what they need of the frame
scene.cull.main.begin_frame(slot, value, ctx.completed());
}
scene.lamps.begin_frame(ctx.commands, value);
if (scene.terrain)
{
// textures of new heightmap terrain layers go to the arrays the terrain shader reads them from
scene.terrain->begin_frame(ctx.commands, value);
scene.terrain->update();
}
// the copies of the light buffer were in the part of the ring of another frame
m_lightheaders.clear();
// the content other threads gave the textures (python)
{
std::vector<vk14::vulkan14_texture *> pending;
{
std::lock_guard<std::mutex> lock(ctx.pending.mutex);
pending.swap(ctx.pending.textures);
}
for (auto *texture : pending)
{
texture->apply_pending();
}
}
ctx.acquired.clear();
for (auto &window : ctx.windows)
{
switch (window->acquire(slot))
{
case vk14::swapchain::acquire_result::ready:
ctx.acquired.push_back(window.get());
break;
case vk14::swapchain::acquire_result::skipped:
break;
default:
if (window != ctx.windows.front())
{
// a window of an extra viewport which can't be shown doesn't stop the others
break;
}
ctx.acquired.clear();
report_device_lost("acquiring an image");
return false;
}
}
frame.number = ctx.framecount;
ctx.passes.begin_frame(ctx.commands, slot, ctx.framecount);
// the stand-ins put in place the first time: the empty maps cleared
if (false == scene.images_ready)
{
scene.images_ready = true;
std::vector<vk14::image *> const standins{scene.shadow_empty.get(), scene.env_empty.get(), scene.volume_empty.get(), scene.cube_shadow_empty.get(), scene.headlight_shadow_empty.get(), scene.depth_empty.get()};
std::vector<vk14::image_use> clears;
for (auto *image : standins)
{
clears.push_back(vk14::use_of(*image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_2_CLEAR_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT));
}
ctx.passes.begin_pass("frame setup", clears);
// the empty shadow maps hold the farthest depth, the empty images black
VkClearDepthStencilValue const farthest{0.f, 0};
VkClearColorValue const black{{0.f, 0.f, 0.f, 0.f}};
std::vector<vk14::image_use> uses;
for (auto *image : standins)
{
VkImageSubresourceRange const range{image->aspect(), 0, image->desc().mips, 0, image->desc().layers};
if (image->aspect() == VK_IMAGE_ASPECT_DEPTH_BIT)
{
ctx.commands->clear_depth_stencil_image(image->handle(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &farthest, 1, &range);
}
else
{
ctx.commands->clear_color_image(image->handle(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &black, 1, &range);
}
uses.push_back(vk14::use_of(*image, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access));
}
// the maps which aren't drawn yet are read before they are (the pass sets hold them)
if (scene.shadow_map)
{
uses.push_back(vk14::use_of(*scene.shadow_map, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access));
}
if (scene.env_map)
{
uses.push_back(vk14::use_of(*scene.env_map, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access));
}
vk14::barriers(ctx.commands, uses);
// no light is assigned to any cluster until the culling pass runs
ctx.commands->fill_buffer(scene.clusters.ranges.handle(), 0, VK_WHOLE_SIZE, 0);
VkMemoryBarrier2 barrier{};
barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2;
barrier.srcStageMask = VK_PIPELINE_STAGE_2_CLEAR_BIT;
barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT;
barrier.dstStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
barrier.dstAccessMask = VK_ACCESS_2_MEMORY_READ_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT;
VkDependencyInfo dependency{};
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
dependency.memoryBarrierCount = 1;
dependency.pMemoryBarriers = &barrier;
ctx.commands->pipeline_barrier2(&dependency);
// the light of the sky and the exposure from their first state
clear_sky_images();
ctx.passes.end_pass();
}
// the block of the weather and the scene for the compute passes before the first pass of the scene
m_draw->scene_offset = 0;
m_draw->light_offset = 0;
m_draw->cluster_offset = 0;
upload_scene_block();
upload_weather_block();
// fetch simulation data
if (simulation::is_ready)
{
static_cast<basic_light &>(m_sunlight) = Global.DayLight;
// quantize sun angle to reduce shadow crawl
auto const quantizationstep{0.004f};
m_sunlight.direction = glm::normalize(quantizationstep * glm::roundEven(m_sunlight.direction * (1.f / quantizationstep)));
m_fogcolor = Global.FogColor;
// the light of the sky for the render: colours of the sun, ambient light and fog from the atmosphere where it is used, the
// directional part of the ambient light. the volumetric clouds of the previous frame tell the sky whether to look overcast
// and dim the sun itself
m_skylight.set_clouds(m_weatherstate.clouds_drawn(), m_weatherstate.cloud_shadows());
update_sky();
// fog, clouds and stars of the weather, in the light of the sky worked out above
if (false == Global.gfx_skiprendering)
{
update_weather();
}
// lamps of models and vehicles around the camera and the cab lamp, for the views of the frame to take into their light buffers
if ((false == Global.gfx_skiprendering) && (simulation::Region != nullptr))
{
m_locallights.gather(Global.pCamera.Pos, *simulation::Region, m_geometry, m_framestamp);
}
}
else
{
// a scenery is being loaded: the cells, instances and models the lights were taken from may be gone
m_locallights.reset();
scene.lamps.reset_lamps();
scene.lamps.reset_headlights();
// and so may the tracks the sleeper records were made for, and the models drawn on their own
Clear_Indirect_Records();
scene.glare_visible.clear();
}
if ((EditorModeFlag) || (m_nodestateversion != scene::node_state_version))
{
// the editor moves and resizes model instances in place, events show and hide them: the instance records are out of date
++m_instancerecordsversion;
m_nodestateversion = scene::node_state_version;
}
// the sleeper records of tracks out of sight for a while, and the records of the models drawn on their own
Expire_Indirect_Records();
// scenery lamps which get shadows in this frame. their maps hold the scenery as of the version of the instance records
if (simulation::is_ready && (false == Global.gfx_skiprendering))
{
scene.lamps.update_lamps(m_locallights.lights(), Global.pCamera.Pos, m_instancerecordsversion, m_framestamp);
}
m_renderpass.draw_mode = rendermode::none;
// the vehicles which moved in the last frame (motion blur)
m_vehiclesmoved = m_vehiclesmoving;
m_vehiclesmoving = false;
m_renderpass.draw_stats = debug_stats();
m_draw->draws = 0;
auto const drawscene{simulation::is_ready && (false == Global.gfx_skiprendering)};
if (drawscene)
{
render_scene();
if (m_pickcontrolswanted && simulation::Train)
{
render_picking(rendermode::pickcontrols);
}
if (m_picknodewanted)
{
render_picking(rendermode::pickscenery);
}
}
m_pickcontrolswanted = false;
m_picknodewanted = false;
// the user interface after the scene, as in the opengl renderer: imgui makes its frame, the images it asks for are uploaded in
// this frame
Timer::subsystem.gfx_gui.start();
draw_debug_ui();
Application.render_ui();
Timer::subsystem.gfx_gui.stop();
// the windows: the image of their viewport, the user interface over the main one
bool uidrawn{false};
for (auto *window : ctx.acquired)
{
std::size_t viewport{m_viewports.size()};
for (std::size_t i = 0; i < m_viewports.size(); ++i)
{
if (ctx.windows[m_viewports[i].window].get() == window)
{
viewport = i;
break;
}
}
auto const ismain{window == ctx.windows.front().get()};
ctx.passes.begin_pass(ismain ? "ui" : "other viewports", {window->use(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, colour_stage, colour_access)});
VkClearValue clear{};
clear.color = {{0.f, 0.f, 0.f, 1.f}};
auto attachment{attachment_info(window->view(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, true, clear)};
VkRenderingInfo rendering{};
rendering.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
rendering.renderArea = {{0, 0}, window->extent()};
rendering.layerCount = 1;
rendering.colorAttachmentCount = 1;
rendering.pColorAttachments = &attachment;
ctx.commands->begin_rendering(&rendering);
if (drawscene && (viewport < scene.targets.size()) && scene.targets[viewport].output)
{
present_viewport(viewport);
}
if (ismain)
{
ctx.imgui.record(ctx.commands, window->extent(), window->format(), window->srgb());
uidrawn = true;
}
ctx.commands->end_rendering();
ctx.passes.end_pass();
vk14::barriers(ctx.commands, {window->use(VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_PIPELINE_STAGE_2_NONE, VK_ACCESS_2_NONE)});
}
if (false == uidrawn)
{
// the main window is minimized: the frame of imgui has nowhere to go
ctx.imgui.skip();
}
// the screenshot asked for: the output of the main viewport, read back once the gpu is done with the frame
if (scene.screenshot_wanted && drawscene && (false == scene.targets.empty()) && scene.targets.front().output && (false == scene.screenshot_pending))
{
scene.screenshot_wanted = false;
auto &output{*scene.targets.front().output};
auto const extent{scene.targets.front().extent};
scene.screenshot = std::make_shared<vk14::buffer>();
if (scene.screenshot->create(ctx.memory, static_cast<VkDeviceSize>(extent.width) * extent.height * 4, VK_BUFFER_USAGE_TRANSFER_DST_BIT, vk14::memory_kind::readback, "screenshot"))
{
vk14::barriers(ctx.commands, {vk14::use_of(output, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_2_COPY_BIT, VK_ACCESS_2_TRANSFER_READ_BIT)});
VkBufferImageCopy2 region{};
region.sType = VK_STRUCTURE_TYPE_BUFFER_IMAGE_COPY_2;
region.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
region.imageExtent = {extent.width, extent.height, 1};
VkCopyImageToBufferInfo2 copy{};
copy.sType = VK_STRUCTURE_TYPE_COPY_IMAGE_TO_BUFFER_INFO_2;
copy.srcImage = output.handle();
copy.srcImageLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
copy.dstBuffer = scene.screenshot->handle();
copy.regionCount = 1;
copy.pRegions = &region;
ctx.commands->copy_image_to_buffer2(&copy);
host_read_barrier(ctx.commands);
scene.screenshot_pending = true;
scene.screenshot_value = value;
scene.screenshot_extent = extent;
}
}
ctx.passes.end_frame();
vulkan14_renderer::context::render_job job;
job.target = &frame;
job.uploaded = ctx.uploads.end_frame();
// the stream of the frame is the render thread's now: nothing records into it any more
ctx.commands = nullptr;
m_draw->commands = nullptr;
scene.lamps.end_frame();
if (scene.terrain)
{
scene.terrain->end_frame();
}
ctx.ring.flush();
scene.draws.ring.flush();
scene.instances.ring.flush();
for (auto *window : ctx.acquired)
{
VkSemaphoreSubmitInfo wait{};
wait.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO;
wait.semaphore = window->acquire_semaphore(slot);
wait.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
job.waits.push_back(wait);
VkSemaphoreSubmitInfo signal{};
signal.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO;
signal.semaphore = window->present_semaphore();
signal.stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
job.signals.push_back(signal);
}
VkSemaphoreSubmitInfo timelinesignal{};
timelinesignal.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO;
timelinesignal.semaphore = ctx.timeline;
timelinesignal.value = value;
timelinesignal.stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
job.signals.push_back(timelinesignal);
if (ctx.render_threaded())
{
// the render thread records, submits and presents the frame while the simulation goes on; the value of the frame is
// known now, the gpu reaches it once the thread submitted it
job.windows.swap(ctx.acquired);
frame.done = ctx.timelinevalue = value;
ctx.hand_over(std::move(job));
}
else
{
char const *failure{nullptr};
if (false == ctx.execute(job, failure))
{
report_device_lost(failure);
return false;
}
frame.done = ctx.timelinevalue = value;
}
m_laststats = m_colorpass.draw_stats;
++m_framestamp;
Timer::subsystem.gfx_total.stop();
return true;
}
void vulkan14_renderer::render_scene()
{
auto &ctx{*m_context};
auto &scene{ctx.scene};
if (scene.targets.size() < m_viewports.size())
{
scene.targets.resize(m_viewports.size());
}
for (std::size_t i = 0; i < m_viewports.size(); ++i)
{
auto const &viewport{m_viewports[i]};
auto const srgb{false == Global.gfx_shadergamma};
auto const *history{scene.targets[i].taa_history[0].get()};
if (false == make_targets(ctx.memory, ctx.deletions, ctx.uploads.current_value(), scene.formats_count, ctx.device.caps().limits.framebufferColorSampleCounts & ctx.device.caps().limits.framebufferDepthSampleCounts, scene.bloom_format,
scene.targets[i], viewport.width, viewport.height, srgb, viewport.main && Global.gfx_taa()))
{
continue;
}
if (viewport.main && (scene.targets[i].taa_history[0].get() != history))
{
// new images of the history, nothing in them yet
m_taa.reset();
}
render_viewport(i);
}
m_current_viewport = (m_viewports.empty() ? nullptr : &m_viewports.front());
}
// the passes of the walk
void vulkan14_renderer::start_draws(vk14::target_formats const &Targets, int const Width, int const Height, int const Set, bool const Shadows)
{
auto &draw{*m_draw};
draw.commands = m_context->commands;
draw.targets = &Targets;
draw.extent = {static_cast<std::uint32_t>(Width), static_cast<std::uint32_t>(Height)};
draw.set = static_cast<vk14::scene_state::pass_set_kind>(Set);
draw.shadows = Shadows;
draw.rendering = true;
draw.set_dirty = true;
draw.textures_dirty = true;
draw.pipeline = VK_NULL_HANDLE;
draw.vertices = VK_NULL_HANDLE;
draw.userdata = VK_NULL_HANDLE;
draw.indices = VK_NULL_HANDLE;
draw.topology = VK_PRIMITIVE_TOPOLOGY_MAX_ENUM;
draw.cull = VK_CULL_MODE_FLAG_BITS_MAX_ENUM;
draw.depthtest = -1;
draw.depthwrite = -1;
draw.pushed_draw = ~0u;
draw.block = ~0u;
draw.cullface = true;
draw.glare = false;
draw.depthmask_off = false;
vk14::set_draw_state(draw.commands, draw.extent);
// the faces counter-clockwise in the window of opengl are clockwise in the framebuffer of vulkan, which counts its rows the
// other way (with the same memory of the images, decision K8)
draw.commands->set_front_face(VK_FRONT_FACE_CLOCKWISE);
}
void vulkan14_renderer::end_draws()
{
auto &draw{*m_draw};
draw.rendering = false;
draw.program = nullptr;
draw.resume.valid = false;
draw.cull_sets = nullptr;
}
void vulkan14_renderer::upload_scene_block()
{
auto &ctx{*m_context};
auto const slice{ctx.ring.allocate(sizeof(gl::scene_ubs), ctx.device.caps().limits.minUniformBufferOffsetAlignment)};
if (false == bool(slice))
{
return;
}
std::memcpy(slice.data, &scene_ubs, sizeof(gl::scene_ubs));
m_draw->scene_offset = static_cast<std::uint32_t>(slice.offset);
m_draw->set_dirty = true;
}
void vulkan14_renderer::upload_light_header()
{
// the header with the lights in use: what changes between the draws (the sun in tunnels, the ambient light of the cab) goes
// with a copy of the lights, the shaders read the buffer whole
auto &ctx{*m_context};
// the lights stay the same between the calls of update_lights(): a header with the sun given already goes again
auto const *header{reinterpret_cast<std::uint8_t const *>(&light_data)};
for (auto const &[copy, offset] : m_lightheaders)
{
if (0 == std::memcmp(copy.data(), header, copy.size()))
{
if (m_draw && (m_draw->light_offset != offset))
{
m_draw->light_offset = offset;
m_draw->set_dirty = true;
}
return;
}
}
auto const size{offsetof(gl::light_data, lights) + sizeof(gl::light_element) * std::max<std::uint32_t>(m_lightcount, 1)};
auto const slice{ctx.ring.allocate(size, ctx.device.caps().limits.minStorageBufferOffsetAlignment)};
if (false == bool(slice))
{
return;
}
std::memcpy(slice.data, &light_data, size);
if (m_lightheaders.size() < 32)
{
auto &entry{m_lightheaders.emplace_back()};
std::memcpy(entry.first.data(), header, entry.first.size());
entry.second = static_cast<std::uint32_t>(slice.offset);
}
if (m_draw)
{
m_draw->light_offset = static_cast<std::uint32_t>(slice.offset);
m_draw->set_dirty = true;
}
}
void vulkan14_renderer::upload_lights()
{
upload_light_header();
}
void vulkan14_renderer::setup_matrices()
{
OpenGLMatrices.mode(GL_PROJECTION);
OpenGLMatrices.load_matrix(m_renderpass.pass_camera.projection());
// trim modelview matrix just to rotation, since rendering is done in camera-centric world space
OpenGLMatrices.mode(GL_MODELVIEW);
OpenGLMatrices.load_matrix(glm::mat4{glm::mat3{m_renderpass.pass_camera.modelview()}});
}
void vulkan14_renderer::setup_shadow_matrices()
{
if (false == Global.gfx_shadowmap_enabled)
{
return;
}
for (std::size_t idx = 0; idx < m_shadowpass.size(); ++idx)
{
glm::mat4 depthproj = m_shadowpass[idx].pass_camera.projection();
// NOTE: we strip transformations from camera projections to remove jitter that occurs
// with large (and unneded as we only need the offset) transformations back and forth
auto const depthcam{glm::mat3{m_shadowpass[idx].pass_camera.modelview()}};
auto const worldcam{glm::mat3{m_renderpass.pass_camera.modelview()}};
scene_ubs.lightview[idx] = coordmove * depthproj * glm::translate(glm::mat4{depthcam}, glm::vec3{m_renderpass.pass_camera.position() - m_shadowpass[idx].pass_camera.position()}) * glm::mat4{glm::inverse(worldcam)};
}
}
// the camera of the pass, as opengl46_renderer::setup_pass() sets it up (without the vr and the cascades fitted to the depth)
void vulkan14_renderer::setup_pass(viewport_config &Viewport, renderpass_config &Config, rendermode const Mode, float const Znear, float const Zfar, bool const Ignoredebug)
{
Config.draw_mode = Mode;
if (false == simulation::is_ready)
{
return;
}
switch (Mode)
{
case rendermode::color:
Config.draw_range = Global.BaseDrawRange * Global.fDistanceFactor * Viewport.draw_range;
break;
case rendermode::shadows:
Config.draw_range = Global.shadowtune.range;
break;
case rendermode::reflections:
Config.draw_range = Global.BaseDrawRange * Viewport.draw_range;
break;
case rendermode::pickcontrols:
Config.draw_range = 50.f;
break;
case rendermode::pickscenery:
Config.draw_range = Global.BaseDrawRange * Viewport.draw_range * 0.5f;
break;
default:
Config.draw_range = 0.f;
break;
}
auto &camera = Config.pass_camera;
glm::dmat4 viewmatrix(1.0);
glm::mat4 frustumtest_proj;
Config.viewport_camera.position() = Global.pCamera.Pos;
auto const editor_ortho_projection = [](viewport_config const &Viewport, float const Zfar, glm::mat4 &Projection, glm::mat4 &Frustum) {
if (false == (EditorModeFlag && Global.EditorOrtho && Viewport.main))
{
return;
}
auto const height{Global.EditorOrthoExtent};
auto const width{height * Global.window_size.x / std::max(1.f, static_cast<float>(Global.window_size.y))};
Projection = ortho_projection(-width, width, -height, height, -Zfar, Zfar);
Frustum = ortho_frustumtest_projection(-width, width, -height, height, -Zfar, Zfar);
};
switch (Mode)
{
case rendermode::color:
{
if ((Viewport.proj_type == viewport_config::normal) && Viewport.main)
{
float const fovy = glm::radians(Global.FieldOfView / Global.ZoomFactor);
// setup virtual screen
glm::vec2 screen_h = glm::vec2(Global.window_size) / 2.0f;
float const dist = screen_h.y / glm::tan(fovy / 2.0f);
Viewport.projection.pa = glm::vec3(-screen_h.x, -screen_h.y, -dist);
Viewport.projection.pb = glm::vec3(screen_h.x, -screen_h.y, -dist);
Viewport.projection.pc = glm::vec3(-screen_h.x, screen_h.y, -dist);
Viewport.projection.pe = glm::vec3(0.0f, 0.0f, 0.0f);
}
if (Global.headtrack_conf.magic_window)
{
Viewport.projection.pe = Global.viewport_move;
}
TCamera *cam = ((false == DebugCameraFlag) || (true == Ignoredebug)) ? &Global.pCamera : &Global.pDebugCamera;
camera.position() = cam->Pos;
cam->SetMatrix(viewmatrix);
if (!Global.headtrack_conf.magic_window)
{
camera.position() += Global.viewport_move * glm::mat3(viewmatrix);
viewmatrix = glm::dmat4(glm::inverse(Global.viewport_rotate)) * viewmatrix;
}
// optimization: don't bother drawing things completely blended with the background
// but ensure at least 2 km range to account for signals and lights
Config.draw_range = std::min(Config.draw_range, m_fogrange * 2);
Config.draw_range = std::max(2000.f, Config.draw_range);
auto zfar = (Zfar > 1.f ? Zfar : Config.draw_range * Zfar);
auto const znear = (Znear > 1.f ? Znear : Znear > 0.f ? Znear * zfar : 0.1f * Global.ZoomFactor);
if (EditorModeFlag && Viewport.main)
{
zfar = editor_track_view_range(zfar, true);
}
Config.znear = znear;
Config.zfar = zfar;
camera.projection() = perspective_projection(Viewport.projection, znear, zfar, frustumtest_proj);
editor_ortho_projection(Viewport, zfar, camera.projection(), frustumtest_proj);
break;
}
case rendermode::shadows:
{
auto const zfar = (Zfar > 1.f ? Zfar : Zfar * Global.shadowtune.range * std::max(Zfar, Global.ZoomFactor * 0.5f));
std::size_t bounding_viewports_count = 0;
std::vector<glm::vec4> frustumchunkshapepoints;
glm::dvec3 pass_camera{0.0};
for (auto &conf : m_viewports)
{
if (!conf.shadow)
{
continue;
}
renderpass_config worldview;
setup_pass(conf, worldview, rendermode::color, Znear, zfar, true);
auto &points = worldview.pass_camera.frustum_points();
frustumchunkshapepoints.insert(frustumchunkshapepoints.end(), points.begin(), points.end());
pass_camera += worldview.pass_camera.position();
bounding_viewports_count++;
}
pass_camera /= std::max<std::size_t>(bounding_viewports_count, 1);
// ...a cascade fitted to the depth of the view (gfx.shadow.sdsm) covers the area of the pixels it takes, within the slice of the
// view...
auto quantizationstep{gfx::shadow_fit::fit_slice(frustumchunkshapepoints, m_shadowfitarea, gfx::shadow_fit::light_rotation(m_sunlight.direction), m_shadowslicesize)};
// ...modelview matrix: determine the centre of frustum chunk in world space...
glm::vec3 frustumchunkmin, frustumchunkmax;
bounding_box(frustumchunkmin, frustumchunkmax, std::begin(frustumchunkshapepoints), std::end(frustumchunkshapepoints));
auto const frustumchunkcentre = (frustumchunkmin + frustumchunkmax) * 0.5f;
// ...cap the vertical angle to keep shadows from getting too long...
auto const lightvector{gfx::shadow_fit::light_vector(m_sunlight.direction)};
// ...place the light source at the calculated centre and setup world space light view matrix...
camera.position() = pass_camera + glm::dvec3{frustumchunkcentre};
viewmatrix *= glm::lookAt(camera.position(), camera.position() + glm::dvec3{lightvector}, glm::dvec3{0.f, 1.f, 0.f});
// ...projection matrix: calculate boundaries of the frustum chunk in light space...
auto const lightviewmatrix = glm::translate(glm::mat4{glm::mat3{viewmatrix}}, -frustumchunkcentre);
for (auto &point : frustumchunkshapepoints)
{
point = lightviewmatrix * point;
}
bounding_box(frustumchunkmin, frustumchunkmax, std::begin(frustumchunkshapepoints), std::end(frustumchunkshapepoints));
// quantize the frustum points and add some padding, to reduce shadow shimmer on scale changes
auto const frustumchunkdepth{zfar - (Znear > 1.f ? Znear : 0.f)};
if (quantizationstep <= 0.f)
{
quantizationstep = std::min(frustumchunkdepth, 50.f);
}
frustumchunkmin = quantizationstep * glm::floor(frustumchunkmin * (1.f / quantizationstep));
frustumchunkmax = quantizationstep * glm::ceil(frustumchunkmax * (1.f / quantizationstep));
m_shadowdrawnsize = std::max(frustumchunkmax.x - frustumchunkmin.x, frustumchunkmax.y - frustumchunkmin.y);
// ...use the dimensions to set up light projection boundaries...
camera.projection() = ortho_projection(frustumchunkmin.x, frustumchunkmax.x, frustumchunkmin.y, frustumchunkmax.y, frustumchunkmin.z - 500.f, frustumchunkmax.z + 500.f);
frustumtest_proj = ortho_frustumtest_projection(frustumchunkmin.x, frustumchunkmax.x, frustumchunkmin.y, frustumchunkmax.y, frustumchunkmin.z - 500.f, frustumchunkmax.z + 500.f);
// ... and adjust the projection to sample complete shadow map texels
auto const shadowbuffersize{static_cast<float>(m_context->scene.shadow_size)};
auto shadowmaptexel = glm::vec2{camera.projection() * glm::mat4{viewmatrix} * glm::vec4{0.f, 0.f, 0.f, 1.f}};
shadowmaptexel *= shadowbuffersize * 0.5f;
auto shadowmapadjustment = glm::round(shadowmaptexel) - shadowmaptexel;
shadowmapadjustment /= shadowbuffersize * 0.5f;
camera.projection() = glm::translate(glm::mat4{1.f}, glm::vec3{shadowmapadjustment, 0.f}) * camera.projection();
break;
}
case rendermode::pickcontrols:
case rendermode::pickscenery:
{
camera.position() = Global.pCamera.Pos;
Global.pCamera.SetMatrix(viewmatrix);
viewport_proj_config proj = Viewport.projection;
float znear = 0.1f * Global.ZoomFactor;
float zfar = Config.draw_range * Global.fDistanceFactor;
if (EditorModeFlag && Viewport.main && Mode == rendermode::pickscenery)
{
zfar = editor_track_view_range(zfar, true);
}
camera.projection() = perspective_projection(proj, znear, zfar, frustumtest_proj);
editor_ortho_projection(Viewport, zfar, camera.projection(), frustumtest_proj);
break;
}
case rendermode::reflections:
{
// the faces of the map are drawn over a few frames. each is drawn from the place the camera was at
// when the update began, so they fit together along the edges when the camera moves in the meantime
camera.position() = m_environmentupdatelocation;
glm::dvec3 const cubefacetargetvectors[6] = {{1.0, 0.0, 0.0}, {-1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, -1.0, 0.0}, {0.0, 0.0, 1.0}, {0.0, 0.0, -1.0}};
glm::dvec3 const cubefaceupvectors[6] = {{0.0, -1.0, 0.0}, {0.0, -1.0, 0.0}, {0.0, 0.0, 1.0}, {0.0, 0.0, -1.0}, {0.0, -1.0, 0.0}, {0.0, -1.0, 0.0}};
auto const cubefaceindex = m_environmentcubetextureface;
viewmatrix *= glm::lookAt(camera.position(), camera.position() + cubefacetargetvectors[cubefaceindex], cubefaceupvectors[cubefaceindex]);
float znear = 0.1f * Global.ZoomFactor;
float zfar = Config.draw_range * Global.fDistanceFactor;
viewport_proj_config proj({{-0.5f, -0.5f, -0.5f}, {0.5f, -0.5f, -0.5f}, {-0.5f, 0.5f, -0.5f}, {0.0f, 0.0f, 0.0f}});
camera.projection() = perspective_projection(proj, znear, zfar, frustumtest_proj);
break;
}
default:
break;
}
camera.modelview() = viewmatrix;
camera.update_frustum(frustumtest_proj);
}
// the colour pass of a viewport (opengl46_renderer::Render_pass(), rendermode::color)
void vulkan14_renderer::render_viewport(std::size_t const Index)
{
auto &ctx{*m_context};
auto &scene{ctx.scene};
auto &vp{m_viewports[Index]};
auto &targets{scene.targets[Index]};
auto const commands{ctx.commands};
m_current_viewport = &vp;
setup_pass(vp, m_renderpass, rendermode::color);
// the temporal anti-aliasing: the projection of the frame moved by a part of a pixel, the velocities pointed to the last frame
auto const taa{vp.main && targets.taa && Global.gfx_taa()};
m_motionhistory = taa;
m_cabfuture = glm::mat4{1.f};
if (taa)
{
auto const *cab{((false == FreeFlyModeFlag) && Global.render_cab && (simulation::Train != nullptr)) ? simulation::Train->Dynamic() : nullptr};
m_renderpass.pass_camera.projection() = m_taa.begin_frame(
m_renderpass.pass_camera.projection(), glm::dmat3{m_renderpass.pass_camera.modelview()}, m_renderpass.pass_camera.position(), cab, glm::ivec2{vp.width, vp.height});
m_cabfuture = m_taa.cab_future();
}
m_colorpass = m_renderpass;
scene_ubs.time = Timer::GetTime();
{
float percipitation_intensity = glm::saturate(Global.Overcast - 1.);
scene_ubs.rain_params.x = percipitation_intensity;
scene_ubs.rain_params.y = glm::mix(15., 1., percipitation_intensity);
if (TDynamicObject const *owner = Global.pCamera.m_owner; owner && !!owner->MoverParameters->CabActive)
{
for (int i = 0; i < 4; ++i)
{
if (i < owner->dWiperPos.size())
{
int index = owner->MoverParameters->CabActive > 0 ? i : static_cast<int>(owner->dWiperPos.size() - 1) - i;
scene_ubs.wiper_pos[i] = owner->dWiperPos[index];
if (owner->dWiperPos[index] > 0. && owner->wiperDirection[index])
{
scene_ubs.wiper_pos[i] += 1.;
}
if (owner->dWiperPos[index] < .025)
{
scene_ubs.wiper_timer_out[i] = scene_ubs.time;
}
if (owner->dWiperPos[index] > .975)
{
scene_ubs.wiper_timer_return[i] = scene_ubs.time;
}
}
else
{
scene_ubs.wiper_pos[i] = 0.;
scene_ubs.wiper_timer_out[i] = -1000.;
scene_ubs.wiper_timer_return[i] = -1000.;
}
}
}
else
{
scene_ubs.wiper_pos = glm::vec4{0.};
scene_ubs.wiper_timer_out = glm::vec4{-1000.};
scene_ubs.wiper_timer_return = glm::vec4{-1000.};
}
}
// the stages after D: no contact shadows, no cascades fitted to the depth, no shadow maps of headlights
scene_ubs.contact_shadow_params = glm::vec4{0.f};
scene_ubs.contact_shadow_lamps = glm::vec4{0.f};
scene_ubs.shadow_fit = glm::vec4{0.f};
if (vp.main)
{
// the shadow maps of the lamps made since the last frame, before the passes read them
ready_lamp_maps();
}
if (Global.gfx_shadowmap_enabled && vp.main && scene.shadow_map)
{
Timer::subsystem.gfx_shadows.start();
m_renderpass.draw_stats = {};
render_shadows(vp);
m_renderpass = m_colorpass;
Timer::subsystem.gfx_shadows.stop();
}
if (Global.gfx_envmap_enabled && vp.main && scene.env_map)
{
m_renderpass.draw_stats = {};
Timer::subsystem.gfx_reflections.start();
if (render_reflections(vp))
{
m_renderpass = m_colorpass;
}
// the prefiltered map is made with every update of the environment map, or once the switch is turned on
if (Global.gfx_envmap_ggx && m_environmentready && (false == scene.sky.ggx_ready))
{
prefilter_environment();
}
Timer::subsystem.gfx_reflections.stop();
}
// rough reflections read the prefiltered map with the environment map of the main view; the other views keep to the mipmaps
m_envmap_bound = (vp.main && Global.gfx_envmap_enabled && scene.env_map && scene.sky.ggx_ready);
if (vp.main)
{
// the shadow maps of the cab lamp, the scenery lamps and the headlights
render_local_shadows();
render_lamp_shadows();
render_headlight_shadows();
ready_lamp_maps();
}
// the motion blur only when something in the view moves (see opengl46_renderer::Render_pass())
auto const motionblur{Global.gfx_postfx_motionblur_enabled && targets.motion &&
(m_vehiclesmoved || ((Global.pCamera.m_owner != nullptr) && (Global.pCamera.m_owner->get_future_movement() != glm::dvec3{0.0})))};
setup_matrices();
m_renderpass.draw_stats = {};
model_ubs.future = glm::mat4();
glm::mat4 future;
if (taa)
{
future = m_taa.world_future();
}
else if (Global.pCamera.m_owner != nullptr)
{
auto const *vehicle = Global.pCamera.m_owner;
glm::mat4 mv = OpenGLMatrices.data(GL_MODELVIEW);
future = glm::translate(mv, -glm::vec3(vehicle->get_future_movement())) * glm::inverse(mv);
}
// the light clusters of the viewport (the buffers of the grid are taken by each viewport in turn), and the lights of the pass
auto const clusters{Global.gfx_lights_clustered || Global.gfx_lights_clusterheatmap};
ctx.passes.begin_pass("clear, clusters, lights");
if (clusters)
{
update_clusters(glm::ivec2{vp.width, vp.height});
}
auto const clustered{clusters && Global.gfx_lights_clustered && (scene.clusters.params.valid != 0)};
update_lights(simulation::Lights, true, clustered);
ctx.passes.end_pass();
// the clouds of the view and the fog volume, which take the lights of the clusters and the shadow map of the pass
{
auto const fogtarget{glm::ivec2{vp.width, vp.height}};
scene_ubs.projection = OpenGLMatrices.data(GL_PROJECTION);
scene_ubs.inv_view = glm::inverse(glm::mat4{glm::mat3{m_colorpass.pass_camera.modelview()}});
setup_shadow_matrices();
upload_scene_block();
if (clustered)
{
m_draw->cluster_offset = static_cast<std::uint32_t>(scene.clusters.params_offset);
}
if (vp.main)
{
render_weather_view(OpenGLMatrices.data(GL_PROJECTION), glm::mat4{m_colorpass.pass_camera.modelview()}, m_colorpass.pass_camera.position(), fogtarget, (m_cablamplit && clustered) ? 1 : -1);
}
m_weatherstate.setup_pass(vp.main, fogtarget);
upload_weather_block();
}
// the colour of the scene: drawn to the images with the samples, resolved to the single ones
auto const uses{colour_target_uses(targets)};
ctx.passes.begin_pass("sky", uses);
begin_colour_rendering(Index, true);
// as in the opengl renderer, the shadow map and the environment map are those of the main view only
start_draws(targets.formats, static_cast<int>(targets.extent.width), static_cast<int>(targets.extent.height), vp.main ? vk14::scene_state::set_colour : vk14::scene_state::set_plain, false);
upload_light_header();
if (clustered)
{
m_draw->cluster_offset = static_cast<std::uint32_t>(scene.clusters.params_offset);
}
// the sky
walk_blending(true);
scene_ubs.projection = OpenGLMatrices.data(GL_PROJECTION);
scene_ubs.inv_view = glm::inverse(glm::mat4{glm::mat3{m_colorpass.pass_camera.modelview()}});
upload_scene_block();
render_environment(&simulation::Environment);
setup_shadow_matrices();
upload_scene_block();
// the depth of the whole opaque scene before its colour, which the contact shadows need
m_indirectcolourpending = false;
auto const contactshadows{contact_shadows_wanted(vp)};
if ((Global.gfx_depthprepass_scene || contactshadows) && vp.main && (false == Global.bWireFrame))
{
render_scene_depth(Index, future);
if (contactshadows)
{
setup_contact_shadows(Index);
}
}
// opaque parts...
ctx.passes.begin_pass("colour, opaque");
walk_blending(false);
// without rain/snow we can render the cab early to limit the overdraw
if (!FreeFlyModeFlag && Global.Overcast <= 1.0f && Global.render_cab && simulation::Train)
{
model_ubs.future = m_cabfuture;
auto const *vehicle{simulation::Train->Dynamic()};
// the lamp lighting the shadows of the sun, unless it is a light of its own
if ((vehicle->InteriorLightLevel > 0.f) && (false == m_cablamplit))
{
setup_shadow_color(glm::min(colors::white, m_shadowcolor + glm::vec4(vehicle->InteriorLight * vehicle->InteriorLightLevel, 1.f)));
}
Render_cab(vehicle, vehicle->InteriorLightLevel, false);
}
setup_shadow_color(m_shadowcolor);
model_ubs.future = future;
Render(simulation::Region);
m_indirectcolourpending = false;
// the grass of the terrain, opaque, over the ground drawn
if (vp.main && (false == Global.bWireFrame))
{
ctx.passes.begin_pass("grass");
render_grass(future);
}
// ...translucent parts
ctx.passes.begin_pass("colour, translucent and cab");
walk_blending(true);
Render_Alpha(simulation::Region);
render_editor_overlay();
// the depth drawn so far, which the particles of the gpu fade in front of
m_particlesoft = (Global.gfx_particles_soft > 0.f) && ((Global.Smoke && Global.gfx_smoke_gpu && (false == simulation::Particles.sequence().empty())) || (Global.gfx_precipitation_gpu && (Global.Overcast > 1.f))) &&
setup_particles_depth(Index);
// particles
render_particles();
// precipitation; done at end, only before cab render
render_precipitation();
// cab render
if ((false == FreeFlyModeFlag) && Global.render_cab && simulation::Train)
{
model_ubs.future = m_cabfuture;
auto *vehicle{simulation::Train->Dynamic()};
if ((vehicle->InteriorLightLevel > 0.f) && (false == m_cablamplit))
{
setup_shadow_color(glm::min(colors::white, m_shadowcolor + glm::vec4(vehicle->InteriorLight * vehicle->InteriorLightLevel, 1.f)));
}
if (Global.Overcast > 1.0f)
{
// with active precipitation draw the opaque cab parts here to mask rain/snow placed 'inside' the cab
walk_blending(false);
Render_cab(vehicle, vehicle->InteriorLightLevel, false);
Render_interior(false);
walk_blending(true);
Render_interior(true);
}
Render_cab(vehicle, vehicle->InteriorLightLevel, true);
setup_shadow_color(m_shadowcolor);
if (Global.Overcast > 1.0f)
{
// with the cab in place we can (finally) safely draw translucent part of the occupied vehicle
if (taa)
{
// the vehicle adds its own motion to the view of the world (the cab future has it already)
model_ubs.future = m_taa.world_future();
}
Render_Alpha(vehicle);
}
}
commands->end_rendering();
end_draws();
if (m_contactshadows)
{
// the depth they read is of this view only
m_contactshadows = false;
scene_ubs.contact_shadow_params = glm::vec4{0.f};
scene_ubs.contact_shadow_lamps = glm::vec4{0.f};
}
// the images resolved and drawn are read by the post-processing
m_colorpass.draw_stats = m_renderpass.draw_stats;
// the depth of the finished view, which the cascades of the sun shadows of the next frames are fitted to (gfx.shadow.sdsm)
measure_shadow_fit(Index);
render_post(Index, motionblur);
}
// the depth of the whole opaque scene of the colour pass, before its colour (opengl46_renderer::Render_Scene_Depth() without the
// record of the walk, gfx.depthprepass.replay no): the same walk as the colour pass makes (the cab drawn early, then the region)
// with the programs of the depth prepass, the depth a little farther than the surface, so the colour pass which tests and writes
// its depth as usual passes where it's the same surface. the rendering of the colour is ended for it and begun again after it;
// the depth is resolved or copied for the contact shadows
void vulkan14_renderer::render_scene_depth(std::size_t const Index, glm::mat4 const &Future)
{
auto &ctx{*m_context};
auto &scene{ctx.scene};
auto &targets{scene.targets[Index]};
auto &draw{*m_draw};
auto const commands{ctx.commands};
auto const msaa{targets.samples != VK_SAMPLE_COUNT_1_BIT};
auto const contact{contact_shadows_wanted(m_viewports[Index])};
commands->end_rendering();
// what the draws go on with
auto const set{draw.set};
auto const offsets{std::array<std::uint32_t, 4>{draw.scene_offset, draw.light_offset, draw.cluster_offset, draw.weather_offset}};
end_draws();
if (contact && ((false == bool(targets.scene_depth)) || (targets.scene_depth->desc().extent.width != targets.extent.width) || (targets.scene_depth->desc().extent.height != targets.extent.height)))
{
// the depth is resolved apart from the one the colour pass draws to, which it tests and writes
vk14::image_desc desc;
desc.format = VK_FORMAT_D32_SFLOAT;
desc.extent = {targets.extent.width, targets.extent.height, 1};
desc.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
if (targets.scene_depth)
{
ctx.deletions.push(ctx.uploads.current_value(), [old = std::move(targets.scene_depth)]() {});
}
targets.scene_depth = std::make_shared<vk14::image>();
if (false == targets.scene_depth->create(ctx.memory, desc, "scene depth"))
{
targets.scene_depth.reset();
}
}
auto &depthimage{msaa ? *targets.msaa_depth : *targets.depth};
std::vector<vk14::image_use> uses{vk14::use_of(depthimage, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, depth_stage, depth_access)};
// the first sample resolved to the depth of one sample, as the colour pass resolves it: the contact shadows copy it, the depth
// pyramid of the gpu culling is made from it
if (msaa)
{
uses.push_back(vk14::use_of(*targets.depth, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, resolve_depth_stage, resolve_depth_access));
}
ctx.passes.begin_pass("scene depth", uses);
VkClearValue clear{};
auto depth{attachment_info(depthimage.view(), VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, false, clear)};
if (msaa)
{
depth.resolveMode = VK_RESOLVE_MODE_SAMPLE_ZERO_BIT;
depth.resolveImageView = targets.depth->view();
depth.resolveImageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL;
}
VkRenderingInfo rendering{};
rendering.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
rendering.renderArea = {{0, 0}, targets.extent};
rendering.layerCount = 1;
rendering.pDepthAttachment = &depth;
commands->begin_rendering(&rendering);
remember_rendering(rendering, uses);
start_draws(targets.depth_formats, static_cast<int>(targets.extent.width), static_cast<int>(targets.extent.height), set, false);
draw.scene_offset = offsets[0];
draw.light_offset = offsets[1];
draw.cluster_offset = offsets[2];
draw.weather_offset = offsets[3];
draw.depthpass = true;
draw.depthskip = true;
commands->set_depth_bias_enable(VK_TRUE);
commands->set_depth_bias(-1.f, 0.f, -1.f);
auto const colourstats{m_renderpass.draw_stats};
m_renderpass.draw_stats = {};
walk_blending(false);
// the conditions of the cab drawn early by the colour pass
if ((false == FreeFlyModeFlag) && (Global.Overcast <= 1.0f) && Global.render_cab && simulation::Train)
{
model_ubs.future = m_cabfuture;
auto const *vehicle{simulation::Train->Dynamic()};
Render_cab(vehicle, vehicle->InteriorLightLevel, false);
}
model_ubs.future = Future;
Render(simulation::Region);
m_renderpass.draw_stats = colourstats;
commands->set_depth_bias_enable(VK_FALSE);
draw.depthpass = false;
draw.depthskip = true;
commands->end_rendering();
end_draws();
if (contact && targets.scene_depth)
{
// the depth of one sample (resolved with more of them) copied apart from the one the colour pass goes on with
vk14::barriers(commands, {vk14::use_of(*targets.depth, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_2_COPY_BIT, VK_ACCESS_2_TRANSFER_READ_BIT),
vk14::use_of(*targets.scene_depth, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_2_COPY_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, 0, 1};
region.dstSubresource = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 0, 1};
region.extent = {targets.extent.width, targets.extent.height, 1};
VkCopyImageInfo2 copy{};
copy.sType = VK_STRUCTURE_TYPE_COPY_IMAGE_INFO_2;
copy.srcImage = targets.depth->handle();
copy.srcImageLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
copy.dstImage = targets.scene_depth->handle();
copy.dstImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
copy.regionCount = 1;
copy.pRegions = &region;
commands->copy_image2(&copy);
vk14::barriers(commands, {vk14::use_of(*targets.scene_depth, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access)});
}
// the colour pass goes on where the sky left it
vk14::barriers(commands, colour_target_uses(targets));
begin_colour_rendering(Index, false);
start_draws(targets.formats, static_cast<int>(targets.extent.width), static_cast<int>(targets.extent.height), set, false);
draw.scene_offset = offsets[0];
draw.light_offset = offsets[1];
draw.cluster_offset = offsets[2];
draw.weather_offset = offsets[3];
}
// the contact shadows read the depth of the scene drawn before the colour, which the main view has, and work out the distance
// of a surface from its depth the way a perspective projection with the far plane at infinity gives it (depth = near / w): not
// in the orthographic view of the editor (opengl46_renderer::Contact_Shadows_Wanted())
bool vulkan14_renderer::contact_shadows_wanted(viewport_config const &Viewport) const
{
if ((Global.gfx_contactshadows <= 0) || (false == Viewport.main) || Global.bWireFrame || Global.gfx_skippipeline)
{
return false;
}
auto const &projection{scene_ubs.projection};
return (projection[2][3] != 0.f) && (projection[2][2] == 0.f) && (projection[3][2] > 0.f);
}
void vulkan14_renderer::setup_contact_shadows(std::size_t const Index)
{
auto &targets{m_context->scene.targets[Index]};
if (false == bool(targets.scene_depth))
{
return;
}
// steps of the ray towards the sun, of the rays towards lamps and headlights, and how many of those a pixel traces
struct contact_level
{
float sunsteps;
float lampsteps;
float lamps;
};
static contact_level const levels[]{{8.f, 0.f, 0.f}, {12.f, 8.f, 2.f}, {16.f, 12.f, 4.f}};
auto const &level{levels[std::clamp(Global.gfx_contactshadows, 1, 3) - 1]};
scene_ubs.contact_shadow_params = glm::vec4{Global.gfx_contactshadows_length, Global.gfx_contactshadows_range, level.sunsteps, level.lampsteps};
scene_ubs.contact_shadow_lamps = glm::vec4{level.lamps, 0.f, 0.f, 0.f};
upload_scene_block();
m_contactshadows = true;
}
// the rendering of the colour of a viewport: begun with the images cleared, or again after the depth under the cursor was read
void vulkan14_renderer::begin_colour_rendering(std::size_t const Index, bool const Clear)
{
auto &ctx{*m_context};
auto &targets{ctx.scene.targets[Index]};
auto const msaa{targets.samples != VK_SAMPLE_COUNT_1_BIT};
VkClearValue colourclear{};
colourclear.color = {{0.f, 0.f, 0.f, 0.f}};
VkClearValue depthclear{};
depthclear.depthStencil = {0.f, 0};
std::array<VkRenderingAttachmentInfo, 2> colours{};
colours[0] = attachment_info((msaa ? targets.msaa_colour : targets.colour)->view(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, Clear, colourclear);
if (msaa)
{
colours[0].resolveMode = VK_RESOLVE_MODE_AVERAGE_BIT;
colours[0].resolveImageView = targets.colour->view();
colours[0].resolveImageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
}
if (targets.motion)
{
colours[1] = attachment_info((msaa ? targets.msaa_motion : targets.motion_image)->view(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, Clear, colourclear);
if (msaa)
{
colours[1].resolveMode = VK_RESOLVE_MODE_AVERAGE_BIT;
colours[1].resolveImageView = targets.motion_image->view();
colours[1].resolveImageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
}
}
auto depth{attachment_info((msaa ? targets.msaa_depth : targets.depth)->view(), VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, Clear, depthclear)};
if (msaa)
{
// the depth of the first sample, for the motion blur and the cursor of the editor
depth.resolveMode = VK_RESOLVE_MODE_SAMPLE_ZERO_BIT;
depth.resolveImageView = targets.depth->view();
depth.resolveImageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL;
}
VkRenderingInfo rendering{};
rendering.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
rendering.renderArea = {{0, 0}, targets.extent};
rendering.layerCount = 1;
rendering.colorAttachmentCount = targets.formats.colours;
rendering.pColorAttachments = colours.data();
rendering.pDepthAttachment = &depth;
ctx.commands->begin_rendering(&rendering);
remember_rendering(rendering, colour_target_uses(targets));
}
// the depth of the ground under the cursor of the editor: the rendering is ended (which resolves the depth), the depth under the
// cursor copied, and the rendering begun again where it was
void vulkan14_renderer::walk_editor_cursor()
{
auto &ctx{*m_context};
auto &scene{ctx.scene};
auto &draw{*m_draw};
if ((m_current_viewport == nullptr) || (false == m_current_viewport->main) || (false == draw.rendering) || scene.cursor.pending || scene.targets.empty())
{
return;
}
auto &targets{scene.targets.front()};
auto const commands{ctx.commands};
glm::vec2 mousepos = Global.cursor_pos;
// cursor coordinates are flipped compared to opengl, whose rows the images keep
mousepos.y = Global.window_size.y - mousepos.y;
auto bufferpos{glm::ivec2(mousepos * glm::vec2(targets.extent.width, targets.extent.height) / glm::max(glm::vec2(Global.window_size), glm::vec2(1.f)))};
bufferpos = glm::clamp(bufferpos, glm::ivec2(0, 0), glm::ivec2(targets.extent.width - 1, targets.extent.height - 1));
commands->end_rendering();
auto readback{std::make_shared<vk14::buffer>()};
if (readback->create(ctx.memory, sizeof(float), VK_BUFFER_USAGE_TRANSFER_DST_BIT, vk14::memory_kind::readback, "cursor depth"))
{
vk14::barriers(commands, {vk14::use_of(*targets.depth, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_2_COPY_BIT, VK_ACCESS_2_TRANSFER_READ_BIT)});
VkBufferImageCopy2 region{};
region.sType = VK_STRUCTURE_TYPE_BUFFER_IMAGE_COPY_2;
region.imageSubresource = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 0, 1};
region.imageOffset = {bufferpos.x, bufferpos.y, 0};
region.imageExtent = {1, 1, 1};
VkCopyImageToBufferInfo2 copy{};
copy.sType = VK_STRUCTURE_TYPE_COPY_IMAGE_TO_BUFFER_INFO_2;
copy.srcImage = targets.depth->handle();
copy.srcImageLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
copy.dstBuffer = readback->handle();
copy.regionCount = 1;
copy.pRegions = &region;
commands->copy_image_to_buffer2(&copy);
host_read_barrier(commands);
scene.cursor.data = readback;
scene.cursor.value = ctx.timelinevalue + 1;
scene.cursor.pending = true;
scene.cursor.position = bufferpos;
vk14::barriers(commands, {vk14::use_of(*targets.depth, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, resolve_depth_stage, resolve_depth_access)});
}
// what the rendering stored before is loaded again
vk14::barriers(commands, colour_target_uses(targets));
begin_colour_rendering(0, false);
// the dynamic state starts anew with the rendering
auto const set{draw.set};
auto const scene_offset{draw.scene_offset};
auto const light_offset{draw.light_offset};
auto const cluster_offset{draw.cluster_offset};
auto const blending{draw.blending};
auto *program{draw.program};
auto const textures{draw.textures};
start_draws(targets.formats, static_cast<int>(targets.extent.width), static_cast<int>(targets.extent.height), set, false);
draw.scene_offset = scene_offset;
draw.light_offset = light_offset;
draw.cluster_offset = cluster_offset;
draw.blending = blending;
draw.program = program;
draw.textures = textures;
}
// the cascades of the sun shadows
void vulkan14_renderer::render_shadows(viewport_config &Viewport)
{
auto &ctx{*m_context};
auto &scene{ctx.scene};
auto const commands{ctx.commands};
setup_pass(Viewport, m_renderpass, rendermode::shadows);
auto csmstageboundaries{gfx::shadow_fit::fixed_splits()};
// the cascades fitted to the depth of the view (gfx.shadow.sdsm) go between the distances of its pixels, each map covering the
// area of the pixels its cascade takes
gfx::shadow_fit::areas fitareas;
auto const fitted{shadow_fit(Viewport, csmstageboundaries, fitareas)};
scene_ubs.shadow_fit = glm::vec4{fitted ? 1.f : 0.f, 0.f, 0.f, 0.f};
m_shadowfitstate.splits = csmstageboundaries;
static char const *const parts[] = {"sun shadows 1", "sun shadows 2", "sun shadows 3"};
for (std::size_t idx = 0; idx < m_shadowpass.size(); ++idx)
{
ctx.passes.begin_pass(parts[idx], {vk14::use_of(*scene.shadow_map, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, depth_stage, depth_access)});
m_shadowfitarea = (fitareas[idx] ? &(*fitareas[idx]) : nullptr);
setup_pass(Viewport, m_renderpass, rendermode::shadows, csmstageboundaries[idx], csmstageboundaries[idx + 1]);
m_shadowfitarea = nullptr;
m_shadowpass[idx] = m_renderpass;
m_shadowfitstate.slice[idx] = m_shadowslicesize;
m_shadowfitstate.drawn[idx] = m_shadowdrawnsize;
setup_matrices();
scene_ubs.projection = OpenGLMatrices.data(GL_PROJECTION);
auto const csmstagezfar{(csmstageboundaries[idx + 1]) + (m_shadowpass.size() - idx)};
scene_ubs.cascade_end[idx] = csmstagezfar * csmstagezfar;
VkClearValue clear{};
clear.depthStencil = {0.f, 0};
auto depth{attachment_info(scene.shadow_layers[idx], VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, true, clear)};
VkRenderingInfo rendering{};
rendering.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
rendering.renderArea = {{0, 0}, {scene.shadow_size, scene.shadow_size}};
rendering.layerCount = 1;
rendering.pDepthAttachment = &depth;
commands->begin_rendering(&rendering);
remember_rendering(rendering, {vk14::use_of(*scene.shadow_map, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, depth_stage, depth_access)});
start_draws(scene.shadow_formats, static_cast<int>(scene.shadow_size), static_cast<int>(scene.shadow_size), vk14::scene_state::set_plain, true);
m_shadowcascade = static_cast<int>(idx);
walk_blending(false);
upload_scene_block();
if (m_shadowcolor != colors::white)
{
Render(simulation::Region);
// render cab only in the closest csm stage
if ((idx == 0) && !FreeFlyModeFlag && Global.render_cab && simulation::Train)
{
Render_cab(simulation::Train->Dynamic(), 0.0f, false);
Render_cab(simulation::Train->Dynamic(), 0.0f, true);
}
}
m_shadowcascade = -1;
commands->end_rendering();
end_draws();
ctx.passes.end_pass();
}
vk14::barriers(commands, {vk14::use_of(*scene.shadow_map, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access)});
}
// the environment map, a face of it per frame (opengl46_renderer::Render_reflections())
bool vulkan14_renderer::render_reflections(viewport_config &Viewport)
{
auto &ctx{*m_context};
auto &scene{ctx.scene};
if (Global.reflectiontune.update_interval == 0)
{
return false;
}
if (m_environmentupdateface >= 6)
{
// no update in progress, check whether it's time for another one
auto const timestamp{Timer::GetRenderTime()};
if ((timestamp - m_environmentupdatetime < Global.reflectiontune.update_interval) && (glm::length2(m_renderpass.pass_camera.position() - m_environmentupdatelocation) < sq(1000.0)))
{
return false;
}
m_environmentupdatetime = timestamp;
m_environmentupdatelocation = m_renderpass.pass_camera.position();
m_environmentupdateface = 0;
}
// the first update is done in one go, as there's nothing to show in the meantime
auto const lastface{m_environmentready ? m_environmentupdateface : 5};
ctx.passes.begin_pass("reflections", {vk14::use_of(*scene.env_map, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, colour_stage, colour_access),
vk14::use_of(*scene.env_depth, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, depth_stage, depth_access)});
for (; m_environmentupdateface <= lastface; ++m_environmentupdateface)
{
// the depth of the faces is the same image: the clear of a face waits for the draws of the one before
vk14::barriers(ctx.commands, {vk14::use_of(*scene.env_depth, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, depth_stage, depth_access)});
m_environmentcubetextureface = m_environmentupdateface;
render_reflection_face(Viewport);
}
if (m_environmentupdateface >= 6)
{
// the mipmaps, each level from the one before it
auto const commands{ctx.commands};
auto &image{*scene.env_map};
auto const mips{image.desc().mips};
auto const size{static_cast<std::int32_t>(image.desc().extent.width)};
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 = image.handle();
entry.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, Level, Count, 0, 6};
VkDependencyInfo dependency{};
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
dependency.imageMemoryBarrierCount = 1;
dependency.pImageMemoryBarriers = &entry;
commands->pipeline_barrier2(&dependency);
};
// the first level was drawn, the others are undefined
barrier(0, 1, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, colour_stage, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_READ_BIT);
if (mips > 1)
{
barrier(1, mips - 1, VK_IMAGE_LAYOUT_UNDEFINED, 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);
}
for (std::uint32_t level = 1; level < mips; ++level)
{
VkImageBlit2 blit{};
blit.sType = VK_STRUCTURE_TYPE_IMAGE_BLIT_2;
blit.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, level - 1, 0, 6};
blit.srcOffsets[1] = {std::max(size >> (level - 1), 1), std::max(size >> (level - 1), 1), 1};
blit.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, level, 0, 6};
blit.dstOffsets[1] = {std::max(size >> level, 1), std::max(size >> level, 1), 1};
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, 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);
}
barrier(0, mips, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_READ_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT, sampled_stage, sampled_access);
image.state = vk14::image_state{VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access};
m_environmentready = true;
ctx.passes.end_pass();
if (Global.gfx_envmap_ggx)
{
prefilter_environment();
}
return true;
}
else
{
vk14::barriers(ctx.commands, {vk14::use_of(*scene.env_map, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access)});
}
return true;
}
void vulkan14_renderer::render_reflection_face(viewport_config &Viewport)
{
auto &ctx{*m_context};
auto &scene{ctx.scene};
auto const commands{ctx.commands};
auto const size{scene.env_map->desc().extent.width};
setup_pass(Viewport, m_renderpass, rendermode::reflections);
VkClearValue colourclear{};
colourclear.color = {{0.f, 0.f, 0.f, 1.f}};
VkClearValue depthclear{};
depthclear.depthStencil = {0.f, 0};
auto colour{attachment_info(scene.env_faces[m_environmentcubetextureface], VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, true, colourclear)};
auto depth{attachment_info(scene.env_depth->view(), VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, true, depthclear)};
VkRenderingInfo rendering{};
rendering.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
rendering.renderArea = {{0, 0}, {size, size}};
rendering.layerCount = 1;
rendering.colorAttachmentCount = 1;
rendering.pColorAttachments = &colour;
rendering.pDepthAttachment = &depth;
commands->begin_rendering(&rendering);
remember_rendering(rendering, {vk14::use_of(*scene.env_map, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, colour_stage, colour_access),
vk14::use_of(*scene.env_depth, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, depth_stage, depth_access)});
start_draws(scene.reflection_formats, static_cast<int>(size), static_cast<int>(size), vk14::scene_state::set_reflections, false);
setup_matrices();
// the fog volume is the one of the main view
m_weatherstate.setup_pass(false, glm::ivec2{static_cast<int>(gl::ENVMAP_SIZE)});
upload_weather_block();
// the light buffer holds what the last color pass left in it; the lights go in anew, without the headlights
auto const envmapbound{m_envmap_bound};
m_envmap_bound = false;
update_lights(simulation::Lights, false);
m_envmap_bound = envmapbound;
upload_light_header();
// environment...
walk_blending(true);
scene_ubs.projection = OpenGLMatrices.data(GL_PROJECTION);
scene_ubs.inv_view = glm::inverse(glm::mat4{glm::mat3{m_renderpass.pass_camera.modelview()}});
upload_scene_block();
render_environment(&simulation::Environment);
// opaque parts...
walk_blending(false);
setup_shadow_matrices();
upload_scene_block();
Render(simulation::Region);
commands->end_rendering();
end_draws();
}
// the sky dome (with the atmosphere), the stars, the sun and the moon, the clouds and the fog over the sky
// (opengl46_renderer::Render(world_environment *))
void vulkan14_renderer::render_environment(world_environment *Environment)
{
auto &ctx{*m_context};
auto &scene{ctx.scene};
auto &draw{*m_draw};
m_shadowcolor = colors::white; // prevent shadow from affecting sky
setup_shadow_color(m_shadowcolor);
if (Global.bWireFrame)
{
return;
}
walk_material(null_handle);
::glPushMatrix();
auto const fogdensity{model_ubs.fog_density};
model_ubs.fog_density = 0.f;
// skydome, drawn with 500m radius to blend in if the fog range is low
auto &skydome{simulation::Environment.skydome()};
skydome.is_dirty() = false;
if (false == skydome.indices().empty())
{
::glPushMatrix();
::glScalef(500.0f, 500.0f, 500.0f);
model_ubs.set_modelview(OpenGLMatrices.data(GL_MODELVIEW));
auto const atmosphere{m_skylight.atmosphere_weight() > 0.f};
if (atmosphere)
{
m_skylight.sky_params(model_ubs.param[0], model_ubs.param[1]);
}
auto &domeprogram{atmosphere ? *scene.sky.atmosphere_dome : *scene.skydome};
auto const &vertices{skydome.vertices()};
auto const &colours{skydome.colors()};
auto const &indices{skydome.indices()};
auto const vertexslice{ctx.ring.allocate(vertices.size() * sizeof(glm::vec3), 16)};
auto const colourslice{ctx.ring.allocate(colours.size() * sizeof(glm::vec3), 16)};
auto const indexslice{ctx.ring.allocate(indices.size() * sizeof(std::uint16_t), 16)};
if (vertexslice && colourslice && indexslice && bind_scene_pipeline(domeprogram, *draw.targets, draw.blending ? vk14::blend_mode::scene_alpha : vk14::blend_mode::none, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST))
{
std::memcpy(vertexslice.data, vertices.data(), vertexslice.size);
std::memcpy(colourslice.data, colours.data(), colourslice.size);
std::memcpy(indexslice.data, indices.data(), indexslice.size);
apply_draw_state();
draw.block = push_draw_block();
if (draw.block != ~0u)
{
if (draw.set_dirty)
{
bind_pass_set();
}
if (draw.textures_dirty)
{
push_textures();
}
vk14::draw_constants constants;
constants.draw = draw.block;
push_constants(&constants);
VkBuffer const buffers[2]{vertexslice.buffer, colourslice.buffer};
VkDeviceSize const offsets[2]{vertexslice.offset, colourslice.offset};
draw.commands->bind_vertex_buffers(0, 2, buffers, offsets);
draw.commands->bind_index_buffer(indexslice.buffer, indexslice.offset, VK_INDEX_TYPE_UINT16);
draw.commands->draw_indexed(static_cast<std::uint32_t>(indices.size()), 1, 0, 0, 0);
draw.vertices = VK_NULL_HANDLE;
draw.userdata = VK_NULL_HANDLE;
draw.indices = VK_NULL_HANDLE;
}
}
::glPopMatrix();
}
auto const &modelview = OpenGLMatrices.data(GL_MODELVIEW);
// stars of the catalogue, turned by the sidereal time; the fog and the clouds go over them
render_stars(modelview);
// the bodies give their colour premultiplied, and as alpha how much of the sky behind them they hide (celestial.frag)
auto const &sun{Environment->sun()};
auto const &moon{Environment->moon()};
// the fog and the clouds laid over the sky hide the sun and the moon; without the clouds the overcast fades them
auto const overcastfactor{m_weatherstate.clouds_drawn() ? 1.f : std::clamp(1.5f - Global.Overcast, 0.f, 1.f)};
float const duskfactor = 1.0f - std::clamp(std::abs(sun.getAngle()), 0.0f, 12.0f) / 12.0f;
glm::vec3 suncolor = glm::mix(glm::vec3(1.0f, 242.0f / 255.0f, 231.0f / 255.0f), glm::vec3(235.0f / 255.0f, 140.0f / 255.0f, 36.0f / 255.0f), duskfactor);
suncolor = m_skylight.sun_disc_color(suncolor);
glm::vec2 zenith{glm::mat3{modelview} * glm::vec3{0.f, 1.f, 0.f}};
zenith = (glm::length(zenith) > 1e-3f ? glm::normalize(zenith) : glm::vec2{0.f, 1.f});
auto const flattening = [](float const Elevation, float const Radius) {
auto const refraction = [](float const Height) {
auto const height{std::max(Height, -2.f)};
return 1.f / std::tan(glm::radians(height + 7.31f / (height + 4.4f)));
};
auto const radius{glm::degrees(Radius)};
return std::clamp(1.f - (refraction(Elevation - radius) - refraction(Elevation + radius)) / (2.f * radius * 60.f), 0.5f, 1.f);
};
auto const mainview{(m_renderpass.draw_mode == rendermode::color) && (m_current_viewport != nullptr) && m_current_viewport->main};
auto const draw_body = [&](std::array<glm::vec4, 3> const &Body) {
if (false == bind_scene_pipeline(*scene.celestial, *draw.targets, vk14::blend_mode::premultiplied, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP))
{
return;
}
apply_draw_state();
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 (std::size_t i = 0; i < Body.size(); ++i)
{
constants.data[i] = Body[i];
}
push_constants(&constants);
draw.commands->draw(4, 1, 0, 0);
};
std::array<glm::vec4, 3> body{};
// sun
{
float const sunradius{glm::radians(0.2666f)};
float const quadmargin{1.25f};
glm::vec3 const limbdarkening{0.50f, 0.60f, 0.75f};
float const limbpeak{1.f / (1.f - 0.75f / 3.f)};
float const radiancemax{60000.f};
auto const sunvector = sun.getDirection();
glm::vec3 radiance{suncolor * overcastfactor};
if (mainview)
{
auto const solidangle{2.f * glm::pi<float>() * (1.f - std::cos(sunradius))};
auto const hidden{m_weatherstate.clouds_drawn() ? 0.f : glm::smoothstep(0.5f, 1.f, Global.Overcast)};
auto const physical{glm::vec3{m_sunlight.diffuse} * (1.5f * glm::pi<float>() / solidangle * Global.gfx_sun_disc)};
radiance = glm::mix(physical, radiance, hidden);
auto const peak{std::max({radiance.r, radiance.g, radiance.b}) * limbpeak};
if (peak > radiancemax)
{
radiance *= radiancemax / peak;
}
}
body[0] = glm::vec4{zenith, flattening(sun.getAngle(), sunradius), 1.f / quadmargin};
body[1] = glm::vec4{0.f};
body[2] = glm::vec4{limbdarkening, 0.f};
model_ubs.param[0] = glm::vec4{radiance, 1.f};
model_ubs.param[1] = glm::vec4(glm::vec3(modelview * glm::vec4(sunvector, 1.0f)), std::tan(sunradius) * quadmargin);
model_ubs.param[2] = glm::vec4(0.f, 0.f, 0.f, 1.f);
draw_body(body);
}
// moon
{
glm::vec2 const discposition{0.5f, 0.5f};
float const discradius{0.1635f};
float const quadmargin{1.1f};
float const earthshine{0.005f};
glm::vec3 const extinction{0.10f, 0.17f, 0.30f};
walk_texture(0, m_moontexture);
glm::vec3 mooncolor(1.0f, 0.9490196f, 0.9058824f);
float visibility = std::max<float>(0.f, 1.0 - 0.5 * Global.fLuminance - 0.65 * std::max(0.f, glm::dot(sun.getDirection(), moon.getDirection()))) * overcastfactor;
if (mainview)
{
visibility = 1.4f * overcastfactor;
}
auto const moonvector = moon.getDirection();
auto const moonradius{moon.getAngularRadius()};
auto const moonelevation{moon.getAngle()};
auto const moonview{glm::vec3(modelview * glm::vec4(moonvector, 1.0f))};
if (moon.getPhase() == 50)
{
model_ubs.param[0] = glm::vec4(mooncolor, visibility);
model_ubs.param[1] = glm::vec4(moonview, std::tan(moonradius) * (0.1665f / discradius));
model_ubs.param[2] = glm::vec4(0.66f, 1.0f - 0.667f, 0.333f, 0.f);
}
else
{
auto const zenithangle{90.f - std::max(moonelevation, -1.f)};
auto const airmass{1.f / (std::cos(glm::radians(zenithangle)) + 0.50572f * std::pow(std::max(96.07995f - zenithangle, 0.1f), -1.6364f))};
auto const transmittance{glm::exp(-extinction * (airmass - 1.f))};
auto const night{1.f - glm::smoothstep(-15.f, -9.f, sun.getAngle())};
auto const earthphase{0.5f * (1.f - std::cos(glm::radians(moon.getPhaseAngle())))};
body[0] = glm::vec4{zenith, flattening(moonelevation, moonradius), 1.f / quadmargin};
body[1] = glm::vec4{glm::normalize(glm::mat3{modelview} * sun.getDirection()), earthshine * earthphase};
body[2] = glm::vec4{0.f};
model_ubs.param[0] = glm::vec4(mooncolor * transmittance * (visibility * Global.gfx_moon_disc), night * overcastfactor);
model_ubs.param[1] = glm::vec4(moonview, std::tan(moonradius) * quadmargin);
model_ubs.param[2] = glm::vec4(discposition, discradius, 2.f);
}
draw_body(body);
}
// clouds of the weather and the fog over the sky: of the main view in its colour pass, of the panorama around the camera in the
// other views and in the faces of the environment map
render_sky_weather(OpenGLMatrices.data(GL_PROJECTION), mainview);
::glPopMatrix();
model_ubs.fog_density = fogdensity;
m_sunlight.factor = 1.f;
// calculate shadow tone, based on positions of celestial bodies
m_shadowcolor = glm::mix(glm::vec4{colors::shadow}, glm::vec4{colors::white}, std::clamp(-sun.getAngle(), 0.f, 6.f) / 6.f);
if ((sun.getAngle() < -18.f) && (moon.getAngle() > 0.f))
{
// turn on moon shadows after astronomical twilight, if the moon is actually up
auto const moonshadow{std::clamp(4.f * moon.getBrightness() * Global.gfx_moonlight, 0.f, 1.f) * std::clamp((-18.f - sun.getAngle()) / 2.f, 0.f, 1.f) * std::clamp(moon.getAngle() / 5.f, 0.f, 1.f)};
m_shadowcolor = glm::mix(glm::vec4{colors::white}, glm::vec4{colors::shadow}, moonshadow);
}
// soften shadows depending on sky overcast factor, unless the shadows of the clouds hold back the sun where they fall
if (false == m_weatherstate.cloud_shadows())
{
m_shadowcolor = glm::min(colors::white, m_shadowcolor + ((colors::white - colors::shadow) * Global.Overcast));
}
setup_shadow_color(m_shadowcolor);
}
void vulkan14_renderer::push_constants(void const *Constants)
{
auto &draw{*m_draw};
VkPushConstantsInfo info{};
info.sType = VK_STRUCTURE_TYPE_PUSH_CONSTANTS_INFO;
info.layout = m_context->scene.scene_layout;
info.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
info.offset = 0;
info.size = sizeof(vk14::draw_constants);
info.pValues = Constants;
draw.commands->push_constants2(&info);
draw.pushed_draw = ~0u;
}
// the grass of the heightmap terrain (opengl46_renderer::Render_grass(), opengl46_grass::render()): a draw of the instances of a
// blade for each tile seen, the textures of its chunk pushed in place of those of a material
void vulkan14_renderer::render_grass(glm::mat4 const &Future)
{
auto &ctx{*m_context};
auto &scene{ctx.scene};
auto &draw{*m_draw};
auto &grass{scene.grass};
if ((false == Global.gfx_grass) || grass.empty())
{
return;
}
auto const &camera{m_renderpass.pass_camera};
if (false == grass.prepare(camera.position(), [&](scene::bounding_area const &Area) { return camera.visible(Area); }))
{
return;
}
if ((scene.grass_program == nullptr) && (false == scene.grass_tried))
{
scene.grass_tried = true;
try
{
auto program{std::make_shared<vk14::scene_program>()};
program->vertex = ctx.shaders.get("grass.vert");
program->fragment = ctx.shaders.get("grass.frag");
program->fragment_name = "grass.frag";
program->vertex_name = "grass.vert";
program->vertices = vk14::vertex_kind::none;
program->name = "grass.vert + grass.frag";
scene.grass_program = program;
}
catch (gfx::shader_exception const &e)
{
ErrorLog("heightmap terrain: can't use the grass shader: " + std::string(e.what()));
}
}
if ((scene.grass_program == nullptr) || grass.chunk_draws().empty())
{
return;
}
// the blades bring their colour, the lights do the rest
model_ubs.future = Future;
model_ubs.param[0] = glm::vec4(1.f);
model_ubs.param[1] = glm::vec4(1.f, 1.f, 0.f, 1.f); // diffuse, specular, reflection, glossiness
model_ubs.param[2] = glm::vec4(0.f);
model_ubs.emission = 0.f;
model_ubs.alpha_mult = 1.f;
model_ubs.opacity = 0.5f;
if (false == bind_scene_pipeline(*scene.grass_program, *draw.targets, vk14::blend_mode::none, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP))
{
return;
}
auto const cullface{draw.cullface};
draw.cullface = false;
apply_draw_state();
if (draw.set_dirty)
{
bind_pass_set();
}
glm::mat4 const view{OpenGLMatrices.data(GL_MODELVIEW)};
auto const &frame{grass.frame()};
auto const &tiles{grass.tile_draws()};
for (auto const &chunk : grass.chunk_draws())
{
auto const *images{grass.images(chunk)};
if (images == nullptr)
{
continue;
}
// the chunk is drawn relative to its corner, the heights as they are
model_ubs.set_modelview(glm::translate(view, glm::vec3{chunk.offset}));
auto const block{push_draw_block()};
if (block == ~0u)
{
break;
}
std::array<VkDescriptorImageInfo, gl::MAX_TEXTURES> descriptors;
for (std::size_t unit = 0; unit < gl::MAX_TEXTURES; ++unit)
{
descriptors[unit] = (unit < images->images.size()) ? VkDescriptorImageInfo{images->samplers[unit], images->images[unit]->view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL}
: VkDescriptorImageInfo{ctx.sampling.repeat_aniso, scene.white->view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL};
}
scene.material_set.push(draw.commands, scene.scene_layout, descriptors.data());
vk14::draw_constants constants;
constants.draw = block;
constants.data[2] = chunk.camera;
constants.data[3] = frame.lod;
constants.data[4] = frame.wind;
constants.data[5] = frame.season;
for (auto i = chunk.first; i < chunk.first + chunk.count; ++i)
{
auto const &tile{tiles[i]};
constants.data[0] = tile.tile;
constants.data[1] = tile.part;
push_constants(&constants);
draw.commands->draw(static_cast<std::uint32_t>(tile.segments * 2 + 1), static_cast<std::uint32_t>(tile.count), 0, 0);
++draw.draws;
}
}
m_renderpass.draw_stats.triangles += static_cast<int>(grass.triangles());
// the textures of the next material go in place of the ones of the grass
draw.textures_dirty = true;
draw.cullface = cullface;
apply_draw_state();
}
// what the particles of the gpu are drawn with in the colour pass under way (opengl46_renderer::Particles_View())
gfx::gpu_particles::view vulkan14_renderer::particles_view() const
{
gfx::gpu_particles::view view;
view.modelview = OpenGLMatrices.data(GL_MODELVIEW);
view.projection = OpenGLMatrices.data(GL_PROJECTION);
view.camera = m_renderpass.pass_camera.position();
view.height = static_cast<int>(m_draw->extent.height);
view.soft = m_particlesoft;
return view;
}
// the depth of the scene drawn so far copied for the particles of the gpu, which fade in front of it (opengl46_renderer::
// Setup_Particles_Depth()): the rendering is ended (which resolves the depth of many samples), the depth copied to the depth of
// the scene the pass sets read (the one of the contact shadows, as in the opengl renderer), and the rendering begun again
bool vulkan14_renderer::setup_particles_depth(std::size_t const Index)
{
// the fading works out the distance from the depth as the contact shadows do (contact_shadows_wanted())
auto &ctx{*m_context};
auto &targets{ctx.scene.targets[Index]};
auto &draw{*m_draw};
if (Global.gfx_skippipeline || Global.bWireFrame || (false == draw.rendering) || (false == draw.resume.valid))
{
return false;
}
auto const &projection{scene_ubs.projection};
if ((projection[2][3] == 0.f) || (projection[2][2] != 0.f) || (projection[3][2] <= 0.f))
{
return false;
}
if ((false == bool(targets.scene_depth)) || (targets.scene_depth->desc().extent.width != targets.extent.width) || (targets.scene_depth->desc().extent.height != targets.extent.height))
{
vk14::image_desc desc;
desc.format = VK_FORMAT_D32_SFLOAT;
desc.extent = {targets.extent.width, targets.extent.height, 1};
desc.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
if (targets.scene_depth)
{
ctx.deletions.push(ctx.uploads.current_value(), [old = std::move(targets.scene_depth)]() {});
}
targets.scene_depth = std::make_shared<vk14::image>();
if (false == targets.scene_depth->create(ctx.memory, desc, "scene depth"))
{
targets.scene_depth.reset();
return false;
}
}
auto const commands{ctx.commands};
suspend_rendering();
vk14::barriers(commands, {vk14::use_of(*targets.depth, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_2_COPY_BIT, VK_ACCESS_2_TRANSFER_READ_BIT),
vk14::use_of(*targets.scene_depth, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_2_COPY_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, 0, 1};
region.dstSubresource = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 0, 1};
region.extent = {targets.extent.width, targets.extent.height, 1};
VkCopyImageInfo2 copy{};
copy.sType = VK_STRUCTURE_TYPE_COPY_IMAGE_INFO_2;
copy.srcImage = targets.depth->handle();
copy.srcImageLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
copy.dstImage = targets.scene_depth->handle();
copy.dstImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
copy.regionCount = 1;
copy.pRegions = &region;
commands->copy_image2(&copy);
vk14::barriers(commands, {vk14::use_of(*targets.scene_depth, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access)});
resume_rendering();
return true;
}
// the smoke of the cpu (opengl46_particles)
void vulkan14_renderer::render_particles()
{
auto &ctx{*m_context};
auto &scene{ctx.scene};
auto &draw{*m_draw};
model_ubs.set_modelview(OpenGLMatrices.data(GL_MODELVIEW));
walk_texture(0, m_smoketexture);
// the smoke of the gpu, when its programs could be made; otherwise the particle manager simulates it on the cpu. a switch
// takes effect from the next update of the manager
auto const gpu{Global.gfx_smoke_gpu && scene.particles.smoke_available()};
simulation::Particles.gpu(gpu);
if (gpu)
{
if (Global.Smoke)
{
scene.particles.simulate_smoke(simulation::Particles, m_renderpass.pass_camera.position());
m_renderpass.draw_stats.particles = static_cast<int>(scene.particles.render_smoke(particles_view()));
// the compute passes ended the rendering, which a draw may not have begun again
resume_rendering();
}
return;
}
if (false == Global.Smoke)
{
return;
}
auto const &camera{m_renderpass.pass_camera};
std::multimap<float, smoke_source const &> sources;
for (auto const &source : simulation::Particles.sequence())
{
if (false == camera.visible(source.area()))
{
continue;
}
sources.emplace(static_cast<float>(glm::length(camera.position() - source.area().center) - source.area().radius), source);
}
if (sources.empty())
{
return;
}
std::vector<particle_vertex> vertices;
auto const camerarotation{glm::mat3(camera.modelview())};
particle_vertex vertex;
for (auto const &[sourcekey, sourcedata] : sources)
{
auto const particlecolor{glm::clamp(sourcedata.color() * (glm::vec3{Global.DayLight.ambient} + 0.35f * glm::vec3{Global.DayLight.diffuse}) * simulation::Environment.light_intensity(), glm::vec3{0.f}, glm::vec3{1.f})};
for (auto const &particle : sourcedata.sequence())
{
vertex.color[0] = particlecolor.r;
vertex.color[1] = particlecolor.g;
vertex.color[2] = particlecolor.b;
vertex.color.a = std::clamp(particle.opacity, 0.0f, 1.0f) * sourcedata.fade_in(particle.age);
auto const offset{glm::vec3{particle.position - camera.position()}};
auto const rotation{glm::angleAxis(particle.rotation, glm::vec3{0.f, 0.f, 1.f})};
for (auto const &[billboardoffset, billboardtexture] : billboard_vertices)
{
vertex.position = offset + (rotation * billboardoffset * particle.size) * camerarotation;
vertex.texture = billboardtexture;
vertices.emplace_back(vertex);
}
}
}
m_renderpass.draw_stats.particles = static_cast<int>(vertices.size() / 6);
auto const slice{ctx.ring.allocate(vertices.size() * sizeof(particle_vertex), 16)};
if ((false == bool(slice)) || (false == bind_scene_pipeline(*scene.smoke, *draw.targets, vk14::blend_mode::scene_alpha, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)))
{
return;
}
std::memcpy(slice.data, vertices.data(), slice.size);
apply_draw_state();
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;
push_constants(&constants);
VkDeviceSize const offset{slice.offset};
draw.commands->bind_vertex_buffers(0, 1, &slice.buffer, &offset);
draw.commands->draw(static_cast<std::uint32_t>(vertices.size()), 1, 0, 0);
draw.vertices = VK_NULL_HANDLE;
draw.userdata = VK_NULL_HANDLE;
}
// the rain and the snow of the cpu (opengl46_renderer::Render_precipitation(), opengl46_precipitation)
void vulkan14_renderer::render_precipitation()
{
if (Global.Overcast <= 1.f)
{
return;
}
auto &ctx{*m_context};
auto &scene{ctx.scene};
auto &draw{*m_draw};
if (Global.gfx_precipitation_gpu && scene.particles.precipitation_available())
{
// the particles of the gpu: the overcast above 1 is how heavy the rain or snow is
model_ubs.set_modelview(OpenGLMatrices.data(GL_MODELVIEW));
scene.particles.render_precipitation(particles_view(), Global.Weather == "snow:", std::clamp(Global.Overcast - 1.f, 0.f, 1.f), m_framestamp);
resume_rendering();
return;
}
if (m_precipitationovercast != Global.Overcast)
{
m_precipitationovercast = Global.Overcast;
std::string const densitysuffix{(m_precipitationovercast < 1.35 ? "_light" : "_medium")};
if (Global.Weather == "rain:")
{
m_precipitationtexture = Fetch_Texture("fx/rain" + densitysuffix);
}
else if (Global.Weather == "snow:")
{
m_precipitationtexture = Fetch_Texture("fx/snow" + densitysuffix);
}
}
::glPushMatrix();
// tilt the precipitation cone against the velocity vector for crude motion blur, include current wind vector while at it
auto const velocity{simulation::Environment.precipitation().m_cameramove * -1.0 + glm::dvec3{simulation::Environment.wind()} * 0.5};
if (glm::length2(velocity) > 0.0)
{
auto const forward{glm::normalize(velocity)};
auto left{glm::cross(forward, {0.0, 1.0, 0.0})};
auto const rotationangle{std::min(45.0, ((FreeFlyModeFlag || simulation::Train == nullptr) ? 5 * glm::length(velocity) : simulation::Train->Dynamic()->GetVelocity() * 0.2))};
::glRotated(rotationangle, left.x, 0.0, left.z);
}
if ((false == FreeFlyModeFlag) && (simulation::Train != nullptr))
{
// counter potential vehicle roll
auto const roll{0.5 * glm::degrees(simulation::Train->Dynamic()->Roll())};
if (roll != 0.0)
{
auto const forward{simulation::Train->Dynamic()->VectorFront()};
::glRotated(roll, forward.x, 0.0, forward.z);
}
}
if (!Global.iPause)
{
// oddly enough random streaks produce more natural looking rain than ones the eye can follow
m_precipitationrotation = (Global.Weather == "rain:" ? LocalRandom() * 360 : 0.0);
}
::glRotated(m_precipitationrotation, 0.0, 1.0, 0.0);
model_ubs.set_modelview(OpenGLMatrices.data(GL_MODELVIEW));
model_ubs.param[0] = glm::mix(0.5f * (Global.DayLight.diffuse + Global.DayLight.ambient), colors::white, 0.5f * std::clamp((float)Global.fLuminance, 0.f, 1.f));
model_ubs.param[1].x = simulation::Environment.precipitation().get_textureoffset();
walk_texture(0, m_precipitationtexture);
auto const &mesh{precipitation()};
auto const vertexslice{ctx.ring.allocate(mesh.vertices.size() * sizeof(glm::vec3), 16)};
auto const uvslice{ctx.ring.allocate(mesh.uvs.size() * sizeof(glm::vec2), 16)};
auto const indexslice{ctx.ring.allocate(mesh.indices.size() * sizeof(std::uint16_t), 16)};
if (vertexslice && uvslice && indexslice && bind_scene_pipeline(*scene.precipitation, *draw.targets, draw.blending ? vk14::blend_mode::scene_alpha : vk14::blend_mode::none, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST))
{
std::memcpy(vertexslice.data, mesh.vertices.data(), vertexslice.size);
std::memcpy(uvslice.data, mesh.uvs.data(), uvslice.size);
std::memcpy(indexslice.data, mesh.indices.data(), indexslice.size);
apply_draw_state();
draw.block = push_draw_block();
if (draw.block != ~0u)
{
if (draw.set_dirty)
{
bind_pass_set();
}
if (draw.textures_dirty)
{
push_textures();
}
vk14::draw_constants constants;
constants.draw = draw.block;
push_constants(&constants);
VkBuffer const buffers[2]{vertexslice.buffer, uvslice.buffer};
VkDeviceSize const offsets[2]{vertexslice.offset, uvslice.offset};
draw.commands->bind_vertex_buffers(0, 2, buffers, offsets);
draw.commands->bind_index_buffer(indexslice.buffer, indexslice.offset, VK_INDEX_TYPE_UINT16);
draw.commands->draw_indexed(static_cast<std::uint32_t>(mesh.indices.size()), 1, 0, 0, 0);
draw.vertices = VK_NULL_HANDLE;
draw.userdata = VK_NULL_HANDLE;
draw.indices = VK_NULL_HANDLE;
}
}
::glPopMatrix();
}
// the lines the editor draws over the scene (opengl46_renderer::Render_editor_overlay())
void vulkan14_renderer::render_editor_overlay()
{
if (false == EditorModeFlag || EditorOverlay.batches.empty())
{
return;
}
if (m_editor_overlay_revision != EditorOverlay.revision)
{
m_editor_overlay_revision = EditorOverlay.revision;
if (m_editor_overlay_bank == null_handle)
{
m_editor_overlay_bank = Create_Bank();
}
auto const &batches{EditorOverlay.batches};
for (std::size_t i = 0; i < batches.size(); ++i)
{
gfx::vertex_array vertices;
vertices.reserve(batches[i].points.size());
for (auto const &point : batches[i].points)
{
vertices.emplace_back(point, glm::vec3(0.f, 1.f, 0.f), glm::vec2());
}
gfx::userdata_array userdata;
if (i >= m_editor_overlay_geometry.size())
{
m_editor_overlay_geometry.emplace_back();
}
auto &geometry{m_editor_overlay_geometry[i]};
if (geometry != null_handle)
{
m_geometry.replace(vertices, userdata, geometry);
}
else if (false == vertices.empty())
{
geometry = m_geometry.create_chunk(vertices, userdata, m_editor_overlay_bank, batches[i].type);
}
}
}
auto &draw{*m_draw};
::glPushMatrix();
auto const origin{EditorOverlay.origin - m_renderpass.pass_camera.position()};
::glTranslated(origin.x, origin.y, origin.z);
draw.cullface = false;
draw.depthmask_off = true;
walk_program(walk_shader::line);
auto const &batches{EditorOverlay.batches};
for (std::size_t i = 0; i < batches.size() && i < m_editor_overlay_geometry.size(); ++i)
{
if (batches[i].points.empty() || m_editor_overlay_geometry[i] == null_handle)
{
continue;
}
model_ubs.param[0] = batches[i].color;
walk_draw(m_editor_overlay_geometry[i]);
++m_renderpass.draw_stats.drawcalls;
}
draw.depthmask_off = false;
draw.cullface = true;
::glPopMatrix();
}
// a pass of the post-processing: the fragment shader over the whole of the target, reading the images and the exposure
void vulkan14_renderer::post_pass(char const *Fragment, vk14::image &Target, std::vector<vk14::image *> const &Inputs, vk14::draw_constants const &Constants, char const *Pass, unsigned const Repeated)
{
auto &ctx{*m_context};
auto &scene{ctx.scene};
auto const commands{ctx.commands};
VkExtent2D const extent{Target.desc().extent.width, Target.desc().extent.height};
std::vector<vk14::image_use> uses{vk14::use_of(Target, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, colour_stage, colour_access)};
for (auto *input : Inputs)
{
uses.push_back(vk14::use_of(*input, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT, sampled_access));
}
if (Pass != nullptr)
{
ctx.passes.begin_pass(Pass, uses);
}
else
{
vk14::barriers(commands, uses);
}
auto const pipeline{post_pipeline(Fragment, static_cast<int>(Target.desc().format))};
if (pipeline == nullptr)
{
return;
}
VkClearValue clear{};
auto attachment{attachment_info(Target.view(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, false, clear)};
attachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
VkRenderingInfo rendering{};
rendering.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
rendering.renderArea = {{0, 0}, extent};
rendering.layerCount = 1;
rendering.colorAttachmentCount = 1;
rendering.pColorAttachments = &attachment;
commands->begin_rendering(&rendering);
vk14::set_draw_state(commands, extent);
commands->bind_pipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, reinterpret_cast<VkPipeline>(pipeline));
commands->set_primitive_topology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP);
std::array<vk14::descriptor, vk14::bindings::POST_TEXTURES + 1> descriptors;
for (std::size_t i = 0; i < vk14::bindings::POST_TEXTURES; ++i)
{
auto *input{i < Inputs.size() ? Inputs[i] : Inputs.front()};
// a depth as the opengl renderer reads its depth targets (filtered where the device can, the farthest depth past the edges)
auto const depth{input->desc().format == VK_FORMAT_D32_SFLOAT};
auto const repeated{(i < Inputs.size()) && ((Repeated & (1u << i)) != 0)};
descriptors[i] = vk14::image_descriptor(repeated ? ctx.sampling.repeat_nearest : depth ? ctx.sampling.depth : ctx.sampling.clamp_linear, input->view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
}
descriptors[vk14::bindings::POST_EXPOSURE_SSBO] = vk14::buffer_descriptor(scene.exposure.data.handle(), 0, sizeof(gl::exposure_data));
scene.post_set.push(commands, scene.post_layout, descriptors.data());
VkPushConstantsInfo info{};
info.sType = VK_STRUCTURE_TYPE_PUSH_CONSTANTS_INFO;
info.layout = scene.post_layout;
info.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
info.size = sizeof(Constants);
info.pValues = &Constants;
commands->push_constants2(&info);
commands->draw(4, 1, 0, 0);
commands->end_rendering();
}
// the post-processing of a viewport: from the colour of the scene to its output, which the window shows (the part of
// opengl46_renderer::Render_pass() after the colour pass)
void vulkan14_renderer::render_post(std::size_t const Index, bool const Motionblur)
{
auto &ctx{*m_context};
auto &scene{ctx.scene};
auto &targets{scene.targets[Index]};
auto const commands{ctx.commands};
// the occlusion goes into the image before the blur, which smears both alike. the targets are there only when it was on as they
// were made
auto const aomode{(targets.ssao != nullptr) ? Global.gfx_postfx_ao : 0};
if (aomode != 0)
{
ctx.passes.begin_pass("ambient occlusion");
render_ao(Index, aomode);
}
auto *hdr{targets.colour.get()};
// the temporal anti-aliasing: the scene blended into its history (the motion blur takes the result)
if (m_motionhistory && targets.taa && targets.taa_history[0] && targets.taa_history[1] && targets.motion_image)
{
auto const target{m_taa.target()};
vk14::draw_constants constants;
// the near plane gives the distance of the pixels from their depth
constants.data[0] = glm::vec4{m_taa.history_valid() ? 1.f : 0.f, m_colorpass.znear, 0.f, 0.f};
auto const &reprojection{m_taa.reprojection()};
for (int column = 0; column < 4; ++column)
{
constants.data[2 + column] = reprojection[column];
}
post_pass("postfx_taa.frag", *targets.taa_history[target], {targets.colour.get(), targets.motion_image.get(), targets.depth.get(), targets.taa_history[1 - target].get()}, constants, "temporal anti-aliasing");
hdr = targets.taa_history[target].get();
}
if (Motionblur && targets.blurred)
{
vk14::draw_constants constants;
constants.data[0].x = m_framerate / (1.0 / Global.gfx_postfx_motionblur_shutter);
post_pass("postfx_motionblur.frag", *targets.blurred, {hdr, targets.motion_image.get(), targets.depth.get()}, constants, "resolve, ambient occlusion, motion blur");
hdr = targets.blurred.get();
}
ctx.passes.begin_pass("exposure, bloom");
// the exposure follows the main view; the other viewports use it too
if (Global.gfx_postfx_autoexposure && m_viewports[Index].main)
{
measure_exposure(*hdr);
}
vk14::image *bloom{nullptr};
if (Global.gfx_postfx_bloom)
{
bloom = render_bloom(Index, *hdr);
}
vk14::draw_constants tonemap;
tonemap.data[0] = gfx::exposure_settings::tonemap_params(bloom != nullptr ? targets.bloom_sizes.size() : 0);
// the dither goes to the last pass before the window: the chromatic aberration, when it follows
tonemap.data[1] = glm::vec4{targets.aberration ? 0.f : 1.f, 0.f, 0.f, 0.f};
std::vector<vk14::image *> const tonemapinput{hdr, (bloom != nullptr ? bloom : hdr)};
if (targets.aberration && targets.tonemapped)
{
post_pass("postfx_tonemapping.frag", *targets.tonemapped, tonemapinput, tonemap, "tonemapping, output");
post_pass("postfx_chromaticaberration.frag", *targets.output, {targets.tonemapped.get()}, vk14::draw_constants{});
}
else
{
post_pass("postfx_tonemapping.frag", *targets.output, tonemapinput, tonemap, "tonemapping, output");
}
// the lights of the clusters of the pixels over the image, for the debugging of the lights
if (Global.gfx_lights_clusterheatmap)
{
render_cluster_heatmap(Index);
}
vk14::barriers(commands, {vk14::use_of(*targets.output, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT, sampled_access)});
}
// the output of a viewport drawn to its window turned upside down (decision K8), inside the rendering of the window
void vulkan14_renderer::present_viewport(std::size_t const Index)
{
auto &ctx{*m_context};
auto &scene{ctx.scene};
auto &targets{scene.targets[Index]};
auto const &window{*ctx.windows[m_viewports[Index].window]};
auto const commands{ctx.commands};
// the output encoded already goes as it is; an srgb output to a window which isn't (no srgb format of the surface) is encoded
auto const pipeline{post_pipeline("present.frag", static_cast<int>(window.format()), (targets.srgb && (false == window.srgb())) ? 1 : 0)};
if (pipeline == nullptr)
{
return;
}
vk14::set_draw_state(commands, window.extent());
commands->bind_pipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, reinterpret_cast<VkPipeline>(pipeline));
commands->set_primitive_topology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP);
std::array<vk14::descriptor, vk14::bindings::POST_TEXTURES + 1> descriptors;
descriptors.fill(vk14::image_descriptor(ctx.sampling.clamp_linear, targets.output->view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL));
descriptors[vk14::bindings::POST_EXPOSURE_SSBO] = vk14::buffer_descriptor(scene.exposure.data.handle(), 0, sizeof(gl::exposure_data));
scene.post_set.push(commands, scene.post_layout, descriptors.data());
vk14::draw_constants constants;
VkPushConstantsInfo info{};
info.sType = VK_STRUCTURE_TYPE_PUSH_CONSTANTS_INFO;
info.layout = scene.post_layout;
info.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
info.size = sizeof(constants);
info.pValues = &constants;
commands->push_constants2(&info);
commands->draw(4, 1, 0, 0);
}
// a pipeline of the post-processing: quad.vert with the fragment shader, to an image of the format. the copy to a window has the
// colour encoded already (the output of the viewport is srgb, or encoded by the tonemapping with gfx.shadergamma)
void *vulkan14_renderer::post_pipeline(char const *Fragment, int const Format, int const Encode)
{
auto &ctx{*m_context};
auto &scene{ctx.scene};
auto const lookup{scene.post_fragments.find(Fragment)};
if (lookup == scene.post_fragments.end())
{
return nullptr;
}
vk14::pipeline_desc desc;
desc.stages = {scene.quad, lookup->second};
desc.layout = scene.post_layout;
desc.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
desc.colour_formats = {static_cast<VkFormat>(Format)};
auto const gamma{Encode >= 0 ? (Encode > 0) : Global.gfx_shadergamma};
desc.specialization = {gamma ? 1u : 0u, Global.gfx_shadowmap_enabled ? 1u : 0u, Global.gfx_envmap_enabled ? 1u : 0u, Global.gfx_postfx_motionblur_enabled ? 1u : 0u, 1u, Global.gfx_extraeffects ? 1u : 0u};
desc.name = Fragment;
return reinterpret_cast<void *>(ctx.pipelines.get(desc));
}
// the lights of the pass (opengl46_renderer::Update_Lights())
void vulkan14_renderer::update_lights(light_array &Lights, bool const Headlights, bool const Clustered)
{
// arrange the light array from closest to farthest from current position of the camera
auto const camera = m_colorpass.pass_camera.position();
if (Headlights)
{
std::sort(std::begin(Lights.data), std::end(Lights.data), [&camera](light_array::light_record const &Left, light_array::light_record const &Right) {
if (Left.intensity == 0.f)
{
return false;
}
if (Right.intensity == 0.f)
{
return true;
}
return (glm::length2(camera - Left.position) / Left.intensity) < (glm::length2(camera - Right.position) / Right.intensity);
});
}
glm::mat4 mv = OpenGLMatrices.data(GL_MODELVIEW);
// the headlights as the debug window sets them (opengl46_renderer::headlight_config)
auto const in_cutoff{m_headlightconfig.in_cutoff};
auto const out_cutoff{m_headlightconfig.out_cutoff};
auto const falloff_linear{m_headlightconfig.falloff_linear};
auto const falloff_quadratic{m_headlightconfig.falloff_quadratic};
auto const intensity{m_headlightconfig.intensity};
auto const ambient{m_headlightconfig.ambient};
sun_light renderlight;
renderlight.is_directional = false;
std::size_t light_i = 1;
m_lights_dropped = 0;
// the lights other than the headlights are only taken from the lists of the clusters. the lamp of the occupied cab goes first
auto const locallights{Headlights && Clustered};
m_cablamplit = false;
if (locallights)
{
if (auto const *lamp{m_locallights.cab_lamp()})
{
auto &light{light_data.lights[light_i++]};
write_local_light(light, *lamp, camera, mv);
m_cablamplit = true;
auto const *vehicle{m_locallights.cab_lamp_vehicle()};
auto const &lamps{m_context->scene.lamps};
if (lamps.ready(vehicle))
{
// view space to the space of the map: the space of the cab model, with the lamp at the origin. the vehicle is where it
// is now, the map stays right as it moves
auto const toworld{glm::dmat4{glm::transpose(glm::mat3{mv})}};
auto const tomap{glm::translate(glm::dmat4{1.0}, -glm::dvec3{lamps.lamp()}) * glm::inverse(vehicle->mMatrix) * glm::translate(glm::dmat4{1.0}, camera - vehicle->GetPosition()) * toworld};
light.headlight_projection = glm::mat4{tomap};
light.headlight_weights = lamps.weights();
}
}
}
auto const headlightsstart{light_i};
for (auto const &scenelight : Lights.data)
{
if (!Headlights || !Global.gfx_lights_headlights || scenelight.intensity == 0.f)
{
break;
}
auto const lightoffset = glm::vec3{scenelight.position - camera};
if (glm::length2(lightoffset) > sq(1000.f))
{
continue;
}
if (light_i >= gl::MAX_LIGHTS)
{
++m_lights_dropped;
continue;
}
renderlight.position = lightoffset;
renderlight.direction = scenelight.direction;
auto luminance = static_cast<float>(Global.fLuminance);
auto const environment = scenelight.owner->fShade;
if (environment > 0.f)
{
luminance *= environment;
}
renderlight.diffuse = glm::vec4{glm::max(glm::vec3{colors::none}, scenelight.color - glm::vec3{luminance}), renderlight.diffuse[3]};
renderlight.ambient = glm::vec4{glm::max(glm::vec3{colors::none}, scenelight.color * glm::vec3{scenelight.intensity} - glm::vec3{luminance}), renderlight.ambient[3]};
renderlight.factor = (scenelight.count * 0.5) * (scenelight.owner->DimHeadlights ? 0.5 : 1.0);
gl::light_element *light = &light_data.lights[light_i];
light->pos = mv * glm::vec4(renderlight.position, 1.0f);
light->dir = mv * glm::vec4(renderlight.direction, 0.0f);
light->type = gl::light_element::HEADLIGHTS;
light->in_cutoff = in_cutoff;
light->out_cutoff = out_cutoff;
light->color = renderlight.diffuse * renderlight.factor;
light->linear = falloff_linear / 10.0f;
light->quadratic = falloff_quadratic / 100.0f;
light->ambient = ambient;
light->intensity = intensity;
auto const worldcam{glm::mat3{m_renderpass.pass_camera.modelview()}};
{
glm::dvec3 projector;
glm::mat3 lightcam;
glm::mat4 depthproj;
float nearplane, fieldofview;
headlight_projector(scenelight, projector, lightcam, depthproj, nearplane, fieldofview);
light->headlight_projection = coordmove * depthproj * glm::translate(glm::mat4{lightcam}, glm::vec3{m_renderpass.pass_camera.position() - projector}) * glm::mat4{glm::inverse(worldcam)};
}
// headlights weights
light->headlight_weights = {scenelight.state, 0.f};
// the shadow map of the headlights, if they have one: the same projection, from where the projector was when it was drawn
auto const &lamps{m_context->scene.lamps};
if (auto const shadowmap{lamps.headlight_map(scenelight.owner, scenelight.index)}; shadowmap >= 0)
{
auto const &slot{lamps.headlight(static_cast<std::size_t>(shadowmap))};
scene_ubs.headlight_shadow[shadowmap] = coordmove * slot.projection * glm::translate(glm::mat4{slot.rotation}, glm::vec3{m_renderpass.pass_camera.position() - slot.projector}) * glm::mat4{glm::inverse(worldcam)};
scene_ubs.headlight_shadow_params[shadowmap] = lamps.headlight_params(static_cast<std::size_t>(shadowmap));
light->headlight_weights.w = static_cast<float>(shadowmap + 1);
}
++light_i;
}
m_lights_lit = static_cast<uint32_t>(light_i - headlightsstart);
// lamps of models and vehicles, the most important first, those which may be seen lighting something in this view
if (locallights)
{
auto const toworld{glm::dmat4{glm::transpose(glm::mat3{mv})}};
auto const &lamps{m_context->scene.lamps};
uint32_t placed{0};
uint32_t dropped{0};
for (auto const &local : m_locallights.lights())
{
if (false == m_renderpass.pass_camera.visible(scene::bounding_area{local.position, local.range}))
{
continue;
}
if (light_i >= gl::MAX_LIGHTS)
{
++dropped;
continue;
}
auto &light{light_data.lights[light_i++]};
write_local_light(light, local, camera, mv);
std::size_t slot;
if (auto const *lamp{lamps.lamp(local, slot)})
{
// view space to the space of the map: world axes with the lamp at the origin
light.headlight_projection = glm::mat4{glm::translate(glm::dmat4{1.0}, camera - lamp->position) * toworld};
light.headlight_weights = lamps.lamp_weights(slot);
// the map is in the array of the scenery lamps
light.linear = 1.0f;
}
++placed;
}
m_locallights.placed(placed, dropped);
}
light_data.lights_total = static_cast<uint32_t>(light_i);
light_data.lights_count = 1 + std::min<uint32_t>(m_lights_lit, static_cast<uint32_t>(std::max(Global.DynamicLightCount, 0)));
light_data.lights_clustered = Clustered ? 1 : 0;
light_data.env_ggx = (Global.gfx_envmap_ggx && m_envmap_bound) ? 1 : 0;
// fill sunlight data
light_data.ambient = m_sunlight.ambient * m_sunlight.factor;
light_data.sky_ambient_factor = (Global.gfx_ambient_sh ? m_sunlight.factor : 0.f);
light_data.lights[0].type = gl::light_element::DIR;
light_data.lights[0].dir = mv * glm::vec4(m_sunlight.direction, 0.0f);
light_data.lights[0].color = m_sunlight.diffuse * m_sunlight.factor * simulation::Environment.light_intensity();
light_data.lights[0].ambient = 0.0f;
light_data.lights[0].intensity = 1.0f;
// fill fog data
light_data.fog_color = m_fogcolor;
if (Global.fFogEnd > 0)
{
// the fog of the weather; the regular density is a reference the shading scales the fog of a draw with
m_fogrange = m_weatherstate.fog_range();
model_ubs.fog_density = m_weatherstate.fog_density();
}
else
{
model_ubs.fog_density = 0.0f;
}
m_lightcount = static_cast<std::uint32_t>(light_i);
m_lightheaders.clear();
if (m_draw && m_draw->rendering)
{
upload_light_header();
}
// the lights of the clusters: the culling into the lists, for the colour pass which follows
if (Clustered)
{
auto &ctx{*m_context};
auto &scene{ctx.scene};
auto &clusters{scene.clusters};
auto const commands{ctx.commands};
auto const lightsize{offsetof(gl::light_data, lights) + sizeof(gl::light_element) * m_lightcount};
auto const lights{ctx.ring.allocate(sizeof(gl::light_data), ctx.device.caps().limits.minStorageBufferOffsetAlignment)};
if (false == bool(lights))
{
return;
}
std::memcpy(lights.data, &light_data, lightsize);
m_draw->light_offset = static_cast<std::uint32_t>(lights.offset);
commands->fill_buffer(clusters.counters.handle(), 0, VK_WHOLE_SIZE, 0);
VkMemoryBarrier2 barrier{};
barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2;
barrier.srcStageMask = VK_PIPELINE_STAGE_2_CLEAR_BIT | VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT;
barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT | VK_ACCESS_2_SHADER_STORAGE_READ_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT;
barrier.dstStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT;
barrier.dstAccessMask = VK_ACCESS_2_SHADER_STORAGE_READ_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT;
VkDependencyInfo dependency{};
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
dependency.memoryBarrierCount = 1;
dependency.pMemoryBarriers = &barrier;
commands->pipeline_barrier2(&dependency);
std::array<VkDescriptorBufferInfo, 6> buffers{{{ctx.ring.handle(), clusters.params_offset, sizeof(gl::cluster_ubs)},
{clusters.aabbs.handle(), 0, VK_WHOLE_SIZE},
{clusters.ranges.handle(), 0, VK_WHOLE_SIZE},
{clusters.indices.handle(), 0, VK_WHOLE_SIZE},
{clusters.counters.handle(), 0, VK_WHOLE_SIZE},
{lights.buffer, lights.offset, sizeof(gl::light_data)}}};
commands->bind_pipeline(VK_PIPELINE_BIND_POINT_COMPUTE, scene.cluster_cull);
scene.cluster_set.push(commands, scene.cluster_layout, buffers.data());
commands->dispatch((gl::CLUSTER_COUNT + gl::CLUSTER_CULL_GROUP_SIZE - 1) / gl::CLUSTER_CULL_GROUP_SIZE, 1, 1);
barrier.srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT;
barrier.srcAccessMask = VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT;
barrier.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_2_CLEAR_BIT;
barrier.dstAccessMask = VK_ACCESS_2_SHADER_STORAGE_READ_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT;
commands->pipeline_barrier2(&dependency);
clusters.lights_total = light_data.lights_total;
// the counters of the culling for the debug panel, read once the gpu is done with the frame
clusters.counters_readback.push(commands, ctx.slot, ctx.timelinevalue + 1, clusters.counters.handle(), 0, sizeof(gl::cluster_counters));
}
}
void vulkan14_renderer::write_local_light(gl::light_element &Light, gfx::local_lights::light const &Source, glm::dvec3 const &Camera, glm::mat4 const &View)
{
// a cone wider than a half of the space is no limit for the culling, but the shading still fades the light out of it
Light.type = gl::light_element::SPOT;
Light.pos = View * glm::vec4(glm::vec3{Source.position - Camera}, 1.0f);
Light.dir = glm::normalize(glm::vec3{View * glm::vec4(Source.direction, 0.0f)});
Light.in_cutoff = Source.cos_inner;
Light.out_cutoff = Source.cos_outer;
if (Source.cos_outer <= -1.f)
{
Light.type = gl::light_element::POINT;
}
Light.color = Source.color;
Light.intensity = 1.0f;
Light.ambient = 0.0f;
// the shading takes the light to end at 1 / sqrt(quadratic)
Light.linear = 0.0f;
Light.quadratic = 1.0f / sq(std::max(Source.range, 0.01f));
Light.headlight_projection = glm::mat4{1.0f};
// no shadow map
Light.headlight_weights = glm::vec4{-1.0f, 0.0f, 0.0f, 0.0f};
}
void vulkan14_renderer::headlight_projector(light_array::light_record const &Light, glm::dvec3 &Position, glm::mat3 &Rotation, glm::mat4 &Projection, float &Near, float &Fieldofview)
{
auto const &ownerdimensions{Light.owner->MoverParameters->Dim};
auto const up{static_cast<glm::dvec3>(Light.owner->VectorUp())};
auto const size{static_cast<float>(std::max(ownerdimensions.W, ownerdimensions.H) * 1.0)};
auto const cone{5.f};
auto const offset{75.f * (5.f / cone)};
Position = Light.owner->GetPosition() - glm::dvec3(Light.direction * offset) + up * (size * 0.5);
Near = static_cast<float>(ownerdimensions.L * 0.5 + offset - 0.25);
Fieldofview = glm::radians(cone);
Projection = perspective_projection_raw(Fieldofview, 1.0, Near, 1000.0f);
glm::dmat4 viewmatrix{1.0};
viewmatrix *= glm::lookAt(Position, Position - up * 1.0 + glm::dvec3{Light.direction * 1000.f}, glm::dvec3{0.f, 1.f, 0.f});
Rotation = glm::mat3{glm::mat4{viewmatrix}};
}
// the light cluster grid of a viewport for the projection of its colour pass (gl::cluster_grid::update() and build()); the
// viewports take the buffers of the grid in turn, its boxes are made anew when the projection changes
void vulkan14_renderer::update_clusters(glm::ivec2 const &Targetsize)
{
auto &ctx{*m_context};
auto &scene{ctx.scene};
auto &clusters{scene.clusters};
auto const commands{ctx.commands};
auto const projection{OpenGLMatrices.data(GL_PROJECTION)};
auto const znear{m_renderpass.znear};
auto const zfar{m_renderpass.zfar};
auto params{clusters.params};
params.viewport_width = static_cast<float>(Targetsize.x);
params.viewport_height = static_cast<float>(Targetsize.y);
// clip space w is a function of the position in a perspective projection, and a constant in an orthographic one
bool const perspective{glm::length(glm::vec3{projection[0][3], projection[1][3], projection[2][3]}) > 1e-4f};
bool const usable{perspective && znear > 0.f && zfar > znear};
params.valid = usable ? 1 : 0;
if (usable && (projection != clusters.params.projection || znear != clusters.params.znear || zfar != clusters.params.zfar))
{
params.projection = projection;
params.znear = znear;
params.zfar = zfar;
params.slice_scale = static_cast<float>(gl::CLUSTER_GRID_Z) / std::log(zfar / znear);
params.slice_bias = -std::log(znear) * params.slice_scale;
clusters.stale = true;
}
clusters.params = params;
auto const block{ctx.ring.allocate(sizeof(gl::cluster_ubs), ctx.device.caps().limits.minUniformBufferOffsetAlignment)};
if (false == bool(block))
{
clusters.params.valid = 0;
return;
}
std::memcpy(block.data, &clusters.params, sizeof(gl::cluster_ubs));
clusters.params_offset = block.offset;
if (usable && clusters.stale)
{
// the boxes of the clusters depend only on the projection and the depth range
std::array<VkDescriptorBufferInfo, 6> buffers{{{block.buffer, block.offset, sizeof(gl::cluster_ubs)},
{clusters.aabbs.handle(), 0, VK_WHOLE_SIZE},
{clusters.ranges.handle(), 0, VK_WHOLE_SIZE},
{clusters.indices.handle(), 0, VK_WHOLE_SIZE},
{clusters.counters.handle(), 0, VK_WHOLE_SIZE},
{ctx.ring.handle(), 0, sizeof(gl::light_data)}}};
VkMemoryBarrier2 barrier{};
barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2;
barrier.srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT;
barrier.srcAccessMask = VK_ACCESS_2_SHADER_STORAGE_READ_BIT;
barrier.dstStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT;
barrier.dstAccessMask = VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT;
VkDependencyInfo dependency{};
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
dependency.memoryBarrierCount = 1;
dependency.pMemoryBarriers = &barrier;
commands->pipeline_barrier2(&dependency);
commands->bind_pipeline(VK_PIPELINE_BIND_POINT_COMPUTE, scene.cluster_build);
scene.cluster_set.push(commands, scene.cluster_layout, buffers.data());
commands->dispatch((gl::CLUSTER_COUNT + gl::CLUSTER_BUILD_GROUP_SIZE - 1) / gl::CLUSTER_BUILD_GROUP_SIZE, 1, 1);
barrier.srcAccessMask = VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_2_SHADER_STORAGE_READ_BIT;
commands->pipeline_barrier2(&dependency);
clusters.stale = false;
}
}
// picking: the colours of the controls of the cab or of the nodes of the scenery, and the one under the cursor read back
void vulkan14_renderer::render_picking(rendermode const Mode)
{
auto &ctx{*m_context};
auto &scene{ctx.scene};
auto const commands{ctx.commands};
auto &readback{Mode == rendermode::pickcontrols ? scene.pick_controls : scene.pick_scenery};
if (readback.pending || m_viewports.empty())
{
return;
}
auto &vp{m_viewports.front()};
setup_pass(vp, m_renderpass, Mode);
ctx.passes.begin_pass(Mode == rendermode::pickcontrols ? "pick controls" : "pick scenery", {vk14::use_of(*scene.pick_colour, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, colour_stage, colour_access),
vk14::use_of(*scene.pick_depth, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, depth_stage, depth_access)});
VkClearValue colourclear{};
colourclear.color = {{0.f, 0.f, 0.f, 0.f}};
VkClearValue depthclear{};
depthclear.depthStencil = {0.f, 0};
auto colour{attachment_info(scene.pick_colour->view(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, true, colourclear)};
auto depth{attachment_info(scene.pick_depth->view(), VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, true, depthclear)};
VkRenderingInfo rendering{};
rendering.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
rendering.renderArea = {{0, 0}, {static_cast<std::uint32_t>(pick_buffer_size), static_cast<std::uint32_t>(pick_buffer_size)}};
rendering.layerCount = 1;
rendering.colorAttachmentCount = 1;
rendering.pColorAttachments = &colour;
rendering.pDepthAttachment = &depth;
commands->begin_rendering(&rendering);
start_draws(scene.pick_formats, pick_buffer_size, pick_buffer_size, vk14::scene_state::set_plain, false);
setup_matrices();
walk_blending(false);
scene_ubs.projection = OpenGLMatrices.data(GL_PROJECTION);
upload_scene_block();
upload_light_header();
if (Mode == rendermode::pickcontrols)
{
m_picksurfaceitems.clear();
m_pickcontrolsitems.clear();
Render_cab(simulation::Train->Dynamic(), 0.0f);
Render(simulation::Train->Dynamic());
}
else
{
m_picksceneryitems.clear();
Render(simulation::Region);
}
commands->end_rendering();
end_draws();
auto buffer{std::make_shared<vk14::buffer>()};
if (buffer->create(ctx.memory, 4, VK_BUFFER_USAGE_TRANSFER_DST_BIT, vk14::memory_kind::readback, "pick colour"))
{
vk14::barriers(commands, {vk14::use_of(*scene.pick_colour, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_2_COPY_BIT, VK_ACCESS_2_TRANSFER_READ_BIT)});
VkBufferImageCopy2 region{};
region.sType = VK_STRUCTURE_TYPE_BUFFER_IMAGE_COPY_2;
region.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
region.imageOffset = {m_pickposition.x, m_pickposition.y, 0};
region.imageExtent = {1, 1, 1};
VkCopyImageToBufferInfo2 copy{};
copy.sType = VK_STRUCTURE_TYPE_COPY_IMAGE_TO_BUFFER_INFO_2;
copy.srcImage = scene.pick_colour->handle();
copy.srcImageLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
copy.dstBuffer = buffer->handle();
copy.regionCount = 1;
copy.pRegions = &region;
commands->copy_image_to_buffer2(&copy);
host_read_barrier(commands);
readback.data = buffer;
readback.value = ctx.timelinevalue + 1;
readback.pending = true;
}
ctx.passes.end_pass();
m_renderpass = m_colorpass;
}
namespace
{
// the place in the image of the picking under the cursor, in the rows of opengl
glm::ivec2 pick_position()
{
glm::vec2 mousepos = Global.cursor_pos;
mousepos.y = Global.window_size.y - mousepos.y;
auto pickbufferpos{glm::ivec2(mousepos * glm::vec2(pick_buffer_size) / glm::max(glm::vec2(Global.window_size), glm::vec2(1.f)))};
return glm::clamp(pickbufferpos, glm::ivec2(0, 0), glm::ivec2(pick_buffer_size - 1, pick_buffer_size - 1));
}
} // namespace
void vulkan14_renderer::Update_Pick_Control()
{
if (!m_context)
{
return;
}
auto &ctx{*m_context};
auto &readback{ctx.scene.pick_controls};
if (readback.pending)
{
if (ctx.completed() < readback.value)
{
return;
}
readback.pending = false;
readback.data->invalidate(0, 4);
auto const *data{readback.data->mapped()};
auto const controlindex = pick_index(glm::ivec3{data[0], data[1], data[2]});
TSubModel const *control{nullptr};
glm::vec2 position(0.0f);
if ((controlindex > 0) && (controlindex <= m_pickcontrolsitems.size()))
{
control = m_pickcontrolsitems[controlindex - 1];
}
else if (255 - data[0] < m_picksurfaceitems.size())
{
control = m_picksurfaceitems[255 - data[0]];
position.x = data[1] / 255.0f;
position.y = data[2] / 255.0f;
}
m_pickcontrolitem = control;
for (auto const &callback : m_control_pick_requests)
{
callback(m_pickcontrolitem, position);
}
m_control_pick_requests.clear();
readback.data.reset();
}
if (false == m_control_pick_requests.empty())
{
m_pickposition = pick_position();
m_pickcontrolswanted = true;
}
}
void vulkan14_renderer::Update_Pick_Node()
{
if (!m_context)
{
return;
}
auto &ctx{*m_context};
auto &readback{ctx.scene.pick_scenery};
if (readback.pending)
{
if (ctx.completed() < readback.value)
{
return;
}
readback.pending = false;
readback.data->invalidate(0, 4);
auto const *data{readback.data->mapped()};
auto const nodeindex = pick_index(glm::ivec3{data[0], data[1], data[2]});
scene::basic_node *node{nullptr};
if ((nodeindex > 0) && (nodeindex <= m_picksceneryitems.size()))
{
node = m_picksceneryitems[nodeindex - 1];
}
m_picksceneryitem = node;
for (auto const &callback : m_node_pick_requests)
{
callback(m_picksceneryitem);
}
m_node_pick_requests.clear();
readback.data.reset();
}
if (false == m_node_pick_requests.empty())
{
m_pickposition = pick_position();
m_picknodewanted = true;
}
}
void vulkan14_renderer::Pick_Control_Callback(std::function<void(TSubModel const *, glm::vec2 const)> Callback)
{
if (!Global.render_cab)
{
Callback(nullptr, glm::vec2());
return;
}
m_control_pick_requests.push_back(Callback);
}
void vulkan14_renderer::Pick_Node_Callback(std::function<void(scene::basic_node *)> Callback)
{
m_node_pick_requests.push_back(Callback);
}
TSubModel const *vulkan14_renderer::Pick_Control() const
{
return m_pickcontrolitem;
}
scene::basic_node const *vulkan14_renderer::Pick_Node() const
{
return m_picksceneryitem;
}
glm::dvec3 vulkan14_renderer::Mouse_Position() const
{
return m_worldmousecoordinates;
}
bool vulkan14_renderer::Mouse_Hit() const
{
return m_mousehit;
}
glm::mat4 vulkan14_renderer::Camera_View_Matrix() const
{
return glm::mat4(glm::mat3(m_colorpass.pass_camera.modelview()));
}
glm::mat4 vulkan14_renderer::Camera_Projection_Matrix() const
{
return m_colorpass.pass_camera.projection();
}
glm::dvec3 vulkan14_renderer::Camera_Position() const
{
return m_colorpass.pass_camera.position();
}
// the position of the cursor of the editor in the scene: the depth under it read back from an earlier frame
glm::dvec3 vulkan14_renderer::Update_Mouse_Position()
{
if (m_context)
{
auto &ctx{*m_context};
auto &cursor{ctx.scene.cursor};
if (cursor.pending && (ctx.completed() >= cursor.value) && (false == ctx.scene.targets.empty()))
{
cursor.pending = false;
float pointdepth{0.f};
cursor.data->invalidate(0, sizeof(float));
std::memcpy(&pointdepth, cursor.data->mapped(), sizeof(float));
cursor.data.reset();
auto const extent{ctx.scene.targets.front().extent};
m_mousehit = pointdepth > 0.0f;
if (pointdepth > 0.0f)
{
m_worldmousecoordinates = glm::unProjectZO(glm::vec3(cursor.position, pointdepth), glm::mat4(glm::mat3(m_colorpass.pass_camera.modelview())), m_colorpass.pass_camera.projection(),
glm::vec4(0, 0, extent.width, extent.height));
}
}
}
return m_colorpass.pass_camera.position() + glm::dvec3{m_worldmousecoordinates};
}
// screenshots: the output of the main viewport copied with the frame, saved once the gpu is done with it
void vulkan14_renderer::MakeScreenshot()
{
if (m_context)
{
m_context->scene.screenshot_wanted = true;
}
}
void vulkan14_renderer::update_screenshot()
{
if (!m_context)
{
return;
}
auto &ctx{*m_context};
auto &scene{ctx.scene};
if ((false == scene.screenshot_pending) || (ctx.completed() < scene.screenshot_value))
{
return;
}
scene.screenshot_pending = false;
auto const width{static_cast<int>(scene.screenshot_extent.width)};
auto const height{static_cast<int>(scene.screenshot_extent.height)};
scene.screenshot->invalidate(0, VK_WHOLE_SIZE);
auto const *rgba{scene.screenshot->mapped()};
// rgb rows as glReadPixels() of the opengl renderer gives them: the rows of the images are those of opengl (decision K8)
auto *image = new char[static_cast<std::size_t>(width) * height * 3];
for (std::size_t pixel = 0; pixel < static_cast<std::size_t>(width) * height; ++pixel)
{
image[pixel * 3 + 0] = static_cast<char>(rgba[pixel * 4 + 0]);
image[pixel * 3 + 1] = static_cast<char>(rgba[pixel * 4 + 1]);
image[pixel * 3 + 2] = static_cast<char>(rgba[pixel * 4 + 2]);
}
scene.screenshot.reset();
screenshot_manager::save(image, width, height);
}
// textures used only by vehicles farther than gfx.textures.releasedistance times the draw range give their images back
// (opengl46_renderer::Update_Texture_Release())
void vulkan14_renderer::update_texture_release()
{
if (!m_context || !m_context->textures || (m_colorpass.draw_range <= 0.f))
{
return;
}
auto &textures{*m_context->textures};
auto const range{static_cast<double>(Global.gfx_texture_releasedistance) * m_colorpass.draw_range};
auto const camera{m_colorpass.pass_camera.position()};
textures.begin_release_scan();
for (auto *vehicle : simulation::Vehicles.sequence())
{
if (vehicle == nullptr)
{
continue;
}
bool const isnear{glm::length(vehicle->vPosition - camera) - vehicle->radius() <= range};
auto const mark_model = [&](TModel3d *Model) {
if (Model == nullptr)
{
return;
}
for (auto const texture : Model_Textures(Model))
{
textures.mark_as_needed(texture, isnear);
}
};
mark_model(vehicle->mdModel);
mark_model(vehicle->mdLowPolyInt);
mark_model(vehicle->mdLoad);
for (auto *attachment : vehicle->mdAttachments)
{
mark_model(attachment);
}
for (auto const skin : vehicle->Material()->replacable_skins)
{
if (skin <= 0)
{
continue;
}
auto const &material{m_materials.material(skin)};
for (auto const texture : material.textures)
{
textures.mark_as_needed(texture, isnear);
}
for (auto const &variant : material.texture_variants)
{
for (auto const texture : variant)
{
textures.mark_as_needed(texture, isnear);
}
}
}
}
textures.release_unneeded();
}
std::vector<texture_handle> const &vulkan14_renderer::Model_Textures(TModel3d *Model)
{
auto const lookup{m_modeltextures.find(Model)};
if (lookup != m_modeltextures.end())
{
return lookup->second;
}
auto &textures{m_modeltextures[Model]};
std::vector<TSubModel *> submodels;
if (Model->GetSMRoot() != nullptr)
{
submodels.emplace_back(Model->GetSMRoot());
}
while (false == submodels.empty())
{
auto *submodel{submodels.back()};
submodels.pop_back();
if (submodel->NextGet() != nullptr)
{
submodels.emplace_back(submodel->NextGet());
}
if (submodel->ChildGet() != nullptr)
{
submodels.emplace_back(submodel->ChildGet());
}
// negative material is one of replaceable skins of the vehicle, these are handled with the vehicle
if (submodel->GetMaterial() <= 0)
{
continue;
}
auto const &material{m_materials.material(submodel->GetMaterial())};
for (auto const texture : material.textures)
{
if (texture > 0)
{
textures.emplace_back(texture);
}
}
for (auto const &variant : material.texture_variants)
{
for (auto const texture : variant)
{
if (texture > 0)
{
textures.emplace_back(texture);
}
}
}
}
std::sort(std::begin(textures), std::end(textures));
textures.erase(std::unique(std::begin(textures), std::end(textures)), std::end(textures));
return textures;
}
// a preview of a model (opengl46_renderer::Render_Preview()) isn't made by the vulkan renderer yet: the caller falls back to no
// preview
bool vulkan14_renderer::Render_Preview(TAnimModel *Instance, int const Size, int const Margin, bool const Shadows, std::vector<std::uint8_t> &Image)
{
return false;
}