17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-28 21:37:31 +01:00

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

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

View File

@@ -310,7 +310,17 @@ set(SOURCES
"gl/glsl_common.cpp"
"rendering/common/shadersource.cpp"
"rendering/common/scenewalk.cpp"
"rendering/common/scenewalkindirect.cpp"
"rendering/common/instancecull.cpp"
"rendering/common/shadowfit.cpp"
"rendering/common/localshadows.cpp"
"rendering/common/terrainmaterials.cpp"
"rendering/common/grassfield.cpp"
"rendering/common/gpuparticles.cpp"
"rendering/common/locallights.cpp"
"rendering/common/skylight.cpp"
"rendering/common/weatherstate.cpp"
"rendering/common/exposure.cpp"
"gl/buffer.cpp"
"gl/fence.cpp"
"gl/pbo.cpp"
@@ -430,6 +440,7 @@ if (WITH_VULKAN)
"rendering/vulkan14/vulkan14vma.cpp"
"rendering/vulkan14/vulkan14shaders.cpp"
"rendering/vulkan14/vulkan14descriptors.cpp"
"rendering/vulkan14/vulkan14compute.cpp"
"rendering/vulkan14/vulkan14passes.cpp"
"rendering/vulkan14/vulkan14texture.cpp"
"rendering/vulkan14/vulkan14imgui.cpp"
@@ -437,6 +448,18 @@ if (WITH_VULKAN)
"rendering/vulkan14/vulkan14geometry.cpp"
"rendering/vulkan14/vulkan14scene.cpp"
"rendering/vulkan14/vulkan14frame.cpp"
"rendering/vulkan14/vulkan14sky.cpp"
"rendering/vulkan14/vulkan14weather.cpp"
"rendering/vulkan14/vulkan14ao.cpp"
"rendering/vulkan14/vulkan14cull.cpp"
"rendering/vulkan14/vulkan14indirect.cpp"
"rendering/vulkan14/vulkan14shadows.cpp"
"rendering/vulkan14/vulkan14lamps.cpp"
"rendering/vulkan14/vulkan14terrain.cpp"
"rendering/vulkan14/vulkan14grass.cpp"
"rendering/vulkan14/vulkan14particles.cpp"
"rendering/vulkan14/vulkan14debug.cpp"
"rendering/vulkan14/vulkan14commands.cpp"
"ref/volk/volk.c")
set(DEFINITIONS ${DEFINITIONS} "WITH_VULKAN")

View File

@@ -0,0 +1,95 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/common/exposure.h"
#include "gl/ubo.h"
#include "utilities/Globals.h"
namespace
{
// luminance range of the histogram, log2: 2^-12 .. 2^6. darker pixels count as black, brighter ones as the brightest
float const HISTOGRAM_LOG_MIN{-12.f};
float const HISTOGRAM_LOG_MAX{6.f};
// the bloom chain goes down this many levels at most, and not below this size
std::size_t const BLOOM_LEVELS{6};
int const BLOOM_MIN_SIZE{8};
// soft knee of the threshold, a fraction of it
float const BLOOM_KNEE{0.5f};
} // namespace
namespace gfx
{
glm::vec2 exposure_settings::histogram_range()
{
auto const bins{static_cast<float>(gl::EXPOSURE_HISTOGRAM_BINS - 2)};
return glm::vec2{HISTOGRAM_LOG_MIN, bins / (HISTOGRAM_LOG_MAX - HISTOGRAM_LOG_MIN)};
}
glm::vec2 exposure_settings::average_range()
{
auto const bins{static_cast<float>(gl::EXPOSURE_HISTOGRAM_BINS - 2)};
return glm::vec2{HISTOGRAM_LOG_MIN, (HISTOGRAM_LOG_MAX - HISTOGRAM_LOG_MIN) / bins};
}
glm::vec4 exposure_settings::adaptation(double const Deltatime)
{
return glm::vec4{static_cast<float>(std::clamp(Deltatime, 0.0, 1.0)), Global.gfx_postfx_autoexposure_speed_dark, Global.gfx_postfx_autoexposure_speed_bright, Global.gfx_postfx_autoexposure_key};
}
glm::vec2 exposure_settings::exposure_range()
{
return glm::vec2{std::exp2(Global.gfx_postfx_autoexposure_min), std::exp2(Global.gfx_postfx_autoexposure_max)};
}
std::vector<glm::ivec2> exposure_settings::bloom_sizes(int const Width, int const Height)
{
std::vector<glm::ivec2> sizes;
glm::ivec2 size{Width, Height};
for (std::size_t level = 0; level < BLOOM_LEVELS; ++level)
{
size = glm::max(size / 2, glm::ivec2{1});
if (level > 0 && std::min(size.x, size.y) < BLOOM_MIN_SIZE)
break;
sizes.push_back(size);
}
return sizes;
}
glm::vec4 exposure_settings::bloom_params(std::size_t const Level)
{
return glm::vec4{(Level == 0 ? 1.f : 0.f), Global.gfx_postfx_bloom_threshold, BLOOM_KNEE, 0.f};
}
glm::vec4 exposure_settings::tonemap_params(std::size_t const Bloomlevels)
{
auto const bloom{(Global.gfx_postfx_bloom && Bloomlevels > 0) ? Global.gfx_postfx_bloom_intensity : 0.f};
return glm::vec4{bloom, Global.gfx_postfx_autoexposure ? 1.f : 0.f, std::exp2(Global.gfx_postfx_autoexposure_compensation), Bloomlevels > 0 ? 1.f / static_cast<float>(Bloomlevels) : 0.f};
}
void exposure_settings::draw_debug_ui(float const *Readback)
{
ImGui::Checkbox("automatic exposure (gfx.postfx.autoexposure)", &Global.gfx_postfx_autoexposure);
ImGui::SliderFloat("key", &Global.gfx_postfx_autoexposure_key, 0.02f, 2.f, "%.3f", ImGuiSliderFlags_Logarithmic);
ImGui::SliderFloat("min EV", &Global.gfx_postfx_autoexposure_min, -4.f, 0.f);
ImGui::SliderFloat("max EV", &Global.gfx_postfx_autoexposure_max, 0.f, 4.f);
ImGui::SliderFloat("adaptation to dark (1/s)", &Global.gfx_postfx_autoexposure_speed_dark, 0.05f, 10.f);
ImGui::SliderFloat("adaptation to bright (1/s)", &Global.gfx_postfx_autoexposure_speed_bright, 0.05f, 10.f);
ImGui::SliderFloat("compensation EV", &Global.gfx_postfx_autoexposure_compensation, -4.f, 4.f);
ImGui::Text("average luminance %.3f, adapted %.3f, exposure %.3f (%+.2f EV)", Readback[2], Readback[0], Readback[1], std::log2(std::max(Readback[1], 1e-6f)));
ImGui::Checkbox("bloom (gfx.postfx.bloom)", &Global.gfx_postfx_bloom);
ImGui::SliderFloat("bloom intensity", &Global.gfx_postfx_bloom_intensity, 0.f, 0.5f);
ImGui::SliderFloat("bloom threshold", &Global.gfx_postfx_bloom_threshold, 0.f, 10.f);
}
} // namespace gfx

View File

@@ -0,0 +1,44 @@
/*
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/.
*/
// what the automatic exposure and the bloom of the renderers share (the part of the stage S3 of
// claude/gl46-renderer-features-plan.md which isn't the work of the gpu, taken out of opengl46_exposure for the vulkan renderer,
// stage P1 of claude/vulkan14-renderer-plan.md): the parameters of the histogram and of the adaptation
// (exposure_histogram.comp, exposure_average.comp), the sizes of the levels of the bloom chain and the parameters of its steps
// (postfx_bloom_*.frag), the parameters of the tonemapping and the controls of the debug window
#pragma once
#include <cstddef>
#include <vector>
namespace gfx
{
struct exposure_settings
{
// x: log2 of the lowest luminance binned, y: bins per unit of log2 luminance (exposure_histogram.comp)
static glm::vec2 histogram_range();
// x: log2 of the lowest luminance binned, y: log2 luminance per bin (exposure_average.comp)
static glm::vec2 average_range();
// x: time since the last measurement (Deltatime, s), y, z: rates of the adaptation to a darker and a brighter image, w: key
static glm::vec4 adaptation(double const Deltatime);
// the lowest and the highest exposure (multipliers)
static glm::vec2 exposure_range();
// the sizes of the levels of the bloom chain of an image, half the size each, as many as there are (empty: no chain)
static std::vector<glm::ivec2> bloom_sizes(int const Width, int const Height);
// param[0] of a step down the chain (postfx_bloom_down.frag): Level 0 is the first one, from the image itself
static glm::vec4 bloom_params(std::size_t const Level);
// parameters of the tonemapping (postfx_tonemapping.frag), with bloom from a chain of Bloomlevels levels (0: none)
static glm::vec4 tonemap_params(std::size_t const Bloomlevels);
// controls of the debug window; Readback: the adapted luminance, the exposure and the average luminance the gpu worked out
static void draw_debug_ui(float const *Readback);
};
} // namespace gfx

View File

@@ -0,0 +1,321 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/common/gpuparticles.h"
#include "rendering/particles.h"
#include "simulation/simulationenvironment.h"
#include "utilities/Globals.h"
#include "utilities/Logs.h"
#include "utilities/Timer.h"
namespace gfx
{
namespace
{
// places of the smoke at most, all sources together (48 bytes each, as many again for the draw); the sources past it get none
std::uint32_t constexpr MAX_SMOKE_PLACES{1u << 19};
// the origin of the smoke moves by whole steps when the camera gets this far from it
double constexpr ORIGIN_STEP{256.0};
double constexpr ORIGIN_REACH{1024.0};
// how much the smoke scatters the light onwards (Henyey-Greenstein)
float constexpr SMOKE_FORWARD{0.3f};
// the boxes of the rain and snow around the camera: their sizes (metres) and the shares of the particles in each
std::array<float, 3> constexpr BOX_SIZE{10.f, 24.f, 60.f};
std::array<float, 3> constexpr BOX_SHARE{0.2f, 0.35f, 0.45f};
// what the rain and the snow are like: the speed of the fall (m/s), how long a streak lasts (s, the eye's or the camera's
// exposure), opacity, how much a drop or a flake scatters the light onwards and how bright it is against a white surface
struct precipitation_kind
{
float fall;
float exposure;
float opacity;
float forward;
float brightness;
};
precipitation_kind constexpr RAIN{7.f, 0.025f, 0.5f, 0.6f, 0.6f};
precipitation_kind constexpr SNOW{1.1f, 0.01f, 0.85f, 0.25f, 0.9f};
// the share of the wind of the environment the drops and flakes drift with (as the old cylinder leant with it)
double constexpr WIND_SHARE{0.5};
// the camera faster than this from a frame to the next was moved, not driven or flown
double constexpr CAMERA_JUMP{120.0};
} // namespace
bool gpu_particles::init()
{
if (m_state != 0)
return m_state > 0;
m_state = -1;
if (false == gpu_init())
return false;
m_state = 1;
return true;
}
bool gpu_particles::smoke_available()
{
return init();
}
bool gpu_particles::precipitation_available()
{
return init();
}
void gpu_particles::clear()
{
m_pool.clear();
m_requested.clear();
m_poolfull = false;
m_sourcerecords.clear();
m_places = 0;
m_storage = false;
gpu_clear();
m_precipitationcapacity = 0;
m_originset = false;
m_lastupdate = 0;
}
void gpu_particles::layout(particle_manager &Particles)
{
auto const &sources{Particles.sequence()};
// the sources already laid out which asked for the same places since
std::size_t kept{0};
while ((kept < m_requested.size()) && (kept < sources.size()) && (m_requested[kept] == sources[kept].max_particles()))
++kept;
if ((kept == m_requested.size()) && (kept == sources.size()) && m_storage)
return;
if (kept < m_requested.size())
{
// a source went away or changed: all anew
kept = 0;
m_pool.clear();
m_requested.clear();
m_places = 0;
m_poolfull = false;
}
auto const oldplaces{m_places};
auto first{m_places};
m_pool.resize(sources.size());
m_requested.resize(sources.size());
for (std::size_t i = kept; i < sources.size(); ++i)
{
m_requested[i] = sources[i].max_particles();
auto const size{static_cast<std::uint32_t>(std::min<std::size_t>(m_requested[i], MAX_SMOKE_PLACES))};
m_pool[i] = pool_source{};
if (first + size > MAX_SMOKE_PLACES)
{
if (false == m_poolfull)
WriteLog("particles: the smoke of some sources left out, over " + std::to_string(MAX_SMOKE_PLACES) + " places of particles");
m_poolfull = true;
continue;
}
m_pool[i].first = first;
m_pool[i].size = size;
first += size;
}
m_places = first;
std::vector<std::uint32_t> owners(std::max<std::uint32_t>(m_places, 1), 0);
for (std::size_t i = 0; i < m_pool.size(); ++i)
std::fill_n(owners.begin() + m_pool[i].first, m_pool[i].size, static_cast<std::uint32_t>(i));
// the particles of the places kept go over to the new storage, the new places are dead
auto const places{std::max<std::uint32_t>(m_places, 1)};
gpu_layout(places, (m_storage && (oldplaces > 0) && (kept > 0)) ? oldplaces : 0, owners, sources.size());
m_storage = true;
m_sourcerecords.resize(sources.size(), gl::particle_smoke_source{});
}
void gpu_particles::simulate_smoke(particle_manager &Particles, glm::dvec3 const &Camera)
{
if (false == init())
return;
layout(Particles);
if (Particles.updates() == m_lastupdate)
return;
m_lastupdate = Particles.updates();
if (m_places == 0)
return;
// the origin of the positions near the camera; when it moves, the particles move by as much the other way
glm::vec3 shift{0.f};
auto const snapped{glm::floor(Camera / ORIGIN_STEP) * ORIGIN_STEP};
if (false == m_originset)
{
m_origin = snapped;
m_originset = true;
}
else if (glm::length(Camera - m_origin) > ORIGIN_REACH)
{
shift = glm::vec3{m_origin - snapped};
m_origin = snapped;
}
// the light of the smoke as the cpu works it out, for gfx.smoke.lighting off
auto const oldlight{(glm::vec3{Global.DayLight.ambient} + 0.35f * glm::vec3{Global.DayLight.diffuse}) * simulation::Environment.light_intensity()};
auto const &sources{Particles.sequence()};
m_smokealive = 0;
for (std::size_t i = 0; i < sources.size(); ++i)
{
auto const &state{sources[i].gpu()};
auto &pool{m_pool[i]};
auto &record{m_sourcerecords[i]};
auto const spawns{(pool.size > 0) ? std::min(state.spawns, pool.size) : 0u};
record.pool = glm::uvec4{pool.first, pool.size, pool.head, spawns};
record.location = glm::vec4{glm::vec3{state.location - m_origin}, 0.f};
record.previous = glm::vec4{glm::vec3{state.previous - m_origin}, 0.f};
record.launch = glm::vec4{state.inclination[0], state.inclination[1], state.velocity[0], state.velocity[1]};
record.initial = glm::vec4{state.size[0], state.size[1], state.opacity[0], state.opacity[1]};
record.sizechange = glm::vec4{state.sizechange[0], state.sizechange[1], state.sizechange[2], state.spread};
record.opacitychange = glm::vec4{state.opacitychange[0], state.opacitychange[1], state.opacitychange[2], 0.f};
record.motion = glm::vec4{state.rise, state.damping, state.risemin, 0.f};
record.settling = glm::vec4{state.settling, state.drag, state.settles ? 1.f : 0.f, state.fadein};
record.color = glm::vec4{glm::clamp(state.color, glm::vec3{0.f}, glm::vec3{1.f}), 0.f};
record.oldcolor = glm::vec4{glm::clamp(state.color * oldlight, glm::vec3{0.f}, glm::vec3{1.f}), 0.f};
if (pool.size > 0)
{
pool.head = (pool.head + spawns) % pool.size;
if (spawns > 0)
m_smokealive += pool.size;
}
}
smoke_simulation simulation;
simulation.places = m_places;
simulation.step = glm::vec4{static_cast<float>(Particles.timedelta()), shift.x, shift.y, shift.z};
auto const wind{glm::vec3{simulation::Environment.wind()}};
simulation.wind = glm::vec4{wind.x, wind.y, wind.z, 0.f};
simulation.seed = static_cast<std::uint32_t>(m_lastupdate);
gpu_simulate(simulation, m_sourcerecords);
}
std::array<glm::vec4, 5> gpu_particles::planes(view const &View)
{
// the sides and the near side of the clip space, w >= 0 (the far plane is at infinity)
auto const matrix{glm::transpose(View.projection * View.modelview)};
std::array<glm::vec4, 5> result{matrix[3] + matrix[0], matrix[3] - matrix[0], matrix[3] + matrix[1], matrix[3] - matrix[1], matrix[3]};
for (auto &plane : result)
{
auto const length{glm::length(glm::vec3{plane})};
if (length > 0.f)
plane /= length;
}
return result;
}
std::size_t gpu_particles::render_smoke(view const &View)
{
if ((false == init()) || (m_places == 0))
return 0;
smoke_draw draw;
draw.places = m_places;
draw.origin = glm::vec3{m_origin - View.camera};
draw.view = View.modelview;
draw.options = glm::vec4{Global.gfx_smoke_lighting ? 1.f : 0.f, SMOKE_FORWARD, static_cast<float>(m_places), 0.f};
draw.planes = planes(View);
draw.soft = glm::vec4{View.soft ? Global.gfx_particles_soft : 0.f, 0.f, 0.f, 0.f};
gpu_render_smoke(draw, m_smokedrawn);
return m_smokedrawn;
}
std::size_t gpu_particles::render_precipitation(view const &View, bool const Snow, float const Intensity, unsigned int const Frame)
{
if (false == init())
return 0;
gpu_collect_precipitation(m_precipitationdrawn);
auto const &kind{Snow ? SNOW : RAIN};
// once per frame: the time moves on, the particles drift with the wind and fall, the camera has its velocity
if ((false == m_framestarted) || (Frame != m_frame))
{
m_framestarted = true;
m_frame = Frame;
auto const seconds{Timer::GetDeltaTime()};
if (seconds > 0.0)
{
auto const wind{glm::dvec3{simulation::Environment.wind()} * WIND_SHARE};
for (std::size_t box = 0; box < BOX_SIZE.size(); ++box)
{
auto const size{static_cast<double>(BOX_SIZE[box])};
m_drift[box] = glm::mod(m_drift[box] + wind * seconds, size);
m_fall[box] = std::fmod(m_fall[box] + kind.fall * seconds, 16.0 * size);
}
m_time = std::fmod(m_time + seconds, 3600.0);
if (m_camerastarted)
{
auto const velocity{(View.camera - m_lastcamera) / seconds};
if (glm::length(velocity) < CAMERA_JUMP)
m_cameravelocity = glm::mix(m_cameravelocity, velocity, 1.0 - std::exp(-10.0 * seconds));
}
}
m_lastcamera = View.camera;
m_camerastarted = true;
}
// the particles, more of them in heavier rain or snow
auto const most{static_cast<std::uint32_t>(std::max(Global.gfx_precipitation_particles, 0))};
auto const count{static_cast<std::uint32_t>(most * std::clamp(0.15f + 0.85f * Intensity, 0.f, 1.f))};
m_precipitationcount = count;
m_precipitationsnow = Snow;
if (count == 0)
return 0;
if (m_precipitationcapacity < count)
{
m_precipitationcapacity = std::max(count, most);
gpu_precipitation_storage(m_precipitationcapacity);
}
precipitation_draw draw;
draw.count = count;
auto const second{static_cast<std::uint32_t>(count * BOX_SHARE[0])};
auto const third{static_cast<std::uint32_t>(count * (BOX_SHARE[0] + BOX_SHARE[1]))};
draw.counts = glm::uvec4{second, third, count, Snow ? 1u : 0u};
for (std::size_t box = 0; box < BOX_SIZE.size(); ++box)
{
auto const size{static_cast<double>(BOX_SIZE[box])};
draw.offsets[box] = glm::vec4{glm::vec3{glm::mod(m_drift[box] - glm::mod(View.camera, size), size)}, static_cast<float>(m_fall[box])};
}
draw.sizes = glm::vec4{BOX_SIZE[0], BOX_SIZE[1], BOX_SIZE[2], 0.f};
auto const velocity{glm::vec3{glm::dvec3{simulation::Environment.wind()} * WIND_SHARE - m_cameravelocity}};
draw.velocity = glm::vec4{velocity.x, velocity.y, velocity.z, kind.fall};
draw.timing = glm::vec4{static_cast<float>(m_time), kind.exposure, kind.opacity, static_cast<float>(m_precipitationcapacity)};
draw.view = View.modelview;
draw.options = glm::vec4{1.f, kind.forward, kind.brightness, 0.f};
draw.planes = planes(View);
// the width of a pixel a metre in front of the camera, for the least width of a streak
auto const pixel{(View.height > 0) && (View.projection[1][1] != 0.f) ? 2.f / (std::abs(View.projection[1][1]) * static_cast<float>(View.height)) : 0.f};
draw.soft = glm::vec4{View.soft ? 0.25f * Global.gfx_particles_soft : 0.f, 0.f, 0.f, 0.f};
draw.pixel = glm::vec4{pixel, 0.f, 0.f, 0.f};
gpu_render_precipitation(draw);
return m_precipitationdrawn;
}
std::string gpu_particles::info() const
{
if (m_state <= 0)
return {};
std::string text;
if (m_places > 0)
text += " smoke of the gpu: " + std::to_string(m_smokedrawn) + " drawn, " + std::to_string(m_places) + " places of " + std::to_string(m_pool.size()) + " sources ("
+ std::to_string(m_smokealive) + " of the ones which bore lately)" + (m_poolfull ? ", some sources left out" : "") + "\n";
if (m_precipitationcount > 0)
text += std::string(" ") + (m_precipitationsnow ? "snow" : "rain") + " of the gpu: " + std::to_string(m_precipitationdrawn) + " of " + std::to_string(m_precipitationcount)
+ " drawn\n";
return text;
}
} // namespace gfx

View File

@@ -0,0 +1,185 @@
/*
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/.
*/
#pragma once
#include <array>
#include <string>
#include <vector>
#include <glm/glm.hpp>
#include "gl/ubo.h"
class particle_manager;
namespace gfx
{
// particles of the gpu, whichever api draws them: the smoke and the rain or snow.
//
// smoke: the sources (particle_manager in its gpu mode) work out on the cpu only how many particles they bear in an update of
// the simulation and what these start from; the particles are kept in a storage buffer, a ring of places for each source, and
// simulated by a compute pass once per update (particles_smoke_simulate.comp). in every colour pass which draws them another
// compute pass (particles_smoke_prepare.comp) leaves out the dead ones and the ones out of the view, lights the rest with the
// lights of the pass and adds them to an indirect draw of billboards (particles_smoke.vert/.frag).
//
// rain and snow: particles in three boxes around the camera, each repeated along the world axes, which keep no state: their
// place follows from their number, how far the wind took them and how far they fell (particles_precipitation.comp), drawn as
// streaks of their motion against the camera (particles_precipitation.vert/.frag).
//
// the positions of the smoke are kept from an origin near the camera, moved by whole steps as the camera goes, so they keep
// their precision far from the centre of the scenery. what the passes are given is worked out here; the buffers, the compute
// passes and the draws are the renderer's (the gpu_ functions)
class gpu_particles
{
public:
gpu_particles() = default;
virtual ~gpu_particles() = default;
gpu_particles(gpu_particles const &) = delete;
gpu_particles &operator=(gpu_particles const &) = delete;
// what a colour pass draws with
struct view
{
glm::mat4 modelview; // world axes around the camera to the view space of the pass
glm::mat4 projection;
glm::dvec3 camera;
int height{0}; // of the target, pixels
bool soft{false}; // the resolved depth of the scene drawn so far is bound to the depth of the scene of the pass
};
// whether the programs could be made; without them the smoke stays on the cpu and the rain is the old cylinder
bool smoke_available();
bool precipitation_available();
// once per update of the particle manager (in its gpu mode): the births and the change of the smoke. Camera: of the view
void simulate_smoke(particle_manager &Particles, glm::dvec3 const &Camera);
// draws the smoke the view sees, with the texture bound to unit 0. returns: particles drawn, as read back a few frames later
std::size_t render_smoke(view const &View);
// draws the rain (Snow false) or the snow of the intensity 0-1. Frame: number of the frame, the time moves on once per frame.
// returns: particles drawn, read back
std::size_t render_precipitation(view const &View, bool Snow, float Intensity, unsigned int Frame);
// drops what is on the gpu, e.g. when the context goes
void clear();
std::string info() const;
protected:
// the uniforms of particles_smoke_simulate.comp
struct smoke_simulation
{
std::uint32_t places{0};
glm::vec4 step{0.f}; // x: seconds of the update, yzw: added to the positions, as the origin moved
glm::vec4 wind{0.f};
std::uint32_t seed{0};
};
// the uniforms of particles_smoke_prepare.comp and of the draw
struct smoke_draw
{
std::uint32_t places{0};
glm::vec3 origin{0.f}; // of the particles less the camera
glm::mat4 view{1.f};
glm::vec4 options{0.f}; // x: lit by the lights of the pass, y: scattering onwards, z: places for the draws
std::array<glm::vec4, 5> planes{};
glm::vec4 soft{0.f}; // of particles_smoke.vert
};
// the uniforms of particles_precipitation.comp and of the draw
struct precipitation_draw
{
glm::uvec4 counts{0u};
std::array<glm::vec4, 3> offsets{};
glm::vec4 sizes{0.f};
glm::vec4 velocity{0.f};
glm::vec4 timing{0.f};
glm::mat4 view{1.f};
glm::vec4 options{0.f};
std::array<glm::vec4, 5> planes{};
glm::vec4 soft{0.f}; // of particles_precipitation.vert
glm::vec4 pixel{0.f};
std::uint32_t count{0}; // particles, the threads of the compute pass
};
// the programs; false: the particles of the gpu can't be used
virtual bool gpu_init() = 0;
// new storage of the smoke: Places places (at least 1) of particles, all dead but the first Kept of the storage before, which
// keep what they had; the storage of the draws for as many particles, the source of every place (Owners) and room for the
// records of Sources sources
virtual void gpu_layout(std::uint32_t const Places, std::uint32_t const Kept, std::vector<std::uint32_t> const &Owners, std::size_t const Sources) = 0;
// the records of the sources (when there are any) and the compute pass of the simulation
virtual void gpu_simulate(smoke_simulation const &Simulation, std::vector<gl::particle_smoke_source> const &Records) = 0;
// the compute pass and the draw of the smoke the view sees; Drawn: the particles drawn, as the last read back gives them (left
// as it is when nothing came back yet)
virtual void gpu_render_smoke(smoke_draw const &Draw, std::size_t &Drawn) = 0;
// the particles of the rain or snow drawn, as the last read back gives them
virtual void gpu_collect_precipitation(std::size_t &Drawn) = 0;
// room for the draws of Capacity particles of the rain or snow
virtual void gpu_precipitation_storage(std::uint32_t const Capacity) = 0;
// the compute pass and the draw of the rain or snow
virtual void gpu_render_precipitation(precipitation_draw const &Draw) = 0;
// the storage goes (the draw commands and the read backs stay)
virtual void gpu_clear() = 0;
std::uint32_t places() const
{
return m_places;
}
std::uint32_t precipitation_count() const
{
return m_precipitationcount;
}
bool precipitation_snow() const
{
return m_precipitationsnow;
}
private:
struct pool_source
{
std::uint32_t first{0};
std::uint32_t size{0};
std::uint32_t head{0};
};
bool init();
// the places of the sources in the storage: sources added after the others get theirs after the places already given, and
// the particles stay; otherwise all is laid out anew
void layout(particle_manager &Particles);
// the planes of the view of the pass, for the culling of the compute passes
static std::array<glm::vec4, 5> planes(view const &View);
int m_state{0}; // 0: not set up yet, 1: ready, -1: can't be used
// smoke
std::vector<pool_source> m_pool;
std::vector<std::size_t> m_requested; // the sizes the sources asked for
std::vector<gl::particle_smoke_source> m_sourcerecords;
std::uint32_t m_places{0};
bool m_poolfull{false}; // some sources got no places
bool m_storage{false}; // the storage of the smoke was made
std::uint64_t m_lastupdate{0};
glm::dvec3 m_origin{0.0};
bool m_originset{false};
std::size_t m_smokedrawn{0};
std::size_t m_smokealive{0}; // places of the sources which bore particles lately
// rain and snow
std::uint32_t m_precipitationcapacity{0};
unsigned int m_frame{0};
bool m_framestarted{false};
std::array<glm::dvec3, 3> m_drift{}; // how far the wind took the particles of each box, within the box
std::array<double, 3> m_fall{}; // how far they fell, within 16 sizes of the box
double m_time{0.0}; // seconds, for the flutter of the flakes
glm::dvec3 m_lastcamera{0.0};
glm::dvec3 m_cameravelocity{0.0};
bool m_camerastarted{false};
std::size_t m_precipitationdrawn{0};
std::uint32_t m_precipitationcount{0};
bool m_precipitationsnow{false};
};
} // namespace gfx

View File

@@ -0,0 +1,269 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/common/grassfield.h"
#include "simulation/simulationenvironment.h"
#include "simulation/simulationtime.h"
#include "utilities/Globals.h"
#include "utilities/Timer.h"
namespace gfx
{
namespace
{
int constexpr max_blades{65536}; // drawn in a tile at most
float constexpr blade_reach{1.5f}; // metres the bounds of a tile reach above its ground, for the blades
// the share of the blades drawn at specified distance: fewer further away, for speed (from Thinning), and fewer and fewer from
// Fade to Reach, where the grass ends; the same as lod_share() of grass.vert
float lod_share(float const Distance, float const Thinning, float const Fade, float const Reach)
{
auto const share{Distance <= Thinning ? 1.f : std::pow(Thinning / Distance, 1.5f)};
auto const t{std::clamp((Distance - Fade) / std::max(Reach - Fade, 1e-3f), 0.f, 1.f)};
return share * (1.f - t * t * (3.f - 2.f * t));
}
// the grass through the year, by the day of the year: x how dry it is, y how fresh (the green of spring), z how grown (share
// of the height). the turns follow the seasons of the textures (world_environment::compute_season(): winter till day 65,
// spring till 158, summer till 252, autumn till 341): dormant and dry in winter, greening up and growing through spring,
// fresh in May, drying out from August, the dry grass lying lower towards winter
glm::vec3 grass_season(int const Yearday)
{
struct point
{
float day;
glm::vec3 value;
};
static point const year[]{{1.f, {0.80f, 0.f, 0.55f}}, {65.f, {0.80f, 0.f, 0.55f}}, {100.f, {0.45f, 0.3f, 0.70f}}, {135.f, {0.05f, 1.f, 0.90f}},
{170.f, {0.f, 0.4f, 1.f}}, {200.f, {0.10f, 0.f, 1.f}}, {240.f, {0.35f, 0.f, 1.f}}, {280.f, {0.50f, 0.f, 0.95f}},
{320.f, {0.65f, 0.f, 0.80f}}, {341.f, {0.80f, 0.f, 0.60f}}, {367.f, {0.80f, 0.f, 0.55f}}};
auto const day{std::clamp(static_cast<float>(Yearday), 1.f, 366.f)};
for (std::size_t i = 1; i < std::size(year); ++i)
if (day <= year[i].day)
{
auto const t{(day - year[i - 1].day) / (year[i].day - year[i - 1].day)};
return glm::mix(year[i - 1].value, year[i].value, t * t * (3.f - 2.f * t));
}
return year[std::size(year) - 1].value;
}
} // namespace
void grass_field::release(std::uint64_t const Id, chunk &Chunk)
{
if (Chunk.uploaded)
gpu_release(Id);
Chunk.uploaded = false;
}
void grass_field::clear()
{
for (auto &entry : m_chunks)
release(entry.first, entry.second);
m_chunks.clear();
}
void grass_field::upload(std::uint64_t const Id, chunk &Chunk, gfx::grass_chunk const &Source)
{
release(Id, Chunk);
Chunk.version = Source.version;
Chunk.corner = Source.corner;
Chunk.size = Source.size;
Chunk.cells = Source.cells;
Chunk.samples = Source.samples;
Chunk.kinds = Source.kinds;
gpu_upload(Id, Source);
Chunk.uploaded = true;
auto const side{Source.cells + 1};
auto const samples{Source.samples + 1};
auto const plane{static_cast<std::size_t>(samples) * samples};
std::size_t kinds{0};
while (kinds < Source.kinds.size() && Source.kinds[kinds] >= 0)
++kinds;
// the ground and the densest place of every kind of each tile, to skip the empty tiles and to draw as many blades as needed
auto const spacing{static_cast<double>(Source.size) / Source.cells};
auto const step{static_cast<double>(Source.size) / Source.samples};
auto const tilesize{static_cast<double>(Source.size) / tiles};
for (int tz = 0; tz < tiles; ++tz)
for (int tx = 0; tx < tiles; ++tx)
{
auto &part{Chunk.parts[static_cast<std::size_t>(tz) * tiles + tx]};
auto const range = [](double const From, double const To, double const Step, int const Last) {
return std::make_pair(std::clamp(static_cast<int>(std::floor(From / Step)), 0, Last), std::clamp(static_cast<int>(std::ceil(To / Step)), 0, Last));
};
auto const [gx0, gx1]{range(tx * tilesize, (tx + 1) * tilesize, spacing, Source.cells)};
auto const [gz0, gz1]{range(tz * tilesize, (tz + 1) * tilesize, spacing, Source.cells)};
part.low = std::numeric_limits<float>::max();
part.high = -std::numeric_limits<float>::max();
for (int z = gz0; z <= gz1; ++z)
for (int x = gx0; x <= gx1; ++x)
{
auto const height{Source.heights[static_cast<std::size_t>(z) * side + x]};
part.low = std::min(part.low, height);
part.high = std::max(part.high, height);
}
part.most.fill(0.f);
auto const [sx0, sx1]{range(tx * tilesize, (tx + 1) * tilesize, step, Source.samples)};
auto const [sz0, sz1]{range(tz * tilesize, (tz + 1) * tilesize, step, Source.samples)};
for (std::size_t kind = 0; kind < kinds; ++kind)
{
std::uint8_t most{0};
for (int z = sz0; z <= sz1; ++z)
for (int x = sx0; x <= sx1; ++x)
most = std::max(most, Source.density[kind * plane + static_cast<std::size_t>(z) * samples + x]);
part.most[kind] = most / 255.f;
}
}
}
void grass_field::update(std::vector<gfx::grass_kind> const &Kinds, std::vector<gfx::grass_chunk> const &Chunks)
{
m_kinds = Kinds;
for (auto &entry : m_chunks)
entry.second.listed = false;
if (false == Global.gfx_grass)
{
clear();
return;
}
for (auto const &source : Chunks)
{
if (source.heights == nullptr || source.density == nullptr || source.cells <= 0 || source.samples <= 0)
continue;
auto &entry{m_chunks[source.id]};
entry.listed = true;
if (false == entry.uploaded || entry.version != source.version)
upload(source.id, entry, source);
}
// what's no longer near goes
for (auto it = m_chunks.begin(); it != m_chunks.end();)
{
if (it->second.listed)
{
++it;
continue;
}
release(it->first, it->second);
it = m_chunks.erase(it);
}
}
bool grass_field::prepare(glm::dvec3 const &Camera, std::function<bool(scene::bounding_area const &)> const &Visible)
{
if (false == Global.gfx_grass || m_chunks.empty() || m_kinds.empty())
return false;
m_chunkdraws.clear();
m_tiledraws.clear();
m_triangles = 0;
// whole up to the distance, thinning out to the fading distance
auto const fade{std::max(5.f, Global.gfx_grass_distance)};
auto const reach{std::max(fade + 1.f, Global.gfx_grass_fadedistance)};
auto const thinning{std::max(3.f, fade * 0.2f)};
auto const multiplier{std::max(0.f, Global.gfx_grass_density)};
auto const camera{Camera};
m_frame.lod = glm::vec4{thinning, fade, reach, 0.f};
auto const season{grass_season(simulation::Time.year_day())};
m_frame.season = glm::vec4{season.x, season.y, season.z, 0.f};
// the wind sways the blades along it, harder in the gusts
auto const wind{glm::vec3{simulation::Environment.wind()}};
auto const windspeed{glm::length(glm::vec2{wind.x, wind.z})};
auto const winddirection{windspeed > 0.01f ? glm::vec2{wind.x, wind.z} / windspeed : glm::vec2{1.f, 0.f}};
m_frame.wind = glm::vec4{winddirection.x, winddirection.y, std::clamp(windspeed / 15.f, 0.f, 1.f), static_cast<float>(std::fmod(Timer::GetTime(), 3600.0))};
for (auto &entry : m_chunks)
{
auto &chunk{entry.second};
// the kinds of the chunk, and the share of the blades of each against its densest
std::array<glm::vec4, 4> base{}, tip{}, shape{}, dry{};
glm::vec4 share{0.f};
float densest{0.f};
for (std::size_t i = 0; i < chunk.kinds.size(); ++i)
if (chunk.kinds[i] >= 0 && chunk.kinds[i] < static_cast<int>(m_kinds.size()))
densest = std::max(densest, m_kinds[chunk.kinds[i]].density);
if (densest <= 0.f)
continue;
for (std::size_t i = 0; i < chunk.kinds.size(); ++i)
{
if (chunk.kinds[i] < 0 || chunk.kinds[i] >= static_cast<int>(m_kinds.size()))
continue;
auto const &kind{m_kinds[chunk.kinds[i]]};
base[i] = glm::vec4{kind.base, 0.f};
tip[i] = glm::vec4{kind.tip, 0.f};
shape[i] = glm::vec4{kind.height, kind.variation, kind.width, kind.bend};
dry[i] = glm::vec4{kind.dry, kind.seasonal};
share[i] = kind.density / densest;
}
auto const tallest{std::max({shape[0].x, shape[1].x, shape[2].x, shape[3].x}) * 1.5f + blade_reach};
auto const tilesize{chunk.size / tiles};
bool set{false};
for (int tz = 0; tz < tiles; ++tz)
for (int tx = 0; tx < tiles; ++tx)
{
auto const &part{chunk.parts[static_cast<std::size_t>(tz) * tiles + tx]};
auto const fill{std::min(1.f, glm::dot(glm::vec4{part.most[0], part.most[1], part.most[2], part.most[3]}, share))};
if (fill <= 0.f)
continue;
// distance to the box of the tile
glm::dvec3 const low{chunk.corner.x + tx * tilesize, part.low, chunk.corner.y + tz * tilesize};
glm::dvec3 const high{low.x + tilesize, part.high + tallest, low.z + tilesize};
auto const nearest{glm::clamp(camera, low, high)};
auto const distance{static_cast<float>(glm::length(nearest - camera))};
if (distance > reach)
continue;
if (false == Visible(scene::bounding_area{(low + high) * 0.5, static_cast<float>(glm::length(high - low) * 0.5)}))
continue;
auto const full{tilesize * tilesize * densest * multiplier * fill};
auto const count{std::min(max_blades, static_cast<int>(std::ceil(full * lod_share(distance, thinning, fade, reach))))};
if (count <= 0)
continue;
if (false == set)
{
// the chunk is drawn relative to its corner, the heights as they are
set = true;
chunk_draw draw;
draw.id = entry.first;
draw.offset = glm::dvec3{chunk.corner.x - camera.x, -camera.y, chunk.corner.y - camera.z};
draw.chunk = glm::vec4{chunk.size, static_cast<float>(chunk.cells), static_cast<float>(chunk.samples), 0.f};
draw.camera = glm::vec4{static_cast<float>(camera.x - chunk.corner.x), static_cast<float>(camera.y), static_cast<float>(camera.z - chunk.corner.y), 0.f};
draw.base = base;
draw.tip = tip;
draw.shape = shape;
draw.share = share;
draw.dry = dry;
draw.first = m_tiledraws.size();
m_chunkdraws.push_back(draw);
}
// fewer segments further away
auto const segments{distance < thinning * 1.5f ? 3 : distance < fade * 0.5f ? 2 : 1};
// the number of the tile in the world, the seed of its blades
auto const seedx{static_cast<float>(std::floor(chunk.corner.x / tilesize + 0.5) + tx)};
auto const seedz{static_cast<float>(std::floor(chunk.corner.y / tilesize + 0.5) + tz)};
tile_draw draw;
draw.tile = glm::vec4{tx * tilesize, tz * tilesize, tilesize, std::max(1.f, full)};
draw.part = glm::vec4{fill, static_cast<float>(segments), seedx, seedz};
draw.count = count;
draw.segments = segments;
m_tiledraws.push_back(draw);
++m_chunkdraws.back().count;
m_triangles += static_cast<std::size_t>(count) * (segments * 2 - 1);
}
}
return true;
}
} // namespace gfx

View File

@@ -0,0 +1,133 @@
/*
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/.
*/
#pragma once
#include <array>
#include <functional>
#include <unordered_map>
#include <vector>
#include <glm/glm.hpp>
#include "rendering/renderer.h"
#include "scene/scenenode.h"
namespace gfx
{
// grass of the heightmap terrain, whichever api draws it: blades made up by the vertex shader (grass.vert) where the grass is
// painted, in the chunks near the camera.
//
// a chunk is cut into tiles; each tile visible within the reach of the grass is drawn as instances of a blade, as many as
// its densest kind and its distance ask for. a blade finds its place, its kind and its shape from its number, so the same
// blades stand at the same places in every frame. further away the blades thin out and grow wider, and fade out at the end
// of the reach. the heights, the densities and the holes of a chunk are kept in textures, made by the renderer (the gpu_
// functions) when the chunk comes near; what is drawn in a frame is worked out here (prepare())
class grass_field
{
public:
virtual ~grass_field() = default;
// see gfx_renderer::Terrain_Grass()
void update(std::vector<gfx::grass_kind> const &Kinds, std::vector<gfx::grass_chunk> const &Chunks);
// drops everything made on the gpu, e.g. when the context goes
void clear();
bool empty() const
{
return m_chunks.empty();
}
static int constexpr tiles{16}; // along a side of a chunk
// what a frame draws (the uniforms of grass.vert): the same for all draws
struct frame_draw
{
glm::vec4 lod{0.f}; // grass_lod
glm::vec4 season{0.f}; // grass_season
glm::vec4 wind{0.f}; // grass_wind
};
// a chunk with tiles seen, drawn relative to its corner
struct chunk_draw
{
std::uint64_t id{0}; // of the chunk, as the renderer got it in gpu_upload()
glm::dvec3 offset{0.0}; // of the corner from the camera, the heights as they are
glm::vec4 chunk{0.f}; // grass_chunk
glm::vec4 camera{0.f}; // grass_camera
std::array<glm::vec4, 4> base{}, tip{}, shape{}, dry{};
glm::vec4 share{0.f};
std::size_t first{0}; // tiles drawn, in tile_draws()
std::size_t count{0};
};
// a tile drawn: Count instances of a strip of 2 * Segments + 1 vertices
struct tile_draw
{
glm::vec4 tile{0.f}; // grass_tile
glm::vec4 part{0.f}; // grass_part
int count{0};
int segments{0};
};
// the draws of the grass seen from Camera: false when there is nothing to draw (grass off, no chunks or kinds), the draws of
// the last call are left then. Visible: whether the bounds of a tile are seen
bool prepare(glm::dvec3 const &Camera, std::function<bool(scene::bounding_area const &)> const &Visible);
frame_draw const &frame() const
{
return m_frame;
}
std::vector<chunk_draw> const &chunk_draws() const
{
return m_chunkdraws;
}
std::vector<tile_draw> const &tile_draws() const
{
return m_tiledraws;
}
// the triangles of the draws prepared
std::size_t triangles() const
{
return m_triangles;
}
protected:
// the textures of the chunk Id made of Source: heights (R32F, the grid points, cells + 1 along a side), densities (RGBA8, the
// densities of the kinds side by side, samples + 1 along a side), holes (R8, a single texel of 0 without holes)
virtual void gpu_upload(std::uint64_t const Id, gfx::grass_chunk const &Source) = 0;
// the textures of the chunk go; nothing when it has none
virtual void gpu_release(std::uint64_t const Id) = 0;
private:
struct tile
{
float low{0.f}, high{0.f}; // of the ground
std::array<float, 4> most{}; // the highest density of each kind of the chunk within the tile, 0..1
};
struct chunk
{
std::uint64_t version{0};
glm::dvec2 corner{0.0};
float size{0.f};
int cells{0};
int samples{0};
std::array<int, 4> kinds{-1, -1, -1, -1};
std::array<tile, tiles * tiles> parts;
bool uploaded{false}; // has its textures
bool listed{false}; // in the last list given
};
void upload(std::uint64_t const Id, chunk &Chunk, gfx::grass_chunk const &Source);
void release(std::uint64_t const Id, chunk &Chunk);
std::vector<gfx::grass_kind> m_kinds;
std::unordered_map<std::uint64_t, chunk> m_chunks;
frame_draw m_frame;
std::vector<chunk_draw> m_chunkdraws;
std::vector<tile_draw> m_tiledraws;
std::size_t m_triangles{0};
};
} // namespace gfx

View File

@@ -0,0 +1,389 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/common/instancecull.h"
#include "model/AnimModel.h"
#include "model/Model3d.h"
#include "rendering/frustum.h"
#include "utilities/Globals.h"
#include "utilities/utilities.h"
namespace gfx
{
namespace
{
constexpr std::int64_t RECORD_SIZE{sizeof(scene::basic_cell::instance_record)};
} // namespace
instance_cull::instance_cull() : m_records{std::make_shared<record_state>()}
{
m_readbackdata.resize(gl::INSTANCE_COUNTER_HEADER + READBACK_ENTRIES);
}
void instance_cull::begin_pass(pass_setup const &Setup)
{
collect();
m_inpass = true;
m_ran = true;
m_dispatched = false;
m_setup = Setup;
m_buckets.clear();
m_joblist.clear();
m_grouplist.clear();
m_drawgroups.clear();
m_lodlist.clear();
m_groupmap.clear();
m_sleepergroupmap.clear();
m_cpucounts.clear();
m_candidates = 0;
m_entries = 0;
m_sleepertracks = 0;
m_sleepers = 0;
m_models = 0;
m_counts = {};
}
void instance_cull::add_bucket(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area)
{
add(Key, Bucket, Area, false);
}
void instance_cull::skip_bucket(scene::basic_cell::instance_bucket const &Bucket)
{
if (!m_inpass)
return;
++m_counts.skippedbuckets;
m_counts.skippedinstances += Bucket.records.size();
}
void instance_cull::add_sleepers(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area)
{
add(Key, Bucket, Area, true);
}
void instance_cull::add(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area, bool const Sleepers)
{
if (!m_inpass || Bucket.records.empty() || Key.pModel == nullptr)
return;
// range of the squared distances the instances of the bucket can be at, scaled as in the culling, with some slack for
// its single precision: the draws are made only for the lod bands within it. sleepers go by the plain distance from the
// camera of the pass
auto const &distancecamera{Sleepers ? m_setup.camera : m_setup.distancecamera};
auto const distancescale{Sleepers ? 1.0 : static_cast<double>(m_setup.distancescale)};
auto const distanceoffset{Sleepers ? 0.0 : static_cast<double>(m_setup.distanceoffset)};
auto const centerdistance{glm::length(Area.center - distancecamera)};
auto const nearest{std::max(0.0, centerdistance - Area.radius)};
auto const farthest{centerdistance + Area.radius};
auto const distancemin{static_cast<float>(0.999 * (nearest * nearest * distancescale + distanceoffset))};
auto const distancemax{static_cast<float>(1.001 * (farthest * farthest * distancescale + distanceoffset))};
auto &groupmap{Sleepers ? m_sleepergroupmap : m_groupmap};
auto lookup{groupmap.find(Key)};
if (lookup == groupmap.end())
{
// lod bounds of the model, the same set Render_Instanced() splits the distance into bands with, found once for the model
auto const lodfirst{static_cast<std::uint32_t>(m_lodlist.size())};
auto const &lodbounds{Key.pModel->lod_bounds()};
m_lodlist.insert(m_lodlist.end(), lodbounds.begin(), lodbounds.end());
auto const lodcount{static_cast<std::uint32_t>(lodbounds.size())};
group_data const group{static_cast<std::uint32_t>(m_grouplist.size()), m_entries, lodcount + 1, distancemin, distancemax};
m_grouplist.push_back({lodfirst, lodcount, m_entries, 0});
m_entries += group.entrycount;
lookup = groupmap.emplace(Key, group).first;
if (m_setup.drawing)
{
// any instance of the group serves; it's only prepared and asked for its material
TAnimModel *instance{nullptr};
for (auto *candidate : Bucket.instances)
{
if (candidate != nullptr)
{
instance = candidate;
break;
}
}
m_drawgroups.push_back({&lookup->first, instance, Sleepers, lodfirst, lodcount, group.entryfirst, distancemin, distancemax});
}
}
else
{
lookup->second.distancemin = std::min(lookup->second.distancemin, distancemin);
lookup->second.distancemax = std::max(lookup->second.distancemax, distancemax);
if (m_setup.drawing)
{
auto &drawgroup{m_drawgroups[lookup->second.index]};
drawgroup.distancemin = lookup->second.distancemin;
drawgroup.distancemax = lookup->second.distancemax;
if (drawgroup.instance == nullptr)
{
for (auto *candidate : Bucket.instances)
{
if (candidate != nullptr)
{
drawgroup.instance = candidate;
break;
}
}
}
}
}
place(Bucket);
gl::instance_cull_job job{};
job.origin = packed_vec3{Bucket.recordorigin - m_setup.camera};
job.first = Bucket.gpufirst;
job.distanceorigin = packed_vec3{Bucket.recordorigin - m_setup.distancecamera};
job.count = static_cast<std::uint32_t>(Bucket.records.size());
job.group = lookup->second.index;
job.candidatestart = m_candidates;
job.flags = (Sleepers ? gl::INSTANCE_JOB_SLEEPERS : 0u);
m_joblist.push_back(job);
m_buckets.push_back(&Bucket);
m_candidates += job.count;
if (Sleepers)
{
++m_sleepertracks;
m_sleepers += job.count;
}
else if (Key.instance != nullptr)
{
++m_models;
}
}
void instance_cull::place(scene::basic_cell::instance_bucket const &Bucket)
{
auto &shared{*m_records};
auto const count{static_cast<std::uint32_t>(Bucket.records.size())};
auto const current{Bucket.gpugeneration == shared.recordgeneration};
if (current && !Bucket.gpustale)
return;
if (current && count <= Bucket.gpucapacity)
{
send_records(Bucket.gpufirst, Bucket.records.data(), count);
m_counts.sentrecords += count;
Bucket.gpustale = false;
return;
}
// a new place, with some room for instances the editor adds
auto const allocate = [this, &shared](scene::basic_cell::instance_bucket const &Target) {
auto const records{static_cast<std::uint32_t>(Target.records.size())};
Target.gpufirst = shared.recordfill;
Target.gpucapacity = records + records / 4;
Target.gpugeneration = shared.recordgeneration;
Target.gpustale = false;
shared.recordfill += Target.gpucapacity;
send_records(Target.gpufirst, Target.records.data(), records);
m_counts.sentrecords += records;
};
auto const capacity{static_cast<std::uint32_t>(record_buffer_size() / RECORD_SIZE)};
if (shared.recordfill + count + count / 4 > capacity)
{
// the buffer is started over, and only the buckets of this pass get places again. the places left behind by
// buckets which were made anew or are out of sight are dropped this way. the passes of a frame queue different
// buckets: when those of all of them don't fit in the buffer together, some pass would start it over in every frame,
// so a buffer started over again in the same or the next frame grows
std::int64_t live{count + count / 4};
for (auto const *bucket : m_buckets)
live += bucket->records.size() + bucket->records.size() / 4;
auto const again{shared.restarted && (m_setup.frame - shared.restartframe <= 1)};
if ((2 * live * RECORD_SIZE > record_buffer_size()) || again)
resize_record_buffer(std::max(2 * live * RECORD_SIZE, 2 * record_buffer_size()));
else
restart_records();
shared.restarted = true;
shared.restartframe = m_setup.frame;
m_counts.restarted = true;
++shared.recordgeneration;
shared.recordfill = 0;
for (std::size_t job = 0; job < m_buckets.size(); ++job)
{
allocate(*m_buckets[job]);
m_joblist[job].first = m_buckets[job]->gpufirst;
}
}
allocate(Bucket);
}
bool instance_cull::dispatch_begin()
{
m_occlusion = false;
m_twophase = false;
return (m_inpass && !m_joblist.empty());
}
void instance_cull::frustum_planes(glm::vec4 (&Planes)[6]) const
{
auto const &frustumplanes{m_setup.frustum->planes()};
for (int plane = 0; plane < 6; ++plane)
{
auto const *source{frustumplanes[plane]};
Planes[plane] = glm::vec4{source[0], source[1], source[2],
static_cast<float>(source[0] * m_setup.camera.x + source[1] * m_setup.camera.y + source[2] * m_setup.camera.z + source[3])};
}
}
void instance_cull::dispatch_end()
{
m_cpucounts.assign(m_entries, 0);
m_dispatched = true;
}
void instance_cull::draw_result(std::uint32_t const Draws, std::uint32_t const Prepassdraws, std::uint32_t const Calls, std::uint32_t const Prepasscalls, double const Cputime)
{
m_draws = Draws;
m_prepassdraws = Prepassdraws;
m_calls = Calls;
m_prepasscalls = Prepasscalls;
m_cputime = Cputime;
}
void instance_cull::cpu_result(scene::basic_cell::instance_bucket_key const &Key, std::vector<std::uint32_t> const &Bandcounts, bool const Sleepers)
{
if (!m_dispatched)
return;
auto const &groupmap{Sleepers ? m_sleepergroupmap : m_groupmap};
auto const lookup{groupmap.find(Key)};
if (lookup == groupmap.end())
return;
auto const &group{lookup->second};
for (std::size_t band = 0; band < std::min<std::size_t>(Bandcounts.size(), group.entrycount); ++band)
m_cpucounts[group.entryfirst + band] += Bandcounts[band];
}
void instance_cull::cpu_instance(scene::basic_cell::instance_bucket_key const &Key, float const Distancesquared)
{
if (!m_dispatched)
return;
auto const lookup{m_groupmap.find(Key)};
if (lookup == m_groupmap.end())
return;
auto const &group{lookup->second};
auto const &lods{m_grouplist[group.index]};
// the band the culling gives the distance: the number of bounds it reached
auto const first{m_lodlist.begin() + lods.lodfirst};
auto const band{static_cast<std::uint32_t>(std::upper_bound(first, first + lods.lodcount, Distancesquared) - first)};
if (band < group.entrycount)
++m_cpucounts[group.entryfirst + band];
}
void instance_cull::end_pass()
{
// the counts of both phases, for the panel and the comparison
if (m_dispatched)
push_counts();
// the cpu counts are there to compare with only when the cpu drew
if (m_dispatched && !m_setup.drawing)
{
std::uint64_t total{0};
for (auto const count : m_cpucounts)
total += count;
m_cpupasses.push_back({m_setup.frame, m_cpucounts, total});
// the gpu is never this far behind
while (m_cpupasses.size() > 8)
m_cpupasses.pop_front();
}
m_lastsleepertracks = m_sleepertracks;
m_lastsleepers = m_sleepers;
m_lastmodels = m_models;
m_counts.jobs = static_cast<std::uint32_t>(m_joblist.size());
m_counts.candidates = m_candidates;
m_lastcounts = m_counts;
m_inpass = false;
m_dispatched = false;
}
void instance_cull::collect()
{
if (!pop_counts(m_readbackdata.data()))
return;
auto const frame{m_readbackdata[0]};
comparison result;
result.frame = frame;
result.occluded = m_readbackdata[3];
result.gputotal = m_readbackdata[1] - std::min(m_readbackdata[1], result.occluded);
result.entries = m_readbackdata[2];
result.first = m_readbackdata[4];
while (!m_cpupasses.empty() && m_cpupasses.front().frame != frame)
m_cpupasses.pop_front();
if (m_cpupasses.empty())
{
// drawn from the gpu: nothing to compare with
m_last = result;
return;
}
auto const &cpu{m_cpupasses.front()};
result.cputotal = cpu.total;
result.compared = std::min<std::uint32_t>({result.entries, READBACK_ENTRIES, static_cast<std::uint32_t>(cpu.counts.size())});
for (std::uint32_t entry = 0; entry < result.compared; ++entry)
{
auto const gpu{m_readbackdata[gl::INSTANCE_COUNTER_HEADER + entry]};
auto const difference{gpu > cpu.counts[entry] ? gpu - cpu.counts[entry] : cpu.counts[entry] - gpu};
if (difference > 0)
{
++result.mismatched;
result.largestdifference = std::max(result.largestdifference, difference);
}
}
m_cpupasses.pop_front();
m_last = result;
++m_comparedframes;
if ((result.mismatched > 0) || (result.gputotal != result.cputotal) || (result.entries != cpu.counts.size()))
++m_mismatchedframes;
}
std::string instance_cull::info(std::string const &Label, bool const Occlusion)
{
if (Global.gfx_gpucull == 0)
return {};
// the sleepers are among the instances counted
auto sleepers{m_lastsleepertracks > 0 ? "\n" + Label + ": " + std::to_string(m_lastsleepers) + " sleepers of " + std::to_string(m_lastsleepertracks) + " tracks queued" : std::string{}};
// and so are the models drawn on their own
if (m_lastmodels > 0)
sleepers += "\n" + Label + ": " + std::to_string(m_lastmodels) + " models drawn on their own queued";
// what the pass tested and sent to the gpu
sleepers += "\n" + Label + ": " + std::to_string(m_lastcounts.candidates) + " instances tested in " + std::to_string(m_lastcounts.jobs) + " buckets";
if (Global.gfx_gpucull_bucketrange)
sleepers += ", " + std::to_string(m_lastcounts.skippedbuckets) + " buckets (" + std::to_string(m_lastcounts.skippedinstances) + " instances) left out before the culling";
sleepers += ", " + std::to_string(m_lastcounts.sentrecords) + " records sent" + (m_lastcounts.restarted ? ", record buffer started over" : "");
if (Global.gfx_gpucull == 1)
{
if (m_last.frame == 0)
return "\n" + Label + ": no result yet" + sleepers;
if (false == Occlusion)
return "\n" + Label + ": " + std::to_string(m_last.gputotal) + " instances drawn, " + std::to_string(m_last.entries) + " bands; " + std::to_string(m_draws) +
" indirect draws in " + std::to_string(m_calls) + " calls, cpu " + to_string(m_cputime, 3) + " ms" + sleepers;
return "\n" + Label + ": " + std::to_string(m_last.gputotal) + " instances drawn" +
(m_twophase ? " (" + std::to_string(m_last.first) + " of them first, seen in the last frame)" : std::string{}) + ", " + std::to_string(m_last.occluded) +
" hidden by occlusion" + (m_occlusion ? (m_twophase ? " (also by one another)" : "") : " (test off)") + ", " + std::to_string(m_last.entries) + " bands; " +
std::to_string(m_draws) + " indirect draws in " + std::to_string(m_calls) + " calls (+" + std::to_string(m_prepassdraws) + " depth prepass in " +
std::to_string(m_prepasscalls) + "), cpu " + to_string(m_cputime, 3) + " ms" + sleepers;
}
if (m_comparedframes == 0)
return "\n" + Label + ": no result yet" + sleepers;
return "\n" + Label + ": " + std::to_string(m_last.gputotal) + " instances (cpu " + std::to_string(m_last.cputotal) + "), " + std::to_string(m_last.entries) + " bands, " +
std::to_string(m_last.mismatched) + " differ (by up to " + std::to_string(m_last.largestdifference) + "); frames differing " + std::to_string(m_mismatchedframes) + " of " +
std::to_string(m_comparedframes) + sleepers;
}
} // namespace gfx

View File

@@ -0,0 +1,303 @@
/*
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/.
*/
#pragma once
#include <cstdint>
#include <deque>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include "gl/ubo.h"
#include "scene/scene.h"
class cFrustum;
class TAnimModel;
namespace gfx
{
// the cpu side of the culling of the model instances of a render pass in compute passes, shared by the renderers (stage N6c of
// claude/vulkan14-renderer-plan.md); the backend (opengl46_instance_cull, vk14 instance_cull) keeps the buffers and runs the passes:
// - the records of the instances (scene::basic_cell::instance_record) are kept in one buffer, each bucket of a scene cell
// in its own place, and sent again only when the bucket made them anew
// - every bucket queued in the pass becomes a job; the buckets of one model and one set of skins make a group, whose
// lod bands are its entries
// - the passes count the instances which pass per entry, turn the counts into places in the output and write the
// modelview matrices of the instances there
// with Global.gfx_gpucull 1 the instances are drawn from the result: the renderer turns each lod band of a group into a
// list of indirect draw commands (one per submodel drawn in the band), whose instances a fourth pass fills in from the
// counts (instance_cull_commands.comp), and draws them with the output bound as the instance buffer. an occlusion test
// against a depth pyramid of what the pass drew before can leave out more instances. in two phases the instances seen in the
// previous frame are culled and drawn first, then the pyramid is made again with them in it and the others are tested
// against it, so the instances hide one another too. the command buffer holds the commands three times over: the instances
// drawn first, all of them, and the ones drawn second, each copy command_count() commands long (draw_command_offset())
// with Global.gfx_gpucull 2 the cpu still draws, and the result is only compared with its own: the counts per entry are
// read back a few frames later and set against the counts Render_Instanced() reported for the same frame
// each render pass culled on the gpu (the main view, the cascades of the sun shadows, the faces of the environment map, the
// shadow maps of headlights and the scenery parts of those of lamps) has a culling of its own, with its own buffers, while the
// records and the programs are shared by all of them: the records of a bucket go to the gpu once and serve every pass which
// queues the bucket
// the sleepers of tracks (TTrack::m_sleeper_local_transforms) are culled the same way, each track with them a bucket of its
// own, and drawn together with the instances; their groups are kept apart from those of the model instances
// so are the model instances drawn on their own (not instanceable): each one a bucket and a group of its own (the instance in
// its key), whose submodel tree the renderer animates and walks for it alone
class instance_cull
{
public:
// what a pass needs for the tests, the way Render_Instanced() takes it
struct pass_setup
{
unsigned int frame{0};
glm::dvec3 camera{0.0}; // camera of the pass: frustum and placement
glm::dvec3 distancecamera{0.0}; // the distance is measured from it
cFrustum const *frustum{nullptr}; // of the pass camera, in world coordinates
float distancescale{1.f}; // of the squared distance
float distanceoffset{0.f}; // added to the scaled squared distance past the range test
float distancerange{0.f}; // squared range of the scaled distance before the offset
float drawrange{0.f}; // squared draw range
float zoom{1.f};
bool drawing{false}; // the instances are drawn from the result (Global.gfx_gpucull 1), not compared
};
// a group of instances (model and skins) of the pass, for the renderer to make the draw commands of
struct draw_group
{
scene::basic_cell::instance_bucket_key const *key;
TAnimModel *instance; // one of the group, prepares the model and gives the material. none for sleepers
bool sleepers; // the skins come from the key, as Render_Sleepers() sets them
std::uint32_t lodfirst; // in lod_bounds()
std::uint32_t lodcount;
std::uint32_t entryfirst;
float distancemin; // squared distances (scaled as the culling does) the instances of the group can be at
float distancemax;
};
// copies of the commands in the command buffer
enum class command_set : std::uint32_t
{
first = 0, // instances which passed the first dispatch: in one phase all of them
all = 1, // after the second phase
second = 2 // the ones which passed the second phase
};
instance_cull();
instance_cull(const instance_cull &) = delete;
instance_cull &operator=(const instance_cull &) = delete;
virtual ~instance_cull() = default;
// starts collecting the buckets of a pass
void begin_pass(pass_setup const &Setup);
bool in_pass() const
{
return m_inpass;
}
bool drawing() const
{
return m_inpass && m_setup.drawing;
}
// adds the records of a bucket (made already) to the pass, sending them to the gpu when they changed.
// Area: bounds which hold the locations of all its visible instances: their own (scene_walk::Bucket_Area()) or of the cell
// holding the bucket
void add_bucket(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area);
// adds the sleepers of a track, their records made already as a bucket with no instances (only the records and the place
// in the record buffer count). Area: bounds of the locations of the sleepers. the range of the whole track is tested by
// the caller, the culling tests each sleeper against the frustum and gives it the lod band of its plain distance from the
// camera of the pass (Render_Sleepers())
void add_sleepers(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area);
// a bucket the renderer left out of the pass, as none of its instances can pass the culling (Global.gfx_gpucull_bucketrange): only counted,
// for the panel
void skip_bucket(scene::basic_cell::instance_bucket const &Bucket);
// the last dispatch works in two phases
bool two_phase() const
{
return m_twophase;
}
// groups of the pass and the lod bounds they refer to, valid from the dispatch to end_pass()
std::vector<draw_group> const &draw_groups() const
{
return m_drawgroups;
}
std::vector<float> const &lod_bounds() const
{
return m_lodlist;
}
// offset of command Index of a copy in the command buffer
std::intptr_t draw_command_offset(command_set const Set, std::size_t const Index) const
{
return static_cast<std::intptr_t>((static_cast<std::size_t>(Set) * m_commandcount + Index) * sizeof(gl::instance_draw_command));
}
// counts of the last result which came back from the gpu, with the draws of the pass and the calls which made them (fewer
// with multi draws), for the debug panel
void draw_result(std::uint32_t const Draws, std::uint32_t const Prepassdraws, std::uint32_t const Calls, std::uint32_t const Prepasscalls, double const Cputime);
// what draw_result() was given last
std::uint32_t prepass_draws() const
{
return m_prepassdraws;
}
std::uint32_t prepass_calls() const
{
return m_prepasscalls;
}
double cpu_time() const
{
return m_cputime;
}
// instances which passed in the last result which came back from the gpu, a few frames old
std::uint32_t last_count() const
{
return static_cast<std::uint32_t>(m_last.gputotal);
}
// counts per lod band of the instances Render_Instanced() drew for a model and its skins in this pass, or of the sleepers
// Render_Sleepers() drew. needs the dispatch before it
void cpu_result(scene::basic_cell::instance_bucket_key const &Key, std::vector<std::uint32_t> const &Bandcounts, bool const Sleepers = false);
// the cpu drew the instance of a group of its own (Key with the instance) at a squared distance scaled as the culling
// scales it: counted in the lod band the distance falls in. needs the dispatch before it
void cpu_instance(scene::basic_cell::instance_bucket_key const &Key, float const Distancesquared);
// ends the pass: its cpu counts are kept for the comparison
void end_pass();
// takes the newest counts of the gpu which arrived and compares them with the ones of the cpu. done when a pass begins;
// for the panel also by the culls of passes which aren't made every frame
void collect();
// a pass was culled since the start
bool ran() const
{
return m_ran;
}
// lines for the debug panel, Label naming the pass. Occlusion: the pass may test occlusion (the main view)
std::string info(std::string const &Label = "gpu cull", bool const Occlusion = true);
protected:
// what the cullings of all passes share of the record buffer: records placed so far, the generation which tells current places
// from the ones of a replaced buffer, the frame of the last time the buffer was started over, and whether it was
struct record_state
{
std::uint32_t recordfill{0};
unsigned int recordgeneration{1};
unsigned int restartframe{0};
bool restarted{false};
};
// entries read back for the comparison; with more of them only the totals and these are compared
static constexpr std::uint32_t READBACK_ENTRIES{16384};
// what the backend does with the buffers
// size of the record buffer, bytes, and a new one of the given size (the contents are dropped)
virtual std::int64_t record_buffer_size() const = 0;
virtual void resize_record_buffer(std::int64_t const Size) = 0;
// sends records to their place in the record buffer
virtual void send_records(std::uint32_t const First, scene::basic_cell::instance_record const *Records, std::uint32_t const Count) = 0;
// the buffer is started over without growing: the records go to the places of the buffer from its start again
virtual void restart_records() {}
// the counters of the pass (INSTANCE_COUNTER_HEADER + READBACK_ENTRIES words) go back to the cpu, and the oldest of them which
// arrived: false when none did
virtual void push_counts() = 0;
virtual bool pop_counts(std::uint32_t *Counts) = 0;
// the start of the dispatch of the first phase: false when there's nothing to cull
bool dispatch_begin();
// planes of the frustum of the pass moved to its camera, in double precision: the cpu tests world coordinates
void frustum_planes(glm::vec4 (&Planes)[6]) const;
// the end of the dispatch of the first phase
void dispatch_end();
// with a culling made for another pass: shares the records with it
void share_records(instance_cull const &Shared)
{
m_records = Shared.m_records;
}
struct group_data
{
std::uint32_t index;
std::uint32_t entryfirst;
std::uint32_t entrycount;
float distancemin;
float distancemax;
};
// counts of the cpu of a pass, waiting for those of the gpu
struct cpu_pass
{
unsigned int frame;
std::vector<std::uint32_t> counts;
std::uint64_t total;
};
struct comparison
{
unsigned int frame{0};
std::uint64_t gputotal{0};
std::uint64_t cputotal{0};
std::uint32_t entries{0};
std::uint32_t compared{0}; // entries compared, at most READBACK_ENTRIES
std::uint32_t mismatched{0};
std::uint32_t largestdifference{0};
std::uint32_t occluded{0}; // left out by the occlusion test
std::uint32_t first{0}; // drawn first, seen in the previous frame (two phases)
};
using group_map = std::unordered_map<scene::basic_cell::instance_bucket_key, group_data, scene::basic_cell::instance_bucket_key_hash>;
// add_bucket() and add_sleepers()
void add(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area, bool const Sleepers);
// finds a place for the records of a bucket in the record buffer and sends them
void place(scene::basic_cell::instance_bucket const &Bucket);
std::shared_ptr<record_state> m_records;
bool m_inpass{false};
bool m_ran{false};
pass_setup m_setup;
std::vector<scene::basic_cell::instance_bucket const *> m_buckets; // of the jobs, in their order
std::vector<gl::instance_cull_job> m_joblist;
std::vector<gl::instance_cull_group> m_grouplist;
std::vector<draw_group> m_drawgroups;
std::vector<float> m_lodlist;
group_map m_groupmap;
group_map m_sleepergroupmap; // groups of the sleepers
std::uint32_t m_candidates{0};
std::uint32_t m_entries{0};
// tracks with sleepers and their sleepers queued in the pass, and in the last pass, for the panel
std::uint32_t m_sleepertracks{0};
std::uint32_t m_sleepers{0};
std::uint32_t m_lastsleepertracks{0};
std::uint32_t m_lastsleepers{0};
// model instances drawn on their own queued in the pass, and in the last pass, for the panel
std::uint32_t m_models{0};
std::uint32_t m_lastmodels{0};
// what the pass held and sent, and the same of the last pass, for the panel: jobs (buckets queued), buckets left out beyond
// their range and their instances, records sent to the gpu, the record buffer started over
struct pass_counts
{
std::uint32_t jobs{0};
std::uint32_t candidates{0};
std::uint32_t skippedbuckets{0};
std::uint64_t skippedinstances{0};
std::uint64_t sentrecords{0};
bool restarted{false};
};
pass_counts m_counts;
pass_counts m_lastcounts;
bool m_dispatched{false};
std::vector<std::uint32_t> m_cpucounts; // of the current pass, by entry
std::deque<cpu_pass> m_cpupasses;
std::vector<std::uint32_t> m_readbackdata;
comparison m_last;
std::uint64_t m_comparedframes{0};
std::uint64_t m_mismatchedframes{0};
// draws of the last pass drawn from the gpu
std::uint32_t m_draws{0};
std::uint32_t m_prepassdraws{0};
std::uint32_t m_calls{0};
std::uint32_t m_prepasscalls{0};
std::uint32_t m_commandcount{0};
double m_cputime{0.0};
bool m_occlusion{false}; // the last dispatch tested occlusion
bool m_twophase{false}; // in two phases
};
} // namespace gfx

View File

@@ -0,0 +1,648 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/common/localshadows.h"
#include "rendering/openglcamera.h"
#include "simulation/simulation.h"
#include "utilities/Globals.h"
#include "utilities/Logs.h"
#include "utilities/utilities.h"
#include "vehicle/DynObj.h"
#include "vehicle/Train.h"
namespace gfx
{
namespace
{
// a lamp which moved farther than this (an animated submodel) has its scenery drawn anew, metres
double const LAMP_MOVED{0.01};
// lamps with a shorter reach aren't worth a map, metres
float const LAMP_RANGE_MIN{1.f};
} // namespace
local_shadows::level_setup local_shadows::setup(int const Level)
{
switch (Level)
{
case 1:
return {512, 4, 0.f, 1, 1024, 2, 8};
case 2:
return {1024, 2, 1.f, 1, 1024, 1, 4};
case 3:
return {1024, 1, 2.f, 2, 2048, 1, 2};
default:
return {0, 0, 0.f, 0, 0, 0, 0};
}
}
void local_shadows::allocate(int const Size)
{
release();
if (Size <= 0)
return;
if (false == gpu_allocate(Size))
{
ErrorLog("lamp shadow map framebuffer setup failed, the lamps have no shadows");
release();
Global.gfx_lights_shadows = 0;
return;
}
m_size = Size;
WriteLog("lamp shadow maps: " + std::to_string(Size) + " x " + std::to_string(Size) + " cube");
}
void local_shadows::release()
{
gpu_release();
m_size = 0;
m_valid = false;
}
void local_shadows::allocate_lamps(int const Count, int const Size)
{
release_lamps();
if ((Count <= 0) || (Size <= 0))
return;
// the scenery parts and the working copies
if (false == gpu_allocate_lamps(Count, Size))
{
ErrorLog("scenery lamp shadow map framebuffer setup failed, the scenery lamps have no shadows");
release_lamps();
Global.gfx_lights_shadows_lamps = 0;
return;
}
m_lampssize = Size;
m_slots.assign(static_cast<std::size_t>(Count), lamp_slot{});
WriteLog("scenery lamp shadow maps: " + std::to_string(Count) + " x " + std::to_string(Size) + " x " + std::to_string(Size) + " cube (" +
std::to_string(2 * Count * 6 * Size / 1024 * Size * 4 / 1024) + " MB)");
}
void local_shadows::release_lamps()
{
gpu_release_lamps();
m_lampssize = 0;
m_slots.clear();
m_wanted.clear();
}
void local_shadows::allocate_headlights(int const Count, int const Size)
{
release_headlights();
if ((Count <= 0) || (Size <= 0))
return;
if (false == gpu_allocate_headlights(Count, Size))
{
ErrorLog("headlight shadow map framebuffer setup failed, the headlights have no shadows");
release_headlights();
Global.gfx_lights_shadows_headlights = 0;
return;
}
m_headlightssize = Size;
m_headlightslots.assign(static_cast<std::size_t>(Count), headlight_slot{});
WriteLog("headlight shadow maps: " + std::to_string(Count) + " x " + std::to_string(Size) + " x " + std::to_string(Size) + " (" +
std::to_string(Count * Size / 1024 * Size * 4 / 1024) + " MB)");
}
void local_shadows::release_headlights()
{
gpu_release_headlights();
m_headlightssize = 0;
m_headlightslots.clear();
m_headlightswanted.clear();
}
bool local_shadows::update_wanted(TDynamicObject const *Vehicle, TModel3d const *Cab, glm::vec3 const &Lamp, unsigned int const Framestamp)
{
auto const level{std::clamp(Global.gfx_lights_shadows, 0, 3)};
if (level != m_level)
{
m_level = level;
auto const size{setup(level).size};
if (size != m_size)
allocate(size);
m_valid = false;
}
if ((m_size == 0) || (Vehicle == nullptr) || (Cab == nullptr))
{
// with the lamp off nothing reads the map; once it is on again the cab may have changed
m_valid = false;
return false;
}
if ((false == m_valid) || (Vehicle != m_vehicle) || (Cab != m_cab) || (Lamp != m_lamp))
{
m_valid = false;
}
// the moving parts of the cab are followed every few frames
if (m_valid && (Framestamp - m_drawnframe < setup(level).interval))
{
return false;
}
m_vehicle = Vehicle;
m_cab = Cab;
m_lamp = Lamp;
m_drawnframe = Framestamp;
return true;
}
void local_shadows::begin()
{
m_cpustart = std::chrono::steady_clock::now();
gpu_begin_cab();
}
void local_shadows::end(std::uint32_t const Drawcalls)
{
gpu_end_cab();
m_valid = true;
m_drawcalls = Drawcalls;
++m_updatecount;
auto const elapsed{std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - m_cpustart).count()};
m_cputime = (m_cputime == 0.0 ? elapsed : m_cputime * 0.9 + elapsed * 0.1);
}
glm::vec4 local_shadows::weights() const
{
// a face spans twice the distance from the lamp
return glm::vec4{static_cast<float>(cab_slot), shadow_near, 2.f / static_cast<float>(std::max(m_size, 1)), setup(m_level).radius};
}
void local_shadows::update_lamps(std::vector<gfx::local_lights::light> const &Lights, glm::dvec3 const &Camera, std::uint64_t const Version, unsigned int const Framestamp)
{
m_frame = Framestamp;
m_wanted.clear();
auto const level{std::clamp(Global.gfx_lights_shadows, 0, 3)};
auto const count{level > 0 ? Global.gfx_lights_shadows_lamps : 0};
auto const size{count > 0 ? Global.gfx_lights_shadows_lamps_size : 0};
if ((static_cast<std::size_t>(count) != m_slots.size()) || (size != m_lampssize))
{
allocate_lamps(count, size);
}
if (m_slots.empty())
{
return;
}
m_sceneryleft = setup(level).sceneryperframe;
auto const reach{static_cast<double>(Global.gfx_lights_shadows_lamps_range)};
// the lights come the most important first: the first ones within reach get the slots
std::vector<gfx::local_lights::light const *> picked;
for (auto const &light : Lights)
{
if (picked.size() >= m_slots.size())
{
break;
}
if ((light.owner == nullptr) || (light.range < LAMP_RANGE_MIN) || (glm::length(light.position - Camera) > reach))
{
continue;
}
picked.emplace_back(&light);
}
// lamps which already have a slot keep it
std::vector<gfx::local_lights::light const *> unplaced;
for (auto const *light : picked)
{
auto placed{false};
for (std::size_t idx = 0; idx < m_slots.size(); ++idx)
{
auto &slot{m_slots[idx]};
if ((slot.owner == light->owner) && (slot.part == light->part) && (slot.wantedframe != Framestamp))
{
if (glm::length(slot.position - light->position) > LAMP_MOVED)
{
slot.scenery = false;
slot.vehicles = false;
}
slot.position = light->position;
slot.range = light->range;
slot.wantedframe = Framestamp;
m_wanted.emplace_back(idx);
placed = true;
break;
}
}
if (false == placed)
{
unplaced.emplace_back(light);
}
}
// the others take the slots of lamps no longer picked
for (auto const *light : unplaced)
{
for (std::size_t idx = 0; idx < m_slots.size(); ++idx)
{
auto &slot{m_slots[idx]};
if (slot.wantedframe == Framestamp)
{
continue;
}
slot = lamp_slot{};
slot.owner = light->owner;
slot.part = light->part;
slot.position = light->position;
slot.range = light->range;
slot.wantedframe = Framestamp;
m_wanted.emplace_back(idx);
break;
}
}
// the scene changed (events, the editor): the scenery parts are out of date
for (auto &slot : m_slots)
{
if (slot.scenery && (slot.version != Version))
{
slot.scenery = false;
slot.vehicles = false;
}
}
}
void local_shadows::reset_lamps()
{
for (auto &slot : m_slots)
{
slot = lamp_slot{};
}
m_wanted.clear();
}
bool local_shadows::scenery_wanted(std::size_t const Slot)
{
if (m_slots[Slot].scenery || (m_sceneryleft <= 0))
{
return false;
}
--m_sceneryleft;
return true;
}
bool local_shadows::vehicles_wanted(std::size_t const Slot, unsigned int const Framestamp) const
{
auto const &slot{m_slots[Slot]};
return slot.scenery && ((false == slot.vehicles) || (Framestamp - slot.vehiclesframe >= setup(m_level).interval));
}
void local_shadows::begin_lamp(std::size_t const Slot, bool const Vehicles, std::uint64_t const Version, unsigned int const Framestamp)
{
auto &slot{m_slots[Slot]};
auto const scenerycube{static_cast<int>(Slot)};
auto const workingcube{static_cast<int>(m_slots.size() + Slot)};
if (Vehicles)
{
// the vehicles go over the scenery
slot.vehicles = true;
slot.vehiclesframe = Framestamp;
++m_lampsvehicles;
}
else
{
slot.scenery = true;
slot.version = Version;
slot.vehicles = false;
++m_lampsscenery;
}
gpu_begin_lamp(scenerycube, workingcube, Vehicles);
}
void local_shadows::end_lamp(std::uint32_t const Drawcalls)
{
gpu_end_lamp();
m_lampsframedrawcalls += Drawcalls;
}
void local_shadows::lamps_timer_begin()
{
m_lampsframedrawcalls = 0;
m_cpustart = std::chrono::steady_clock::now();
gpu_lamps_timer(true);
m_lampstiming = true;
}
void local_shadows::lamps_timer_end()
{
if (false == m_lampstiming)
{
return;
}
gpu_lamps_timer(false);
m_lampstiming = false;
m_lampsdrawcalls = m_lampsframedrawcalls;
auto const elapsed{std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - m_cpustart).count()};
m_lampscputime = (m_lampscputime == 0.0 ? elapsed : m_lampscputime * 0.9 + elapsed * 0.1);
}
local_shadows::lamp_slot const *local_shadows::lamp(gfx::local_lights::light const &Light, std::size_t &Slot) const
{
if (Light.owner == nullptr)
{
return nullptr;
}
for (auto const idx : m_wanted)
{
auto const &slot{m_slots[idx]};
if ((slot.owner == Light.owner) && (slot.part == Light.part))
{
if (false == slot.scenery)
{
return nullptr;
}
Slot = idx;
return &slot;
}
}
return nullptr;
}
glm::vec4 local_shadows::lamp_weights(std::size_t const Slot) const
{
auto const cube{m_slots[Slot].vehicles ? m_slots.size() + Slot : Slot};
return glm::vec4{static_cast<float>(cube), shadow_near, 2.f / static_cast<float>(std::max(m_lampssize, 1)), setup(m_level).radius};
}
std::vector<TDynamicObject *> const &local_shadows::lamp_vehicles(glm::dvec3 const &Camera)
{
m_lampvehicles.clear();
auto const vehiclesrange{static_cast<double>(Global.gfx_lights_shadows_vehicles_range)};
if (vehiclesrange > 0.0)
{
for (auto *vehicle : simulation::Vehicles.sequence())
{
if (glm::length(vehicle->GetPosition() - Camera) <= vehiclesrange + vehicle->radius())
m_lampvehicles.emplace_back(vehicle);
}
}
return m_lampvehicles;
}
std::vector<TDynamicObject *> const &local_shadows::slot_vehicles(lamp_slot const &Slot)
{
m_slotvehicles.clear();
for (auto *vehicle : m_lampvehicles)
{
if (glm::length(vehicle->GetPosition() - Slot.position) <= Slot.range + vehicle->radius())
m_slotvehicles.emplace_back(vehicle);
}
return m_slotvehicles;
}
std::vector<light_array::light_record const *> const &local_shadows::pick_headlights(light_array const &Lights, opengl_camera const &Camera)
{
m_headlightpicked.clear();
auto const count{(Global.gfx_lights_headlights && (std::clamp(Global.gfx_lights_shadows, 0, 3) > 0)) ? std::clamp(Global.gfx_lights_shadows_headlights, 0, static_cast<int>(gl::MAX_HEADLIGHT_SHADOWS)) : 0};
if (count > 0)
{
m_headlightcandidates.clear();
auto const camera{Camera.position()};
auto const range{static_cast<double>(Global.gfx_lights_shadows_headlights_range)};
for (auto const &light : Lights.data)
{
if (light.intensity == 0.f)
continue;
// as in the light buffers: by day the light around takes the beam down to nothing
auto luminance{static_cast<float>(Global.fLuminance)};
if (light.owner->fShade > 0.f)
luminance *= light.owner->fShade;
auto const beam{light.color - glm::vec3{luminance}};
if (std::max(beam.r, std::max(beam.g, beam.b)) <= 0.f)
continue;
// the part of the beam the shadows are cast in: from the end of the vehicle to the reach ahead of it
auto const &dimensions{light.owner->MoverParameters->Dim};
auto const radius{range * 0.5 + std::max(dimensions.W, dimensions.H) + 2.0};
auto const centre{light.position + glm::dvec3{light.direction} * (range * 0.5 + 2.0)};
if (glm::length(centre - camera) - radius > range)
continue;
if (false == Camera.visible(scene::bounding_area{centre, static_cast<float>(radius)}))
continue;
m_headlightcandidates.emplace_back(static_cast<float>(glm::length2(camera - light.position)) / light.intensity, &light);
}
auto const picked{std::min(m_headlightcandidates.size(), static_cast<std::size_t>(count))};
std::partial_sort(std::begin(m_headlightcandidates), std::begin(m_headlightcandidates) + picked, std::end(m_headlightcandidates),
[](auto const &Left, auto const &Right) { return Left.first < Right.first; });
for (std::size_t idx = 0; idx < picked; ++idx)
{
m_headlightpicked.emplace_back(m_headlightcandidates[idx].second);
}
}
return m_headlightpicked;
}
void local_shadows::update_headlights(std::vector<light_array::light_record const *> const &Lights, unsigned int const Framestamp)
{
m_headlightswanted.clear();
auto const level{std::clamp(Global.gfx_lights_shadows, 0, 3)};
auto const count{level > 0 ? std::clamp(Global.gfx_lights_shadows_headlights, 0, static_cast<int>(gl::MAX_HEADLIGHT_SHADOWS)) : 0};
auto const size{count > 0 ? setup(level).headlightsize : 0};
if ((static_cast<std::size_t>(count) != m_headlightslots.size()) || (size != m_headlightssize))
{
allocate_headlights(count, size);
}
if (m_headlightslots.empty())
{
return;
}
// sets which already have a slot keep it
std::vector<light_array::light_record const *> unplaced;
for (auto const *light : Lights)
{
if (m_headlightswanted.size() + unplaced.size() >= m_headlightslots.size())
{
break;
}
auto placed{false};
for (std::size_t idx = 0; idx < m_headlightslots.size(); ++idx)
{
auto &slot{m_headlightslots[idx]};
if ((slot.owner == light->owner) && (slot.index == light->index) && (slot.wantedframe != Framestamp))
{
slot.wantedframe = Framestamp;
m_headlightswanted.emplace_back(idx);
placed = true;
break;
}
}
if (false == placed)
{
unplaced.emplace_back(light);
}
}
// the others take the slots of sets no longer picked
for (auto const *light : unplaced)
{
for (std::size_t idx = 0; idx < m_headlightslots.size(); ++idx)
{
auto &slot{m_headlightslots[idx]};
if (slot.wantedframe == Framestamp)
{
continue;
}
slot = headlight_slot{};
slot.owner = light->owner;
slot.index = light->index;
slot.wantedframe = Framestamp;
m_headlightswanted.emplace_back(idx);
break;
}
}
}
void local_shadows::reset_headlights()
{
for (auto &slot : m_headlightslots)
{
slot = headlight_slot{};
}
m_headlightswanted.clear();
}
bool local_shadows::headlight_wanted(std::size_t const Slot, bool const Moved, unsigned int const Framestamp) const
{
auto const &slot{m_headlightslots[Slot]};
auto const level{setup(m_level)};
return (false == slot.drawn) || (Framestamp - slot.drawnframe >= (Moved ? level.headlightmoving : level.headlightstill));
}
void local_shadows::begin_headlight(std::size_t const Slot, glm::dvec3 const &Projector, glm::mat3 const &Rotation, glm::mat4 const &Projection, glm::vec3 const &Direction, unsigned int const Framestamp)
{
auto &slot{m_headlightslots[Slot]};
slot.drawn = true;
slot.drawnframe = Framestamp;
slot.projector = Projector;
slot.rotation = Rotation;
slot.projection = Projection;
slot.direction = Direction;
++m_headlightsdrawn;
gpu_begin_headlight(Slot);
}
void local_shadows::end_headlight(std::uint32_t const Drawcalls)
{
gpu_end_headlight();
m_headlightsframedrawcalls += Drawcalls;
}
void local_shadows::headlights_timer_begin()
{
m_headlightsframedrawcalls = 0;
m_headlightscpustart = std::chrono::steady_clock::now();
gpu_headlights_timer(true);
m_headlightstiming = true;
}
void local_shadows::headlights_timer_end()
{
if (false == m_headlightstiming)
{
return;
}
gpu_headlights_timer(false);
m_headlightstiming = false;
m_headlightsdrawcalls = m_headlightsframedrawcalls;
auto const elapsed{std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - m_headlightscpustart).count()};
m_headlightscputime = (m_headlightscputime == 0.0 ? elapsed : m_headlightscputime * 0.9 + elapsed * 0.1);
}
int local_shadows::headlight_map(TDynamicObject const *Owner, int const Index) const
{
for (auto const idx : m_headlightswanted)
{
auto const &slot{m_headlightslots[idx]};
if ((slot.owner == Owner) && (slot.index == Index))
{
return (slot.drawn ? static_cast<int>(idx) : -1);
}
}
return -1;
}
glm::vec4 local_shadows::headlight_params(std::size_t const Slot) const
{
// a map spans 2 tan(fov / 2) one metre from the projector, and the projection holds 1 / tan(fov / 2)
auto const &projection{m_headlightslots[Slot].projection};
return glm::vec4{2.f / (std::max(projection[1][1], 1e-6f) * static_cast<float>(std::max(m_headlightssize, 1))), setup(m_level).radius, 0.f, 0.f};
}
TDynamicObject *local_shadows::shadow_cab()
{
if (FreeFlyModeFlag || (false == Global.render_cab) || (simulation::Train == nullptr))
return nullptr;
auto *cab{simulation::Train->Dynamic()};
if ((cab != nullptr) && ((false == cab->bDisplayCab) || (cab->mdKabina == nullptr) || (cab->mdKabina == cab->mdModel)))
cab = nullptr;
return cab;
}
std::string local_shadows::info() const
{
std::string text;
if (m_size != 0)
{
text += "\nlamp shadows: " + std::to_string(m_size) + " cube, " + (m_valid ? "cab lamp" : "none") + ", " + std::to_string(m_drawcalls) + " drawcalls, cpu " + to_string(m_cputime, 2) +
" ms";
}
if (false == m_slots.empty())
{
auto withvehicles{0};
auto ready{0};
for (auto const idx : m_wanted)
{
ready += (m_slots[idx].scenery ? 1 : 0);
withvehicles += (m_slots[idx].vehicles ? 1 : 0);
}
text += "\nscenery lamp shadows: " + std::to_string(m_wanted.size()) + " of " + std::to_string(m_slots.size()) + " slots (" + std::to_string(ready) + " ready, " +
std::to_string(withvehicles) + " with vehicles), drawn " + std::to_string(m_lampsscenery) + " scenery + " + std::to_string(m_lampsvehicles) + " vehicles, " +
std::to_string(m_lampsdrawcalls) + " drawcalls, cpu " + to_string(m_lampscputime, 2) + " ms";
}
if (false == m_headlightslots.empty())
{
auto ready{0};
for (auto const idx : m_headlightswanted)
{
ready += (m_headlightslots[idx].drawn ? 1 : 0);
}
text += "\nheadlight shadows: " + std::to_string(m_headlightswanted.size()) + " of " + std::to_string(m_headlightslots.size()) + " slots (" + std::to_string(ready) + " ready), " +
std::to_string(m_headlightssize) + " map, drawn " + std::to_string(m_headlightsdrawn) + " times, " + std::to_string(m_headlightsdrawcalls) + " drawcalls, cpu " +
to_string(m_headlightscputime, 2) + " ms";
}
return text;
}
void local_shadows::draw_debug_ui()
{
char const *levels[]{"no", "low (512, every 4th frame)", "medium (1024, every 2nd frame)", "high (1024, every frame, softer)"};
ImGui::Combo("lamp shadows (gfx.lights.shadows)", &Global.gfx_lights_shadows, levels, IM_ARRAYSIZE(levels));
if (m_size != 0)
{
ImGui::Text("cab: cube %d x %d, %s, drawn %u times, %u drawcalls, %.2f ms (cpu)", m_size, m_size, m_valid ? "cab lamp" : "no lamp", m_updatecount, m_drawcalls, m_cputime);
}
ImGui::SliderInt("scenery lamps with shadows (gfx.lights.shadows.lamps)", &Global.gfx_lights_shadows_lamps, 0, 32);
ImGui::SliderFloat("their reach from the camera, m (.lamps.range)", &Global.gfx_lights_shadows_lamps_range, 0.f, 500.f);
ImGui::SliderFloat("vehicles casting them, from the camera, m (.vehicles.range)", &Global.gfx_lights_shadows_vehicles_range, 0.f, 500.f);
if (false == m_slots.empty())
{
auto const text{info()};
auto const line{text.find("\nscenery lamp shadows:")};
auto const end{(line == std::string::npos) ? std::string::npos : text.find('\n', line + 1)};
if (line != std::string::npos)
ImGui::TextUnformatted(text.substr(line + 1, end == std::string::npos ? std::string::npos : end - line - 1).c_str());
}
ImGui::SliderInt("headlights with shadows (gfx.lights.shadows.headlights)", &Global.gfx_lights_shadows_headlights, 0, static_cast<int>(gl::MAX_HEADLIGHT_SHADOWS));
ImGui::SliderFloat("their reach ahead of the vehicle, m (.headlights.range)", &Global.gfx_lights_shadows_headlights_range, 10.f, 1000.f);
if (false == m_headlightslots.empty())
{
auto const text{info()};
auto const line{text.rfind('\n')};
ImGui::TextUnformatted(text.c_str() + (line == std::string::npos ? 0 : line + 1));
}
}
} // namespace gfx

View File

@@ -0,0 +1,288 @@
/*
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/.
*/
#pragma once
#include <array>
#include <chrono>
#include <cstdint>
#include <string>
#include <vector>
#include "rendering/common/locallights.h"
#include "rendering/lightarray.h"
class TDynamicObject;
class TModel3d;
class opengl_camera;
namespace gfx
{
// the shadow maps of the lamps (Global.gfx_lights_shadows), the part shared by the renderers (stage P4 of
// claude/vulkan14-renderer-plan.md): which lamps and headlights get maps, when they are drawn, what the shading reads; the
// backend (opengl46_local_shadows, the vulkan renderer) keeps the maps and draws to them through the gpu_* methods.
// - the lamp of the occupied cab, with the shadows cast by the cab (the model drawn by Render_cab()). the map is kept in the space
// of the cab model, centred on the lamp: it stays right while the vehicle moves and turns, and is drawn anew only every few frames
// (for the moving parts of the cab: levers, doors, wipers) or when the lamp, the cab or the level change;
// - the lamps of scenery models closest to the camera (Global.gfx_lights_shadows_lamps of them, within
// Global.gfx_lights_shadows_lamps_range). the map of such a lamp is kept in world axes, centred on the lamp, and has two parts:
// the scenery (drawn once, when the lamp gets its slot or the scene changes) and a working copy of it, with the vehicles near
// the camera (within Global.gfx_lights_shadows_vehicles_range) and the occupied cab added, made every few frames while there
// are any by the lamp. the shading reads the working copy then, the scenery part otherwise.
// the six faces of a map are drawn at once, so a model costs one draw call per submodel, as in the colour pass. the depth is
// n / d (d the distance along the axis of the face), see local_light_shadow() in light_common.glsl, which reads the map for the
// light with the layer in its headlight_weights.x, from the array of scenery lamps when its linear is 1 and from the one of the
// cab lamp otherwise.
// - the headlights of the vehicles (Global.gfx_lights_shadows_headlights sets of them, the most important ones): a plain map,
// drawn from the projector of the beam with its projection (the beam is a narrow frustum aimed along the vehicle), with what is
// within Global.gfx_lights_shadows_headlights_range ahead of the vehicle, but not the vehicle itself. the map keeps the projector
// it was drawn with, in world space: one a few frames old is still right for what doesn't move. read by headlight_beam_shadow()
// in light_common.glsl, for the headlights with the map in their headlight_weights.w (1 + the layer) and the projection to it in
// gl::scene_ubs::headlight_shadow
class local_shadows
{
public:
// near plane of the maps, metres. surfaces closer to the lamp (its own fitting) cast no shadow
static constexpr float shadow_near{0.15f};
// layer of the map of the cab lamp in its array
static constexpr int cab_slot{0};
local_shadows() = default;
virtual ~local_shadows() = default;
local_shadows(const local_shadows &) = delete;
local_shadows &operator=(const local_shadows &) = delete;
// the cab lamp
// whether the map of the cab lamp is to be drawn in this frame: Vehicle and Cab the occupied vehicle and its cab model,
// Lamp the position of the lamp in the space of the cab model; Vehicle nullptr when the lamp is off. (re)makes the map to
// the size of the level when the level changed
bool update_wanted(TDynamicObject const *Vehicle, TModel3d const *Cab, glm::vec3 const &Lamp, unsigned int const Framestamp);
// sets up the drawing to the map: the target, viewport, cleared faces, the state of the faces. the geometry is to be
// drawn with the programs of the cube maps and a modelview from the space of the model to the space of the map
void begin();
// ends the drawing to the map
void end(std::uint32_t const Drawcalls);
// whether the map holds the shadows of the lamp of Vehicle, the occupied vehicle
bool ready(TDynamicObject const *Vehicle) const
{
return m_valid && (Vehicle != nullptr) && (Vehicle == m_vehicle);
}
// position of the lamp the map was drawn for, in the space of the cab model
glm::vec3 const &lamp() const
{
return m_lamp;
}
// the headlight_weights of the light which reads the map: layer, near plane, size of a texel one metre from the lamp,
// radius of the filter in texels
glm::vec4 weights() const;
// lamps of scenery models
// a lamp with a slot
struct lamp_slot
{
void const *owner{nullptr}; // the light it is, see gfx::local_lights::light
void const *part{nullptr};
glm::dvec3 position{0.0}; // world space
float range{0.f}; // of the light, metres
unsigned int wantedframe{~0u}; // framestamp it was last among the lamps given shadows
bool scenery{false}; // the scenery part of the map is drawn
std::uint64_t version{0}; // of the scene the scenery part was drawn for
bool vehicles{false}; // the working copy is drawn, with vehicles, and is what the shading reads
unsigned int vehiclesframe{0}; // framestamp the working copy was last drawn in
};
// picks the lamps which get shadows in this frame from the gathered lights (the most important first): the scenery lamps
// within reach of Camera. a lamp keeps its slot while it is picked; one which got a slot or moved, and all of them when the
// scene changes (Version), need their scenery drawn. (re)makes the maps when their count or size changed
void update_lamps(std::vector<gfx::local_lights::light> const &Lights, glm::dvec3 const &Camera, std::uint64_t const Version, unsigned int const Framestamp);
// forgets the lamps: the scene they come from is about to be replaced (a scenery being loaded)
void reset_lamps();
// slots of the lamps picked in this frame
std::vector<std::size_t> const &lamps() const
{
return m_wanted;
}
lamp_slot const &slot(std::size_t const Slot) const
{
return m_slots[Slot];
}
// whether the scenery part of the map of the slot is to be drawn in this frame. a few per frame at most, by the level
bool scenery_wanted(std::size_t const Slot);
// whether the working copy of the slot, with vehicles, is to be drawn in this frame (every few frames, by the level)
bool vehicles_wanted(std::size_t const Slot, unsigned int const Framestamp) const;
// the slot has no vehicles by it: the shading reads the scenery part
void no_vehicles(std::size_t const Slot)
{
m_slots[Slot].vehicles = false;
}
// sets up the drawing to the scenery part (cleared) or to the working copy (a copy of the scenery part) of the map of the slot.
// the geometry is to be drawn with the programs of the cube maps and a modelview to world axes centred on the lamp
void begin_lamp(std::size_t const Slot, bool const Vehicles, std::uint64_t const Version, unsigned int const Framestamp);
// ends the drawing to the map of a lamp
void end_lamp(std::uint32_t const Drawcalls);
// the timing of the drawing of the maps of the lamps in this frame
void lamps_timer_begin();
void lamps_timer_end();
// the slot of the lamp the light is, if it has its shadows ready, nullptr otherwise
lamp_slot const *lamp(gfx::local_lights::light const &Light, std::size_t &Slot) const;
// the headlight_weights of the light of the lamp in the slot, see weights()
glm::vec4 lamp_weights(std::size_t const Slot) const;
// the vehicles near the camera, which cast the shadows of the lamps (Global.gfx_lights_shadows_vehicles_range), and those by
// the lamp of a slot, within its reach; valid until the next call
std::vector<TDynamicObject *> const &lamp_vehicles(glm::dvec3 const &Camera);
std::vector<TDynamicObject *> const &slot_vehicles(lamp_slot const &Slot);
std::vector<TDynamicObject *> const &slot_vehicles() const
{
return m_slotvehicles;
}
// headlights of vehicles
// a set of headlights (one end of a vehicle) with a slot
struct headlight_slot
{
TDynamicObject const *owner{nullptr}; // light_array::light_record::owner and index
int index{-1};
unsigned int wantedframe{~0u}; // framestamp it was last among the headlights given shadows
bool drawn{false}; // the map holds the shadows of the headlights
unsigned int drawnframe{0};
// the projector the map was drawn with: world space position, world axes to its axes, projection
glm::dvec3 projector{0.0};
glm::mat3 rotation{1.f};
glm::mat4 projection{1.f};
glm::vec3 direction{0.f}; // of the light when the map was drawn
};
// the headlights which shine, the most important first (in the order the light buffers take them), whose beam may be seen
// from the camera of the colour pass within the reach of the shadows, as many as there are to be slots; valid until the next call
std::vector<light_array::light_record const *> const &pick_headlights(light_array const &Lights, opengl_camera const &Camera);
// picks the headlights which get shadows in this frame: Lights, the most important first, as many as there are slots
// (Global.gfx_lights_shadows_headlights). a set keeps its slot while it is picked. (re)makes the maps when their count or size changed
void update_headlights(std::vector<light_array::light_record const *> const &Lights, unsigned int const Framestamp);
// forgets the headlights: the vehicles may be about to go (a scenery being loaded)
void reset_headlights();
// slots of the headlights picked in this frame
std::vector<std::size_t> const &headlights() const
{
return m_headlightswanted;
}
headlight_slot const &headlight(std::size_t const Slot) const
{
return m_headlightslots[Slot];
}
// whether the map of the slot is to be drawn in this frame: one never drawn, then every few frames by the level, more often
// while the headlights Moved (with the vehicle)
bool headlight_wanted(std::size_t const Slot, bool const Moved, unsigned int const Framestamp) const;
// sets up the drawing to the map of the slot (cleared), drawn from the projector given (see headlight_slot). the geometry is
// to be drawn by the shadow pass with this projection, back faces only
void begin_headlight(std::size_t const Slot, glm::dvec3 const &Projector, glm::mat3 const &Rotation, glm::mat4 const &Projection, glm::vec3 const &Direction, unsigned int const Framestamp);
// ends the drawing to the map of a set of headlights
void end_headlight(std::uint32_t const Drawcalls);
// the timing of the drawing of the maps of the headlights in this frame
void headlights_timer_begin();
void headlights_timer_end();
// the slot of the headlights with their map ready in this frame, -1 if there is none
int headlight_map(TDynamicObject const *Owner, int const Index) const;
// the headlight_shadow_params of the map of the slot, see gl::scene_ubs
glm::vec4 headlight_params(std::size_t const Slot) const;
// the occupied vehicle whose cab casts the shadows of the lamps and headlights, nullptr when it's not drawn
static TDynamicObject *shadow_cab();
// lines for the statistics of the debug panel
std::string info() const;
// controls for the debug window
void draw_debug_ui();
protected:
// size, refresh interval (frames), filter radius (texels, 0: the 2x2 filter of the hardware) of a level, the number of
// scenery parts of lamp maps drawn per frame, and the size and refresh intervals (frames, while the vehicle moves and while
// it doesn't) of the maps of headlights
struct level_setup
{
int size;
unsigned int interval;
float radius;
int sceneryperframe;
int headlightsize;
unsigned int headlightmoving;
unsigned int headlightstill;
};
static level_setup setup(int const Level);
// what the backend does with the maps: makes them (false: they can't be made) and lets them go; the cab lamp: one cube, the
// lamps: 2 x Count cubes (the scenery parts, then the working copies), the headlights: Count layers
virtual bool gpu_allocate(int const Size) = 0;
virtual void gpu_release() = 0;
virtual bool gpu_allocate_lamps(int const Count, int const Size) = 0;
virtual void gpu_release_lamps() = 0;
virtual bool gpu_allocate_headlights(int const Count, int const Size) = 0;
virtual void gpu_release_headlights() = 0;
// the drawing to the map of the cab lamp, to a cube of the lamps (Vehicles: the working copy, from a copy of the scenery part
// Scenerycube; the scenery part cleared otherwise) and to a layer of the headlights
virtual void gpu_begin_cab() = 0;
virtual void gpu_end_cab() = 0;
virtual void gpu_begin_lamp(int const Scenerycube, int const Workingcube, bool const Vehicles) = 0;
virtual void gpu_end_lamp() = 0;
virtual void gpu_begin_headlight(std::size_t const Slot) = 0;
virtual void gpu_end_headlight() = 0;
// the gpu times of the drawings of a frame
virtual void gpu_lamps_timer(bool const Begin) = 0;
virtual void gpu_headlights_timer(bool const Begin) = 0;
void allocate(int const Size);
void release();
void allocate_lamps(int const Count, int const Size);
void release_lamps();
void allocate_headlights(int const Count, int const Size);
void release_headlights();
int m_level{-1}; // level the maps were made for
// cab lamp
int m_size{0}; // of a face of the map, 0 when there is none
bool m_valid{false}; // the map holds the shadows of the lamp of m_vehicle
TDynamicObject const *m_vehicle{nullptr};
TModel3d const *m_cab{nullptr};
glm::vec3 m_lamp{0.f};
unsigned int m_drawnframe{0}; // framestamp the map was last drawn in
// scenery lamps: the scenery parts of the slots first, then their working copies
int m_lampssize{0};
std::vector<lamp_slot> m_slots;
std::vector<std::size_t> m_wanted;
unsigned int m_frame{0}; // framestamp of the last update_lamps()
int m_sceneryleft{0}; // scenery parts which may still be drawn in this frame
bool m_lampstiming{false};
std::vector<TDynamicObject *> m_lampvehicles;
std::vector<TDynamicObject *> m_slotvehicles;
// headlights: a layer per slot
int m_headlightssize{0};
std::vector<headlight_slot> m_headlightslots;
std::vector<std::size_t> m_headlightswanted;
bool m_headlightstiming{false};
std::chrono::steady_clock::time_point m_headlightscpustart;
std::vector<std::pair<float, light_array::light_record const *>> m_headlightcandidates;
std::vector<light_array::light_record const *> m_headlightpicked;
// statistics
std::uint32_t m_drawcalls{0}; // of the last drawing of the cab map
std::uint32_t m_updatecount{0}; // drawings of the cab map so far
double m_cputime{0.0}; // milliseconds, smoothed
std::chrono::steady_clock::time_point m_cpustart;
std::uint32_t m_lampsscenery{0}; // scenery parts drawn so far
std::uint32_t m_lampsvehicles{0}; // working copies drawn so far
std::uint32_t m_lampsdrawcalls{0}; // in the drawings of the lamp maps in the last frame with any
std::uint32_t m_lampsframedrawcalls{0};
double m_lampscputime{0.0}; // milliseconds per frame, smoothed
std::uint32_t m_headlightsdrawn{0}; // maps of headlights drawn so far
std::uint32_t m_headlightsdrawcalls{0}; // in the drawings of the maps of headlights in the last frame with any
std::uint32_t m_headlightsframedrawcalls{0};
double m_headlightscputime{0.0}; // milliseconds per frame, smoothed
};
} // namespace gfx

View File

@@ -26,7 +26,10 @@ http://mozilla.org/MPL/2.0/.
#include "world/TrackSleepers.h"
#include "model/material.h"
#include "gl/ubo.h"
#include "rendering/common/instancecull.h"
#include <array>
#include <tuple>
#include <unordered_map>
#include <utility>
#include <vector>
@@ -230,32 +233,65 @@ class scene_walk
virtual void walk_region_begin() {}
// the opaque ground of the colour pass is drawn, the editor takes the position of the cursor from the depth
virtual void walk_editor_cursor() {}
// a bucket of instances of a cell: false when the backend took the instances (the walk leaves them out); Area is the cell's
virtual bool walk_bucket(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, bool const Bucketrange, scene::bounding_area const &Area)
{
return true;
}
// a bucket of instances of a cell: false when the gpu culling took the instances (the walk leaves them out); Area is the cell's
bool walk_bucket(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, bool const Bucketrange, scene::bounding_area const &Area);
// a bucket left out of the pass as wholly out of sight
virtual void walk_bucket_culled(scene::basic_cell::instance_bucket const &Bucket) {}
// a model drawn on its own: true when the backend took it. Inrange: the cell of the instance lies within the range of the pass
virtual bool walk_queue_model(TAnimModel *Instance, bool const Inrange)
{
return false;
}
// the sleepers of a track: true when the backend took them
virtual bool walk_queue_sleepers(TTrack *Track)
{
return false;
}
void walk_bucket_culled(scene::basic_cell::instance_bucket const &Bucket);
// a model drawn on its own: true when the gpu culling took it. Inrange: the cell of the instance lies within the range of the pass
bool walk_queue_model(TAnimModel *Instance, bool const Inrange);
// the sleepers of a track: true when the gpu culling took them
bool walk_queue_sleepers(TTrack *Track);
// the sleepers the walk drew for a track, by their squared distances, and the lod bounds of their model
virtual void walk_sleepers_drawn(TTrack const &Track, std::vector<float> const &Lodbounds, std::vector<std::pair<float, glm::mat4>> const &Drawn) {}
void walk_sleepers_drawn(TTrack const &Track, std::vector<float> const &Lodbounds, std::vector<std::pair<float, glm::mat4>> const &Drawn);
// the instances of the cells are about to be drawn: whether the walk reports what it drew of each bucket (walk_instances_compare())
virtual bool walk_instances_begin()
{
return false;
}
virtual void walk_instances_compare(scene::basic_cell::instance_bucket_key const &Key, std::vector<std::uint32_t> const &Bandcounts) {}
void walk_instances_compare(scene::basic_cell::instance_bucket_key const &Key, std::vector<std::uint32_t> const &Bandcounts);
virtual void walk_instances_end() {}
// the geometry of the backend and whether it draws blended now, for the draws recorded for the gpu culling
virtual gfx::geometrybank_manager &walk_geometry() = 0;
virtual bool walk_blending_enabled() const = 0;
// the gpu culling of the instances (Global.gfx_gpucull, stage N6c): what the walk queues in the culling of the pass and the
// draws it records for the indirect draws made from its result. the backend picks the culling of the pass, runs it and
// makes the draws
// starts the culling of the pass, or none (nullptr). Mainview: the culling of the main view, which takes the sleepers
void Begin_Pass_Cull(gfx::instance_cull *Cull, bool const Mainview);
// with the cpu drawing (Global.gfx_gpucull 2): what it drew of the sleepers and of the models drawn on their own, for the
// comparison with the culling dispatched already
void Compare_Pass_Cull();
// ends the culling of the pass
void End_Pass_Cull();
// queues the sleepers of a track in the gpu culling of the pass (Global.gfx_gpucull_sleepers), with the gates of
// Render_Sleepers(), making their records first when they're missing or out of date
void Queue_Sleepers(TTrack *Track);
// queues a model instance drawn on its own in the gpu culling of the pass (Global.gfx_gpucull_models), as a bucket of
// its own, making its record first when it's missing or out of date. Inrange: the cell of the instance lies within the
// range of the pass. returns true when the instance isn't drawn by the cpu in the pass: it's drawn from the result of the
// culling, or it was culled and recorded by the depth pass of the whole scene, or it lies out of range
bool Queue_Model(TAnimModel *Instance, bool const Inrange);
// whether a model with its skins has any part the sun shadows draw: the same test Render_Instanced() makes. the answer
// is kept for the rest of the pass
bool Instances_Cast_Shadow(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket);
// the walk of the submodel trees of the groups culled by Cull, once per lod band, which records their draws
// (m_indirectdraws, m_indirectcommands). Cascade: the pass is one of the cascades of the sun shadows, which share the walks
void Walk_Indirect_Draws(gfx::instance_cull const &Cull, bool const Cascade);
// a draw of the walk of Walk_Indirect_Draws(), with the state the walk left for it (model_ubs with the modelview set)
void Record_Indirect_Draw(gfx::geometry_handle const &Geometry);
// multi draws of the recorded draws (Global.gfx_gpucull_multidraw): puts the draws in an order in which the ones with the
// same state follow one another, the draws of one entry kept in the order of the walk
void Order_Indirect_Draws();
// whether two recorded draws can go in one multi draw of the colour pass: the same material and texture variant, geometry
// bank, primitive and blending
bool Indirect_Mergeable(std::size_t const First, std::size_t const Other) const;
// the texture the shadow material binds for the alpha test of a material, or null_handle (no test)
texture_handle Indirect_Shadow_Texture(material_handle const Material) const;
// the records of the sleepers and of the models drawn on their own not queued for a while are let go
void Expire_Indirect_Records();
// a scenery is being loaded: the tracks and the models the records were made for may be gone
void Clear_Indirect_Records();
// members
renderpass_config m_renderpass; // parameters for current render pass
@@ -320,6 +356,150 @@ class scene_walk
scene::basic_cell::instance_bucket_key const *m_modelcomparekey{nullptr};
std::vector<std::pair<scene::basic_cell::instance_bucket_key, float>> m_modelcpudistances;
bool m_indirectchanging{false}; // the walk under way met a submodel which may look different in the next frame
// the gpu culling of the pass under way (Begin_Pass_Cull()), and whether there's one
gfx::instance_cull *m_passcull{nullptr};
bool m_instancecullpass{false};
bool m_modelspass{false}; // the models drawn on their own of the pass under way go to its gpu culling
bool m_indirectcolourmodels{false}; // with m_indirectcolourpending: the depth pass culled and recorded them too
bool m_sleeperspass{false}; // the sleepers of the pass under way go to its gpu culling (the one of the main view)
bool m_indirectcoloursleepers{false}; // with m_indirectcolourpending: the depth pass culled and recorded the sleepers too
// the instances were culled in the depth pass of the whole scene, the colour pass draws them from the recorded draws
bool m_indirectcolourpending{false};
// Instances_Cast_Shadow() of the groups of the shadow pass under way
std::unordered_map<scene::basic_cell::instance_bucket_key, bool, scene::basic_cell::instance_bucket_key_hash> m_shadowcasters;
// the sleepers of tracks culled on the gpu with the instances of the main view (Global.gfx_gpucull_sleepers): records made
// from TTrack::m_sleeper_local_transforms when the track is queued first, and again when they or the instance records
// (editor, m_instancerecordsversion) went out of date. the records of tracks not queued for a while are let go
struct sleeper_records
{
scene::basic_cell::instance_bucket bucket; // the records only, no instances
scene::basic_cell::instance_bucket_key key; // the model, and the skin in the slot Render_Sleepers() sets it in
scene::bounding_area area; // of the locations of the sleepers
TModel3d const *model{nullptr}; // what the records were made from
material_handle skin{null_handle};
glm::mat4 const *transforms{nullptr};
std::size_t count{0};
unsigned int frame{0}; // queued last
};
std::unordered_map<TTrack const *, sleeper_records> m_sleeperrecords;
// the model instances drawn on their own culled on the gpu (Global.gfx_gpucull_models), each one a bucket of its own with
// a group of its own: the record made when the instance is queued first, and again when the instance records went out of
// date (editor, events: m_instancerecordsversion). the records of instances not queued for a while are let go
struct model_records
{
scene::basic_cell::instance_bucket bucket; // the instance alone
scene::basic_cell::instance_bucket_key key; // the model, the skins and the instance
scene::bounding_area area; // the location of the instance
unsigned int frame{0}; // queued last
};
std::unordered_map<TAnimModel const *, model_records> m_modelrecords;
// counts per lod band of the sleepers Render_Sleepers() drew in the pass, by model and skin, for the comparison (they're
// drawn before the culling runs)
std::unordered_map<scene::basic_cell::instance_bucket_key, std::vector<std::uint32_t>, scene::basic_cell::instance_bucket_key_hash> m_sleeperbandcounts;
// a draw recorded from the walk of the submodel tree of a group in one lod band (Walk_Indirect_Draws()), to be replayed once
// the instances of its command are known: the state the walk left for it and what it bound
struct indirect_draw
{
gl::model_ubs ubs;
material_handle material;
TSubModel const *submodel;
gfx::geometry_handle geometry;
unsigned int type; // primitive
bool blending; // drawn with the transparency hack of submodels faded out
std::uint32_t group{0}; // the buffers of the geometry, which the draws of a multi draw share (geometrybank_manager::draw_group())
};
std::vector<indirect_draw> m_indirectdraws;
std::vector<gl::instance_draw_command> m_indirectcommands; // in the order of m_indirectdraws
bool m_indirectrecording{false}; // the draws and the material binds of the backend record instead of drawing
std::uint32_t m_indirectentry{0}; // entry (group and lod band) recorded
material_handle m_indirectmaterial{null_handle}; // what the last material bind noted while recording
TSubModel const *m_indirectsubmodel{nullptr};
// the walks Walk_Indirect_Draws() made for the cascades of the sun shadows in the frame, for the other cascades to copy: the
// draws and commands recorded for a group (model and skins) in each of its lod bands, first none where no cascade walked it
struct cascade_band
{
std::uint32_t first;
std::uint32_t count;
};
struct cascade_record
{
unsigned int frame{0};
std::vector<cascade_band> bands;
};
std::unordered_map<scene::basic_cell::instance_bucket_key, cascade_record, scene::basic_cell::instance_bucket_key_hash> m_cascaderecords;
std::vector<indirect_draw> m_cascadedraws;
std::vector<gl::instance_draw_command> m_cascadecommands;
unsigned int m_cascaderecordframe{0};
// the walks Walk_Indirect_Draws() made in earlier frames (Global.gfx_gpucull_walkcache). what the walk of a group of instances
// in a lod band records depends on the model with its skins, the band and whether the pass draws shadows, not on the camera,
// so it's kept while the shared state of the model stays the same (TModel3d::state_version()) and used instead of walking the
// tree again. a walk meeting an animated submodel, one lit by the luminance or the stars isn't kept. the models drawn on their
// own (with an instance in the key) are walked every time
struct walk_band
{
std::vector<indirect_draw> draws; // as recorded
float bandmin{0.f};
std::uint32_t modelversion{0};
bool blending{false}; // the state the walk began in
bool valid{false};
};
struct walk_record
{
unsigned int frame{0}; // used last
std::array<std::vector<walk_band>, 2> bands; // of the colour passes (the main view, the environment map) and of the shadows
};
std::unordered_map<scene::basic_cell::instance_bucket_key, walk_record, scene::basic_cell::instance_bucket_key_hash> m_walkrecords;
int m_walkrankcutoff{0}; // Global.gfx_shadow_rank_cutoff the walks were made with
// what the walks of the frame did, for the panel: walks made, of them not kept, walks taken from earlier frames, and the cpu time
// of the walks and of putting the draws in order with their blocks uploaded
struct walk_counts
{
std::uint32_t walked{0};
std::uint32_t changing{0};
std::uint32_t reused{0};
double walktime{0.0};
double ordertime{0.0};
};
walk_counts m_walkcounts;
walk_counts m_lastwalkcounts;
unsigned int m_walkcountsframe{0};
// appends the draws of a walk kept from an earlier frame to the recorded draws, with the commands of the entry under way
void Replay_Walk(walk_band const &Walk, bool const Shadows);
// working space of Order_Indirect_Draws(), kept between frames
struct indirect_queue
{
std::uint32_t next; // draw
std::uint32_t end;
std::uint32_t link; // next queue waiting for the same state
};
struct indirect_state
{
std::uint32_t head;
std::uint32_t tail;
};
// what a multi draw has in common, and the draw which has it
struct indirect_state_key
{
material_handle material;
std::uint32_t variant; // of the textures of the material
std::uint32_t bank;
unsigned int type;
std::uint32_t indexed;
bool blending;
std::uint32_t draw;
auto tie() const
{
return std::tie(material, variant, bank, type, indexed, blending);
}
};
std::vector<indirect_state_key> m_indirectstatekeys;
std::vector<std::uint32_t> m_indirectdrawstates;
std::vector<indirect_queue> m_indirectqueues;
std::vector<indirect_state> m_indirectstates;
std::vector<std::uint32_t> m_indirectstateorder;
std::vector<std::uint32_t> m_indirectorder;
std::vector<indirect_draw> m_indirectdrawsordered;
std::vector<gl::instance_draw_command> m_indirectcommandsordered;
// a vehicle drawn in the colour pass of this frame moved (motion blur)
bool m_vehiclesmoving{false};
// set while a preview is drawn: submodels faded out entirely are skipped, instead of being drawn invisible over the stencil

View File

@@ -0,0 +1,829 @@
/*
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 part of the shared walk of the scene which feeds the gpu culling of the instances and records the draws made from its
// result (stage N6c of claude/vulkan14-renderer-plan.md): the records of the sleepers and of the models drawn on their own, the
// queueing of buckets, the walks of the submodel trees of the culled groups and the order of the recorded draws
#include "stdafx.h"
#include "rendering/common/scenewalk.h"
#include "model/AnimModel.h"
#include "model/Model3d.h"
#include "utilities/Globals.h"
#include "utilities/utilities.h"
namespace gfx
{
namespace
{
// the group of the sleepers of a track in the gpu culling: their model, and the skin in the slot Render_Sleepers() sets it in
scene::basic_cell::instance_bucket_key sleeper_key(TTrack const &Track)
{
scene::basic_cell::instance_bucket_key key;
key.pModel = Track.m_sleeper_model;
key.skins[1] = Track.m_sleeper_skin;
return key;
}
} // namespace
void scene_walk::Begin_Pass_Cull(gfx::instance_cull *Cull, bool const Mainview)
{
m_passcull = Cull;
m_instancecullpass = false;
if (m_passcull != nullptr)
{
m_instancecullpass = true;
// the same choices as in Render_Instanced()
auto const reflections{m_renderpass.draw_mode == rendermode::reflections};
gfx::instance_cull::pass_setup setup;
setup.frame = m_framestamp;
setup.camera = m_renderpass.pass_camera.position();
setup.distancecamera = (m_renderpass.draw_mode == rendermode::shadows ? m_renderpass.viewport_camera.position() : m_renderpass.pass_camera.position());
setup.frustum = &m_renderpass.pass_camera.frustum();
setup.distancescale = (reflections ? 1.f : static_cast<float>(1.0 / (static_cast<double>(Global.ZoomFactor) * static_cast<double>(Global.ZoomFactor) * static_cast<double>(Global.fDistanceFactor))));
setup.distanceoffset = (reflections ? static_cast<float>(sq(EU07_REFLECTIONFIDELITYOFFSET)) : 0.f);
setup.distancerange = (reflections ? static_cast<float>(sq(Global.reflectiontune.range_instances)) : std::numeric_limits<float>::max());
setup.drawrange = static_cast<float>(sq(m_renderpass.draw_range + 250));
setup.zoom = Global.ZoomFactor;
setup.drawing = (Global.gfx_gpucull == 1);
m_passcull->begin_pass(setup);
}
// the sleepers of tracks go with the instances of the main view
m_sleeperspass = (Mainview && (m_passcull != nullptr) && Global.gfx_gpucull_sleepers);
if (m_sleeperspass && (false == m_passcull->drawing()))
{
for (auto &counts : m_sleeperbandcounts)
{
counts.second.clear();
}
}
// the models drawn on their own go with the instances of every pass culled on the gpu
m_modelspass = (m_instancecullpass && Global.gfx_gpucull_models);
}
void scene_walk::Compare_Pass_Cull()
{
// the sleepers the cpu drew along the tracks, before the culling ran
if (m_sleeperspass)
{
for (auto const &counts : m_sleeperbandcounts)
{
if (false == counts.second.empty())
{
m_passcull->cpu_result(counts.first, counts.second, true);
}
}
}
// and the models drawn on their own
for (auto const &model : m_modelcpudistances)
{
m_passcull->cpu_instance(model.first, model.second);
}
}
void scene_walk::End_Pass_Cull()
{
if (m_instancecullpass)
{
m_passcull->end_pass();
m_instancecullpass = false;
m_passcull = nullptr;
}
m_sleeperspass = false;
m_modelspass = false;
}
bool scene_walk::walk_bucket(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, bool const Bucketrange, scene::bounding_area const &Area)
{
if (m_instancecullpass)
{
// the gpu tests every instance of the bucket, the way Render_Instanced() does. models with nothing to draw in the
// sun shadows are left out of them, as Render_Instanced() leaves them
if ((m_renderpass.draw_mode != rendermode::shadows) || Instances_Cast_Shadow(Key, Bucket))
{
m_passcull->add_bucket(Key, Bucket, (Bucketrange ? Bucket_Area(Bucket) : Area));
}
// and when the instances are drawn from its result, the cpu has nothing more to do with them
if (m_passcull->drawing())
{
return false;
}
}
// the depth pass of the whole scene culled them already, the colour pass draws what it recorded
if (m_indirectcolourpending)
{
return false;
}
return true;
}
void scene_walk::walk_bucket_culled(scene::basic_cell::instance_bucket const &Bucket)
{
if (m_instancecullpass)
{
m_passcull->skip_bucket(Bucket);
}
}
bool scene_walk::walk_queue_model(TAnimModel *Instance, bool const Inrange)
{
return Queue_Model(Instance, Inrange);
}
bool scene_walk::walk_queue_sleepers(TTrack *Track)
{
if (m_sleeperspass)
{
// culled with the instances of the pass and drawn from the result, or drawn by the cpu as well to compare
Queue_Sleepers(Track);
if (m_passcull->drawing())
{
return true;
}
}
else if (m_indirectcolourpending && m_indirectcoloursleepers)
{
// the depth pass of the whole scene culled and recorded them, the colour pass draws what it recorded
return true;
}
return false;
}
void scene_walk::walk_sleepers_drawn(TTrack const &Track, std::vector<float> const &Lodbounds, std::vector<std::pair<float, glm::mat4>> const &Drawn)
{
if (m_sleeperspass && m_instancecullpass && (false == m_passcull->drawing()))
{
// the comparison with the gpu culling: the sleepers drawn by lod band, the bands as the culling makes them, split by
// the bounds of the model past 0 and short of the largest float
auto const counted = [](float const Bound) {
return (Bound > 0.f) && (Bound < std::numeric_limits<float>::max());
};
auto &counts{m_sleeperbandcounts[sleeper_key(Track)]};
auto const bands{static_cast<std::size_t>(std::count_if(Lodbounds.begin(), Lodbounds.end(), counted)) + 1};
if (counts.size() < bands)
{
counts.resize(bands, 0);
}
for (auto const &survivor : Drawn)
{
std::size_t band{0};
for (auto const bound : Lodbounds)
{
if (counted(bound) && (bound <= survivor.first))
{
++band;
}
}
++counts[band];
}
}
}
void scene_walk::walk_instances_compare(scene::basic_cell::instance_bucket_key const &Key, std::vector<std::uint32_t> const &Bandcounts)
{
m_passcull->cpu_result(Key, Bandcounts);
}
void scene_walk::Queue_Sleepers(TTrack *Track)
{
// the gates of Render_Sleepers(); the range is tested for the whole track
if ((Track->m_sleeper_model == nullptr) || Track->m_sleeper_local_transforms.empty() || (Global.SleeperDistance <= 0.f))
{
return;
}
auto const distsq{glm::length2(Track->location() - m_renderpass.pass_camera.position())};
auto const cutoffsq{static_cast<double>(Global.SleeperDistance) * static_cast<double>(Global.SleeperDistance)};
if (distsq > cutoffsq)
{
return;
}
auto &records{m_sleeperrecords[Track]};
auto const &transforms{Track->m_sleeper_local_transforms};
if ((records.bucket.recordsversion != m_instancerecordsversion) || (records.model != Track->m_sleeper_model) || (records.skin != Track->m_sleeper_skin) ||
(records.transforms != transforms.data()) || (records.count != transforms.size()))
{
// each sleeper is a record placed and turned by its transform, relative to the origin of the track (the centre of its
// cell), with the sphere Render_Sleepers() tests against the frustum
auto const radius{std::max(Track->m_sleeper_model->bounding_radius(), 2.0f)};
auto &bucket{records.bucket};
bucket.records.clear();
bucket.records.reserve(transforms.size());
glm::vec3 low{std::numeric_limits<float>::max()};
glm::vec3 high{std::numeric_limits<float>::lowest()};
for (auto const &local : transforms)
{
scene::basic_cell::instance_record record;
glm::vec3 const position{local[3]};
record.position = packed_vec3{position};
record.radius = radius;
record.rangesquaredmin = 0.f;
record.rangesquaredmax = std::numeric_limits<float>::max();
record.flags = scene::basic_cell::instance_visible | scene::basic_cell::instance_instanceable;
for (int column = 0; column < 3; ++column)
{
record.rotationscale[column] = local[column];
}
bucket.records.emplace_back(record);
low = glm::min(low, position);
high = glm::max(high, position);
}
bucket.recordorigin = Track->m_origin;
bucket.lodrangesquared = std::numeric_limits<float>::max();
bucket.recordsversion = m_instancerecordsversion;
bucket.gpustale = true;
records.key = sleeper_key(*Track);
records.area = scene::bounding_area{Track->m_origin + glm::dvec3{(low + high) * 0.5f}, 0.5f * glm::length(high - low)};
records.model = Track->m_sleeper_model;
records.skin = Track->m_sleeper_skin;
records.transforms = transforms.data();
records.count = transforms.size();
}
records.frame = m_framestamp;
m_passcull->add_sleepers(records.key, records.bucket, records.area);
}
// queues a model instance drawn on its own in the gpu culling of the pass, as a bucket and a group of its own. the culling
// makes the tests of Render( TAnimModel * ): visibility, frustum, the range of the instance, the draw range, the size on the
// screen, and gives it the lod band of the bounds of its submodels (mindistance/maxdistance) its distance falls in, then
// Walk_Indirect_Draws() animates, prepares and walks its submodel tree from its origin, like it does for a group of
// instanced ones
bool scene_walk::Queue_Model(TAnimModel *Instance, bool const Inrange)
{
// a billboard is turned to the camera from the modelview of the instance, which a walk from its origin doesn't have
if ((false == Instance->m_cameraindependent) || (Instance->Model() == nullptr))
{
return false;
}
if (false == m_modelspass)
{
// the depth pass of the whole scene culled and recorded it, the colour pass draws what it recorded
return (m_indirectcolourpending && m_indirectcolourmodels);
}
auto const drawing{m_passcull->drawing()};
// a model hidden by an event, or in a cell wholly out of the range of the pass, would be left out by the culling
if ((false == Instance->visible()) || (false == Inrange))
{
return drawing;
}
auto &records{m_modelrecords[Instance]};
auto &bucket{records.bucket};
if ((bucket.recordsversion != m_instancerecordsversion) || (bucket.records.size() != 1))
{
// the key, the instance and the record anew: the editor may have given the instance another model or skins, or
// another instance may have taken the place of a deleted one
auto &key{records.key};
key = {};
key.pModel = Instance->Model();
if (auto const *material{Instance->Material()})
{
for (int skin = 0; skin < 5; ++skin)
{
key.skins[skin] = material->replacable_skins[skin];
}
}
key.instance = Instance;
bucket.instances.assign(1, Instance);
// the location of the instance as the origin of the record, which keeps the precision
Update_Instance_Records(key.pModel, bucket, Instance->location());
// drawn by its bucket, though it isn't instanceable
bucket.records.front().flags |= scene::basic_cell::instance_instanceable;
records.area = scene::bounding_area{Instance->location(), 0.f};
}
records.frame = m_framestamp;
if (m_renderpass.draw_mode == rendermode::shadows)
{
// a model with nothing to draw in the shadows is left out of them, like the instances. the answer depends on the
// model and the skins only, and is shared by the instances with them
auto modelkey{records.key};
modelkey.instance = nullptr;
if (false == Instances_Cast_Shadow(modelkey, bucket))
{
return drawing;
}
}
// out of the frustum of the pass, beyond its range or too small on the screen by its record: the culling would leave it out,
// and the walk of Walk_Indirect_Draws() isn't made for it
if (Global.gfx_gpucull_bucketrange && (false == EditorModeFlag) && Bucket_Culled(bucket))
{
m_passcull->skip_bucket(bucket);
return drawing;
}
m_passcull->add_bucket(records.key, bucket, records.area);
if (false == drawing)
{
// the cpu draws it too, and tells what it drew for the comparison
m_modelcomparekey = &records.key;
}
return drawing;
}
bool scene_walk::Instances_Cast_Shadow(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket)
{
auto const lookup{m_shadowcasters.find(Key)};
if (lookup != m_shadowcasters.end())
{
return lookup->second;
}
// the skins of the instance Render_Instanced() would take them from
TAnimModel const *instance{nullptr};
for (auto const *candidate : Bucket.instances)
{
if (candidate != nullptr)
{
instance = candidate;
break;
}
}
auto const *skins{instance != nullptr && instance->Material() != nullptr ? instance->Material()->replacable_skins : nullptr};
auto castsshadow{false};
m_instance_lodpending.clear();
m_instance_lodpending.push_back(Key.pModel != nullptr ? Key.pModel->Root : nullptr);
while ((false == castsshadow) && (false == m_instance_lodpending.empty()))
{
auto const *submodel = m_instance_lodpending.back();
m_instance_lodpending.pop_back();
if (submodel == nullptr)
{
continue;
}
if (submodel->eType < TP_ROTATOR)
{
auto const material{submodel->m_material >= 0 ? submodel->m_material : (skins != nullptr ? skins[-submodel->m_material] : null_handle)};
castsshadow = (walk_material_data(material).shadow_rank <= Global.gfx_shadow_rank_cutoff);
}
m_instance_lodpending.push_back(submodel->Child);
m_instance_lodpending.push_back(submodel->Next);
}
m_shadowcasters.emplace(Key, castsshadow);
return castsshadow;
}
void scene_walk::Walk_Indirect_Draws(gfx::instance_cull const &Cull, bool const Cascade)
{
m_indirectdraws.clear();
m_indirectcommands.clear();
auto const &lodbounds{Cull.lod_bounds()};
// the cascades of the sun shadows make the same walks: the mode, the lod band and the state of the models are the same in all
// of them within a frame, only the instances differ, and those the gpu fills in. the walk of a group in a band is made by the
// first cascade which draws it, the others copy its draws and commands with entries of their own
auto const cascade{(m_renderpass.draw_mode == rendermode::shadows) && Cascade};
auto constexpr nowalk{std::numeric_limits<std::uint32_t>::max()};
if (cascade && (m_cascaderecordframe != m_framestamp))
{
m_cascaderecordframe = m_framestamp;
m_cascadedraws.clear();
m_cascadecommands.clear();
if ((m_framestamp % 256) == 0)
{
// the groups not drawn for a while
for (auto records{m_cascaderecords.begin()}; records != m_cascaderecords.end();)
{
if (m_framestamp - records->second.frame > 256)
{
records = m_cascaderecords.erase(records);
}
else
{
++records;
}
}
}
}
if (m_walkcountsframe != m_framestamp)
{
m_walkcountsframe = m_framestamp;
m_lastwalkcounts = m_walkcounts;
m_walkcounts = {};
}
auto const walkstart{std::chrono::steady_clock::now()};
// the walks of earlier frames. they go when the switch is off, as the models may change meanwhile without the walks noticing
// it the next time (the season), and when the shadows take other submodels
auto const walkcache{Global.gfx_gpucull_walkcache && (false == m_previewing)};
if ((false == Global.gfx_gpucull_walkcache) || (m_walkrankcutoff != Global.gfx_shadow_rank_cutoff))
{
m_walkrecords.clear();
m_walkrankcutoff = Global.gfx_shadow_rank_cutoff;
}
if (walkcache && ((m_framestamp % 256) == 0))
{
// the groups not drawn for a while
for (auto records{m_walkrecords.begin()}; records != m_walkrecords.end();)
{
if (m_framestamp - records->second.frame > 256)
{
records = m_walkrecords.erase(records);
}
else
{
++records;
}
}
}
auto const shadows{m_renderpass.draw_mode == rendermode::shadows};
m_indirectrecording = true;
for (auto const &group : Cull.draw_groups())
{
auto *model{group.key->pModel};
if ((model == nullptr) || (model->Root == nullptr) || ((group.instance == nullptr) && (false == group.sleepers)))
{
continue;
}
// a model drawn on its own is prepared with its lights and animations each time; the others are prepared only for a walk.
// the version is taken before, so a walk prepared by something which changes the model isn't taken up again
walk_record *kept{nullptr};
if (walkcache && (group.key->instance == nullptr))
{
kept = &m_walkrecords[*group.key];
kept->frame = m_framestamp;
auto &bands{kept->bands[shadows ? 1 : 0]};
if (bands.size() != group.lodcount + 1)
{
bands.clear();
bands.resize(group.lodcount + 1);
}
}
auto const modelversion{model->state_version()};
material_handle const *skins{nullptr};
auto alpha{0x30300030};
auto prepared{false};
auto const prepare = [&]() {
prepared = true;
if (group.sleepers)
{
// as Render_Sleepers() sets them up: the skin of the track, if any. like scenery models they pick no texture
// variants of their own
if (group.key->skins[1] != null_handle)
{
skins = group.key->skins.data();
}
TSubModel::iVariantSeed = 0;
}
else
{
if (group.key->instance != nullptr)
{
// a model drawn on its own: its lights and animations, the way Render( TAnimModel * ) sets them up
group.instance->RaAnimate(m_framestamp);
}
group.instance->RaPrepare();
auto const *material{group.instance->Material()};
if (material != nullptr)
{
skins = material->replacable_skins;
alpha = material->textures_alpha;
}
}
alpha ^= 0x0F0F000F;
};
// the walks of the group the cascades made in this frame. a group whose bands were all walked already isn't prepared
cascade_record *walks{nullptr};
if (cascade)
{
walks = &m_cascaderecords[*group.key];
if ((walks->frame != m_framestamp) || (walks->bands.size() != group.lodcount + 1))
{
walks->frame = m_framestamp;
walks->bands.assign(group.lodcount + 1, cascade_band{nowalk, 0});
}
}
else if (kept == nullptr)
{
prepare();
}
for (std::uint32_t band = 0; band <= group.lodcount; ++band)
{
// a band holds the squared distances from its lower bound up to the next one; any of them selects the same lod
auto const bandmin{band == 0 ? 0.f : lodbounds[group.lodfirst + band - 1]};
auto const bandmax{band == group.lodcount ? std::numeric_limits<float>::max() : lodbounds[group.lodfirst + band]};
if ((bandmin > group.distancemax) || (bandmax <= group.distancemin))
{
continue;
}
m_indirectentry = group.entryfirst + band;
if ((walks != nullptr) && (walks->bands[band].first != nowalk))
{
// walked by another cascade: its draws, for the instances of this entry
auto const &walk{walks->bands[band]};
m_indirectdraws.insert(m_indirectdraws.end(), m_cascadedraws.begin() + walk.first, m_cascadedraws.begin() + walk.first + walk.count);
for (std::uint32_t draw = 0; draw < walk.count; ++draw)
{
auto &command{m_indirectcommands.emplace_back(m_cascadecommands[walk.first + draw])};
command.entry = m_indirectentry;
}
// as the walk would count them
m_renderpass.draw_stats.submodels += static_cast<int>(walk.count);
m_renderpass.draw_stats.drawcalls += static_cast<int>(walk.count);
continue;
}
auto const walkfirst{m_indirectdraws.size()};
auto *keptwalk{kept != nullptr ? &kept->bands[shadows ? 1 : 0][band] : nullptr};
if ((keptwalk != nullptr) && (keptwalk->valid) && (keptwalk->bandmin == bandmin) && (keptwalk->modelversion == modelversion) &&
(keptwalk->blending == walk_blending_enabled()))
{
// walked in an earlier frame, with the model as it is now
Replay_Walk(*keptwalk, shadows);
++m_walkcounts.reused;
}
else
{
if (false == prepared)
{
prepare();
}
auto const blending{walk_blending_enabled()};
m_indirectchanging = false;
::glPushMatrix();
::glLoadIdentity();
model->Root->fSquareDist = bandmin;
model->Root->ReplacableSet(skins, alpha);
model->Root->pRoot = model;
Render(model->Root);
::glPopMatrix();
++m_walkcounts.walked;
if (m_indirectchanging)
{
++m_walkcounts.changing;
}
if (keptwalk != nullptr)
{
keptwalk->valid = (false == m_indirectchanging);
if (keptwalk->valid)
{
keptwalk->draws.assign(m_indirectdraws.begin() + walkfirst, m_indirectdraws.end());
keptwalk->bandmin = bandmin;
keptwalk->modelversion = modelversion;
keptwalk->blending = blending;
}
else
{
keptwalk->draws.clear();
}
}
}
if (walks != nullptr)
{
walks->bands[band] = cascade_band{static_cast<std::uint32_t>(m_cascadedraws.size()), static_cast<std::uint32_t>(m_indirectdraws.size() - walkfirst)};
m_cascadedraws.insert(m_cascadedraws.end(), m_indirectdraws.begin() + walkfirst, m_indirectdraws.end());
m_cascadecommands.insert(m_cascadecommands.end(), m_indirectcommands.begin() + walkfirst, m_indirectcommands.end());
}
}
}
m_indirectrecording = false;
m_walkcounts.walktime += std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - walkstart).count();
}
void scene_walk::Record_Indirect_Draw(gfx::geometry_handle const &Geometry)
{
// the walk of Walk_Indirect_Draws(): the draw is made later, from the gpu culling, with this state
auto &geometry{walk_geometry()};
gfx::indirect_source source;
if (geometry.indirect_source(Geometry, source))
{
m_indirectcommands.push_back({source.count, 0, source.first, static_cast<std::uint32_t>(source.basevertex), 0, m_indirectentry, (source.indexed ? 1u : 0u), 0});
m_indirectdraws.push_back({model_ubs, m_indirectmaterial, m_indirectsubmodel, Geometry, source.type, walk_blending_enabled(), geometry.draw_group(Geometry)});
}
}
void scene_walk::Replay_Walk(walk_band const &Walk, bool const Shadows)
{
auto &geometry{walk_geometry()};
for (auto const &walkdraw : Walk.draws)
{
// the place of the chunk in its bank is asked for again: the bank may have been let go since and made anew, which the
// question (as the walk) takes care of, uploading the chunk
gfx::indirect_source source;
if (false == geometry.indirect_source(walkdraw.geometry, source))
{
continue;
}
m_indirectcommands.push_back({source.count, 0, source.first, static_cast<std::uint32_t>(source.basevertex), 0, m_indirectentry, (source.indexed ? 1u : 0u), 0});
auto &draw{m_indirectdraws.emplace_back(walkdraw)};
draw.type = source.type;
draw.group = geometry.draw_group(walkdraw.geometry);
// the walk sets the placement of the submodel, and before each draw of the colour passes the parameters of its material
// (the material bind); the rest of the block is what the pass has at the moment, as the walk would leave it
auto const &walked{walkdraw.ubs};
draw.ubs = model_ubs;
draw.ubs.modelview = walked.modelview;
draw.ubs.modelviewnormal = walked.modelviewnormal;
if (false == Shadows)
{
std::memcpy(&draw.ubs.param[0], &walked.param[0], sizeof(draw.ubs.param));
draw.ubs.opacity = walked.opacity;
draw.ubs.alpha_mult = walked.alpha_mult;
}
}
// as the walk would count them
m_renderpass.draw_stats.submodels += static_cast<int>(Walk.draws.size());
m_renderpass.draw_stats.drawcalls += static_cast<int>(Walk.draws.size());
}
texture_handle scene_walk::Indirect_Shadow_Texture(material_handle const Material) const
{
if (Material == null_handle)
{
return null_handle;
}
return walk_material_data(Material).textures[0];
}
// the order of the recorded draws for multi draws. draws go in one multi draw when they follow one another with the same
// state (material and its texture variant, geometry bank, primitive, blending; Indirect_Mergeable()). the draws of one entry,
// the walk of one submodel tree in one lod band, keep their order: submodels of a model may lie on one another and rely on
// the later one being drawn over the earlier one. the draws of different entries are drawn for different instances, which
// the gpu draws in no particular order anyway, so they're free to move. the entries are queues of draws; the draws at the
// heads of the queues which have the state taken next all go, each queue as far as its draws keep that state, then the
// queues wait for the state of their next draw. the states go in the order in which the queues first reach them
void scene_walk::Order_Indirect_Draws()
{
auto const count{m_indirectdraws.size()};
if (count < 2)
{
return;
}
// a number for each state the draws have, by sorting the states (the buffers are kept between frames)
m_indirectstatekeys.clear();
// the sun shadows draw with the program and the texture of the alpha test, whatever the material
auto const shadows{m_renderpass.draw_mode == rendermode::shadows};
for (std::size_t index = 0; index < count; ++index)
{
auto const &draw{m_indirectdraws[index]};
if (shadows)
{
m_indirectstatekeys.push_back({Indirect_Shadow_Texture(draw.material), 0u, draw.group, draw.type, m_indirectcommands[index].indexed, draw.blending, static_cast<std::uint32_t>(index)});
continue;
}
auto variant{0u};
if (draw.material != null_handle)
{
auto const &material{walk_material_data(draw.material)};
if (false == material.texture_variants.empty())
{
variant = TSubModel::VariantId(draw.submodel) % static_cast<std::uint32_t>(material.texture_variants.size());
}
}
m_indirectstatekeys.push_back({draw.material, variant, draw.group, draw.type, m_indirectcommands[index].indexed, draw.blending, static_cast<std::uint32_t>(index)});
}
std::sort(std::begin(m_indirectstatekeys), std::end(m_indirectstatekeys), [](indirect_state_key const &Left, indirect_state_key const &Right) { return Left.tie() < Right.tie(); });
m_indirectdrawstates.resize(count);
std::uint32_t statecount{0};
for (std::size_t index = 0; index < count; ++index)
{
if ((index > 0) && (m_indirectstatekeys[index].tie() != m_indirectstatekeys[index - 1].tie()))
{
++statecount;
}
m_indirectdrawstates[m_indirectstatekeys[index].draw] = statecount;
}
++statecount;
if (statecount == count)
{
// no two draws share a state, nothing would go together
return;
}
auto constexpr none{std::numeric_limits<std::uint32_t>::max()};
m_indirectstates.assign(statecount, indirect_state{none, none});
m_indirectstateorder.clear();
m_indirectqueues.clear();
// the queue waits for the state of its next draw, behind the queues waiting for it already
auto const enqueue = [this, none](std::uint32_t const Queue) {
auto &queue{m_indirectqueues[Queue]};
auto &state{m_indirectstates[m_indirectdrawstates[queue.next]]};
queue.link = none;
if (state.head == none)
{
state.head = Queue;
m_indirectstateorder.push_back(m_indirectdrawstates[queue.next]);
}
else
{
m_indirectqueues[state.tail].link = Queue;
}
state.tail = Queue;
};
for (std::size_t index = 0; index < count;)
{
auto end{index + 1};
while ((end < count) && (m_indirectcommands[end].entry == m_indirectcommands[index].entry))
{
++end;
}
m_indirectqueues.push_back({static_cast<std::uint32_t>(index), static_cast<std::uint32_t>(end), none});
index = end;
}
for (std::uint32_t queue = 0; queue < m_indirectqueues.size(); ++queue)
{
enqueue(queue);
}
m_indirectorder.clear();
for (std::size_t taken = 0; taken < m_indirectstateorder.size(); ++taken)
{
auto const stateindex{m_indirectstateorder[taken]};
auto queueindex{m_indirectstates[stateindex].head};
m_indirectstates[stateindex] = {none, none};
while (queueindex != none)
{
auto &queue{m_indirectqueues[queueindex]};
auto const nextqueue{queue.link};
while ((queue.next < queue.end) && (m_indirectdrawstates[queue.next] == stateindex))
{
m_indirectorder.push_back(queue.next);
++queue.next;
}
if (queue.next < queue.end)
{
enqueue(queueindex);
}
queueindex = nextqueue;
}
}
m_indirectdrawsordered.clear();
m_indirectcommandsordered.clear();
for (auto const index : m_indirectorder)
{
m_indirectdrawsordered.push_back(m_indirectdraws[index]);
m_indirectcommandsordered.push_back(m_indirectcommands[index]);
}
m_indirectdraws.swap(m_indirectdrawsordered);
m_indirectcommands.swap(m_indirectcommandsordered);
}
bool scene_walk::Indirect_Mergeable(std::size_t const First, std::size_t const Other) const
{
auto const &first{m_indirectdraws[First]};
auto const &other{m_indirectdraws[Other]};
if ((first.material != other.material) || (first.blending != other.blending) || (first.type != other.type) || (first.group != other.group) ||
(m_indirectcommands[First].indexed != m_indirectcommands[Other].indexed))
{
return false;
}
if (first.material == null_handle)
{
return true;
}
// the texture set the material bind binds
auto const &material{walk_material_data(first.material)};
if (material.texture_variants.empty())
{
return true;
}
return (&material.GetTextures(TSubModel::VariantId(first.submodel)) == &material.GetTextures(TSubModel::VariantId(other.submodel)));
}
void scene_walk::Expire_Indirect_Records()
{
if ((m_framestamp % 256) == 0)
{
// the sleeper records of tracks out of sight for a while. their places in the record buffer of the gpu culling are
// dropped when it starts over
for (auto records{m_sleeperrecords.begin()}; records != m_sleeperrecords.end();)
{
if (m_framestamp - records->second.frame > 256)
{
records = m_sleeperrecords.erase(records);
}
else
{
++records;
}
}
// and so are the records of the models drawn on their own
for (auto records{m_modelrecords.begin()}; records != m_modelrecords.end();)
{
if (m_framestamp - records->second.frame > 256)
{
records = m_modelrecords.erase(records);
}
else
{
++records;
}
}
}
}
void scene_walk::Clear_Indirect_Records()
{
m_sleeperrecords.clear();
m_modelrecords.clear();
m_cascaderecords.clear();
m_walkrecords.clear();
}
} // namespace gfx

View File

@@ -0,0 +1,273 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/common/shadowfit.h"
#include <cstring>
#include "rendering/openglcamera.h"
#include "utilities/Globals.h"
namespace gfx
{
namespace
{
// the nearest distance the first cascade may start from and the least one the last reaches, metres: the cascades don't
// get any closer to the camera than this, however close the pixels are
float const SPLIT_NEAR_MIN{0.5f};
float const SPLIT_FAR_MIN{16.f};
// how far past its distances a cascade takes the pixels, this many metres and this share of the distance: the camera moves
// in the frame or two before the cascades are drawn from the measurement, and a pixel which came into another cascade since
// has to be within its map
float const MARGIN_METRES{1.f};
float const MARGIN_SHARE{0.05f};
} // namespace
gl::shadow_fit_data shadow_fit::begin_measurement(view const &View, unsigned int const Frame)
{
// numbers start from 1, 0 is no measurement
if (++m_measurement == 0)
++m_measurement;
gl::shadow_fit_data data{};
data.measurement = m_measurement;
data.depthmin = std::numeric_limits<float>::infinity();
for (std::size_t cascade = 0; cascade < gl::MAX_CASCADES; ++cascade)
{
for (std::size_t axis = 0; axis < 4; ++axis)
{
data.bounds[cascade * 8 + axis] = 0xFFFFFFFFu;
data.bounds[cascade * 8 + 4 + axis] = 0u;
}
}
m_pending[m_measurement % PENDING] = pending{m_measurement, View, Frame};
return data;
}
glm::vec4 shadow_fit::bounds_params(view const &View)
{
auto const farmin{std::min(SPLIT_FAR_MIN, View.range)};
return glm::vec4{View.range, std::min(SPLIT_NEAR_MIN, farmin * 0.25f), farmin, std::clamp(Global.gfx_shadow_sdsm_split, 0.f, 1.f)};
}
glm::vec4 shadow_fit::margin_params()
{
return glm::vec4{MARGIN_METRES, MARGIN_SHARE, 0.f, 0.f};
}
void shadow_fit::arrived(gl::shadow_fit_data const &Data)
{
auto const &source{m_pending[Data.measurement % PENDING]};
if ((Data.measurement != 0) && (source.measurement == Data.measurement))
m_latest = result{Data, source.source, source.frame};
}
float shadow_fit::unorder(std::uint32_t const Value)
{
auto const bits{((Value & 0x80000000u) != 0u) ? (Value & 0x7FFFFFFFu) : ~Value};
float result;
std::memcpy(&result, &bits, sizeof(result));
return result;
}
glm::vec3 shadow_fit::light_vector(glm::vec3 const &Sun)
{
// cap the vertical angle to keep shadows from getting too long
return glm::normalize(glm::vec3{Sun.x, std::min(Sun.y, Global.gfx_shadow_angle_min), Sun.z});
}
glm::mat3 shadow_fit::light_rotation(glm::vec3 const &Sun)
{
// the rotation of the view of the shadow pass (setup_pass())
return glm::mat3{glm::lookAt(glm::vec3{0.f}, light_vector(Sun), glm::vec3{0.f, 1.f, 0.f})};
}
std::array<float, gl::MAX_CASCADES + 1> shadow_fit::fixed_splits()
{
static_assert(gl::MAX_CASCADES == 3, "the fixed cascades are made for 3 of them");
std::array<float, gl::MAX_CASCADES + 1> splits{0.0f, Global.shadowtune.range / 32, Global.shadowtune.range / 8, Global.shadowtune.range};
if (Global.shadowtune.map_size > 2048)
{
// increase coverage if our shadow map is large enough to produce decent results
for (std::size_t idx = 1; idx < splits.size(); ++idx)
splits[idx] *= Global.shadowtune.map_size / 2048;
}
return splits;
}
char const *shadow_fit::unavailable(bool const Postprocessing, bool const Shadow, std::size_t const Shadowviews)
{
if (false == Global.gfx_shadowmap_enabled)
return "no shadow maps";
if (Global.bWireFrame)
return "wireframe";
// the shadows follow the camera, the view shows another one
if (DebugCameraFlag)
return "debug camera";
if (false == Postprocessing)
return "no post processing";
if (EditorModeFlag && Global.EditorOrtho)
return "orthographic view";
if ((false == Shadow) || (Shadowviews != 1))
return "the shadows cover more than one view";
return nullptr;
}
bool shadow_fit::fit(unsigned int const Frame, opengl_camera const &Camera, char const *Unavailable, std::array<float, gl::MAX_CASCADES + 1> &Splits, areas &Areas, fit_state &State) const
{
// a measurement older than this many frames, made with the camera this many metres away or the view turned this many
// degrees from the one now, isn't used
constexpr unsigned int AGE_MAX{6};
constexpr float SHIFT_MAX{25.f};
constexpr float TURN_MAX{20.f};
// metres an area grows by on each side: the vehicles move in the frames since the measurement too
constexpr float MARGIN{0.25f};
auto &state{State};
state = fit_state{};
if (false == Global.gfx_shadow_sdsm)
return false;
if ((state.reason = Unavailable) != nullptr)
return false;
auto const *latest{this->latest()};
if (latest == nullptr)
{
state.reason = "no measurement yet";
return false;
}
auto const &data{latest->data};
auto const &source{latest->source};
state.age = Frame - latest->frame;
state.depthmin = data.depthmin;
state.depthmax = data.depthmax;
if (state.age > AGE_MAX)
{
state.reason = "the measurement is too old";
return false;
}
if (data.samples == 0)
{
state.reason = "only the sky in the view";
return false;
}
if (source.range != fixed_splits().back())
{
state.reason = "the range of the shadows changed";
return false;
}
// the shadows of this frame are made for its colour pass
auto const &camera{Camera};
auto const shift{glm::vec3{source.camera - camera.position()}};
if (glm::length(shift) > SHIFT_MAX)
{
state.reason = "the camera jumped";
return false;
}
auto const turnmatrix{glm::mat3{camera.modelview()} * glm::transpose(source.rotation)};
auto const turn{std::acos(std::clamp((turnmatrix[0][0] + turnmatrix[1][1] + turnmatrix[2][2] - 1.f) * 0.5f, -1.f, 1.f))};
state.turn = glm::degrees(turn);
if (state.turn > TURN_MAX)
{
state.reason = "the view turned too fast";
return false;
}
auto const &projection{camera.projection()};
if ((std::abs(projection[0][0] - source.projection[0][0]) > 0.02f * std::abs(projection[0][0])) || (std::abs(projection[1][1] - source.projection[1][1]) > 0.02f * std::abs(projection[1][1])))
{
state.reason = "the field of view changed";
return false;
}
// the first cascade from the camera, like the fixed ones. setup_pass() takes distances up to 1 m as shares of the far
// end, so the others start past that
Splits[0] = 0.f;
for (std::size_t split = 1; split < Splits.size(); ++split)
Splits[split] = std::max(data.splits[split], Splits[split - 1] + 1.5f);
if (false == std::isfinite(Splits.back()))
{
state.reason = "invalid measurement";
return false;
}
auto const inverselight{glm::transpose(source.light)};
// the scenery stays where it was while the camera moved, the cab and the train the camera rides with moved along. an area
// covers both
auto const lightshift{source.light * shift};
auto const chord{2.f * std::sin(turn * 0.5f)};
for (std::size_t cascade = 0; cascade < gl::MAX_CASCADES; ++cascade)
{
// with nothing of the view in a cascade, its map covers the whole slice
if (data.counts[cascade] == 0)
continue;
auto const *bounds{&data.bounds[cascade * 8]};
glm::vec3 low{unorder(bounds[0]), unorder(bounds[1]), unorder(bounds[2])};
glm::vec3 high{unorder(bounds[4]), unorder(bounds[5]), unorder(bounds[6])};
if ((false == std::isfinite(low.x + low.y + high.x + high.y)) || (low.x > high.x) || (low.y > high.y))
continue;
low = glm::min(low, low + lightshift);
high = glm::max(high, high + lightshift);
// and what came into the view as it turned, out to the reach of the cascade
auto const reach{Splits[cascade + 1] + static_cast<float>(gl::MAX_CASCADES - cascade)};
auto const margin{reach * chord + MARGIN + 0.01f * std::max(high.x - low.x, high.y - low.y)};
low -= glm::vec3{margin};
high += glm::vec3{margin};
auto &area{Areas[cascade].emplace()};
for (int corner = 0; corner < 8; ++corner)
area[corner] = inverselight * glm::vec3{(corner & 1) ? high.x : low.x, (corner & 2) ? high.y : low.y, (corner & 4) ? high.z : low.z};
}
state.fitted = true;
return true;
}
float shadow_fit::fit_slice(std::vector<glm::vec4> &Points, std::array<glm::vec3, 8> const *Area, glm::mat3 const &Lightrotation, float &Slicesize)
{
// 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. the areas are compared across the light, the depth along it stays the one of the slice
auto quantizationstep{0.f};
glm::vec3 slicemin{std::numeric_limits<float>::max()}, slicemax{std::numeric_limits<float>::lowest()};
for (auto const &point : Points)
{
auto const lightpoint{Lightrotation * glm::vec3{point}};
slicemin = glm::min(slicemin, lightpoint);
slicemax = glm::max(slicemax, lightpoint);
}
Slicesize = std::max(slicemax.x - slicemin.x, slicemax.y - slicemin.y);
if (Area != nullptr)
{
glm::vec3 areamin{std::numeric_limits<float>::max()}, areamax{std::numeric_limits<float>::lowest()};
for (auto const &point : *Area)
{
auto const lightpoint{Lightrotation * point};
areamin = glm::min(areamin, lightpoint);
areamax = glm::max(areamax, lightpoint);
}
auto const fitmin{glm::vec2{glm::max(glm::vec2{slicemin}, glm::vec2{areamin})}};
auto const fitmax{glm::vec2{glm::min(glm::vec2{slicemax}, glm::vec2{areamax})}};
if ((fitmin.x < fitmax.x) && (fitmin.y < fitmax.y))
{
// the size changes in steps of a power of two of about 1/16 of it, so that the texels of the map keep their size until
// the area grows or shrinks by a step
auto const size{std::max(fitmax.x - fitmin.x, fitmax.y - fitmin.y)};
quantizationstep = std::exp2(std::ceil(std::log2(std::max(size, 1.f) / 16.f)));
Points.clear();
auto const inverserotation{glm::transpose(Lightrotation)};
for (int corner = 0; corner < 8; ++corner)
{
auto const lightpoint{glm::vec3{(corner & 1) ? fitmax.x : fitmin.x, (corner & 2) ? fitmax.y : fitmin.y, (corner & 4) ? slicemax.z : slicemin.z}};
Points.emplace_back(inverserotation * lightpoint, 1.f);
}
}
}
return quantizationstep;
}
} // namespace gfx

View File

@@ -0,0 +1,116 @@
/*
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/.
*/
#pragma once
#include <array>
#include <optional>
#include <vector>
#include "gl/ubo.h"
class opengl_camera;
namespace gfx
{
// the cpu side of the cascades of the sun shadows fitted to the depth of the main view (sample distribution shadow maps,
// gfx.shadow.sdsm), shared by the renderers (opengl46_shadow_fit, the vulkan renderer). at the end of the colour pass of the main
// view two compute passes go over its resolved depth: the nearest and the farthest distance of the pixels which aren't the sky
// (shadow_fit_depth.comp), then the distances split between the cascades and, for each cascade, the box of the pixels it takes
// in the space of the light (shadow_fit_bounds.comp). the result comes back to the cpu a frame or two later without waiting,
// with the camera and the light it was measured with, and the cascades of the frame cover the boxes (fit()) instead of the
// whole slices of the view between the fixed distances of shadowtune
class shadow_fit
{
public:
// the view and the light a measurement was made with
struct view
{
glm::dvec3 camera; // position of the camera
glm::mat4 projection; // projection of the view
glm::mat3 rotation; // world to view space, rotation only
glm::mat3 light; // world to the space of the light, rotation only
float range{0.f}; // the farthest distance the cascades may reach
};
// a measurement which came back
struct result
{
gl::shadow_fit_data data;
view source;
unsigned int frame{0}; // frame of the renderer it was made in
};
// what the last shadow pass did, for the debug panel
struct fit_state
{
bool fitted{false};
char const *reason{nullptr}; // why not, with gfx.shadow.sdsm on
unsigned int age{0}; // frames since the measurement
float depthmin{0.f};
float depthmax{0.f};
float turn{0.f}; // of the view since the measurement, degrees
std::array<float, gl::MAX_CASCADES + 1> splits{};
// size of the area of the map of each cascade: of the whole slice of the view, and as drawn; metres
std::array<float, gl::MAX_CASCADES> slice{};
std::array<float, gl::MAX_CASCADES> drawn{};
};
using areas = std::array<std::optional<std::array<glm::vec3, 8>>, gl::MAX_CASCADES>;
// a measurement made: its number in the data the passes start from (no pixels, the nearest distance infinite, the bounds
// empty), kept with the view until it comes back
gl::shadow_fit_data begin_measurement(view const &View, unsigned int const Frame);
// the parameters of shadow_fit_bounds.comp: the range, the nearest end of the first split, the least far end of the last
// one, the split between the logarithmic and the even splits; and the margins of the bounds
static glm::vec4 bounds_params(view const &View);
static glm::vec4 margin_params();
// a measurement which came back from the gpu
void arrived(gl::shadow_fit_data const &Data);
// the newest measurement taken in so far
result const *latest() const
{
return m_latest ? &(*m_latest) : nullptr;
}
// the ends of the bounds, as the passes store them (shadow_fit_order() in shadow_fit_bounds.comp)
static float unorder(std::uint32_t const Value);
// the cascades of the frame fitted to the latest measurement: the distances they go from and to, and for each cascade the
// corners of the area its map is to cover, camera-relative world space (none: the whole slice of the view). false: the fixed
// cascades. Camera: of the colour pass the shadows are made for; Unavailable: why the cascades can't be fitted, or null
bool fit(unsigned int const Frame, opengl_camera const &Camera, char const *Unavailable, std::array<float, gl::MAX_CASCADES + 1> &Splits, areas &Areas, fit_state &State) const;
// the direction the sun shadows are cast in (the sun, its height capped by gfx.shadow.angle.min), and the rotation from the
// world to the space of the light of the cascades: x and y across the maps, z along the light
static glm::vec3 light_vector(glm::vec3 const &Sun);
static glm::mat3 light_rotation(glm::vec3 const &Sun);
// the distances the fixed cascades of the sun shadows go from and to (shadowtune)
static std::array<float, gl::MAX_CASCADES + 1> fixed_splits();
// why the cascades can't be fitted to the depth of a viewport, null if they can. Postprocessing: the viewport has the images
// of the post-processing (the resolved depth); Shadow: its view is taken into the shadows; Shadowviews: the viewports which are
static char const *unavailable(bool const Postprocessing, bool const Shadow, std::size_t const Shadowviews);
// the points of the slice of the view a shadow pass covers (setup_pass()), in its place the corners of the area fitted to the
// depth, when there is one (and it lies within the slice); Slicesize: the size of the slice across the light. returns the step
// the bounds of the map are quantized to, 0 when the area isn't fitted
static float fit_slice(std::vector<glm::vec4> &Points, std::array<glm::vec3, 8> const *Area, glm::mat3 const &Lightrotation, float &Slicesize);
private:
// a measurement on its way back, kept by its number
struct pending
{
std::uint32_t measurement{0}; // 0: none
view source;
unsigned int frame{0};
};
static constexpr std::size_t PENDING = 8; // more than the copies the readbacks keep in flight
std::array<pending, PENDING> m_pending;
std::uint32_t m_measurement{0}; // number of the last measurement
std::optional<result> m_latest;
};
} // namespace gfx

View File

@@ -0,0 +1,289 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/common/skylight.h"
#include "environment/skydome.h"
#include "utilities/Globals.h"
namespace
{
// irradiance of a radiance given as spherical harmonics, divided by pi (the units of the ambient light of the shading:
// under a uniform sky of radiance L it is L): the coefficients of band l are scaled by A_l / pi (Ramamoorthi, Hanrahan 2001)
float const SH_IRRADIANCE_BAND[3]{1.f, 2.f / 3.f, 0.25f};
int const SH_BAND[gl::SKY_SH_COEFFICIENTS]{0, 1, 1, 1, 2, 2, 2, 2, 2};
float const SH_Y00{0.282095f};
float const SH_Y1{0.488603f};
float const PI{3.14159265359f};
// real spherical harmonics of order 2 in a world space direction, in the order of light_common.glsl
std::array<float, gl::SKY_SH_COEFFICIENTS> sh_basis(glm::vec3 const &n)
{
return {SH_Y00,
SH_Y1 * n.y,
SH_Y1 * n.z,
SH_Y1 * n.x,
1.092548f * n.x * n.y,
1.092548f * n.y * n.z,
0.315392f * (3.f * n.z * n.z - 1.f),
1.092548f * n.x * n.z,
0.546274f * (n.x * n.x - n.y * n.y)};
}
float luminance(glm::vec3 const &Color)
{
return glm::dot(Color, glm::vec3{0.2126f, 0.7152f, 0.0722f});
}
glm::vec3 desaturate(glm::vec3 const &Color, float const Amount)
{
return glm::mix(Color, glm::vec3{luminance(Color)}, Amount);
}
// the response of color.frag to the vertex colours of the dome, so the light taken from the dome is the light it shows
glm::vec3 sky_shoulder(glm::vec3 const &Color)
{
float const knee{0.25f};
float const range{0.95f - knee};
auto const excess{glm::max(Color - glm::vec3{knee}, glm::vec3{0.f})};
return glm::min(Color, glm::vec3{knee}) + range * (glm::vec3{1.f} - glm::exp(-excess / range));
}
// the atmosphere is calibrated against the old sky: these scales make a sky of the atmosphere, with the sun 15 to 60 degrees
// high on a clear day, as bright as the old Perez dome is drawn on average, and the ambient light and the sunlight as strong
// as the old ones, with the default ground albedo (measured on the tables worked out by the same shaders on a software
// renderer, see the notes of the plan, stage S2)
float const ATMOSPHERE_SKY_SCALE{16.f};
float const ATMOSPHERE_AMBIENT_SCALE{44.f};
float const ATMOSPHERE_SUN_SCALE{1.45f};
// sun elevations (degrees) between which the sky goes over from the old model (night, twilight) to the atmosphere. the
// atmosphere darkens at dusk much faster than the old model, which the rest of the lighting was tuned to
float const ATMOSPHERE_FADE_START{-6.f};
float const ATMOSPHERE_FADE_END{10.f};
// the shading scales the sunlight and the ambient light by these (light_common.glsl), so the light the sun gives the
// ground, in the units of the ambient light, is the diffuse colour times their ratio
float const SHADING_SUN_TO_AMBIENT{1.5f / 0.65f};
// parameters of the earth's atmosphere after Hillaire 2020 (km, 1/km)
glm::vec3 const RAYLEIGH_SCATTERING{5.802e-3f, 13.558e-3f, 33.1e-3f};
float const RAYLEIGH_SCALE_HEIGHT{8.f};
float const MIE_SCATTERING{3.996e-3f};
float const MIE_EXTINCTION{4.440e-3f};
float const MIE_SCALE_HEIGHT{1.2f};
float const MIE_ASYMMETRY{0.8f};
glm::vec3 const OZONE_ABSORPTION{0.650e-3f, 1.881e-3f, 0.085e-3f};
float const OZONE_ALTITUDE{25.f};
float const OZONE_HALF_WIDTH{15.f};
float const BOTTOM_RADIUS{6360.f};
float const TOP_RADIUS{6460.f};
} // namespace
namespace gfx
{
sky_light::sky_light()
{
// a valid atmosphere for the sky shader from the start, the tables stay black until the first update
m_atmosphere.rayleigh = glm::vec4{RAYLEIGH_SCATTERING, RAYLEIGH_SCALE_HEIGHT};
m_atmosphere.mie = glm::vec4{MIE_SCATTERING, MIE_EXTINCTION, MIE_SCALE_HEIGHT, MIE_ASYMMETRY};
m_atmosphere.ozone = glm::vec4{OZONE_ABSORPTION, OZONE_ALTITUDE};
m_atmosphere.ground = glm::vec4{glm::vec3{Global.gfx_ambient_groundalbedo}, OZONE_HALF_WIDTH};
m_atmosphere.sun_direction = glm::vec4{0.f, 1.f, 0.f, 0.f};
m_atmosphere.bottom_radius = BOTTOM_RADIUS;
m_atmosphere.top_radius = TOP_RADIUS;
m_atmosphere.observer_radius = BOTTOM_RADIUS + 0.2f;
}
sky_light::sh_coefficients sky_light::dome_directional(CSkyDome const &Skydome, glm::vec3 const &Ambient, glm::vec3 const &Sunlight, float const Sunheight) const
{
sh_coefficients radiance{};
auto const &vertices{Skydome.vertices()};
auto const &colors{Skydome.colors()};
// the vertices make a grid even in the elevation and the azimuth, so the solid angle of a vertex goes with the cosine of
// its elevation. the bottom ring lies a little under the horizon and is taken as the horizon
float totalweight{0.f};
for (std::size_t i = 0; i < vertices.size() && i < colors.size(); ++i)
{
auto direction{glm::normalize(vertices[i])};
// the dome darkens its colours towards the top and brightens the band above the horizon for the looks
// (CSkyDome::RebuildColors), which would make the zenith almost black and the horizon the main source of light.
// both are undone here, so the light comes from the sky the Perez model describes
auto const height{std::max(direction.y, 0.05f)};
auto const horizonband{std::clamp(1.f - height / 0.2f, 0.f, 1.f)};
auto const looks{std::clamp(1.f - height * 0.75f, 0.25f, 1.f) * std::lerp(1.f, 1.5f + 0.75f * m_overcast, horizonband)};
direction.y = std::max(direction.y, 0.f);
direction = glm::normalize(direction);
auto const weight{std::sqrt(std::max(1.f - direction.y * direction.y, 0.f))};
auto const color{sky_shoulder(glm::pow(glm::max(colors[i] / looks, glm::vec3{0.f}), glm::vec3{2.2f}))};
auto const basis{sh_basis(direction)};
for (std::size_t c = 0; c < radiance.size(); ++c)
radiance[c] += color * (basis[c] * weight);
totalweight += weight;
}
if (totalweight <= 0.f)
return {};
for (auto &coefficient : radiance)
coefficient *= 2.f * PI / totalweight;
// the ground, lit by the sky and the sun, fills the lower hemisphere with uniform light (see atmosphere_ambient.comp).
// the dome is in its own units: the light of the sky on an upward facing surface is taken to be the ambient light,
// which converts the sunlight to them
auto const up{sh_basis(glm::vec3{0.f, 1.f, 0.f})};
glm::vec3 skyup{0.f};
for (std::size_t c = 0; c < radiance.size(); ++c)
skyup += radiance[c] * (SH_IRRADIANCE_BAND[SH_BAND[c]] * up[c]);
auto const tosky{luminance(skyup) / std::max(luminance(Ambient), 1e-4f)};
auto const groundradiance{Global.gfx_ambient_groundalbedo * (skyup + Sunlight * SHADING_SUN_TO_AMBIENT * std::max(Sunheight, 0.f) * tosky)};
radiance[0] += groundradiance * (SH_Y00 * 2.f * PI);
radiance[1] += groundradiance * (SH_Y1 * -PI);
// to the irradiance, scaled so its average over all directions is the ambient light, channel by channel
sh_coefficients directional{};
glm::vec3 const average{radiance[0] * SH_Y00};
auto const scale{Ambient / glm::max(average, glm::vec3{1e-4f})};
for (std::size_t c = 1; c < radiance.size(); ++c)
directional[c] = radiance[c] * SH_IRRADIANCE_BAND[SH_BAND[c]] * scale;
return directional;
}
void sky_light::begin_update()
{
m_overcast = std::clamp(Global.Overcast, 0.f, 1.f);
m_weight = 0.f;
}
bool sky_light::atmosphere_changed(glm::vec3 const &Sundirection, bool &Medium)
{
// aerosols grow with the turbidity the weather sets (3 clear .. 9 overcast)
auto const turbidity{std::clamp(Global.fTurbidity, 3.f, 9.f)};
auto const miescale{Global.gfx_atmosphere_haze * (1.f + 0.5f * (turbidity - 3.f))};
glm::vec4 const medium{miescale, Global.gfx_ambient_groundalbedo, Global.gfx_atmosphere_altitude, 0.f};
auto const mediumchanged{!m_tables_medium || *m_tables_medium != medium};
// the sky-view table follows the sun when it moved by more than about a twentieth of a degree
auto const sunchanged{!m_tables_sun || glm::dot(*m_tables_sun, Sundirection) < 0.9999995f};
Medium = mediumchanged;
if (!mediumchanged && !sunchanged)
return false;
m_atmosphere.mie = glm::vec4{MIE_SCATTERING * miescale, MIE_EXTINCTION * miescale, MIE_SCALE_HEIGHT, MIE_ASYMMETRY};
m_atmosphere.ground = glm::vec4{glm::vec3{Global.gfx_ambient_groundalbedo}, OZONE_HALF_WIDTH};
m_atmosphere.sun_direction = glm::vec4{Sundirection, 0.f};
m_atmosphere.observer_radius = BOTTOM_RADIUS + std::max(Global.gfx_atmosphere_altitude, 0.f) * 0.001f + 0.001f;
m_tables_medium = medium;
m_tables_sun = Sundirection;
return true;
}
void sky_light::end_update(basic_light &Sunlight, glm::vec3 &Fogcolor, CSkyDome const &Skydome, glm::vec3 const &Sundirection, float const Sunangle)
{
// the light the simulation set, which is kept where the atmosphere is not used
glm::vec3 const oldambient{Sunlight.ambient};
glm::vec3 const olddiffuse{Sunlight.diffuse};
glm::vec3 const oldfog{Fogcolor};
if (Global.gfx_atmosphere && m_result)
m_weight = glm::smoothstep(ATMOSPHERE_FADE_START, ATMOSPHERE_FADE_END, Sunangle);
sh_coefficients directional{};
if (Global.gfx_ambient_sh)
{
// the dome changes with the sun, the weather and the light the simulation works out from them
std::array<glm::vec4, 3> const key{glm::vec4{Sundirection, Sunlight.direction.y}, glm::vec4{oldambient, m_overcast}, glm::vec4{olddiffuse, Global.gfx_ambient_groundalbedo}};
if (!m_dome_key || *m_dome_key != key)
{
m_dome_sh = dome_directional(Skydome, oldambient, olddiffuse, -Sunlight.direction.y);
m_dome_key = key;
}
directional = m_dome_sh;
}
m_sunlight = olddiffuse;
m_ambient = oldambient;
m_fog = oldfog;
m_sun_disc_color = glm::vec3{1.f};
if (m_weight > 0.f)
{
auto const &result{*m_result};
// the camera's white balance: the sun high in the sky is white
auto const balance{1.f / glm::max(glm::vec3{result.zenith_transmittance}, glm::vec3{1e-3f})};
m_white_balance = balance / std::max(luminance(balance), 1e-4f);
// the atmosphere has no clouds: the overcast takes away the sunlight, greys the sky and evens out its light
// with the shadows of the clouds the sun is held back where they are, not everywhere
auto const overcastsun{m_cloud_shadows ? 1.f : (1.f - 0.65f * m_overcast)};
auto const sun{glm::vec3{result.sun_transmittance} * m_white_balance * (ATMOSPHERE_SUN_SCALE * Global.gfx_atmosphere_sun) * overcastsun};
auto const ambientscale{ATMOSPHERE_AMBIENT_SCALE * Global.gfx_atmosphere_ambient};
auto const ambient{desaturate(glm::vec3{result.radiance_sh[0]} * SH_Y00 * m_white_balance * ambientscale, 0.5f * m_overcast)};
m_sky_scale = ATMOSPHERE_SKY_SCALE * Global.gfx_atmosphere_sky;
auto const fog{desaturate(glm::vec3{result.horizon} * m_white_balance * m_sky_scale, 0.5f * m_overcast)};
m_sky_flat_luminance = luminance(glm::vec3{result.average} * m_white_balance * m_sky_scale);
m_sunlight = glm::mix(olddiffuse, sun, m_weight);
m_ambient = glm::mix(oldambient, ambient, m_weight);
m_fog = glm::mix(oldfog, fog, m_weight);
auto const sundisc{glm::vec3{result.sun_transmittance} * m_white_balance};
m_sun_disc_color = sundisc / std::max({sundisc.r, sundisc.g, sundisc.b, 1e-4f});
if (Global.gfx_ambient_sh)
{
auto const evenness{1.f - 0.75f * m_overcast};
for (std::size_t c = 1; c < directional.size(); ++c)
{
auto const atmosphere{desaturate(glm::vec3{result.radiance_sh[c]} * m_white_balance * (SH_IRRADIANCE_BAND[SH_BAND[c]] * ambientscale * evenness), 0.5f * m_overcast)};
directional[c] = glm::mix(directional[c], atmosphere, m_weight);
}
}
Sunlight.diffuse = glm::vec4{m_sunlight, Sunlight.diffuse.a};
Sunlight.ambient = glm::vec4{m_ambient, Sunlight.ambient.a};
Fogcolor = m_fog;
}
// directional part of the ambient light for the shading
gl::sky_data skydata{};
for (std::size_t c = 1; c < directional.size(); ++c)
skydata.ambient_sh[c] = glm::vec4{directional[c] * Global.gfx_ambient_sh_strength, 0.f};
m_sky_data = skydata;
}
void sky_light::sky_params(glm::vec4 &Param0, glm::vec4 &Param1) const
{
// the volumetric clouds make the overcast sky themselves
Param0 = glm::vec4{m_weight, m_sky_scale, m_clouds ? 0.f : 0.75f * m_overcast, m_sky_flat_luminance};
Param1 = glm::vec4{m_white_balance, 0.f};
}
glm::vec3 sky_light::sun_disc_color(glm::vec3 const &Oldcolor) const
{
return glm::mix(Oldcolor, m_sun_disc_color, m_weight);
}
void sky_light::draw_debug_ui() const
{
ImGui::Checkbox("directional ambient light (gfx.ambient.sh)", &Global.gfx_ambient_sh);
ImGui::SliderFloat("directional strength", &Global.gfx_ambient_sh_strength, 0.f, 2.f);
ImGui::SliderFloat("ground albedo", &Global.gfx_ambient_groundalbedo, 0.f, 1.f);
ImGui::Checkbox("GGX prefiltered reflections (gfx.envmap.ggx)", &Global.gfx_envmap_ggx);
ImGui::Checkbox("physical atmosphere (gfx.atmosphere)", &Global.gfx_atmosphere);
ImGui::SliderFloat("sky", &Global.gfx_atmosphere_sky, 0.f, 4.f);
ImGui::SliderFloat("ambient", &Global.gfx_atmosphere_ambient, 0.f, 4.f);
ImGui::SliderFloat("sun", &Global.gfx_atmosphere_sun, 0.f, 4.f);
ImGui::SliderFloat("haze", &Global.gfx_atmosphere_haze, 0.f, 8.f);
ImGui::SliderFloat("altitude (m)", &Global.gfx_atmosphere_altitude, 0.f, 3000.f);
ImGui::Text("atmosphere share %.2f%s", m_weight, (Global.gfx_atmosphere && !m_result) ? " (waiting for the first read back)" : "");
ImGui::Text("sun %.3f %.3f %.3f", m_sunlight.r, m_sunlight.g, m_sunlight.b);
ImGui::Text("ambient %.3f %.3f %.3f", m_ambient.r, m_ambient.g, m_ambient.b);
ImGui::Text("fog %.3f %.3f %.3f", m_fog.r, m_fog.g, m_fog.b);
ImGui::Text("up / down ambient %.3f / %.3f", luminance(m_ambient + glm::vec3{m_sky_data.ambient_sh[1]} * SH_Y1), luminance(m_ambient - glm::vec3{m_sky_data.ambient_sh[1]} * SH_Y1));
}
} // namespace gfx

131
rendering/common/skylight.h Normal file
View File

@@ -0,0 +1,131 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
// the light of the sky the renderers share (the part of the stage S of claude/gl46-renderer-features-plan.md which isn't the
// work of the gpu, taken out of opengl46_sky for the vulkan renderer, stage P1 of claude/vulkan14-renderer-plan.md):
// - the directional part of the ambient light, as spherical harmonics (gl::sky_data), worked out from the colours of the sky
// dome or from the physical atmosphere
// - the parameters of the physical atmosphere (gl::atmosphere_ubs) and when its look-up tables are to be made again
// - what the result of the atmosphere read back from the gpu (gl::atmosphere_result) makes of the sunlight, the ambient light
// and the fog colour of the frame, the white balance, the share of the atmosphere in the sky and the colour of the sun disc
// switched by Global.gfx_ambient_sh and gfx_atmosphere; off, the light the simulation set is left as it is
#pragma once
#include <array>
#include <optional>
#include "gl/ubo.h"
#include "rendering/light.h"
class CSkyDome;
namespace gfx
{
class sky_light
{
public:
// the parameters of the atmosphere valid from the start, the tables stay black until they are first made
sky_light();
// the first part of the update of a frame: the overcast of the frame, no atmosphere yet
void begin_update();
// the inputs of the look-up tables of the atmosphere for the frame (Sundirection normalized): true when the tables are to be
// made now, Medium when the transmittance and multiple scattering ones are too (the sky-view one and the ambient light follow
// the sun). the parameters are set up for them, and the tables are taken as made
bool atmosphere_changed(glm::vec3 const &Sundirection, bool &Medium);
// a result of the atmosphere read back from the gpu
void set_result(gl::atmosphere_result const &Result)
{
m_result = Result;
}
// the rest of the update: replaces the colours of Sunlight (diffuse, ambient) and Fogcolor, which come in as the simulation
// set them, with the light of the sky, and works out the directional part of the ambient light. Sundirection and Sunangle
// (degrees) are those of the sun, also when the moon is the key light
void end_update(basic_light &Sunlight, glm::vec3 &Fogcolor, CSkyDome const &Skydome, glm::vec3 const &Sundirection, float const Sunangle);
gl::atmosphere_ubs const &atmosphere() const
{
return m_atmosphere;
}
// the directional part of the ambient light for the shading
gl::sky_data const &sky_data() const
{
return m_sky_data;
}
// share of the atmosphere in the sky of the frame; 0 when the old sky is drawn alone
float atmosphere_weight() const
{
return m_weight;
}
bool has_result() const
{
return m_result.has_value();
}
glm::vec3 const &white_balance() const
{
return m_white_balance;
}
float sky_scale() const
{
return m_sky_scale;
}
float overcast() const
{
return m_overcast;
}
// the volumetric clouds are drawn over the sky (it doesn't take the overcast look then), and their shadows hold back the
// sunlight (it isn't dimmed by the overcast then)
void set_clouds(bool const Clouds, bool const Shadows)
{
m_clouds = Clouds;
m_cloud_shadows = Shadows;
}
// the shader parameters of the sky drawn with the atmosphere (sky_atmosphere.frag), param[0] and param[1]
void sky_params(glm::vec4 &Param0, glm::vec4 &Param1) const;
// colour of the sun disc, the old one blended with the colour the atmosphere gives it
glm::vec3 sun_disc_color(glm::vec3 const &Oldcolor) const;
// controls of the debug window
void draw_debug_ui() const;
private:
// spherical harmonics of order 2, rgb
using sh_coefficients = std::array<glm::vec3, gl::SKY_SH_COEFFICIENTS>;
// directional part of the ambient light from the colours of the dome, scaled so its average is Ambient
sh_coefficients dome_directional(CSkyDome const &Skydome, glm::vec3 const &Ambient, glm::vec3 const &Sunlight, float const Sunheight) const;
gl::atmosphere_ubs m_atmosphere{};
// inputs the tables were last made for: the transmittance and multiple scattering tables depend on the medium only,
// the sky-view table also on the sun
std::optional<glm::vec4> m_tables_medium;
std::optional<glm::vec3> m_tables_sun;
std::optional<gl::atmosphere_result> m_result;
gl::sky_data m_sky_data{};
// what the update made of the frame, for the sky, the sun disc and the debug window
float m_weight{0.f};
glm::vec3 m_white_balance{1.f};
float m_sky_scale{0.f};
float m_overcast{0.f};
bool m_clouds{false};
bool m_cloud_shadows{false};
float m_sky_flat_luminance{0.f};
glm::vec3 m_sun_disc_color{1.f};
glm::vec3 m_sunlight{0.f};
glm::vec3 m_ambient{0.f};
glm::vec3 m_fog{0.f};
// cache of the directional ambient light of the dome, remade when its inputs change
std::optional<std::array<glm::vec4, 3>> m_dome_key;
sh_coefficients m_dome_sh{};
};
} // namespace gfx

View File

@@ -0,0 +1,373 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/common/terrainmaterials.h"
#include "rendering/common/shadersource.h"
#include "utilities/Logs.h"
namespace gfx
{
namespace
{
int constexpr initial_capacity{8};
int constexpr copy_attempts{600}; // frames to wait for the textures of a layer to load before it's left with what it has
float constexpr default_repeat{8.f};
// array textures: diffuse, normal maps, specular/gloss maps
std::array<int, 3> constexpr array_sizes{1024, 512, 256};
} // namespace
bool terrain_materials::init()
{
if (m_state != 0)
return m_state > 0;
m_state = -1;
if (false == gpu_init())
return false;
m_white = make_texture("internal_src:terrain/white", texture_kind::white);
gpu_white(m_textures.texture(m_white));
char const *names[]{"internal_src:terrain/layers_diffuse", "internal_src:terrain/layers_normal", "internal_src:terrain/layers_specgloss"};
for (std::size_t i = 0; i < m_arrays.size(); ++i)
{
auto &array{m_arrays[i]};
array.handle = make_texture(names[i], texture_kind::layers);
array.size = array_sizes[i];
array.srgb = (i == 0);
gpu_storage(array, initial_capacity, false);
}
m_capacity = initial_capacity;
gpu_changed();
// the shader is checked now, so a broken one makes the terrain fall back on plain materials instead of the failure indicator
try
{
m_program = GfxRenderer->Fetch_Shader("terrain");
}
catch (gfx::shader_exception const &e)
{
ErrorLog("heightmap terrain: can't use the terrain shader: " + std::string(e.what()));
return false;
}
m_state = 1;
return true;
}
texture_handle terrain_materials::make_texture(std::string const &Name, texture_kind const Kind)
{
auto const handle{m_textures.create(Name, false, GL_RGBA)};
auto &texture{m_textures.texture(handle)};
gpu_texture(texture, Kind);
// the content is supplied here, the texture manager isn't to load, release or replace it
texture.is_ready = true;
texture.is_static = true;
texture.is_rendertarget = true;
texture.is_texstub = false;
texture.data_state = resource_state::good;
return handle;
}
void terrain_materials::read_material(layer &Layer)
{
auto const &material{m_materials.material(Layer.material)};
Layer.repeat = (Layer.size > 0.f ? Layer.size : (material.size.x > 0.f ? material.size.x : default_repeat));
Layer.normalmap = (material.shader && material.shader->texture_conf.count("normalmap") > 0);
Layer.specgloss = (material.shader && material.shader->texture_conf.count("specgloss") > 0);
Layer.reflection = (std::isfinite(material.params[1].z) ? material.params[1].z : 0.f);
}
int terrain_materials::layer_index(gfx::terrain_layer const &Layer)
{
for (auto const &existing : m_layers)
if (existing.material == Layer.material && existing.size == Layer.size)
return existing.index;
layer entry;
entry.material = Layer.material;
entry.size = Layer.size;
entry.index = static_cast<int>(m_layers.size());
read_material(entry);
m_layers.push_back(entry);
m_pending = true;
if (static_cast<int>(m_layers.size()) > m_capacity)
{
// more room: the arrays are made anew, and every layer copied again
auto const capacity{m_capacity * 2};
for (auto &array : m_arrays)
gpu_storage(array, capacity, true);
m_capacity = capacity;
for (auto &existing : m_layers)
{
existing.copied = false;
existing.attempts = 0;
}
gpu_changed();
}
return entry.index;
}
std::size_t terrain_materials::acquire_slot(material_handle const Reuse)
{
if (Reuse != null_handle)
{
auto const lookup{m_slotof.find(Reuse)};
if (lookup != m_slotof.end())
{
m_slots[lookup->second].used = true;
return lookup->second;
}
}
if (false == m_free.empty())
{
auto const index{m_free.back()};
m_free.pop_back();
m_slots[index].used = true;
return index;
}
auto const index{m_slots.size()};
auto const suffix{std::to_string(index)};
slot entry;
entry.weights[0] = make_texture("internal_src:terrain/weights0_" + suffix, texture_kind::weights);
entry.weights[1] = make_texture("internal_src:terrain/weights1_" + suffix, texture_kind::weights);
entry.table = make_texture("internal_src:terrain/table_" + suffix, texture_kind::table);
entry.holes = make_texture("internal_src:terrain/holes_" + suffix, texture_kind::holes);
// the textures get their content before the material is made of them, which checks they're there
gpu_blank(entry);
gpu_changed();
auto const name = [&](texture_handle const Handle) { return "internal_src:" + m_textures.texture(Handle).name; };
std::string const definition{
"shader: terrain\n"
"texture1: " + name(m_white) + "\n"
"texture2: " + name(entry.weights[0]) + "\n"
"texture3: " + name(entry.weights[1]) + "\n"
"texture4: " + name(m_arrays[0].handle) + "\n"
"texture5: " + name(m_arrays[1].handle) + "\n"
"texture6: " + name(m_arrays[2].handle) + "\n"
"texture7: " + name(entry.table) + "\n"
"texture8: " + name(entry.holes) + "\n"};
entry.material = m_materials.create_from_text("internal_src:terrain/chunk_" + suffix, definition);
if (entry.material == null_handle || false == m_materials.material(entry.material).is_good)
{
ErrorLog("heightmap terrain: can't make the material of a chunk");
m_state = -1;
return static_cast<std::size_t>(-1);
}
entry.used = true;
m_slots.push_back(entry);
m_slotof.emplace(entry.material, index);
return index;
}
material_handle terrain_materials::make(material_handle const Reuse, std::vector<gfx::terrain_layer> const &Layers, int const Samples, std::uint8_t const *Weights, glm::vec3 const &Placement,
int const HoleSamples, std::uint8_t const *Holes)
{
if (Layers.empty() || false == init())
return null_handle;
auto const count{std::min(Layers.size(), max_layers)};
std::array<int, max_layers> indices{};
for (std::size_t i = 0; i < count; ++i)
{
if (Layers[i].material == null_handle)
return null_handle;
indices[i] = layer_index(Layers[i]);
}
auto const slotindex{acquire_slot(Reuse)};
if (slotindex == static_cast<std::size_t>(-1))
return null_handle;
auto &entry{m_slots[slotindex]};
// weights, four layers to a texture
auto const painted{Weights != nullptr && Samples > 0 && count > 1};
auto const side{painted ? Samples + 1 : 1};
auto const samples{static_cast<std::size_t>(side) * side};
std::vector<std::uint8_t> texels(samples * 4);
for (std::size_t texture = 0; texture < 2; ++texture)
{
std::fill(texels.begin(), texels.end(), 0);
for (std::size_t channel = 0; channel < 4; ++channel)
{
auto const layer{texture * 4 + channel};
if (layer >= count)
break;
if (false == painted)
{
if (layer == 0)
texels[channel] = 255;
continue;
}
auto const *source{Weights + layer * samples};
for (std::size_t i = 0; i < samples; ++i)
texels[i * 4 + channel] = source[i];
}
gpu_weights(entry.weights[texture], side, texels.data());
}
entry.count = count;
entry.layers = indices;
for (std::size_t i = 0; i < count; ++i)
entry.rotations[i] = Layers[i].rotation;
write_table(entry);
// the holes: the cells cut out entirely are left out of the mesh, the program of the holes cuts the rest of the openings
auto const cut{Holes != nullptr && HoleSamples > 0};
{
auto const holeside{cut ? HoleSamples + 1 : 1};
std::uint8_t const none{0};
gpu_holes(entry.holes, holeside, cut ? Holes : &none);
gpu_changed();
}
auto &material{m_materials.material(entry.material)};
material.params[2] = glm::vec4(Placement.x, Placement.y, 1.f / std::max(1.f, Placement.z), static_cast<float>(side - 1));
if (m_holesprogram == nullptr && cut)
{
try
{
m_holesprogram = GfxRenderer->Fetch_Shader("terrain_holes");
}
catch (gfx::shader_exception const &e)
{
ErrorLog("heightmap terrain: can't use the shader of the holes: " + std::string(e.what()));
}
}
// the program of the holes discards fragments, which the depth prepass of the plain one never does
material.shader = (cut && m_holesprogram != nullptr) ? m_holesprogram : m_program;
return entry.material;
}
void terrain_materials::write_table(slot const &Slot)
{
// what the layers are: index in the arrays, repeat, which maps they have, reflection
std::array<float, max_layers * 2 * 4> table{};
for (std::size_t i = 0; i < Slot.count; ++i)
{
auto const &layer{m_layers[Slot.layers[i]]};
table[i * 4 + 0] = static_cast<float>(layer.index);
table[i * 4 + 1] = 1.f / std::max(0.01f, layer.repeat);
table[i * 4 + 2] = layer.normalmap ? 1.f : 0.f;
table[i * 4 + 3] = layer.specgloss ? 1.f : 0.f;
table[(max_layers + i) * 4 + 0] = layer.reflection;
// the textures turned by the layer of the chunk (the same material can be turned differently in another layer)
auto const angle{glm::radians(static_cast<double>(Slot.rotations[i]))};
table[(max_layers + i) * 4 + 1] = static_cast<float>(std::cos(angle));
table[(max_layers + i) * 4 + 2] = static_cast<float>(std::sin(angle));
}
gpu_table(Slot.table, table.data());
gpu_changed();
}
void terrain_materials::on_environment_change()
{
if (m_state <= 0)
return;
bool changed{false};
for (auto &layer : m_layers)
{
auto const &material{m_materials.material(layer.material)};
if (false == (material.update_on_season_change || material.update_on_weather_change))
continue;
// the reloaded material can have other textures, other maps and another size
read_material(layer);
layer.copied = false;
layer.attempts = 0;
changed = true;
}
if (false == changed)
return;
m_pending = true;
// the tables of the chunks hold what the layers have, freed slots get theirs anew when they're used again
for (auto const &entry : m_slots)
if (entry.used)
write_table(entry);
}
void terrain_materials::release(material_handle const Material)
{
auto const lookup{m_slotof.find(Material)};
if (lookup == m_slotof.end() || false == m_slots[lookup->second].used)
return;
m_slots[lookup->second].used = false;
m_free.push_back(lookup->second);
}
bool terrain_materials::copy_layer(layer &Layer)
{
auto const &material{m_materials.material(Layer.material)};
auto const slot = [&](char const *Name, int const Fallback) {
if (material.shader)
{
auto const lookup{material.shader->texture_conf.find(Name)};
if (lookup != material.shader->texture_conf.end())
return static_cast<int>(lookup->second.id);
return -1;
}
return Fallback;
};
std::array<int, 3> const slots{slot("diffuse", 0), Layer.normalmap ? slot("normalmap", 1) : -1, Layer.specgloss ? slot("specgloss", 2) : -1};
// all maps of the layer have to be ready, so they go together
std::array<texture_source *, 3> sources{};
for (std::size_t i = 0; i < slots.size(); ++i)
{
if (slots[i] < 0 || material.textures[slots[i]] == null_handle)
continue;
auto &texture{m_textures.mark_as_used(material.textures[slots[i]])};
if (false == texture.is_ready)
{
// a texture which is on the card already may have dropped its data after the upload (create() would refuse it then),
// one released by the sweep or never read has to be read first
if (texture.data_state == resource_state::none)
{
texture.reload_on_use = false;
texture.load();
}
if (texture.data_state == resource_state::failed)
continue;
if (false == texture.create())
{
if (++Layer.attempts < copy_attempts)
return false;
WriteLog("heightmap terrain: texture \"" + texture.name + "\" isn't ready, the terrain goes without it");
continue; // waited long enough, the layer goes without it
}
}
sources[i] = &texture;
}
// defaults for maps the layer doesn't have: grey, flat, without specular reflections
std::array<std::array<float, 4>, 3> const defaults{{{0.5f, 0.5f, 0.5f, 1.f}, {0.5f, 0.5f, 1.f, 1.f}, {0.f, 1.f, 0.f, 1.f}}};
for (std::size_t i = 0; i < m_arrays.size(); ++i)
gpu_copy(m_arrays[i], Layer.index, sources[i], defaults[i]);
Layer.copied = true;
return true;
}
void terrain_materials::update()
{
if (false == m_pending || m_state <= 0)
return;
gpu_begin_copies();
bool copied{false};
m_pending = false;
for (auto &layer : m_layers)
{
if (layer.copied)
continue;
if (copy_layer(layer))
copied = true;
else
m_pending = true;
}
gpu_end_copies(copied);
}
} // namespace gfx

View File

@@ -0,0 +1,142 @@
/*
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/.
*/
#pragma once
#include <array>
#include <memory>
#include <unordered_map>
#include <vector>
#include "rendering/renderer.h"
#include "model/material.h"
#include "model/Texture.h"
namespace gfx
{
// materials of the heightmap terrain (shader mat_terrain, mat_terrain_holes for chunks with holes), whichever api draws them.
// the textures of the layer materials are copied into texture arrays shared by all chunks; each chunk gets a material
// of its own, with textures holding the weights of its layers and a table of what its layers are.
// what the layers and the chunks are is kept here; the textures are made and filled by the renderer (the gpu_ functions)
class terrain_materials
{
public:
terrain_materials(material_manager &Materials, texture_bank &Textures) : m_materials(Materials), m_textures(Textures) {}
virtual ~terrain_materials() = default;
// see gfx_renderer::Terrain_Material. returns null_handle if the terrain shader can't be used
material_handle make(material_handle const Reuse, std::vector<gfx::terrain_layer> const &Layers, int const Samples, std::uint8_t const *Weights, glm::vec3 const &Placement,
int const HoleSamples = 0, std::uint8_t const *Holes = nullptr);
void release(material_handle const Material);
// copies the textures of new layers into the arrays; to be called before the frame is drawn
void update();
// the layer materials which change with the season or the weather were reloaded, their textures are copied again
void on_environment_change();
protected:
static constexpr std::size_t max_layers{8};
struct layer
{
material_handle material{null_handle};
float size{0.f}; // as given by the chunk
float repeat{8.f}; // metres the textures are repeated at
int index{0}; // in the arrays
bool normalmap{false};
bool specgloss{false};
float reflection{0.f};
bool copied{false};
int attempts{0};
};
struct texture_array
{
texture_handle handle{null_handle};
int size{0};
bool srgb{false};
};
struct slot
{
material_handle material{null_handle};
std::array<texture_handle, 2> weights{null_handle, null_handle};
texture_handle table{null_handle};
texture_handle holes{null_handle}; // where the ground is cut, R8; a single texel of 0 without holes
bool used{false};
// what the table was made of, kept to make it again when a layer changes
std::size_t count{0};
std::array<int, max_layers> layers{};
std::array<float, max_layers> rotations{};
};
// what a texture made here holds
enum class texture_kind
{
white, // a single white texel, rgba8
layers, // an array of the layer maps, rgba8 (srgb for the diffuse)
weights, // weights of four layers, rgba8 with mipmaps, clamped
table, // the table of the layers of a chunk, max_layers x 2 rgba32f, nearest
holes // the mask of the holes, r8, led between the samples, clamped
};
// the textures of the arrays: diffuse, normal maps, specular/gloss
std::array<texture_array, 3> const &arrays() const
{
return m_arrays;
}
// the objects the textures are drawn with (the copy of the layer maps); false: the terrain can't be drawn
virtual bool gpu_init() = 0;
// the object of a texture made by the bank, of the kind
virtual void gpu_texture(texture_source &Texture, texture_kind const Kind) = 0;
// the white texel
virtual void gpu_white(texture_source &Texture) = 0;
// room for Layers layers in the array; Fresh: the array had layers before, they go
virtual void gpu_storage(texture_array const &Array, int const Layers, bool const Fresh) = 0;
// the content of the textures of a new slot, before its material is made of them: no weights, a table of zeros, no holes
virtual void gpu_blank(slot const &Slot) = 0;
// Side x Side texels of rgba8, the mipmaps made of them
virtual void gpu_weights(texture_handle const Texture, int const Side, std::uint8_t const *Texels) = 0;
// max_layers x 2 texels of rgba32f
virtual void gpu_table(texture_handle const Texture, float const *Table) = 0;
// Side x Side samples of r8
virtual void gpu_holes(texture_handle const Texture, int const Side, std::uint8_t const *Samples) = 0;
// the copies of the layer maps into the arrays: begun, then a copy per array of each layer copied, ended; Copied: whether any
// was made (the mipmaps of the arrays are made then)
virtual void gpu_begin_copies() = 0;
// Source: the map of the layer, or null when it has none, the array is filled with Default then
virtual void gpu_copy(texture_array const &Array, int const Layer, texture_source *Source, std::array<float, 4> const &Default) = 0;
virtual void gpu_end_copies(bool const Copied) = 0;
// textures were made or given new content (the opengl renderer forgets the textures bound)
virtual void gpu_changed() {}
material_manager &m_materials;
texture_bank &m_textures;
private:
bool init();
texture_handle make_texture(std::string const &Name, texture_kind const Kind);
void read_material(layer &Layer);
int layer_index(gfx::terrain_layer const &Layer);
void write_table(slot const &Slot);
bool copy_layer(layer &Layer);
std::size_t acquire_slot(material_handle const Reuse);
int m_state{0}; // 0: not set up yet, 1: ready, -1: can't be used
texture_handle m_white{null_handle};
std::array<texture_array, 3> m_arrays; // diffuse, normal maps, specular/gloss
int m_capacity{0}; // layers the arrays have room for
std::vector<layer> m_layers;
bool m_pending{false};
std::vector<slot> m_slots;
std::vector<std::size_t> m_free;
std::unordered_map<material_handle, std::size_t> m_slotof;
std::shared_ptr<gfx::shader_program> m_program; // of the chunks, mat_terrain
std::shared_ptr<gfx::shader_program> m_holesprogram; // of the chunks with holes, mat_terrain_holes
};
} // namespace gfx

View File

@@ -0,0 +1,343 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/common/weatherstate.h"
#include "environment/starcatalog.h"
#include "environment/weather.h"
#include "simulation/simulationenvironment.h"
#include "utilities/Globals.h"
#include "utilities/Timer.h"
#include "widgets/weatherpanel.h"
namespace
{
// how many times the cell of a genus fits in the tile of the shape noise, and its spread in the tile of the coverage noise
double const SHAPE_TILE_CELLS{4.0};
double const COVERAGE_TILE_SPREADS{4.0};
// size of the patches of the ground fog
float const FOG_PATCH_SIZE{300.f};
// the haze the atmosphere draws in the sky already, as an extinction: about the haze of a day with 30 km of visibility
float const SKY_HAZE_EXTINCTION{3.f / 30000.f};
// near end of the fog volume (view depth, metres)
float const FOG_VOLUME_NEAR{0.5f};
// bands the panorama is drawn in, one a frame
int const PANORAMA_BANDS{8};
// brightness of a star of magnitude 0 in the units of the scene
float const STAR_BRIGHTNESS{4.f};
// stars dimmer than this aren't drawn
float const STAR_LIMIT{6.5f};
float luminance(glm::vec3 const &Color)
{
return glm::dot(Color, glm::vec3{0.2126f, 0.7152f, 0.0722f});
}
// the position in a tiling noise, of a period in metres, kept small for the single precision of the shaders
double wrap(double const Value, double const Period)
{
return Value - Period * std::floor(Value / Period);
}
// the colour of a star of a colour temperature, linear, of luminance 1, somewhat paler than the black body: the eye sees most
// stars white
glm::vec3 star_color(float const Temperature)
{
auto const t{std::clamp(Temperature, 1500.f, 40000.f) / 100.f};
glm::vec3 color;
color.r = (t <= 66.f) ? 255.f : 329.698727446f * std::pow(t - 60.f, -0.1332047592f);
color.g = (t <= 66.f) ? 99.4708025861f * std::log(t) - 161.1195681661f : 288.1221695283f * std::pow(t - 60.f, -0.0755148492f);
color.b = (t >= 66.f) ? 255.f : (t <= 19.f) ? 0.f : 138.5177312231f * std::log(t - 10.f) - 305.0447927307f;
color = glm::pow(glm::clamp(color / 255.f, glm::vec3{0.f}, glm::vec3{1.f}), glm::vec3{2.2f});
color /= std::max(luminance(color), 1e-3f);
return glm::mix(color, glm::vec3{1.f}, 0.5f);
}
} // namespace
namespace gfx
{
weather_state::quality_level weather_state::quality(int const Clouds, int const Fog)
{
quality_level level;
switch (std::clamp(Clouds, 1, 4))
{
case 1: level = {4, 32, 3, false, {512, 128}, {}}; break;
case 2: level = {2, 48, 4, true, {1024, 256}, {}}; break;
case 3: level = {2, 80, 6, true, {1024, 256}, {}}; break;
default: level = {2, 128, 8, true, {2048, 512}, {}}; break;
}
switch (std::clamp(Fog, 1, 4))
{
case 1: level.fog_grid = {80, 45, 48}; break;
case 2: level.fog_grid = {160, 90, 64}; break;
case 3: level.fog_grid = {160, 90, 96}; break;
default: level.fog_grid = {240, 135, 128}; break;
}
return level;
}
weather_state::weather_state()
{
// a grey fog and no clouds until the first update
for (auto &color : m_weather.fog_horizon)
color = glm::vec4{0.5f, 0.5f, 0.5f, 1.f};
m_weather.fog_haze = glm::vec4{3.f / 10000.f, 1.f / 800.f, SKY_HAZE_EXTINCTION, 1.f};
m_weather.fog_light = glm::vec4{0.f, 1.f, 0.6f, 1.f / 5000.f};
}
void weather_state::update(float const Atmosphereweight, bool const Skyview, glm::vec3 const &Keylight, glm::vec3 const &Keycolor, glm::vec3 const &Ambient, glm::vec3 const &Fogcolor, glm::dvec3 const &Camera)
{
auto const &weather{simulation::Environment.weather()};
auto const &conditions{weather.conditions()};
m_fog_volume_valid = false;
m_clouds_view_valid = false;
++m_frame;
auto const height{static_cast<float>(Camera.y) - weather.ground()};
auto const time{weather.time()};
auto const sun{glm::normalize(Keylight)};
// the fog. the visibility is where a dark object fades to 5%: the extinction is 3 over it
auto const haze{3.f / conditions.visibility};
auto const hazerate{1.f / conditions.haze_height};
m_weather.fog_haze = glm::vec4{haze, hazerate, SKY_HAZE_EXTINCTION, 1.f};
if (conditions.groundfog_thickness > 0.f)
{
auto const thickness{conditions.groundfog_thickness};
m_weather.fog_ground = glm::vec4{std::max(3.f / conditions.groundfog_visibility - haze, 0.f), 1.f / (0.25f * thickness), 0.75f * thickness, conditions.fog_noise};
}
else
m_weather.fog_ground = glm::vec4{0.f, 1.f, 0.f, conditions.fog_noise};
// the patches drift with the wind, slower than the clouds
auto const drift{glm::dvec2{weather.cloud_offset()} * 0.3};
auto const patch{static_cast<double>(FOG_PATCH_SIZE)};
m_weather.fog_noise = glm::vec4{static_cast<float>(wrap(Camera.x - drift.x, patch)), static_cast<float>(wrap(Camera.y, patch)), static_cast<float>(wrap(Camera.z - drift.y, patch)), 1.f / FOG_PATCH_SIZE};
// the extinction at the height of the camera tells how far things are hidden
auto const extinction{haze * std::exp(-hazerate * std::max(height, 0.f)) +
m_weather.fog_ground.x * std::exp(-m_weather.fog_ground.y * std::max(height - m_weather.fog_ground.z, 0.f))};
// 2% left at 1.3 visibilities; the draw range is twice this
m_fog_range = std::clamp(3.f / std::max(extinction, 1e-7f) / 1.5f, 5.f, 1e6f);
// the share of the light of the fog the sun gives, which the shadows hold back
auto const sunlight{luminance(Keycolor) * 1.5f * std::max(sun.y, 0.f)};
auto const skylight{luminance(Ambient) * 0.65f};
m_sun_share = (sunlight + skylight > 1e-6f) ? sunlight / (sunlight + skylight) : 0.f;
// clouds
auto const layers{std::min(conditions.clouds.size(), gl::MAX_CLOUD_LAYERS)};
m_clouds_drawn = (Global.gfx_clouds > 0) && (layers > 0);
m_average_transmittance = 1.f;
auto const velocity{weather.cloud_velocity()};
auto const speed{glm::length(velocity)};
auto const wind{speed > 1e-3f ? glm::dvec2{velocity / speed} : glm::dvec2{1.0, 0.0}};
m_weather.cloud_wind = glm::vec4{static_cast<float>(wind.x), static_cast<float>(wind.y), speed, m_cloud_detail_scale};
for (std::size_t i = 0; i < gl::MAX_CLOUD_LAYERS; ++i)
{
auto &target{m_weather.cloud_layers[i]};
if (i >= layers)
{
target = gl::cloud_layer_ubs{};
continue;
}
auto const &layer{conditions.clouds[i]};
auto const &genus{cloud_genus_info(layer.type)};
target.heights = glm::vec4{layer.base, layer.top, 1.f / std::max(layer.top - layer.base, 1.f), static_cast<float>(layer.type)};
auto const shapetile{static_cast<double>(genus.cell) * SHAPE_TILE_CELLS};
auto const coveragetile{static_cast<double>(genus.spread) * COVERAGE_TILE_SPREADS};
target.shape = glm::vec4{layer.coverage, genus.extinction * layer.density, static_cast<float>(1.0 / shapetile), genus.erosion};
target.profile = glm::vec4{genus.bottom, genus.top, genus.top_variation, genus.anvil};
// the higher, the faster the wind carries them
auto const offset{glm::dvec2{weather.cloud_offset()} * (1.0 + static_cast<double>(layer.base) / 8000.0)};
// the camera's place in the noise, in the space stretched along the wind as the shaders take it
auto const place{glm::dvec2{Camera.x, Camera.z} - offset};
auto const along{glm::dot(place, wind)};
auto const stretched{(place - wind * along) + wind * (along / std::max(static_cast<double>(genus.stretch), 1.0))};
target.motion = glm::vec4{static_cast<float>(wrap(stretched.x, shapetile)), static_cast<float>(wrap(stretched.y, shapetile)), static_cast<float>(1.0 / coveragetile), genus.stretch};
// the clouds change their shapes slowly; the seed moves the layers apart in the noise
auto const evolution{std::fmod(static_cast<double>(conditions.seed) * 0.6180339887 + static_cast<double>(i) * 0.37 + time * 0.00002, 1.0)};
target.extra = glm::vec4{genus.flat ? 1.f : 0.f, static_cast<float>(evolution), static_cast<float>(wrap(stretched.x, coveragetile)), static_cast<float>(wrap(stretched.y, coveragetile))};
m_average_transmittance *= 1.f - std::clamp(layer.coverage * genus.opacity * std::min(layer.density, 1.5f), 0.f, 1.f);
}
if (Global.gfx_clouds <= 0)
m_average_transmittance = 1.f;
m_weather.fog_light = glm::vec4{m_sun_share, m_average_transmittance, 0.6f, fog_density()};
m_weather.weather_camera = glm::vec4{height, static_cast<float>(std::fmod(time, 100000.0)), m_clouds_drawn ? 1.f : 0.f, m_clouds_drawn ? static_cast<float>(layers) : 0.f};
m_weather.weather_sun_direction = glm::vec4{sun, sun.y > -0.02f ? 1.f : 0.f};
m_weather.weather_sun_color = glm::vec4{Keycolor, m_cloud_sun_scale};
m_weather.weather_ambient = glm::vec4{Ambient * 1.3f, m_cloud_sky_scale};
m_weather.fog_volume_params = glm::vec4{0.f, FOG_VOLUME_NEAR, 1.f, 1.f};
m_weather.fog_volume_target = glm::vec4{1.f, 1.f, Global.gfx_fog_range, 1.f - Atmosphereweight};
// the colours of the horizon: from the atmosphere on the gpu, the old fog colour otherwise
m_horizon_on_gpu = (Atmosphereweight > 0.f) && Skyview;
if (false == m_horizon_on_gpu)
{
for (auto &color : m_weather.fog_horizon)
color = glm::vec4{Fogcolor, 1.f};
}
// the shadows of the clouds
m_shadows_wanted = m_clouds_drawn && Global.gfx_clouds_shadows && (sun.y > 0.02f);
m_weather.cloud_shadow_params = glm::vec4{0.f};
m_report = to_metar(conditions);
}
void weather_state::place_shadows(int const Size, glm::dvec3 const &Camera)
{
// a map snapped to its texels so it doesn't crawl as the camera moves
auto const extent{static_cast<double>(Global.gfx_clouds_shadows_range)};
auto const texel{extent / static_cast<double>(Size)};
auto const centre{glm::dvec2{std::round(Camera.x / texel) * texel - Camera.x, std::round(Camera.z / texel) * texel - Camera.z}};
m_weather.cloud_shadow_params = glm::vec4{static_cast<float>(centre.x), static_cast<float>(centre.y), static_cast<float>(1.0 / extent), 0.f};
}
void weather_state::shadows_drawn(bool const Drawn)
{
m_shadows_valid = Drawn;
if (Drawn)
m_weather.cloud_shadow_params.w = 1.f;
}
void weather_state::panorama_resized()
{
m_panorama_full = false;
m_panorama_band = 0;
}
void weather_state::panorama_band(glm::ivec2 const &Size, int &First, int &Rows)
{
auto const bandrows{(Size.y + PANORAMA_BANDS - 1) / PANORAMA_BANDS};
First = m_panorama_full ? m_panorama_band * bandrows : 0;
Rows = m_panorama_full ? bandrows : Size.y;
m_panorama_band = m_panorama_full ? (m_panorama_band + 1) % PANORAMA_BANDS : 0;
m_panorama_full = true;
}
bool weather_state::continuity(view_state const &Previous, glm::dvec3 const &Camera)
{
// a jump of the camera (another vehicle, the free camera) leaves nothing to blend with
return Previous.valid && glm::length(Previous.camera - Camera) < 50.0;
}
glm::vec3 weather_state::fog_jitter(bool const Temporal) const
{
// the cells are sampled at another point in every frame: along the depth in the Halton sequence of 2, across the view in
// those of 3 and 5 (fog_inject.comp moves the point of every cell by a value of its own)
auto const halton = [](int Index, int const Base) {
float value{0.f}, fraction{1.f};
for (; Index > 0; Index /= Base)
{
fraction /= static_cast<float>(Base);
value += fraction * static_cast<float>(Index % Base);
}
return value;
};
auto const sample{(m_frame % 64) + 1};
return Temporal ? glm::vec3{halton(sample, 3), halton(sample, 5), halton(sample, 2)} : glm::vec3{0.5f};
}
glm::vec4 weather_state::fog_slicing(int const Slices, glm::vec3 const &Jitter)
{
auto const farthest{std::max(Global.gfx_fog_range, FOG_VOLUME_NEAR * 4.f)};
return glm::vec4{FOG_VOLUME_NEAR, farthest, static_cast<float>(Slices) / std::log((farthest + gl::FOG_VOLUME_BIAS) / (FOG_VOLUME_NEAR + gl::FOG_VOLUME_BIAS)), Jitter.z};
}
void weather_state::fog_made(glm::vec4 const &Slicing, glm::ivec3 const &Grid)
{
m_fog_slicing = Slicing;
m_fog_grid = Grid;
m_fog_volume_valid = true;
}
void weather_state::setup_pass(bool const Volume, glm::ivec2 const &Size)
{
auto const volume{Volume && m_fog_volume_valid && (Global.gfx_fog == 1)};
m_weather.fog_volume_params = volume ? glm::vec4{1.f, m_fog_slicing.x, m_fog_slicing.z, static_cast<float>(m_fog_grid.z)} : glm::vec4{0.f, FOG_VOLUME_NEAR, 1.f, 1.f};
m_weather.fog_volume_target.x = 1.f / static_cast<float>(std::max(Size.x, 1));
m_weather.fog_volume_target.y = 1.f / static_cast<float>(std::max(Size.y, 1));
m_weather.fog_volume_target.z = volume ? m_fog_slicing.y : Global.gfx_fog_range;
}
std::vector<glm::vec4> weather_state::star_vertices()
{
// the stars, in the equatorial frame
std::vector<glm::vec4> stars;
auto const *catalogue{star_catalogue::stars()};
for (std::size_t i = 0; i < star_catalogue::size(); ++i)
{
auto const &star{catalogue[i]};
auto const magnitude{static_cast<float>(star.magnitude) * 0.01f};
if (magnitude > STAR_LIMIT)
continue;
auto const ascension{glm::radians(static_cast<float>(star.right_ascension) * 0.01f)};
auto const declination{glm::radians(static_cast<float>(star.declination) * 0.01f)};
stars.emplace_back(std::cos(declination) * std::cos(ascension), std::cos(declination) * std::sin(ascension), std::sin(declination), magnitude);
// a phase of the twinkling which doesn't repeat from star to star
auto const phase{std::fmod(static_cast<float>(i) * 0.6180339887f, 1.f)};
stars.emplace_back(star_color(static_cast<float>(star.temperature) * 10.f), phase);
}
return stars;
}
bool weather_state::stars(float const Sunangle, double const Siderealtime, double const Latitude, glm::mat4 const &View, glm::mat4 &Toworld, glm::mat4 &Toview, glm::vec4 &Params) const
{
if (!Global.gfx_stars)
return false;
// the stars come out between the civil and the nautical twilight
auto const night{1.f - glm::smoothstep(-12.f, -3.f, Sunangle)};
if (night <= 0.f)
return false;
// the equatorial frame (z to the pole, x to the vernal equinox) to the sky of the observer (x west, y up, z north)
auto const theta{glm::radians(Siderealtime)};
auto const phi{glm::radians(Latitude)};
auto const st{static_cast<float>(std::sin(theta))}, ct{static_cast<float>(std::cos(theta))};
auto const sp{static_cast<float>(std::sin(phi))}, cp{static_cast<float>(std::cos(phi))};
glm::mat3 equatorial;
equatorial[0] = glm::vec3{st, cp * ct, -sp * ct};
equatorial[1] = glm::vec3{-ct, cp * st, -sp * st};
equatorial[2] = glm::vec3{0.f, sp, cp};
Toworld = glm::mat4{equatorial};
Toview = glm::mat4{glm::mat3{View}};
Params = glm::vec4{STAR_BRIGHTNESS * Global.gfx_stars_brightness * night, static_cast<float>(std::fmod(Timer::GetTime(), 10000.0)), 2.5f, 0.f};
return true;
}
void weather_state::draw_debug_ui()
{
char const *fogmodes[]{"analytic (along the ray, as the old fog)", "volumetric"};
ImGui::Combo("fog (gfx.fog)", &Global.gfx_fog, fogmodes, IM_ARRAYSIZE(fogmodes));
char const *levels[]{"low", "medium", "high", "ultra"};
auto fogquality{std::clamp(Global.gfx_fog_quality, 1, 4) - 1};
if (ImGui::Combo("fog volume quality (gfx.fog.quality)", &fogquality, levels, IM_ARRAYSIZE(levels)))
Global.gfx_fog_quality = fogquality + 1;
ImGui::SliderFloat("fog volume depth, m (gfx.fog.range)", &Global.gfx_fog_range, 100.f, 10000.f, "%.0f", ImGuiSliderFlags_Logarithmic);
ImGui::SliderFloat("lamps in the fog (gfx.fog.lights)", &Global.gfx_fog_lights, 0.f, 4.f);
ImGui::Checkbox("shadows of the sun and headlights in the fog (gfx.fog.shadows)", &Global.gfx_fog_shadows);
ImGui::Checkbox("fog blended over frames (gfx.fog.temporal)", &Global.gfx_fog_temporal);
ImGui::SliderFloat("share of a new frame in the fog (gfx.fog.temporal.share)", &Global.gfx_fog_temporal_share, 0.02f, 1.f);
char const *cloudlevels[]{"none", "low", "medium", "high", "ultra"};
ImGui::Combo("clouds (gfx.clouds)", &Global.gfx_clouds, cloudlevels, IM_ARRAYSIZE(cloudlevels));
ImGui::SliderFloat("cloud distance, m (gfx.clouds.distance)", &Global.gfx_clouds_distance, 5000.f, 200000.f, "%.0f", ImGuiSliderFlags_Logarithmic);
ImGui::Checkbox("cloud shadows (gfx.clouds.shadows)", &Global.gfx_clouds_shadows);
ImGui::SliderFloat("cloud shadow map, m (gfx.clouds.shadows.range)", &Global.gfx_clouds_shadows_range, 1000.f, 100000.f, "%.0f", ImGuiSliderFlags_Logarithmic);
ImGui::SliderFloat("cloud sunlight (tuning, not saved)", &m_cloud_sun_scale, 0.25f, 3.f);
ImGui::SliderFloat("cloud sky light (tuning, not saved)", &m_cloud_sky_scale, 0.f, 3.f);
ImGui::SliderFloat("cloud edge detail (tuning, not saved)", &m_cloud_detail_scale, 0.f, 3.f);
ImGui::Checkbox("stars (gfx.stars)", &Global.gfx_stars);
ImGui::SliderFloat("star brightness", &Global.gfx_stars_brightness, 0.f, 8.f);
ImGui::Text("fog range %.0f m, sun share of the fog light %.2f, clouds let through %.2f on average", m_fog_range, m_sun_share, m_average_transmittance);
ImGui::Separator();
ui::weather_controls(simulation::Environment.weather());
}
} // namespace gfx

View File

@@ -0,0 +1,168 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
// the weather the renderers draw (the part of the stage F of claude/gl46-renderer-features-plan.md which isn't the work of the
// gpu, taken out of opengl46_weather for the vulkan renderer, stage P2 of claude/vulkan14-renderer-plan.md): the block of the
// weather of the shaders (gl::weather_ubs) made from the weather of the simulation (environment/weather.h) for the frame and for
// each pass, the quality levels, the place of the map of the shadows of the clouds, the bands of the panorama of the clouds, the
// sampling of the fog volume over the frames, and the stars of the catalogue (environment/starcatalog.h)
#pragma once
#include <string>
#include <vector>
#include "gl/ubo.h"
namespace gfx
{
class weather_state
{
public:
struct quality_level
{
int divisor; // of the resolution of the clouds of the main view
int steps; // samples through a layer at most
int light_steps; // samples towards the sun
bool detail; // with the small noise
glm::ivec2 panorama; // size of the panorama
glm::ivec3 fog_grid; // cells of the fog volume
};
static quality_level quality(int const Clouds, int const Fog);
// the camera of the previous frame of the main view, for the blending over frames
struct view_state
{
glm::mat4 projection_view{1.f}; // world axes to clip space, camera at the origin
glm::dvec3 camera{0.0};
glm::vec4 slicing{0.f};
bool valid{false};
};
// a grey fog and no clouds until the first update
weather_state();
// what the weather of the simulation is for the frame: the block, whether the clouds are drawn and cast shadows. before the
// gpu work of the frame. Atmosphereweight: share of the atmosphere in the sky, Skyview: its sky-view table is there to
// give the colours of the horizon; Keylight: world direction towards the sun or the moon, Keycolor: its colour as the
// shading gets it, Ambient: the ambient light of the sky, Fogcolor: the old colour of the fog
void update(float const Atmosphereweight, bool const Skyview, glm::vec3 const &Keylight, glm::vec3 const &Keycolor, glm::vec3 const &Ambient, glm::vec3 const &Fogcolor, glm::dvec3 const &Camera);
// the shadows of the clouds are to be drawn in this frame (after update())
bool shadows_wanted() const
{
return m_shadows_wanted;
}
// the place of the map of the shadows of Size texels, snapped to its texels so it doesn't crawl as the camera moves
void place_shadows(int const Size, glm::dvec3 const &Camera);
// the map of the shadows was drawn (or not) in this frame
void shadows_drawn(bool const Drawn);
// the rows of the panorama of the clouds of Size to draw in this frame: a band of it, all of it the first time after
// panorama_resized()
void panorama_band(glm::ivec2 const &Size, int &First, int &Rows);
void panorama_resized();
// a camera close enough to the one of the previous frame to blend with
static bool continuity(view_state const &Previous, glm::dvec3 const &Camera);
// the point the cells of the fog volume are sampled at in this frame (Temporal: it moves from frame to frame) and the slicing of
// the volume of Slices slices it makes
glm::vec3 fog_jitter(bool const Temporal) const;
static glm::vec4 fog_slicing(int const Slices, glm::vec3 const &Jitter);
// the clouds of the main view and the fog volume were made in this frame
void clouds_made()
{
m_clouds_view_valid = true;
}
void fog_made(glm::vec4 const &Slicing, glm::ivec3 const &Grid);
// the fog of a pass: the volume of the main view (made in the same frame), or along the ray. Size: the target of the pass
void setup_pass(bool const Volume, glm::ivec2 const &Size);
gl::weather_ubs const &block() const
{
return m_weather;
}
// the colours of the horizon come from the sky-view table of the atmosphere on the gpu, not from the cpu
bool horizon_on_gpu() const
{
return m_horizon_on_gpu;
}
// true when the volumetric clouds are drawn (the sky leaves its own overcast look then)
bool clouds_drawn() const
{
return m_clouds_drawn;
}
bool clouds_view_valid() const
{
return m_clouds_view_valid;
}
// true when the clouds cast shadows (the sunlight isn't dimmed by the overcast then, the shadows do it)
bool cloud_shadows() const
{
return m_shadows_valid;
}
bool fog_volume_valid() const
{
return m_fog_volume_valid;
}
// fog density of a regular draw (model_ubs.fog_density): 1 / the fog range
float fog_density() const
{
return 1.f / m_fog_range;
}
// distance the fog hides things at, for the draw range: a dark object fades to about 2% there
float fog_range() const
{
return m_fog_range;
}
// frames updated, the sequence of the sampling over frames
int frame() const
{
return m_frame;
}
// the stars, in the equatorial frame: for each star the direction and the magnitude, then the colour and the phase of its
// twinkling
static std::vector<glm::vec4> star_vertices();
// whether the stars are drawn in the sky pass, and what with: the rotation from the equatorial frame to the world, from the
// world to the view of the pass (View: rotation of its camera), the brightness, the time, the size of the points
bool stars(float const Sunangle, double const Siderealtime, double const Latitude, glm::mat4 const &View, glm::mat4 &Toworld, glm::mat4 &Toview, glm::vec4 &Params) const;
// controls of the debug window, Volumeavailable: the renderer has the fog volume
void draw_debug_ui();
// the cameras of the clouds and of the fog volume of the previous frame
view_state previous_clouds;
view_state previous_fog;
private:
gl::weather_ubs m_weather{};
bool m_horizon_on_gpu{false};
bool m_shadows_wanted{false};
bool m_shadows_valid{false};
bool m_clouds_drawn{false};
bool m_clouds_view_valid{false};
int m_panorama_band{0};
bool m_panorama_full{false};
glm::ivec3 m_fog_grid{0};
bool m_fog_volume_valid{false};
float m_fog_range{5000.f};
glm::vec4 m_fog_slicing{0.f};
int m_frame{0};
// tuning of the look of the clouds in the debug window, not saved: scales of their sunlight, of their sky light and of the
// erosion of their edges by the detail noise (weather_sun_color.w, weather_ambient.w, cloud_wind.w)
float m_cloud_sun_scale{1.f};
float m_cloud_sky_scale{1.f};
float m_cloud_detail_scale{1.f};
// what the debug window shows
float m_sun_share{0.f};
float m_average_transmittance{1.f};
std::string m_report;
};
} // namespace gfx

View File

@@ -9,6 +9,7 @@ http://mozilla.org/MPL/2.0/.
#include "stdafx.h"
#include "rendering/opengl46/opengl46exposure.h"
#include "rendering/common/exposure.h"
#include "model/Texture.h"
#include "utilities/Globals.h"
@@ -17,16 +18,8 @@ http://mozilla.org/MPL/2.0/.
namespace
{
// luminance range of the histogram, log2: 2^-12 .. 2^6. darker pixels count as black, brighter ones as the brightest
float const HISTOGRAM_LOG_MIN{-12.f};
float const HISTOGRAM_LOG_MAX{6.f};
// the bloom chain goes down this many levels at most, and not below this size
std::size_t const BLOOM_LEVELS{6};
int const BLOOM_MIN_SIZE{8};
// format of the levels: no alpha, no negative values, half the memory of rgba16f
GLint const BLOOM_FORMAT{GL_R11F_G11F_B10F};
// soft knee of the threshold, a fraction of it
float const BLOOM_KNEE{0.5f};
} // namespace
@@ -58,12 +51,8 @@ void opengl46_exposure::bind()
std::unique_ptr<opengl46_exposure::bloom_chain> opengl46_exposure::make_bloom_chain(int const Width, int const Height)
{
auto chain{std::make_unique<bloom_chain>()};
glm::ivec2 size{Width, Height};
for (std::size_t level = 0; level < BLOOM_LEVELS; ++level)
for (auto const &size : gfx::exposure_settings::bloom_sizes(Width, Height))
{
size = glm::max(size / 2, glm::ivec2{1});
if (level > 0 && std::min(size.x, size.y) < BLOOM_MIN_SIZE)
break;
chain->sizes.push_back(size);
auto &texture{chain->down.emplace_back(std::make_unique<opengl_texture>())};
texture->alloc_rendertarget(BLOOM_FORMAT, GL_RGB, size.x, size.y, 1, 1, GL_CLAMP_TO_EDGE);
@@ -94,19 +83,18 @@ void opengl46_exposure::measure(opengl_texture &Image, double const Deltatime)
m_exposure_timer.begin();
m_exposure_ssbo->bind_base();
Image.bind(0);
auto const bins{static_cast<float>(gl::EXPOSURE_HISTOGRAM_BINS - 2)};
glProgramUniform2f(*m_histogram_program, 0, HISTOGRAM_LOG_MIN, bins / (HISTOGRAM_LOG_MAX - HISTOGRAM_LOG_MIN));
auto const histogramrange{gfx::exposure_settings::histogram_range()};
glProgramUniform2f(*m_histogram_program, 0, histogramrange.x, histogramrange.y);
auto const groupsize{2 * static_cast<int>(gl::EXPOSURE_HISTOGRAM_GROUP_SIZE)};
m_histogram_program->dispatch((Image.get_width() + groupsize - 1) / groupsize, (Image.get_height() + groupsize - 1) / groupsize);
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
glProgramUniform2f(*m_average_program, 0, HISTOGRAM_LOG_MIN, (HISTOGRAM_LOG_MAX - HISTOGRAM_LOG_MIN) / bins);
glProgramUniform4f(*m_average_program, 1,
static_cast<float>(std::clamp(Deltatime, 0.0, 1.0)),
Global.gfx_postfx_autoexposure_speed_dark,
Global.gfx_postfx_autoexposure_speed_bright,
Global.gfx_postfx_autoexposure_key);
glProgramUniform2f(*m_average_program, 2, std::exp2(Global.gfx_postfx_autoexposure_min), std::exp2(Global.gfx_postfx_autoexposure_max));
auto const averagerange{gfx::exposure_settings::average_range()};
auto const adaptation{gfx::exposure_settings::adaptation(Deltatime)};
auto const exposurerange{gfx::exposure_settings::exposure_range()};
glProgramUniform2f(*m_average_program, 0, averagerange.x, averagerange.y);
glProgramUniform4f(*m_average_program, 1, adaptation.x, adaptation.y, adaptation.z, adaptation.w);
glProgramUniform2f(*m_average_program, 2, exposurerange.x, exposurerange.y);
m_average_program->dispatch(1);
// read by the tonemapping, and copied for the debug window
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT);
@@ -122,7 +110,7 @@ opengl_texture *opengl46_exposure::bloom(bloom_chain &Chain, opengl_texture &Ima
// down, the first step from the image itself
for (std::size_t level = 0; level < Chain.sizes.size(); ++level)
{
Model.param[0] = glm::vec4{(level == 0 ? 1.f : 0.f), Global.gfx_postfx_bloom_threshold, BLOOM_KNEE, 0.f};
Model.param[0] = gfx::exposure_settings::bloom_params(level);
Modelubo.update(Model);
glViewport(0, 0, Chain.sizes[level].x, Chain.sizes[level].y);
m_bloom_down->apply(level == 0 ? Image : *Chain.down[level - 1], Chain.down_fb[level].get());
@@ -141,12 +129,7 @@ opengl_texture *opengl46_exposure::bloom(bloom_chain &Chain, opengl_texture &Ima
glm::vec4 opengl46_exposure::tonemap_params(std::size_t const Bloomlevels) const
{
auto const bloom{(Global.gfx_postfx_bloom && Bloomlevels > 0) ? Global.gfx_postfx_bloom_intensity : 0.f};
return glm::vec4{
bloom,
Global.gfx_postfx_autoexposure ? 1.f : 0.f,
std::exp2(Global.gfx_postfx_autoexposure_compensation),
Bloomlevels > 0 ? 1.f / static_cast<float>(Bloomlevels) : 0.f};
return gfx::exposure_settings::tonemap_params(Bloomlevels);
}
std::string opengl46_exposure::info_times()
@@ -161,15 +144,5 @@ std::string opengl46_exposure::info_times()
void opengl46_exposure::draw_debug_ui()
{
ImGui::Checkbox("automatic exposure (gfx.postfx.autoexposure)", &Global.gfx_postfx_autoexposure);
ImGui::SliderFloat("key", &Global.gfx_postfx_autoexposure_key, 0.02f, 2.f, "%.3f", ImGuiSliderFlags_Logarithmic);
ImGui::SliderFloat("min EV", &Global.gfx_postfx_autoexposure_min, -4.f, 0.f);
ImGui::SliderFloat("max EV", &Global.gfx_postfx_autoexposure_max, 0.f, 4.f);
ImGui::SliderFloat("adaptation to dark (1/s)", &Global.gfx_postfx_autoexposure_speed_dark, 0.05f, 10.f);
ImGui::SliderFloat("adaptation to bright (1/s)", &Global.gfx_postfx_autoexposure_speed_bright, 0.05f, 10.f);
ImGui::SliderFloat("compensation EV", &Global.gfx_postfx_autoexposure_compensation, -4.f, 4.f);
ImGui::Text("average luminance %.3f, adapted %.3f, exposure %.3f (%+.2f EV)", m_readback_data[2], m_readback_data[0], m_readback_data[1], std::log2(std::max(m_readback_data[1], 1e-6f)));
ImGui::Checkbox("bloom (gfx.postfx.bloom)", &Global.gfx_postfx_bloom);
ImGui::SliderFloat("bloom intensity", &Global.gfx_postfx_bloom_intensity, 0.f, 0.5f);
ImGui::SliderFloat("bloom threshold", &Global.gfx_postfx_bloom_threshold, 0.f, 10.f);
gfx::exposure_settings::draw_debug_ui(m_readback_data);
}

View File

@@ -10,46 +10,13 @@ http://mozilla.org/MPL/2.0/.
#include "stdafx.h"
#include "rendering/opengl46/opengl46gpuparticles.h"
#include "rendering/particles.h"
#include "simulation/simulationenvironment.h"
#include "utilities/Globals.h"
#include "utilities/Logs.h"
#include "utilities/Timer.h"
#include "utilities/utilities.h"
#include "gl/vao.h"
namespace
{
// places of the smoke at most, all sources together (48 bytes each, as many again for the draw); the sources past it get none
std::uint32_t constexpr MAX_SMOKE_PLACES{1u << 19};
// the origin of the smoke moves by whole steps when the camera gets this far from it
double constexpr ORIGIN_STEP{256.0};
double constexpr ORIGIN_REACH{1024.0};
// how much the smoke scatters the light onwards (Henyey-Greenstein)
float constexpr SMOKE_FORWARD{0.3f};
// the boxes of the rain and snow around the camera: their sizes (metres) and the shares of the particles in each
std::array<float, 3> constexpr BOX_SIZE{10.f, 24.f, 60.f};
std::array<float, 3> constexpr BOX_SHARE{0.2f, 0.35f, 0.45f};
// what the rain and the snow are like: the speed of the fall (m/s), how long a streak lasts (s, the eye's or the camera's
// exposure), opacity, how much a drop or a flake scatters the light onwards and how bright it is against a white surface
struct precipitation_kind
{
float fall;
float exposure;
float opacity;
float forward;
float brightness;
};
precipitation_kind constexpr RAIN{7.f, 0.025f, 0.5f, 0.6f, 0.6f};
precipitation_kind constexpr SNOW{1.1f, 0.01f, 0.85f, 0.25f, 0.9f};
// the share of the wind of the environment the drops and flakes drift with (as the old cylinder leant with it)
double constexpr WIND_SHARE{0.5};
// the camera faster than this from a frame to the next was moved, not driven or flown
double constexpr CAMERA_JUMP{120.0};
std::unique_ptr<gl::program> make_program(std::vector<std::string> const &Names)
{
std::vector<std::unique_ptr<gl::shader>> shaders;
@@ -76,11 +43,8 @@ opengl46_gpu_particles::~opengl46_gpu_particles()
::glDeleteVertexArrays(1, &m_vao);
}
bool opengl46_gpu_particles::init()
bool opengl46_gpu_particles::gpu_init()
{
if (m_state != 0)
return m_state > 0;
m_state = -1;
try
{
m_smokesimulate = make_program({"particles_smoke_simulate.comp"});
@@ -109,88 +73,26 @@ bool opengl46_gpu_particles::init()
m_precipitationcommand = std::make_unique<gl::ssbo>(4 * sizeof(GLuint), gl::PARTICLE_COMMAND_SSBO_BINDING);
m_smokereadback = std::make_unique<gl::readback>(4 * sizeof(GLuint));
m_precipitationreadback = std::make_unique<gl::readback>(4 * sizeof(GLuint));
m_state = 1;
return true;
}
bool opengl46_gpu_particles::smoke_available()
void opengl46_gpu_particles::gpu_clear()
{
return init();
}
bool opengl46_gpu_particles::precipitation_available()
{
return init();
}
void opengl46_gpu_particles::clear()
{
m_pool.clear();
m_requested.clear();
m_poolfull = false;
m_sourcerecords.clear();
m_places = 0;
m_particles.reset();
m_sources.reset();
m_owners.reset();
m_smokeoutput.reset();
m_precipitationoutput.reset();
m_precipitationcapacity = 0;
m_originset = false;
m_lastupdate = 0;
}
void opengl46_gpu_particles::layout(particle_manager &Particles)
void opengl46_gpu_particles::gpu_layout(std::uint32_t const Places, std::uint32_t const Kept, std::vector<std::uint32_t> const &Owners, std::size_t const Sources)
{
auto const &sources{Particles.sequence()};
// the sources already laid out which asked for the same places since
std::size_t kept{0};
while ((kept < m_requested.size()) && (kept < sources.size()) && (m_requested[kept] == sources[kept].max_particles()))
++kept;
if ((kept == m_requested.size()) && (kept == sources.size()) && m_particles)
return;
if (kept < m_requested.size())
{
// a source went away or changed: all anew
kept = 0;
m_pool.clear();
m_requested.clear();
m_places = 0;
m_poolfull = false;
}
auto const oldplaces{m_places};
auto first{m_places};
m_pool.resize(sources.size());
m_requested.resize(sources.size());
for (std::size_t i = kept; i < sources.size(); ++i)
{
m_requested[i] = sources[i].max_particles();
auto const size{static_cast<std::uint32_t>(std::min<std::size_t>(m_requested[i], MAX_SMOKE_PLACES))};
m_pool[i] = pool_source{};
if (first + size > MAX_SMOKE_PLACES)
{
if (false == m_poolfull)
WriteLog("particles: the smoke of some sources left out, over " + std::to_string(MAX_SMOKE_PLACES) + " places of particles");
m_poolfull = true;
continue;
}
m_pool[i].first = first;
m_pool[i].size = size;
first += size;
}
m_places = first;
std::vector<std::uint32_t> owners(std::max<std::uint32_t>(m_places, 1), 0);
for (std::size_t i = 0; i < m_pool.size(); ++i)
std::fill_n(owners.begin() + m_pool[i].first, m_pool[i].size, static_cast<std::uint32_t>(i));
auto const places{static_cast<GLsizeiptr>(std::max<std::uint32_t>(m_places, 1))};
auto const places{static_cast<GLsizeiptr>(Places)};
// the particles of the places kept go over to the new storage, the new places are dead
auto particles{std::make_unique<gl::ssbo>(places * static_cast<GLsizeiptr>(sizeof(gl::particle_smoke)), gl::PARTICLE_SSBO_BINDING)};
::glClearNamedBufferData(*particles, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, nullptr);
if (m_particles && (oldplaces > 0) && (kept > 0))
::glCopyNamedBufferSubData(*m_particles, *particles, 0, 0, static_cast<GLsizeiptr>(oldplaces) * static_cast<GLsizeiptr>(sizeof(gl::particle_smoke)));
if (m_particles && (Kept > 0))
::glCopyNamedBufferSubData(*m_particles, *particles, 0, 0, static_cast<GLsizeiptr>(Kept) * static_cast<GLsizeiptr>(sizeof(gl::particle_smoke)));
m_particles = std::move(particles);
auto const ensure = [](std::unique_ptr<gl::ssbo> &Buffer, GLsizeiptr const Size, GLuint const Binding) {
@@ -201,96 +103,29 @@ void opengl46_gpu_particles::layout(particle_manager &Particles)
};
ensure(m_owners, places * static_cast<GLsizeiptr>(sizeof(std::uint32_t)), gl::PARTICLE_OWNER_SSBO_BINDING);
ensure(m_smokeoutput, places * static_cast<GLsizeiptr>(sizeof(gl::particle_draw)), gl::PARTICLE_OUTPUT_SSBO_BINDING);
ensure(m_sources, static_cast<GLsizeiptr>(std::max<std::size_t>(sources.size(), 1) * sizeof(gl::particle_smoke_source)), gl::PARTICLE_SOURCE_SSBO_BINDING);
m_owners->update(owners.data(), 0, static_cast<GLsizeiptr>(owners.size() * sizeof(std::uint32_t)));
m_sourcerecords.resize(sources.size(), gl::particle_smoke_source{});
ensure(m_sources, static_cast<GLsizeiptr>(std::max<std::size_t>(Sources, 1) * sizeof(gl::particle_smoke_source)), gl::PARTICLE_SOURCE_SSBO_BINDING);
m_owners->update(Owners.data(), 0, static_cast<GLsizeiptr>(Owners.size() * sizeof(std::uint32_t)));
}
void opengl46_gpu_particles::simulate_smoke(particle_manager &Particles, glm::dvec3 const &Camera)
void opengl46_gpu_particles::gpu_simulate(smoke_simulation const &Simulation, std::vector<gl::particle_smoke_source> const &Records)
{
if (false == init())
return;
layout(Particles);
if (Particles.updates() == m_lastupdate)
return;
m_lastupdate = Particles.updates();
if (m_places == 0)
return;
// the origin of the positions near the camera; when it moves, the particles move by as much the other way
glm::vec3 shift{0.f};
auto const snapped{glm::floor(Camera / ORIGIN_STEP) * ORIGIN_STEP};
if (false == m_originset)
{
m_origin = snapped;
m_originset = true;
}
else if (glm::length(Camera - m_origin) > ORIGIN_REACH)
{
shift = glm::vec3{m_origin - snapped};
m_origin = snapped;
}
// the light of the smoke as the cpu works it out, for gfx.smoke.lighting off
auto const oldlight{(glm::vec3{Global.DayLight.ambient} + 0.35f * glm::vec3{Global.DayLight.diffuse}) * simulation::Environment.light_intensity()};
auto const &sources{Particles.sequence()};
m_smokealive = 0;
for (std::size_t i = 0; i < sources.size(); ++i)
{
auto const &state{sources[i].gpu()};
auto &pool{m_pool[i]};
auto &record{m_sourcerecords[i]};
auto const spawns{(pool.size > 0) ? std::min(state.spawns, pool.size) : 0u};
record.pool = glm::uvec4{pool.first, pool.size, pool.head, spawns};
record.location = glm::vec4{glm::vec3{state.location - m_origin}, 0.f};
record.previous = glm::vec4{glm::vec3{state.previous - m_origin}, 0.f};
record.launch = glm::vec4{state.inclination[0], state.inclination[1], state.velocity[0], state.velocity[1]};
record.initial = glm::vec4{state.size[0], state.size[1], state.opacity[0], state.opacity[1]};
record.sizechange = glm::vec4{state.sizechange[0], state.sizechange[1], state.sizechange[2], state.spread};
record.opacitychange = glm::vec4{state.opacitychange[0], state.opacitychange[1], state.opacitychange[2], 0.f};
record.motion = glm::vec4{state.rise, state.damping, state.risemin, 0.f};
record.settling = glm::vec4{state.settling, state.drag, state.settles ? 1.f : 0.f, state.fadein};
record.color = glm::vec4{glm::clamp(state.color, glm::vec3{0.f}, glm::vec3{1.f}), 0.f};
record.oldcolor = glm::vec4{glm::clamp(state.color * oldlight, glm::vec3{0.f}, glm::vec3{1.f}), 0.f};
if (pool.size > 0)
{
pool.head = (pool.head + spawns) % pool.size;
if (spawns > 0)
m_smokealive += pool.size;
}
}
if (false == m_sourcerecords.empty())
m_sources->update(m_sourcerecords.data(), 0, static_cast<GLsizeiptr>(m_sourcerecords.size() * sizeof(gl::particle_smoke_source)));
if (false == Records.empty())
m_sources->update(Records.data(), 0, static_cast<GLsizeiptr>(Records.size() * sizeof(gl::particle_smoke_source)));
m_simulatetimer.begin();
m_particles->bind_base();
m_sources->bind_base();
m_owners->bind_base();
auto const wind{glm::vec3{simulation::Environment.wind()}};
auto &program{*m_smokesimulate};
::glProgramUniform1ui(program, 0, m_places);
::glProgramUniform4f(program, 1, static_cast<float>(Particles.timedelta()), shift.x, shift.y, shift.z);
::glProgramUniform4f(program, 2, wind.x, wind.y, wind.z, 0.f);
::glProgramUniform1ui(program, 3, static_cast<GLuint>(m_lastupdate));
program.dispatch(groups(m_places));
::glProgramUniform1ui(program, 0, Simulation.places);
::glProgramUniform4fv(program, 1, 1, glm::value_ptr(Simulation.step));
::glProgramUniform4fv(program, 2, 1, glm::value_ptr(Simulation.wind));
::glProgramUniform1ui(program, 3, static_cast<GLuint>(Simulation.seed));
program.dispatch(groups(Simulation.places));
::glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
m_simulatetimer.end();
}
std::array<glm::vec4, 5> opengl46_gpu_particles::planes(view const &View)
{
// the sides and the near side of the clip space, w >= 0 (the far plane is at infinity)
auto const matrix{glm::transpose(View.projection * View.modelview)};
std::array<glm::vec4, 5> result{matrix[3] + matrix[0], matrix[3] - matrix[0], matrix[3] + matrix[1], matrix[3] - matrix[1], matrix[3]};
for (auto &plane : result)
{
auto const length{glm::length(glm::vec3{plane})};
if (length > 0.f)
plane /= length;
}
return result;
}
void opengl46_gpu_particles::collect(gl::readback &Readback, std::size_t &Drawn)
{
GLuint command[4];
@@ -317,11 +152,9 @@ void opengl46_gpu_particles::draw(GLuint const Output, GLuint const Command, gl:
::glEnable(GL_CULL_FACE);
}
std::size_t opengl46_gpu_particles::render_smoke(view const &View)
void opengl46_gpu_particles::gpu_render_smoke(smoke_draw const &Draw, std::size_t &Drawn)
{
if ((false == init()) || (m_places == 0))
return 0;
collect(*m_smokereadback, m_smokedrawn);
collect(*m_smokereadback, Drawn);
m_smoketimer.begin();
GLuint const command[4]{4, 0, 0, 0};
@@ -332,125 +165,60 @@ std::size_t opengl46_gpu_particles::render_smoke(view const &View)
m_smokeoutput->bind_base();
m_smokecommand->bind_base();
auto &program{*m_smokeprepare};
auto const origin{glm::vec3{m_origin - View.camera}};
auto const viewplanes{planes(View)};
::glProgramUniform1ui(program, 0, m_places);
::glProgramUniform3f(program, 1, origin.x, origin.y, origin.z);
::glProgramUniformMatrix4fv(program, 2, 1, GL_FALSE, glm::value_ptr(View.modelview));
::glProgramUniform4f(program, 3, Global.gfx_smoke_lighting ? 1.f : 0.f, SMOKE_FORWARD, static_cast<float>(m_places), 0.f);
::glProgramUniform4fv(program, 4, 5, glm::value_ptr(viewplanes[0]));
program.dispatch(groups(m_places));
::glProgramUniform1ui(program, 0, Draw.places);
::glProgramUniform3fv(program, 1, 1, glm::value_ptr(Draw.origin));
::glProgramUniformMatrix4fv(program, 2, 1, GL_FALSE, glm::value_ptr(Draw.view));
::glProgramUniform4fv(program, 3, 1, glm::value_ptr(Draw.options));
::glProgramUniform4fv(program, 4, 5, glm::value_ptr(Draw.planes[0]));
program.dispatch(groups(Draw.places));
::glMemoryBarrier(GL_COMMAND_BARRIER_BIT | GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT);
::glProgramUniform4f(*m_smokedraw, 0, View.soft ? Global.gfx_particles_soft : 0.f, 0.f, 0.f, 0.f);
::glProgramUniform4fv(*m_smokedraw, 0, 1, glm::value_ptr(Draw.soft));
draw(*m_smokeoutput, *m_smokecommand, *m_smokedraw);
m_smoketimer.end();
m_smokereadback->push(*m_smokecommand);
return m_smokedrawn;
}
std::size_t opengl46_gpu_particles::render_precipitation(view const &View, bool const Snow, float const Intensity, unsigned int const Frame)
void opengl46_gpu_particles::gpu_collect_precipitation(std::size_t &Drawn)
{
if (false == init())
return 0;
collect(*m_precipitationreadback, m_precipitationdrawn);
collect(*m_precipitationreadback, Drawn);
}
auto const &kind{Snow ? SNOW : RAIN};
// once per frame: the time moves on, the particles drift with the wind and fall, the camera has its velocity
if ((false == m_framestarted) || (Frame != m_frame))
{
m_framestarted = true;
m_frame = Frame;
auto const seconds{Timer::GetDeltaTime()};
if (seconds > 0.0)
{
auto const wind{glm::dvec3{simulation::Environment.wind()} * WIND_SHARE};
for (std::size_t box = 0; box < BOX_SIZE.size(); ++box)
{
auto const size{static_cast<double>(BOX_SIZE[box])};
m_drift[box] = glm::mod(m_drift[box] + wind * seconds, size);
m_fall[box] = std::fmod(m_fall[box] + kind.fall * seconds, 16.0 * size);
}
m_time = std::fmod(m_time + seconds, 3600.0);
if (m_camerastarted)
{
auto const velocity{(View.camera - m_lastcamera) / seconds};
if (glm::length(velocity) < CAMERA_JUMP)
m_cameravelocity = glm::mix(m_cameravelocity, velocity, 1.0 - std::exp(-10.0 * seconds));
}
}
m_lastcamera = View.camera;
m_camerastarted = true;
}
// the particles, more of them in heavier rain or snow
auto const most{static_cast<std::uint32_t>(std::max(Global.gfx_precipitation_particles, 0))};
auto const count{static_cast<std::uint32_t>(most * std::clamp(0.15f + 0.85f * Intensity, 0.f, 1.f))};
m_precipitationcount = count;
m_precipitationsnow = Snow;
if (count == 0)
return 0;
if (m_precipitationcapacity < count)
{
m_precipitationcapacity = std::max(count, most);
auto const size{static_cast<GLsizeiptr>(m_precipitationcapacity) * static_cast<GLsizeiptr>(sizeof(gl::particle_draw))};
if (!m_precipitationoutput)
m_precipitationoutput = std::make_unique<gl::ssbo>(size, gl::PARTICLE_OUTPUT_SSBO_BINDING);
else
m_precipitationoutput->resize(size);
}
void opengl46_gpu_particles::gpu_precipitation_storage(std::uint32_t const Capacity)
{
auto const size{static_cast<GLsizeiptr>(Capacity) * static_cast<GLsizeiptr>(sizeof(gl::particle_draw))};
if (!m_precipitationoutput)
m_precipitationoutput = std::make_unique<gl::ssbo>(size, gl::PARTICLE_OUTPUT_SSBO_BINDING);
else
m_precipitationoutput->resize(size);
}
void opengl46_gpu_particles::gpu_render_precipitation(precipitation_draw const &Draw)
{
m_precipitationtimer.begin();
GLuint const command[4]{4, 0, 0, 0};
m_precipitationcommand->update(command, 0, sizeof(command));
m_precipitationoutput->bind_base();
m_precipitationcommand->bind_base();
auto &program{*m_precipitationprepare};
auto const second{static_cast<std::uint32_t>(count * BOX_SHARE[0])};
auto const third{static_cast<std::uint32_t>(count * (BOX_SHARE[0] + BOX_SHARE[1]))};
::glProgramUniform4ui(program, 0, second, third, count, Snow ? 1u : 0u);
std::array<glm::vec4, 3> offsets;
for (std::size_t box = 0; box < BOX_SIZE.size(); ++box)
{
auto const size{static_cast<double>(BOX_SIZE[box])};
offsets[box] = glm::vec4{glm::vec3{glm::mod(m_drift[box] - glm::mod(View.camera, size), size)}, static_cast<float>(m_fall[box])};
}
::glProgramUniform4fv(program, 1, 3, glm::value_ptr(offsets[0]));
::glProgramUniform4f(program, 4, BOX_SIZE[0], BOX_SIZE[1], BOX_SIZE[2], 0.f);
auto const velocity{glm::vec3{glm::dvec3{simulation::Environment.wind()} * WIND_SHARE - m_cameravelocity}};
::glProgramUniform4f(program, 5, velocity.x, velocity.y, velocity.z, kind.fall);
::glProgramUniform4f(program, 6, static_cast<float>(m_time), kind.exposure, kind.opacity, static_cast<float>(m_precipitationcapacity));
::glProgramUniformMatrix4fv(program, 7, 1, GL_FALSE, glm::value_ptr(View.modelview));
::glProgramUniform4f(program, 11, 1.f, kind.forward, kind.brightness, 0.f);
auto const viewplanes{planes(View)};
::glProgramUniform4fv(program, 12, 5, glm::value_ptr(viewplanes[0]));
program.dispatch(groups(count));
::glProgramUniform4uiv(program, 0, 1, glm::value_ptr(Draw.counts));
::glProgramUniform4fv(program, 1, 3, glm::value_ptr(Draw.offsets[0]));
::glProgramUniform4fv(program, 4, 1, glm::value_ptr(Draw.sizes));
::glProgramUniform4fv(program, 5, 1, glm::value_ptr(Draw.velocity));
::glProgramUniform4fv(program, 6, 1, glm::value_ptr(Draw.timing));
::glProgramUniformMatrix4fv(program, 7, 1, GL_FALSE, glm::value_ptr(Draw.view));
::glProgramUniform4fv(program, 11, 1, glm::value_ptr(Draw.options));
::glProgramUniform4fv(program, 12, 5, glm::value_ptr(Draw.planes[0]));
program.dispatch(groups(Draw.count));
::glMemoryBarrier(GL_COMMAND_BARRIER_BIT | GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT);
// the width of a pixel a metre in front of the camera, for the least width of a streak
auto const pixel{(View.height > 0) && (View.projection[1][1] != 0.f) ? 2.f / (std::abs(View.projection[1][1]) * static_cast<float>(View.height)) : 0.f};
::glProgramUniform4f(*m_precipitationdraw, 0, View.soft ? 0.25f * Global.gfx_particles_soft : 0.f, 0.f, 0.f, 0.f);
::glProgramUniform4f(*m_precipitationdraw, 1, pixel, 0.f, 0.f, 0.f);
::glProgramUniform4fv(*m_precipitationdraw, 0, 1, glm::value_ptr(Draw.soft));
::glProgramUniform4fv(*m_precipitationdraw, 1, 1, glm::value_ptr(Draw.pixel));
draw(*m_precipitationoutput, *m_precipitationcommand, *m_precipitationdraw);
m_precipitationtimer.end();
m_precipitationreadback->push(*m_precipitationcommand);
return m_precipitationdrawn;
}
std::string opengl46_gpu_particles::info() const
{
if (m_state <= 0)
return {};
std::string text;
if (m_places > 0)
text += " smoke of the gpu: " + std::to_string(m_smokedrawn) + " drawn, " + std::to_string(m_places) + " places of " + std::to_string(m_pool.size()) + " sources ("
+ std::to_string(m_smokealive) + " of the ones which bore lately)" + (m_poolfull ? ", some sources left out" : "") + "\n";
if (m_precipitationcount > 0)
text += std::string(" ") + (m_precipitationsnow ? "snow" : "rain") + " of the gpu: " + std::to_string(m_precipitationdrawn) + " of " + std::to_string(m_precipitationcount)
+ " drawn\n";
return text;
}
std::string opengl46_gpu_particles::info_times()
@@ -460,13 +228,13 @@ std::string opengl46_gpu_particles::info_times()
if (auto const time = Timer.result())
text += std::string(text.empty() ? " particles:" : ",") + " " + Name + " " + to_string(static_cast<double>(*time) / 1000000.0, 3) + " ms";
};
if (m_places > 0)
if (places() > 0)
{
add(m_simulatetimer, "smoke simulation");
add(m_smoketimer, "smoke");
}
if (m_precipitationcount > 0)
add(m_precipitationtimer, m_precipitationsnow ? "snow" : "rain");
if (precipitation_count() > 0)
add(m_precipitationtimer, precipitation_snow() ? "snow" : "rain");
if (false == text.empty())
text += "\n";
return text;

View File

@@ -9,87 +9,42 @@ http://mozilla.org/MPL/2.0/.
#pragma once
#include <array>
#include <memory>
#include <string>
#include <vector>
#include <glm/glm.hpp>
#include "rendering/common/gpuparticles.h"
#include "gl/shader.h"
#include "gl/ssbo.h"
#include "gl/ubo.h"
#include "gl/query.h"
#include "gl/readback.h"
class particle_manager;
// particles of the gpu for the opengl 4.6 renderer: the smoke and the rain or snow.
//
// smoke: the sources (particle_manager in its gpu mode) work out on the cpu only how many particles they bear in an update of
// the simulation and what these start from; the particles are kept in a storage buffer, a ring of places for each source, and
// simulated by a compute pass once per update (particles_smoke_simulate.comp). in every colour pass which draws them another
// compute pass (particles_smoke_prepare.comp) leaves out the dead ones and the ones out of the view, lights the rest with the
// lights of the pass and adds them to an indirect draw of billboards (particles_smoke.vert/.frag).
//
// rain and snow: particles in three boxes around the camera, each repeated along the world axes, which keep no state: their
// place follows from their number, how far the wind took them and how far they fell (particles_precipitation.comp), drawn as
// streaks of their motion against the camera (particles_precipitation.vert/.frag).
//
// the positions of the smoke are kept from an origin near the camera, moved by whole steps as the camera goes, so they keep
// their precision far from the centre of the scenery
class opengl46_gpu_particles
// particles of the gpu for the opengl 4.6 renderer (gfx::gpu_particles): the storage buffers, the compute programs and the
// indirect draws
class opengl46_gpu_particles : public gfx::gpu_particles
{
public:
using view = gfx::gpu_particles::view;
opengl46_gpu_particles() = default;
~opengl46_gpu_particles();
opengl46_gpu_particles(opengl46_gpu_particles const &) = delete;
opengl46_gpu_particles &operator=(opengl46_gpu_particles const &) = delete;
~opengl46_gpu_particles() override;
// what a colour pass draws with
struct view
{
glm::mat4 modelview; // world axes around the camera to the view space of the pass
glm::mat4 projection;
glm::dvec3 camera;
int height{0}; // of the target, pixels
bool soft{false}; // the resolved depth of the scene drawn so far is bound to gl::SCENE_DEPTH_TEX
};
// whether the programs could be made; without them the smoke stays on the cpu and the rain is the old cylinder
bool smoke_available();
bool precipitation_available();
// once per update of the particle manager (in its gpu mode): the births and the change of the smoke. Camera: of the view
void simulate_smoke(particle_manager &Particles, glm::dvec3 const &Camera);
// draws the smoke the view sees, with the texture bound to unit 0. returns: particles drawn, as read back a few frames later
std::size_t render_smoke(view const &View);
// draws the rain (Snow false) or the snow of the intensity 0-1. Frame: number of the frame, the time moves on once per frame.
// returns: particles drawn, read back
std::size_t render_precipitation(view const &View, bool Snow, float Intensity, unsigned int Frame);
// drops what is on the gpu, e.g. when the context goes
void clear();
std::string info() const;
std::string info_times();
private:
struct pool_source
{
std::uint32_t first{0};
std::uint32_t size{0};
std::uint32_t head{0};
};
protected:
bool gpu_init() override;
void gpu_layout(std::uint32_t const Places, std::uint32_t const Kept, std::vector<std::uint32_t> const &Owners, std::size_t const Sources) override;
void gpu_simulate(smoke_simulation const &Simulation, std::vector<gl::particle_smoke_source> const &Records) override;
void gpu_render_smoke(smoke_draw const &Draw, std::size_t &Drawn) override;
void gpu_collect_precipitation(std::size_t &Drawn) override;
void gpu_precipitation_storage(std::uint32_t const Capacity) override;
void gpu_render_precipitation(precipitation_draw const &Draw) override;
void gpu_clear() override;
bool init();
// the places of the sources in the storage: sources added after the others get theirs after the places already given, and
// the particles stay; otherwise all is laid out anew
void layout(particle_manager &Particles);
private:
// reads back the instances counted by a pass; Drawn is left as it was when nothing came back yet
static void collect(gl::readback &Readback, std::size_t &Drawn);
// the planes of the view of the pass, for the culling of the compute passes
static std::array<glm::vec4, 5> planes(view const &View);
void draw(GLuint Output, GLuint Command, gl::program &Program);
int m_state{0}; // 0: not set up yet, 1: ready, -1: can't be used
std::unique_ptr<gl::program> m_smokesimulate;
std::unique_ptr<gl::program> m_smokeprepare;
std::unique_ptr<gl::program> m_smokedraw;
@@ -98,22 +53,12 @@ class opengl46_gpu_particles
GLuint m_vao{0}; // the attributes of the instances, from the buffer of the draw bound to it before the draw
// smoke
std::vector<pool_source> m_pool;
std::vector<std::size_t> m_requested; // the sizes the sources asked for
std::vector<gl::particle_smoke_source> m_sourcerecords;
std::uint32_t m_places{0};
bool m_poolfull{false}; // some sources got no places
std::unique_ptr<gl::ssbo> m_particles;
std::unique_ptr<gl::ssbo> m_sources;
std::unique_ptr<gl::ssbo> m_owners;
std::unique_ptr<gl::ssbo> m_smokeoutput;
std::unique_ptr<gl::ssbo> m_smokecommand;
std::unique_ptr<gl::readback> m_smokereadback;
std::uint64_t m_lastupdate{0};
glm::dvec3 m_origin{0.0};
bool m_originset{false};
std::size_t m_smokedrawn{0};
std::size_t m_smokealive{0}; // places of the sources which bore particles lately
gl::gpu_timer m_simulatetimer;
gl::gpu_timer m_smoketimer;
@@ -121,17 +66,5 @@ class opengl46_gpu_particles
std::unique_ptr<gl::ssbo> m_precipitationoutput;
std::unique_ptr<gl::ssbo> m_precipitationcommand;
std::unique_ptr<gl::readback> m_precipitationreadback;
std::uint32_t m_precipitationcapacity{0};
unsigned int m_frame{0};
bool m_framestarted{false};
std::array<glm::dvec3, 3> m_drift{}; // how far the wind took the particles of each box, within the box
std::array<double, 3> m_fall{}; // how far they fell, within 16 sizes of the box
double m_time{0.0}; // seconds, for the flutter of the flakes
glm::dvec3 m_lastcamera{0.0};
glm::dvec3 m_cameravelocity{0.0};
bool m_camerastarted{false};
std::size_t m_precipitationdrawn{0};
std::uint32_t m_precipitationcount{0};
bool m_precipitationsnow{false};
gl::gpu_timer m_precipitationtimer;
};

View File

@@ -12,51 +12,12 @@ http://mozilla.org/MPL/2.0/.
#include "rendering/openglcamera.h"
#include "model/Texture.h"
#include "simulation/simulationenvironment.h"
#include "simulation/simulationtime.h"
#include "utilities/Globals.h"
#include "utilities/Logs.h"
#include "utilities/Timer.h"
namespace
{
int constexpr max_blades{65536}; // drawn in a tile at most
float constexpr blade_reach{1.5f}; // metres the bounds of a tile reach above its ground, for the blades
// the share of the blades drawn at specified distance: fewer further away, for speed (from Thinning), and fewer and fewer from
// Fade to Reach, where the grass ends; the same as lod_share() of grass.vert
float lod_share(float const Distance, float const Thinning, float const Fade, float const Reach)
{
auto const share{Distance <= Thinning ? 1.f : std::pow(Thinning / Distance, 1.5f)};
auto const t{std::clamp((Distance - Fade) / std::max(Reach - Fade, 1e-3f), 0.f, 1.f)};
return share * (1.f - t * t * (3.f - 2.f * t));
}
// the grass through the year, by the day of the year: x how dry it is, y how fresh (the green of spring), z how grown (share
// of the height). the turns follow the seasons of the textures (world_environment::compute_season(): winter till day 65,
// spring till 158, summer till 252, autumn till 341): dormant and dry in winter, greening up and growing through spring,
// fresh in May, drying out from August, the dry grass lying lower towards winter
glm::vec3 grass_season(int const Yearday)
{
struct point
{
float day;
glm::vec3 value;
};
static point const year[]{{1.f, {0.80f, 0.f, 0.55f}}, {65.f, {0.80f, 0.f, 0.55f}}, {100.f, {0.45f, 0.3f, 0.70f}}, {135.f, {0.05f, 1.f, 0.90f}},
{170.f, {0.f, 0.4f, 1.f}}, {200.f, {0.10f, 0.f, 1.f}}, {240.f, {0.35f, 0.f, 1.f}}, {280.f, {0.50f, 0.f, 0.95f}},
{320.f, {0.65f, 0.f, 0.80f}}, {341.f, {0.80f, 0.f, 0.60f}}, {367.f, {0.80f, 0.f, 0.55f}}};
auto const day{std::clamp(static_cast<float>(Yearday), 1.f, 366.f)};
for (std::size_t i = 1; i < std::size(year); ++i)
if (day <= year[i].day)
{
auto const t{(day - year[i - 1].day) / (year[i].day - year[i - 1].day)};
return glm::mix(year[i - 1].value, year[i].value, t * t * (3.f - 2.f * t));
}
return year[std::size(year) - 1].value;
}
GLuint make_texture(GLenum const Format, int const Width, int const Height, GLenum const Filter)
{
GLuint texture{0};
@@ -112,37 +73,28 @@ bool opengl46_grass::init()
return true;
}
void opengl46_grass::release(chunk &Chunk)
void opengl46_grass::gpu_release(std::uint64_t const Id)
{
for (auto *texture : {&Chunk.heights, &Chunk.density, &Chunk.holes})
auto const lookup{m_textures.find(Id)};
if (lookup == m_textures.end())
return;
auto &chunk{lookup->second};
for (auto *texture : {&chunk.heights, &chunk.density, &chunk.holes})
if (*texture != 0)
{
::glDeleteTextures(1, texture);
*texture = 0;
}
m_textures.erase(lookup);
}
void opengl46_grass::clear()
void opengl46_grass::gpu_upload(std::uint64_t const Id, gfx::grass_chunk const &Source)
{
for (auto &entry : m_chunks)
release(entry.second);
m_chunks.clear();
}
void opengl46_grass::upload(chunk &Chunk, gfx::grass_chunk const &Source)
{
release(Chunk);
Chunk.version = Source.version;
Chunk.corner = Source.corner;
Chunk.size = Source.size;
Chunk.cells = Source.cells;
Chunk.samples = Source.samples;
Chunk.kinds = Source.kinds;
auto &chunk{m_textures[Id]};
auto const side{Source.cells + 1};
Chunk.heights = make_texture(GL_R32F, side, side, GL_NEAREST);
chunk.heights = make_texture(GL_R32F, side, side, GL_NEAREST);
::glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
::glTextureSubImage2D(Chunk.heights, 0, 0, 0, side, side, GL_RED, GL_FLOAT, Source.heights);
::glTextureSubImage2D(chunk.heights, 0, 0, 0, side, side, GL_RED, GL_FLOAT, Source.heights);
// the densities of the kinds side by side, as the channels of a texel
auto const samples{Source.samples + 1};
@@ -154,186 +106,57 @@ void opengl46_grass::upload(chunk &Chunk, gfx::grass_chunk const &Source)
for (std::size_t kind = 0; kind < kinds; ++kind)
for (std::size_t i = 0; i < plane; ++i)
texels[i * 4 + kind] = Source.density[kind * plane + i];
Chunk.density = make_texture(GL_RGBA8, samples, samples, GL_LINEAR);
::glTextureSubImage2D(Chunk.density, 0, 0, 0, samples, samples, GL_RGBA, GL_UNSIGNED_BYTE, texels.data());
chunk.density = make_texture(GL_RGBA8, samples, samples, GL_LINEAR);
::glTextureSubImage2D(chunk.density, 0, 0, 0, samples, samples, GL_RGBA, GL_UNSIGNED_BYTE, texels.data());
std::uint8_t const none{0};
auto const holeside{(Source.holes > 0 && Source.hole_mask != nullptr) ? Source.holes + 1 : 1};
Chunk.holes = make_texture(GL_R8, holeside, holeside, GL_LINEAR);
chunk.holes = make_texture(GL_R8, holeside, holeside, GL_LINEAR);
::glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
::glTextureSubImage2D(Chunk.holes, 0, 0, 0, holeside, holeside, GL_RED, GL_UNSIGNED_BYTE, holeside > 1 ? Source.hole_mask : &none);
::glTextureSubImage2D(chunk.holes, 0, 0, 0, holeside, holeside, GL_RED, GL_UNSIGNED_BYTE, holeside > 1 ? Source.hole_mask : &none);
::glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
// the ground and the densest place of every kind of each tile, to skip the empty tiles and to draw as many blades as needed
auto const spacing{static_cast<double>(Source.size) / Source.cells};
auto const step{static_cast<double>(Source.size) / Source.samples};
auto const tilesize{static_cast<double>(Source.size) / tiles};
for (int tz = 0; tz < tiles; ++tz)
for (int tx = 0; tx < tiles; ++tx)
{
auto &part{Chunk.parts[static_cast<std::size_t>(tz) * tiles + tx]};
auto const range = [](double const From, double const To, double const Step, int const Last) {
return std::make_pair(std::clamp(static_cast<int>(std::floor(From / Step)), 0, Last), std::clamp(static_cast<int>(std::ceil(To / Step)), 0, Last));
};
auto const [gx0, gx1]{range(tx * tilesize, (tx + 1) * tilesize, spacing, Source.cells)};
auto const [gz0, gz1]{range(tz * tilesize, (tz + 1) * tilesize, spacing, Source.cells)};
part.low = std::numeric_limits<float>::max();
part.high = -std::numeric_limits<float>::max();
for (int z = gz0; z <= gz1; ++z)
for (int x = gx0; x <= gx1; ++x)
{
auto const height{Source.heights[static_cast<std::size_t>(z) * side + x]};
part.low = std::min(part.low, height);
part.high = std::max(part.high, height);
}
part.most.fill(0.f);
auto const [sx0, sx1]{range(tx * tilesize, (tx + 1) * tilesize, step, Source.samples)};
auto const [sz0, sz1]{range(tz * tilesize, (tz + 1) * tilesize, step, Source.samples)};
for (std::size_t kind = 0; kind < kinds; ++kind)
{
std::uint8_t most{0};
for (int z = sz0; z <= sz1; ++z)
for (int x = sx0; x <= sx1; ++x)
most = std::max(most, Source.density[kind * plane + static_cast<std::size_t>(z) * samples + x]);
part.most[kind] = most / 255.f;
}
}
}
void opengl46_grass::update(std::vector<gfx::grass_kind> const &Kinds, std::vector<gfx::grass_chunk> const &Chunks)
{
m_kinds = Kinds;
for (auto &entry : m_chunks)
entry.second.listed = false;
if (false == Global.gfx_grass)
{
clear();
return;
}
for (auto const &source : Chunks)
{
if (source.heights == nullptr || source.density == nullptr || source.cells <= 0 || source.samples <= 0)
continue;
auto &entry{m_chunks[source.id]};
entry.listed = true;
if (entry.heights == 0 || entry.version != source.version)
upload(entry, source);
}
// what's no longer near goes
for (auto it = m_chunks.begin(); it != m_chunks.end();)
{
if (it->second.listed)
{
++it;
continue;
}
release(it->second);
it = m_chunks.erase(it);
}
}
std::size_t opengl46_grass::render(opengl_camera const &Camera, glm::mat4 const &View, std::function<void(glm::mat4 const &)> const &Modelview)
{
if (false == Global.gfx_grass || m_chunks.empty() || m_kinds.empty() || false == init())
if (false == prepare(Camera.position(), [&](scene::bounding_area const &Area) { return Camera.visible(Area); }) || false == init())
return 0;
// whole up to the distance, thinning out to the fading distance
auto const fade{std::max(5.f, Global.gfx_grass_distance)};
auto const reach{std::max(fade + 1.f, Global.gfx_grass_fadedistance)};
auto const thinning{std::max(3.f, fade * 0.2f)};
auto const multiplier{std::max(0.f, Global.gfx_grass_density)};
auto const camera{Camera.position()};
m_program->bind();
m_vao->bind();
::glDisable(GL_CULL_FACE);
::glUniform4f(m_uniforms.lod, thinning, fade, reach, 0.f);
auto const season{grass_season(simulation::Time.year_day())};
::glUniform4f(m_uniforms.season, season.x, season.y, season.z, 0.f);
// the wind sways the blades along it, harder in the gusts
auto const wind{glm::vec3{simulation::Environment.wind()}};
auto const windspeed{glm::length(glm::vec2{wind.x, wind.z})};
auto const winddirection{windspeed > 0.01f ? glm::vec2{wind.x, wind.z} / windspeed : glm::vec2{1.f, 0.f}};
::glUniform4f(m_uniforms.wind, winddirection.x, winddirection.y, std::clamp(windspeed / 15.f, 0.f, 1.f), static_cast<float>(std::fmod(Timer::GetTime(), 3600.0)));
auto const &frame{this->frame()};
::glUniform4fv(m_uniforms.lod, 1, glm::value_ptr(frame.lod));
::glUniform4fv(m_uniforms.season, 1, glm::value_ptr(frame.season));
::glUniform4fv(m_uniforms.wind, 1, glm::value_ptr(frame.wind));
std::size_t triangles{0};
for (auto &entry : m_chunks)
auto const &tiles{tile_draws()};
for (auto const &chunk : chunk_draws())
{
auto &chunk{entry.second};
// the kinds of the chunk, and the share of the blades of each against its densest
std::array<glm::vec4, 4> base{}, tip{}, shape{}, dry{};
glm::vec4 share{0.f};
float densest{0.f};
for (std::size_t i = 0; i < chunk.kinds.size(); ++i)
if (chunk.kinds[i] >= 0 && chunk.kinds[i] < static_cast<int>(m_kinds.size()))
densest = std::max(densest, m_kinds[chunk.kinds[i]].density);
if (densest <= 0.f)
continue;
for (std::size_t i = 0; i < chunk.kinds.size(); ++i)
// the chunk is drawn relative to its corner, the heights as they are
Modelview(glm::translate(View, glm::vec3{chunk.offset}));
::glUniform4fv(m_uniforms.chunk, 1, glm::value_ptr(chunk.chunk));
::glUniform4fv(m_uniforms.camera, 1, glm::value_ptr(chunk.camera));
::glUniform4fv(m_uniforms.base, 4, glm::value_ptr(chunk.base[0]));
::glUniform4fv(m_uniforms.tip, 4, glm::value_ptr(chunk.tip[0]));
::glUniform4fv(m_uniforms.shape, 4, glm::value_ptr(chunk.shape[0]));
::glUniform4fv(m_uniforms.share, 1, glm::value_ptr(chunk.share));
::glUniform4fv(m_uniforms.dry, 4, glm::value_ptr(chunk.dry[0]));
auto const &textures{m_textures[chunk.id]};
GLuint const units[]{textures.heights, textures.density, textures.holes};
::glBindTextures(0, 3, units);
for (auto i = chunk.first; i < chunk.first + chunk.count; ++i)
{
if (chunk.kinds[i] < 0 || chunk.kinds[i] >= static_cast<int>(m_kinds.size()))
continue;
auto const &kind{m_kinds[chunk.kinds[i]]};
base[i] = glm::vec4{kind.base, 0.f};
tip[i] = glm::vec4{kind.tip, 0.f};
shape[i] = glm::vec4{kind.height, kind.variation, kind.width, kind.bend};
dry[i] = glm::vec4{kind.dry, kind.seasonal};
share[i] = kind.density / densest;
auto const &tile{tiles[i]};
::glUniform4fv(m_uniforms.tile, 1, glm::value_ptr(tile.tile));
::glUniform4fv(m_uniforms.part, 1, glm::value_ptr(tile.part));
::glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, tile.segments * 2 + 1, tile.count);
}
auto const tallest{std::max({shape[0].x, shape[1].x, shape[2].x, shape[3].x}) * 1.5f + blade_reach};
auto const tilesize{chunk.size / tiles};
bool set{false};
for (int tz = 0; tz < tiles; ++tz)
for (int tx = 0; tx < tiles; ++tx)
{
auto const &part{chunk.parts[static_cast<std::size_t>(tz) * tiles + tx]};
auto const fill{std::min(1.f, glm::dot(glm::vec4{part.most[0], part.most[1], part.most[2], part.most[3]}, share))};
if (fill <= 0.f)
continue;
// distance to the box of the tile
glm::dvec3 const low{chunk.corner.x + tx * tilesize, part.low, chunk.corner.y + tz * tilesize};
glm::dvec3 const high{low.x + tilesize, part.high + tallest, low.z + tilesize};
auto const nearest{glm::clamp(camera, low, high)};
auto const distance{static_cast<float>(glm::length(nearest - camera))};
if (distance > reach)
continue;
if (false == Camera.visible(scene::bounding_area{(low + high) * 0.5, static_cast<float>(glm::length(high - low) * 0.5)}))
continue;
auto const full{tilesize * tilesize * densest * multiplier * fill};
auto const count{std::min(max_blades, static_cast<int>(std::ceil(full * lod_share(distance, thinning, fade, reach))))};
if (count <= 0)
continue;
if (false == set)
{
// the chunk is drawn relative to its corner, the heights as they are
set = true;
Modelview(glm::translate(View, glm::vec3{glm::dvec3{chunk.corner.x - camera.x, -camera.y, chunk.corner.y - camera.z}}));
::glUniform4f(m_uniforms.chunk, chunk.size, static_cast<float>(chunk.cells), static_cast<float>(chunk.samples), 0.f);
::glUniform4f(m_uniforms.camera, static_cast<float>(camera.x - chunk.corner.x), static_cast<float>(camera.y), static_cast<float>(camera.z - chunk.corner.y), 0.f);
::glUniform4fv(m_uniforms.base, 4, glm::value_ptr(base[0]));
::glUniform4fv(m_uniforms.tip, 4, glm::value_ptr(tip[0]));
::glUniform4fv(m_uniforms.shape, 4, glm::value_ptr(shape[0]));
::glUniform4fv(m_uniforms.share, 1, glm::value_ptr(share));
::glUniform4fv(m_uniforms.dry, 4, glm::value_ptr(dry[0]));
GLuint const textures[]{chunk.heights, chunk.density, chunk.holes};
::glBindTextures(0, 3, textures);
}
// fewer segments further away
auto const segments{distance < thinning * 1.5f ? 3 : distance < fade * 0.5f ? 2 : 1};
// the number of the tile in the world, the seed of its blades
auto const seedx{static_cast<float>(std::floor(chunk.corner.x / tilesize + 0.5) + tx)};
auto const seedz{static_cast<float>(std::floor(chunk.corner.y / tilesize + 0.5) + tz)};
::glUniform4f(m_uniforms.tile, tx * tilesize, tz * tilesize, tilesize, std::max(1.f, full));
::glUniform4f(m_uniforms.part, fill, static_cast<float>(segments), seedx, seedz);
::glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, segments * 2 + 1, count);
triangles += static_cast<std::size_t>(count) * (segments * 2 - 1);
}
}
::glEnable(GL_CULL_FACE);
gl::vao::unbind();
// the units of the textures were taken behind the back of the texture cache
opengl_texture::reset_unit_cache();
return triangles;
return triangles();
}

View File

@@ -13,69 +13,44 @@ http://mozilla.org/MPL/2.0/.
#include <functional>
#include <memory>
#include <unordered_map>
#include <vector>
#include <glm/glm.hpp>
#include "rendering/renderer.h"
#include "rendering/common/grassfield.h"
#include "gl/shader.h"
#include "gl/vao.h"
class opengl_camera;
// grass of the heightmap terrain for the opengl 4.6 renderer: blades made up by the vertex shader (grass.vert) where the
// grass is painted, in the chunks near the camera.
//
// a chunk is cut into tiles; each tile visible within the reach of the grass is drawn as instances of a blade, as many as
// its densest kind and its distance ask for. a blade finds its place, its kind and its shape from its number, so the same
// blades stand at the same places in every frame. further away the blades thin out and grow wider, and fade out at the end
// of the reach. the heights, the densities and the holes of a chunk are kept in textures, made when the chunk comes near
class opengl46_grass
// grass of the heightmap terrain for the opengl 4.6 renderer (gfx::grass_field): the textures of the chunks, and the draws of
// the tiles seen, instances of a blade made up by grass.vert
class opengl46_grass : public gfx::grass_field
{
public:
opengl46_grass() = default;
~opengl46_grass();
~opengl46_grass() override;
// see gfx_renderer::Terrain_Grass()
void update(std::vector<gfx::grass_kind> const &Kinds, std::vector<gfx::grass_chunk> const &Chunks);
// draws the grass seen by the camera. View: the modelview of the pass with the camera at its origin; Modelview: hands a
// modelview to the model block of the draws which follow. returns: triangles drawn
std::size_t render(opengl_camera const &Camera, glm::mat4 const &View, std::function<void(glm::mat4 const &)> const &Modelview);
// drops everything made on the gpu, e.g. when the context goes
void clear();
bool empty() const { return m_chunks.empty(); }
static int constexpr tiles{16}; // along a side of a chunk
protected:
void gpu_upload(std::uint64_t const Id, gfx::grass_chunk const &Source) override;
void gpu_release(std::uint64_t const Id) override;
private:
struct tile
struct chunk_textures
{
float low{0.f}, high{0.f}; // of the ground
std::array<float, 4> most{}; // the highest density of each kind of the chunk within the tile, 0..1
};
struct chunk
{
std::uint64_t version{0};
glm::dvec2 corner{0.0};
float size{0.f};
int cells{0};
int samples{0};
std::array<int, 4> kinds{-1, -1, -1, -1};
GLuint heights{0}; // R32F, the grid points
GLuint density{0}; // RGBA8, the densities of the kinds
GLuint holes{0}; // R8, a single texel of 0 without holes
std::array<tile, tiles * tiles> parts;
bool listed{false}; // in the last list given
};
bool init();
void upload(chunk &Chunk, gfx::grass_chunk const &Source);
void release(chunk &Chunk);
int m_state{0}; // 0: not set up yet, 1: ready, -1: can't be used
std::unique_ptr<gl::program> m_program;
std::unique_ptr<gl::vao> m_vao; // without attributes, the blades are made of the numbers of their vertices
std::vector<gfx::grass_kind> m_kinds;
std::unordered_map<std::uint64_t, chunk> m_chunks;
std::unordered_map<std::uint64_t, chunk_textures> m_textures;
// locations of the uniforms of the program
struct locations
{

View File

@@ -10,10 +10,7 @@ http://mozilla.org/MPL/2.0/.
#include "stdafx.h"
#include "rendering/opengl46/opengl46instancecull.h"
#include "model/AnimModel.h"
#include "model/Model3d.h"
#include "model/Texture.h"
#include "rendering/frustum.h"
#include "utilities/Globals.h"
#include "utilities/utilities.h"
@@ -36,6 +33,7 @@ void opengl46_instance_cull::init(opengl46_instance_cull const *Shared)
if (Shared != nullptr)
{
m_shared = Shared->m_shared;
share_records(*Shared);
}
else
{
@@ -59,203 +57,11 @@ void opengl46_instance_cull::init(opengl46_instance_cull const *Shared)
m_output = std::make_unique<gl::ssbo>(65536 * sizeof(glm::mat4), gl::INSTANCE_OUTPUT_SSBO_BINDING);
m_commands = std::make_unique<gl::ssbo>(4096 * sizeof(gl::instance_draw_command), gl::INSTANCE_COMMAND_SSBO_BINDING);
m_readback = std::make_unique<gl::readback>(readbacksize);
m_readbackdata.resize(gl::INSTANCE_COUNTER_HEADER + READBACK_ENTRIES);
}
void opengl46_instance_cull::begin_pass(pass_setup const &Setup)
{
collect();
m_inpass = true;
m_ran = true;
m_dispatched = false;
m_setup = Setup;
m_buckets.clear();
m_joblist.clear();
m_grouplist.clear();
m_drawgroups.clear();
m_lodlist.clear();
m_groupmap.clear();
m_sleepergroupmap.clear();
m_cpucounts.clear();
m_candidates = 0;
m_entries = 0;
m_sleepertracks = 0;
m_sleepers = 0;
m_models = 0;
m_counts = {};
}
void opengl46_instance_cull::add_bucket(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area)
{
add(Key, Bucket, Area, false);
}
void opengl46_instance_cull::skip_bucket(scene::basic_cell::instance_bucket const &Bucket)
{
if (!m_inpass)
return;
++m_counts.skippedbuckets;
m_counts.skippedinstances += Bucket.records.size();
}
void opengl46_instance_cull::add_sleepers(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area)
{
add(Key, Bucket, Area, true);
}
void opengl46_instance_cull::add(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area, bool const Sleepers)
{
if (!m_inpass || Bucket.records.empty() || Key.pModel == nullptr)
return;
// range of the squared distances the instances of the bucket can be at, scaled as in the culling, with some slack for
// its single precision: the draws are made only for the lod bands within it. sleepers go by the plain distance from the
// camera of the pass
auto const &distancecamera{Sleepers ? m_setup.camera : m_setup.distancecamera};
auto const distancescale{Sleepers ? 1.0 : static_cast<double>(m_setup.distancescale)};
auto const distanceoffset{Sleepers ? 0.0 : static_cast<double>(m_setup.distanceoffset)};
auto const centerdistance{glm::length(Area.center - distancecamera)};
auto const nearest{std::max(0.0, centerdistance - Area.radius)};
auto const farthest{centerdistance + Area.radius};
auto const distancemin{static_cast<float>(0.999 * (nearest * nearest * distancescale + distanceoffset))};
auto const distancemax{static_cast<float>(1.001 * (farthest * farthest * distancescale + distanceoffset))};
auto &groupmap{Sleepers ? m_sleepergroupmap : m_groupmap};
auto lookup{groupmap.find(Key)};
if (lookup == groupmap.end())
{
// lod bounds of the model, the same set Render_Instanced() splits the distance into bands with, found once for the model
auto const lodfirst{static_cast<std::uint32_t>(m_lodlist.size())};
auto const &lodbounds{Key.pModel->lod_bounds()};
m_lodlist.insert(m_lodlist.end(), lodbounds.begin(), lodbounds.end());
auto const lodcount{static_cast<std::uint32_t>(lodbounds.size())};
group_data const group{static_cast<std::uint32_t>(m_grouplist.size()), m_entries, lodcount + 1, distancemin, distancemax};
m_grouplist.push_back({lodfirst, lodcount, m_entries, 0});
m_entries += group.entrycount;
lookup = groupmap.emplace(Key, group).first;
if (m_setup.drawing)
{
// any instance of the group serves; it's only prepared and asked for its material
TAnimModel *instance{nullptr};
for (auto *candidate : Bucket.instances)
{
if (candidate != nullptr)
{
instance = candidate;
break;
}
}
m_drawgroups.push_back({&lookup->first, instance, Sleepers, lodfirst, lodcount, group.entryfirst, distancemin, distancemax});
}
}
else
{
lookup->second.distancemin = std::min(lookup->second.distancemin, distancemin);
lookup->second.distancemax = std::max(lookup->second.distancemax, distancemax);
if (m_setup.drawing)
{
auto &drawgroup{m_drawgroups[lookup->second.index]};
drawgroup.distancemin = lookup->second.distancemin;
drawgroup.distancemax = lookup->second.distancemax;
if (drawgroup.instance == nullptr)
{
for (auto *candidate : Bucket.instances)
{
if (candidate != nullptr)
{
drawgroup.instance = candidate;
break;
}
}
}
}
}
place(Bucket);
gl::instance_cull_job job{};
job.origin = packed_vec3{Bucket.recordorigin - m_setup.camera};
job.first = Bucket.gpufirst;
job.distanceorigin = packed_vec3{Bucket.recordorigin - m_setup.distancecamera};
job.count = static_cast<std::uint32_t>(Bucket.records.size());
job.group = lookup->second.index;
job.candidatestart = m_candidates;
job.flags = (Sleepers ? gl::INSTANCE_JOB_SLEEPERS : 0u);
m_joblist.push_back(job);
m_buckets.push_back(&Bucket);
m_candidates += job.count;
if (Sleepers)
{
++m_sleepertracks;
m_sleepers += job.count;
}
else if (Key.instance != nullptr)
{
++m_models;
}
}
void opengl46_instance_cull::place(scene::basic_cell::instance_bucket const &Bucket)
{
auto &shared{*m_shared};
auto const count{static_cast<std::uint32_t>(Bucket.records.size())};
auto const current{Bucket.gpugeneration == shared.recordgeneration};
if (current && !Bucket.gpustale)
return;
if (current && count <= Bucket.gpucapacity)
{
shared.records->update(Bucket.records.data(), static_cast<GLintptr>(Bucket.gpufirst) * RECORD_SIZE, static_cast<GLsizeiptr>(count) * RECORD_SIZE);
m_counts.sentrecords += count;
Bucket.gpustale = false;
return;
}
// a new place, with some room for instances the editor adds
auto const allocate = [this, &shared](scene::basic_cell::instance_bucket const &Target) {
auto const records{static_cast<std::uint32_t>(Target.records.size())};
Target.gpufirst = shared.recordfill;
Target.gpucapacity = records + records / 4;
Target.gpugeneration = shared.recordgeneration;
Target.gpustale = false;
shared.recordfill += Target.gpucapacity;
shared.records->update(Target.records.data(), static_cast<GLintptr>(Target.gpufirst) * RECORD_SIZE, static_cast<GLsizeiptr>(records) * RECORD_SIZE);
m_counts.sentrecords += records;
};
auto const capacity{static_cast<std::uint32_t>(shared.records->size() / RECORD_SIZE)};
if (shared.recordfill + count + count / 4 > capacity)
{
// the buffer is started over, and only the buckets of this pass get places again. the places left behind by
// buckets which were made anew or are out of sight are dropped this way. the passes of a frame queue different
// buckets: when those of all of them don't fit in the buffer together, some pass would start it over in every frame,
// so a buffer started over again in the same or the next frame grows
GLsizeiptr live{count + count / 4};
for (auto const *bucket : m_buckets)
live += bucket->records.size() + bucket->records.size() / 4;
auto const again{shared.restarted && (m_setup.frame - shared.restartframe <= 1)};
if ((2 * live * RECORD_SIZE > shared.records->size()) || again)
shared.records->resize(std::max(2 * live * RECORD_SIZE, 2 * shared.records->size()));
shared.restarted = true;
shared.restartframe = m_setup.frame;
m_counts.restarted = true;
++shared.recordgeneration;
shared.recordfill = 0;
for (std::size_t job = 0; job < m_buckets.size(); ++job)
{
allocate(*m_buckets[job]);
m_joblist[job].first = m_buckets[job]->gpufirst;
}
}
allocate(Bucket);
}
void opengl46_instance_cull::dispatch(glm::mat4 const &View, occlusion_setup const *Occlusion, bool const Twophase)
{
m_occlusion = false;
m_twophase = false;
if (!m_inpass || m_joblist.empty())
if (!dispatch_begin())
return;
auto const countersize{static_cast<GLsizeiptr>((gl::INSTANCE_COUNTER_HEADER + 3 * m_entries) * sizeof(std::uint32_t))};
@@ -277,14 +83,8 @@ void opengl46_instance_cull::dispatch(glm::mat4 const &View, occlusion_setup con
bind_buffers();
// planes moved to the camera of the pass, in double precision: the cpu tests world coordinates
auto const &frustumplanes{m_setup.frustum->planes()};
glm::vec4 planes[6];
for (int plane = 0; plane < 6; ++plane)
{
auto const *source{frustumplanes[plane]};
planes[plane] = glm::vec4{source[0], source[1], source[2],
static_cast<float>(source[0] * m_setup.camera.x + source[1] * m_setup.camera.y + source[2] * m_setup.camera.z + source[3])};
}
frustum_planes(planes);
glProgramUniformMatrix4fv(*m_shared->cull_program, 0, 1, GL_FALSE, glm::value_ptr(View));
glProgramUniform4fv(*m_shared->cull_program, 1, 6, glm::value_ptr(planes[0]));
glProgramUniform4f(*m_shared->cull_program, 7, m_setup.distancescale, m_setup.distanceoffset, m_setup.distancerange, m_setup.drawrange);
@@ -328,8 +128,7 @@ void opengl46_instance_cull::dispatch(glm::mat4 const &View, occlusion_setup con
opengl_texture::reset_unit_cache();
}
m_cpucounts.assign(m_entries, 0);
m_dispatched = true;
dispatch_end();
}
void opengl46_instance_cull::bind_buffers()
@@ -401,144 +200,29 @@ void opengl46_instance_cull::bind_draw()
glBindBuffersBase(GL_SHADER_STORAGE_BUFFER, gl::INSTANCE_SSBO_BINDING, 1, &output);
}
void opengl46_instance_cull::draw_result(std::uint32_t const Draws, std::uint32_t const Prepassdraws, std::uint32_t const Calls, std::uint32_t const Prepasscalls, double const Cputime)
std::int64_t opengl46_instance_cull::record_buffer_size() const
{
m_draws = Draws;
m_prepassdraws = Prepassdraws;
m_calls = Calls;
m_prepasscalls = Prepasscalls;
m_cputime = Cputime;
return m_shared->records->size();
}
void opengl46_instance_cull::cpu_result(scene::basic_cell::instance_bucket_key const &Key, std::vector<std::uint32_t> const &Bandcounts, bool const Sleepers)
void opengl46_instance_cull::resize_record_buffer(std::int64_t const Size)
{
if (!m_dispatched)
return;
auto const &groupmap{Sleepers ? m_sleepergroupmap : m_groupmap};
auto const lookup{groupmap.find(Key)};
if (lookup == groupmap.end())
return;
auto const &group{lookup->second};
for (std::size_t band = 0; band < std::min<std::size_t>(Bandcounts.size(), group.entrycount); ++band)
m_cpucounts[group.entryfirst + band] += Bandcounts[band];
m_shared->records->resize(static_cast<GLsizeiptr>(Size));
}
void opengl46_instance_cull::cpu_instance(scene::basic_cell::instance_bucket_key const &Key, float const Distancesquared)
void opengl46_instance_cull::send_records(std::uint32_t const First, scene::basic_cell::instance_record const *Records, std::uint32_t const Count)
{
if (!m_dispatched)
return;
auto const lookup{m_groupmap.find(Key)};
if (lookup == m_groupmap.end())
return;
auto const &group{lookup->second};
auto const &lods{m_grouplist[group.index]};
// the band the culling gives the distance: the number of bounds it reached
auto const first{m_lodlist.begin() + lods.lodfirst};
auto const band{static_cast<std::uint32_t>(std::upper_bound(first, first + lods.lodcount, Distancesquared) - first)};
if (band < group.entrycount)
++m_cpucounts[group.entryfirst + band];
m_shared->records->update(Records, static_cast<GLintptr>(First) * RECORD_SIZE, static_cast<GLsizeiptr>(Count) * RECORD_SIZE);
}
void opengl46_instance_cull::end_pass()
void opengl46_instance_cull::push_counts()
{
// the counts of both phases, for the panel and the comparison
if (m_dispatched)
m_readback->push(*m_counters);
// the cpu counts are there to compare with only when the cpu drew
if (m_dispatched && !m_setup.drawing)
{
std::uint64_t total{0};
for (auto const count : m_cpucounts)
total += count;
m_cpupasses.push_back({m_setup.frame, m_cpucounts, total});
// the gpu is never this far behind
while (m_cpupasses.size() > 8)
m_cpupasses.pop_front();
}
m_lastsleepertracks = m_sleepertracks;
m_lastsleepers = m_sleepers;
m_lastmodels = m_models;
m_counts.jobs = static_cast<std::uint32_t>(m_joblist.size());
m_counts.candidates = m_candidates;
m_lastcounts = m_counts;
m_inpass = false;
m_dispatched = false;
m_readback->push(*m_counters);
}
void opengl46_instance_cull::collect()
bool opengl46_instance_cull::pop_counts(std::uint32_t *Counts)
{
if (!m_readback || !m_readback->pop(m_readbackdata.data()))
return;
auto const frame{m_readbackdata[0]};
comparison result;
result.frame = frame;
result.occluded = m_readbackdata[3];
result.gputotal = m_readbackdata[1] - std::min(m_readbackdata[1], result.occluded);
result.entries = m_readbackdata[2];
result.first = m_readbackdata[4];
while (!m_cpupasses.empty() && m_cpupasses.front().frame != frame)
m_cpupasses.pop_front();
if (m_cpupasses.empty())
{
// drawn from the gpu: nothing to compare with
m_last = result;
return;
}
auto const &cpu{m_cpupasses.front()};
result.cputotal = cpu.total;
result.compared = std::min<std::uint32_t>({result.entries, READBACK_ENTRIES, static_cast<std::uint32_t>(cpu.counts.size())});
for (std::uint32_t entry = 0; entry < result.compared; ++entry)
{
auto const gpu{m_readbackdata[gl::INSTANCE_COUNTER_HEADER + entry]};
auto const difference{gpu > cpu.counts[entry] ? gpu - cpu.counts[entry] : cpu.counts[entry] - gpu};
if (difference > 0)
{
++result.mismatched;
result.largestdifference = std::max(result.largestdifference, difference);
}
}
m_cpupasses.pop_front();
m_last = result;
++m_comparedframes;
if ((result.mismatched > 0) || (result.gputotal != result.cputotal) || (result.entries != cpu.counts.size()))
++m_mismatchedframes;
}
std::string opengl46_instance_cull::info(std::string const &Label, bool const Occlusion)
{
if (Global.gfx_gpucull == 0)
return {};
// the sleepers are among the instances counted
auto sleepers{m_lastsleepertracks > 0 ? "\n" + Label + ": " + std::to_string(m_lastsleepers) + " sleepers of " + std::to_string(m_lastsleepertracks) + " tracks queued" : std::string{}};
// and so are the models drawn on their own
if (m_lastmodels > 0)
sleepers += "\n" + Label + ": " + std::to_string(m_lastmodels) + " models drawn on their own queued";
// what the pass tested and sent to the gpu
sleepers += "\n" + Label + ": " + std::to_string(m_lastcounts.candidates) + " instances tested in " + std::to_string(m_lastcounts.jobs) + " buckets";
if (Global.gfx_gpucull_bucketrange)
sleepers += ", " + std::to_string(m_lastcounts.skippedbuckets) + " buckets (" + std::to_string(m_lastcounts.skippedinstances) + " instances) left out before the culling";
sleepers += ", " + std::to_string(m_lastcounts.sentrecords) + " records sent" + (m_lastcounts.restarted ? ", record buffer started over" : "");
if (Global.gfx_gpucull == 1)
{
if (m_last.frame == 0)
return "\n" + Label + ": no result yet" + sleepers;
if (false == Occlusion)
return "\n" + Label + ": " + std::to_string(m_last.gputotal) + " instances drawn, " + std::to_string(m_last.entries) + " bands; " + std::to_string(m_draws) +
" indirect draws in " + std::to_string(m_calls) + " calls, cpu " + to_string(m_cputime, 3) + " ms" + sleepers;
return "\n" + Label + ": " + std::to_string(m_last.gputotal) + " instances drawn" +
(m_twophase ? " (" + std::to_string(m_last.first) + " of them first, seen in the last frame)" : std::string{}) + ", " + std::to_string(m_last.occluded) +
" hidden by occlusion" + (m_occlusion ? (m_twophase ? " (also by one another)" : "") : " (test off)") + ", " + std::to_string(m_last.entries) + " bands; " +
std::to_string(m_draws) + " indirect draws in " + std::to_string(m_calls) + " calls (+" + std::to_string(m_prepassdraws) + " depth prepass in " +
std::to_string(m_prepasscalls) + "), cpu " + to_string(m_cputime, 3) + " ms" + sleepers;
}
if (m_comparedframes == 0)
return "\n" + Label + ": no result yet" + sleepers;
return "\n" + Label + ": " + std::to_string(m_last.gputotal) + " instances (cpu " + std::to_string(m_last.cputotal) + "), " + std::to_string(m_last.entries) + " bands, " +
std::to_string(m_last.mismatched) + " differ (by up to " + std::to_string(m_last.largestdifference) + "); frames differing " + std::to_string(m_mismatchedframes) + " of " +
std::to_string(m_comparedframes) + sleepers;
return (m_readback && m_readback->pop(Counts));
}
std::string opengl46_instance_cull::info_times(std::string const &Label)

View File

@@ -21,53 +21,15 @@ http://mozilla.org/MPL/2.0/.
#include "gl/query.h"
#include "gl/ubo.h"
#include "scene/scene.h"
#include "rendering/common/instancecull.h"
class cFrustum;
class TAnimModel;
// culling of the model instances of a render pass in compute passes (instance_cull.comp, instance_cull_offsets.comp),
// the gpu side of opengl46_renderer::Queue_Instances() and Render_Instanced():
// - the records of the instances (scene::basic_cell::instance_record) are kept in one buffer, each bucket of a scene cell
// in its own place, and sent again only when the bucket made them anew
// - every bucket queued in the pass becomes a job; the buckets of one model and one set of skins make a group, whose
// lod bands are its entries
// - the passes count the instances which pass per entry, turn the counts into places in the output and write the
// modelview matrices of the instances there
// with Global.gfx_gpucull 1 the instances are drawn from the result: the renderer turns each lod band of a group into a
// list of indirect draw commands (one per submodel drawn in the band), whose instances a fourth pass fills in from the
// counts (instance_cull_commands.comp), and draws them with the output bound as the instance buffer
// (opengl46_renderer::Render_Instances_Indirect()). an occlusion test against a depth pyramid of what the pass drew before
// (opengl46_hiz) can leave out more instances. in two phases the instances seen in the previous frame are culled and drawn
// first, then the pyramid is made again with them in it (dispatch_second()) and the others are tested against it, so the
// instances hide one another too. the command buffer holds the commands three times over: the instances drawn first, all
// of them, and the ones drawn second, each copy command_count() commands long (draw_command_offset())
// with Global.gfx_gpucull 2 the cpu still draws, and the result is only compared with its own: the counts per entry are
// read back a few frames later and set against the counts Render_Instanced() reported for the same frame
// each render pass culled on the gpu (the main view, the cascades of the sun shadows, the faces of the environment map, the
// shadow maps of headlights and the scenery parts of those of lamps) has a culling of its own, with its own buffers, while the
// records and the programs are shared by all of them: the records of a bucket go to the gpu once and serve every pass which
// queues the bucket
// the sleepers of tracks (TTrack::m_sleeper_local_transforms) are culled the same way, each track with them a bucket of its
// own, and drawn together with the instances; their groups are kept apart from those of the model instances
// so are the model instances drawn on their own (not instanceable): each one a bucket and a group of its own (the instance in
// its key), whose submodel tree the renderer animates and walks for it alone
class opengl46_instance_cull
// culling of the model instances of a render pass in compute passes (instance_cull.comp, instance_cull_offsets.comp,
// instance_cull_commands.comp): the buffers and the passes of gfx::instance_cull, which tells what they do. with
// Global.gfx_gpucull 1 the renderer draws the result with opengl46_renderer::Render_Instances_Indirect(); the occlusion test
// takes the depth pyramid of opengl46_hiz
class opengl46_instance_cull : public gfx::instance_cull
{
public:
// what a pass needs for the tests, the way Render_Instanced() takes it
struct pass_setup
{
unsigned int frame{0};
glm::dvec3 camera{0.0}; // camera of the pass: frustum and placement
glm::dvec3 distancecamera{0.0}; // the distance is measured from it
cFrustum const *frustum{nullptr}; // of the pass camera, in world coordinates
float distancescale{1.f}; // of the squared distance
float distanceoffset{0.f}; // added to the scaled squared distance past the range test
float distancerange{0.f}; // squared range of the scaled distance before the offset
float drawrange{0.f}; // squared draw range
float zoom{1.f};
bool drawing{false}; // the instances are drawn from the result (Global.gfx_gpucull 1), not compared
};
// occlusion test of a pass
struct occlusion_setup
{
@@ -77,47 +39,11 @@ class opengl46_instance_cull
glm::vec2 screen{0.f}; // size of the depth it was made from, pixels
glm::mat4 projection{1.f}; // of the pass
};
// a group of instances (model and skins) of the pass, for the renderer to make the draw commands of
struct draw_group
{
scene::basic_cell::instance_bucket_key const *key;
TAnimModel *instance; // one of the group, prepares the model and gives the material. none for sleepers
bool sleepers; // the skins come from the key, as Render_Sleepers() sets them
std::uint32_t lodfirst; // in lod_bounds()
std::uint32_t lodcount;
std::uint32_t entryfirst;
float distancemin; // squared distances (scaled as the culling does) the instances of the group can be at
float distancemax;
};
opengl46_instance_cull() = default;
opengl46_instance_cull(const opengl46_instance_cull &) = delete;
opengl46_instance_cull &operator=(const opengl46_instance_cull &) = delete;
// creates the buffers, and the programs and the record buffer, or takes the ones of Shared. throws gl::shader_exception
void init(opengl46_instance_cull const *Shared = nullptr);
// starts collecting the buckets of a pass
void begin_pass(pass_setup const &Setup);
bool in_pass() const
{
return m_inpass;
}
bool drawing() const
{
return m_inpass && m_setup.drawing;
}
// adds the records of a bucket (made already) to the pass, sending them to the gpu when they changed.
// Area: bounds which hold the locations of all its visible instances: their own (opengl46_renderer::Bucket_Area()) or of the cell
// holding the bucket
void add_bucket(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area);
// adds the sleepers of a track, their records made already as a bucket with no instances (only the records and the place
// in the record buffer count). Area: bounds of the locations of the sleepers. the range of the whole track is tested by
// the caller, the culling tests each sleeper against the frustum and gives it the lod band of its plain distance from the
// camera of the pass (Render_Sleepers())
void add_sleepers(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area);
// a bucket the renderer left out of the pass, as none of its instances can pass the culling (Global.gfx_gpucull_bucketrange): only counted,
// for the panel
void skip_bucket(scene::basic_cell::instance_bucket const &Bucket);
// runs the culling passes for the buckets added. View: rotation of the view of the pass. Occlusion: the depth pyramid
// to test the instances against, or null. Twophase: with the occlusion test, only the instances seen in the previous
// frame pass for now, dispatch_second() takes the others
@@ -126,108 +52,22 @@ class opengl46_instance_cull
// the others are tested against it, then the commands of all instances and of the ones of this phase are filled in.
// needs fill_commands() before it
void dispatch_second(occlusion_setup const &Occlusion);
// the last dispatch() works in two phases
bool two_phase() const
{
return m_twophase;
}
// groups of the pass and the lod bounds they refer to, valid from dispatch() to end_pass()
std::vector<draw_group> const &draw_groups() const
{
return m_drawgroups;
}
std::vector<float> const &lod_bounds() const
{
return m_lodlist;
}
// sends the indirect draw commands made for the entries of the pass and fills in their instances (of the instances drawn
// first, in two phases). after this the commands are in the buffer bound by bind_draw(), the modelview matrices of the
// instances in the output
void fill_commands(std::vector<gl::instance_draw_command> const &Commands);
// copies of the commands in the command buffer
enum class command_set : std::uint32_t
{
first = 0, // instances which passed dispatch(): in one phase all of them
all = 1, // after dispatch_second()
second = 2 // the ones which passed dispatch_second()
};
// offset of command Index of a copy in the command buffer
std::intptr_t draw_command_offset(command_set const Set, std::size_t const Index) const
{
return static_cast<std::intptr_t>((static_cast<std::size_t>(Set) * m_commandcount + Index) * sizeof(gl::instance_draw_command));
}
// binds the commands to GL_DRAW_INDIRECT_BUFFER and the output in place of the instance buffer of the frame
void bind_draw();
// counts of the last result which came back from the gpu, with the draws of the pass and the gl calls which made them (fewer
// with multi draws), for the debug panel
void draw_result(std::uint32_t const Draws, std::uint32_t const Prepassdraws, std::uint32_t const Calls, std::uint32_t const Prepasscalls, double const Cputime);
// what draw_result() was given last
std::uint32_t prepass_draws() const
{
return m_prepassdraws;
}
std::uint32_t prepass_calls() const
{
return m_prepasscalls;
}
double cpu_time() const
{
return m_cputime;
}
// instances which passed in the last result which came back from the gpu, a few frames old
std::uint32_t last_count() const
{
return static_cast<std::uint32_t>(m_last.gputotal);
}
// counts per lod band of the instances Render_Instanced() drew for a model and its skins in this pass, or of the sleepers
// Render_Sleepers() drew. needs dispatch() before it
void cpu_result(scene::basic_cell::instance_bucket_key const &Key, std::vector<std::uint32_t> const &Bandcounts, bool const Sleepers = false);
// the cpu drew the instance of a group of its own (Key with the instance) at a squared distance scaled as the culling
// scales it: counted in the lod band the distance falls in. needs dispatch() before it
void cpu_instance(scene::basic_cell::instance_bucket_key const &Key, float const Distancesquared);
// ends the pass: its cpu counts are kept for the comparison
void end_pass();
// takes the newest counts of the gpu which arrived and compares them with the ones of the cpu. done when a pass begins;
// for the panel also by the culls of passes which aren't made every frame
void collect();
// a pass was culled since the start
bool ran() const
{
return m_ran;
}
// lines for the debug panel, Label naming the pass. Occlusion: the pass may test occlusion (the main view)
std::string info(std::string const &Label = "gpu cull", bool const Occlusion = true);
std::string info_times(std::string const &Label = "instance cull");
private:
struct group_data
{
std::uint32_t index;
std::uint32_t entryfirst;
std::uint32_t entrycount;
float distancemin;
float distancemax;
};
// counts of the cpu of a pass, waiting for those of the gpu
struct cpu_pass
{
unsigned int frame;
std::vector<std::uint32_t> counts;
std::uint64_t total;
};
struct comparison
{
unsigned int frame{0};
std::uint64_t gputotal{0};
std::uint64_t cputotal{0};
std::uint32_t entries{0};
std::uint32_t compared{0}; // entries compared, at most READBACK_ENTRIES
std::uint32_t mismatched{0};
std::uint32_t largestdifference{0};
std::uint32_t occluded{0}; // left out by the occlusion test
std::uint32_t first{0}; // drawn first, seen in the previous frame (two phases)
};
protected:
std::int64_t record_buffer_size() const override;
void resize_record_buffer(std::int64_t const Size) override;
void send_records(std::uint32_t const First, scene::basic_cell::instance_record const *Records, std::uint32_t const Count) override;
void push_counts() override;
bool pop_counts(std::uint32_t *Counts) override;
private:
// what the culls of all passes share
struct shared_data
{
@@ -235,26 +75,11 @@ class opengl46_instance_cull
std::unique_ptr<gl::program> offsets_program;
std::unique_ptr<gl::program> commands_program;
std::unique_ptr<gl::ssbo> records;
// record buffer: records placed so far, the generation which tells current places from the ones of a replaced buffer
std::uint32_t recordfill{0};
unsigned int recordgeneration{1};
// frame of the last time the buffer was started over, and whether it was
unsigned int restartframe{0};
bool restarted{false};
};
using group_map = std::unordered_map<scene::basic_cell::instance_bucket_key, group_data, scene::basic_cell::instance_bucket_key_hash>;
// add_bucket() and add_sleepers()
void add(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area, bool const Sleepers);
// finds a place for the records of a bucket in the record buffer and sends them
void place(scene::basic_cell::instance_bucket const &Bucket);
// binds the buffers of the culling passes
void bind_buffers();
// entries read back for the comparison; with more of them only the totals and these are compared
static constexpr std::uint32_t READBACK_ENTRIES{16384};
std::shared_ptr<shared_data> m_shared;
std::unique_ptr<gl::ssbo> m_jobs;
std::unique_ptr<gl::ssbo> m_groups;
@@ -265,55 +90,4 @@ class opengl46_instance_cull
std::unique_ptr<gl::readback> m_readback;
gl::gpu_timer m_timer;
gl::gpu_timer m_secondtimer;
bool m_inpass{false};
bool m_ran{false};
pass_setup m_setup;
std::vector<scene::basic_cell::instance_bucket const *> m_buckets; // of the jobs, in their order
std::vector<gl::instance_cull_job> m_joblist;
std::vector<gl::instance_cull_group> m_grouplist;
std::vector<draw_group> m_drawgroups;
std::vector<float> m_lodlist;
group_map m_groupmap;
group_map m_sleepergroupmap; // groups of the sleepers
std::uint32_t m_candidates{0};
std::uint32_t m_entries{0};
// tracks with sleepers and their sleepers queued in the pass, and in the last pass, for the panel
std::uint32_t m_sleepertracks{0};
std::uint32_t m_sleepers{0};
std::uint32_t m_lastsleepertracks{0};
std::uint32_t m_lastsleepers{0};
// model instances drawn on their own queued in the pass, and in the last pass, for the panel
std::uint32_t m_models{0};
std::uint32_t m_lastmodels{0};
// what the pass held and sent, and the same of the last pass, for the panel: jobs (buckets queued), buckets left out beyond
// their range and their instances, records sent to the gpu, the record buffer started over
struct pass_counts
{
std::uint32_t jobs{0};
std::uint32_t candidates{0};
std::uint32_t skippedbuckets{0};
std::uint64_t skippedinstances{0};
std::uint64_t sentrecords{0};
bool restarted{false};
};
pass_counts m_counts;
pass_counts m_lastcounts;
bool m_dispatched{false};
std::vector<std::uint32_t> m_cpucounts; // of the current pass, by entry
std::deque<cpu_pass> m_cpupasses;
std::vector<std::uint32_t> m_readbackdata;
comparison m_last;
std::uint64_t m_comparedframes{0};
std::uint64_t m_mismatchedframes{0};
// draws of the last pass drawn from the gpu
std::uint32_t m_draws{0};
std::uint32_t m_prepassdraws{0};
std::uint32_t m_calls{0};
std::uint32_t m_prepasscalls{0};
std::uint32_t m_commandcount{0};
double m_cputime{0.0};
bool m_occlusion{false}; // the last dispatch tested occlusion
bool m_twophase{false}; // in two phases
};

View File

@@ -19,17 +19,13 @@ http://mozilla.org/MPL/2.0/.
namespace
{
// near plane of the maps, metres. surfaces closer to the lamp (its own fitting) cast no shadow
float const SHADOW_NEAR{0.15f};
// near plane of the maps (gfx::local_shadows)
float const SHADOW_NEAR{gfx::local_shadows::shadow_near};
// uniform locations of shadowcube.geom
GLint const NEAR_LOCATION{0};
GLint const LAYER_LOCATION{1};
// layer of the map of the cab lamp in the array
int const CAB_SLOT{0};
// a lamp which moved farther than this (an animated submodel) has its scenery drawn anew, metres
double const LAMP_MOVED{0.01};
// lamps with a shorter reach aren't worth a map, metres
float const LAMP_RANGE_MIN{1.f};
int const CAB_SLOT{gfx::local_shadows::cab_slot};
// cube map array of depth maps compared to the reference, one level, Cubes cubes of Size x Size faces
GLuint make_cube_array(int const Size, int const Cubes)
@@ -99,9 +95,9 @@ void setup_drawing(GLuint const Framebuffer, int const Size)
opengl46_local_shadows::~opengl46_local_shadows()
{
release();
release_lamps();
release_headlights();
gpu_release();
gpu_release_lamps();
gpu_release_headlights();
if (m_empty != 0)
glDeleteTextures(1, &m_empty);
if (m_emptyheadlights != 0)
@@ -167,40 +163,14 @@ void opengl46_local_shadows::set_layer(int const Layer)
glProgramUniform1i(*m_alpha_multidraw_program, LAYER_LOCATION, Layer);
}
opengl46_local_shadows::level_setup opengl46_local_shadows::setup(int const Level)
bool opengl46_local_shadows::gpu_allocate(int const Size)
{
switch (Level)
{
case 1:
return {512, 4, 0.f, 1, 1024, 2, 8};
case 2:
return {1024, 2, 1.f, 1, 1024, 1, 4};
case 3:
return {1024, 1, 2.f, 2, 2048, 1, 2};
default:
return {0, 0, 0.f, 0, 0, 0, 0};
}
}
void opengl46_local_shadows::allocate(int const Size)
{
release();
if (Size <= 0)
return;
m_texture = make_cube_array(Size, CAB_SLOT + 1);
m_framebuffer = make_framebuffer(m_texture);
if (m_framebuffer == 0)
{
ErrorLog("lamp shadow map framebuffer setup failed, the lamps have no shadows");
release();
Global.gfx_lights_shadows = 0;
return;
}
m_size = Size;
WriteLog("lamp shadow maps: " + std::to_string(Size) + " x " + std::to_string(Size) + " cube");
return (m_framebuffer != 0);
}
void opengl46_local_shadows::release()
void opengl46_local_shadows::gpu_release()
{
if (m_framebuffer != 0)
glDeleteFramebuffers(1, &m_framebuffer);
@@ -208,32 +178,17 @@ void opengl46_local_shadows::release()
glDeleteTextures(1, &m_texture);
m_framebuffer = 0;
m_texture = 0;
m_size = 0;
m_valid = false;
}
void opengl46_local_shadows::allocate_lamps(int const Count, int const Size)
bool opengl46_local_shadows::gpu_allocate_lamps(int const Count, int const Size)
{
release_lamps();
if ((Count <= 0) || (Size <= 0))
return;
// the scenery parts and the working copies
m_lampstexture = make_cube_array(Size, 2 * Count);
m_lampsframebuffer = make_framebuffer(m_lampstexture);
if (m_lampsframebuffer == 0)
{
ErrorLog("scenery lamp shadow map framebuffer setup failed, the scenery lamps have no shadows");
release_lamps();
Global.gfx_lights_shadows_lamps = 0;
return;
}
m_lampssize = Size;
m_slots.assign(static_cast<std::size_t>(Count), lamp_slot{});
WriteLog("scenery lamp shadow maps: " + std::to_string(Count) + " x " + std::to_string(Size) + " x " + std::to_string(Size) + " cube (" +
std::to_string(2 * Count * 6 * Size / 1024 * Size * 4 / 1024) + " MB)");
return (m_lampsframebuffer != 0);
}
void opengl46_local_shadows::release_lamps()
void opengl46_local_shadows::gpu_release_lamps()
{
if (m_lampsframebuffer != 0)
glDeleteFramebuffers(1, &m_lampsframebuffer);
@@ -241,35 +196,19 @@ void opengl46_local_shadows::release_lamps()
glDeleteTextures(1, &m_lampstexture);
m_lampsframebuffer = 0;
m_lampstexture = 0;
m_lampssize = 0;
m_slots.clear();
m_wanted.clear();
}
void opengl46_local_shadows::allocate_headlights(int const Count, int const Size)
bool opengl46_local_shadows::gpu_allocate_headlights(int const Count, int const Size)
{
release_headlights();
if ((Count <= 0) || (Size <= 0))
return;
m_headlightstexture = make_array(Size, Count);
glCreateFramebuffers(1, &m_headlightsframebuffer);
glNamedFramebufferTextureLayer(m_headlightsframebuffer, GL_DEPTH_ATTACHMENT, m_headlightstexture, 0, 0);
glNamedFramebufferDrawBuffer(m_headlightsframebuffer, GL_NONE);
glNamedFramebufferReadBuffer(m_headlightsframebuffer, GL_NONE);
if (glCheckNamedFramebufferStatus(m_headlightsframebuffer, GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
{
ErrorLog("headlight shadow map framebuffer setup failed, the headlights have no shadows");
release_headlights();
Global.gfx_lights_shadows_headlights = 0;
return;
}
m_headlightssize = Size;
m_headlightslots.assign(static_cast<std::size_t>(Count), headlight_slot{});
WriteLog("headlight shadow maps: " + std::to_string(Count) + " x " + std::to_string(Size) + " x " + std::to_string(Size) + " (" +
std::to_string(Count * Size / 1024 * Size * 4 / 1024) + " MB)");
return (glCheckNamedFramebufferStatus(m_headlightsframebuffer, GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
}
void opengl46_local_shadows::release_headlights()
void opengl46_local_shadows::gpu_release_headlights()
{
if (m_headlightsframebuffer != 0)
glDeleteFramebuffers(1, &m_headlightsframebuffer);
@@ -277,9 +216,6 @@ void opengl46_local_shadows::release_headlights()
glDeleteTextures(1, &m_headlightstexture);
m_headlightsframebuffer = 0;
m_headlightstexture = 0;
m_headlightssize = 0;
m_headlightslots.clear();
m_headlightswanted.clear();
}
void opengl46_local_shadows::bind()
@@ -289,42 +225,8 @@ void opengl46_local_shadows::bind()
glBindTextureUnit(gl::HEADLIGHT_SHADOW_TEX, (m_headlightstexture != 0) ? m_headlightstexture : m_emptyheadlights);
}
bool opengl46_local_shadows::update_wanted(TDynamicObject const *Vehicle, TModel3d const *Cab, glm::vec3 const &Lamp, unsigned int const Framestamp)
void opengl46_local_shadows::gpu_begin_cab()
{
auto const level{std::clamp(Global.gfx_lights_shadows, 0, 3)};
if (level != m_level)
{
m_level = level;
auto const size{setup(level).size};
if (size != m_size)
allocate(size);
m_valid = false;
}
if ((m_size == 0) || (Vehicle == nullptr) || (Cab == nullptr))
{
// with the lamp off nothing reads the map; once it is on again the cab may have changed
m_valid = false;
return false;
}
if ((false == m_valid) || (Vehicle != m_vehicle) || (Cab != m_cab) || (Lamp != m_lamp))
{
m_valid = false;
}
// the moving parts of the cab are followed every few frames
if (m_valid && (Framestamp - m_drawnframe < setup(level).interval))
{
return false;
}
m_vehicle = Vehicle;
m_cab = Cab;
m_lamp = Lamp;
m_drawnframe = Framestamp;
return true;
}
void opengl46_local_shadows::begin()
{
m_cpustart = std::chrono::steady_clock::now();
m_timer.begin();
set_layer(CAB_SLOT * 6);
setup_drawing(m_framebuffer, m_size);
@@ -332,308 +234,46 @@ void opengl46_local_shadows::begin()
glClearNamedFramebufferfv(m_framebuffer, GL_DEPTH, 0, &farthest);
}
void opengl46_local_shadows::end(std::uint32_t const Drawcalls)
void opengl46_local_shadows::gpu_end_cab()
{
glEnable(GL_CULL_FACE);
gl::framebuffer::unbind();
m_timer.end();
m_valid = true;
m_drawcalls = Drawcalls;
++m_updatecount;
auto const elapsed{std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - m_cpustart).count()};
m_cputime = (m_cputime == 0.0 ? elapsed : m_cputime * 0.9 + elapsed * 0.1);
}
glm::vec4 opengl46_local_shadows::weights() const
void opengl46_local_shadows::gpu_begin_lamp(int const Scenerycube, int const Workingcube, bool const Vehicles)
{
// a face spans twice the distance from the lamp
return glm::vec4{static_cast<float>(CAB_SLOT), SHADOW_NEAR, 2.f / static_cast<float>(std::max(m_size, 1)), setup(m_level).radius};
}
void opengl46_local_shadows::update_lamps(std::vector<gfx::local_lights::light> const &Lights, glm::dvec3 const &Camera, std::uint64_t const Version, unsigned int const Framestamp)
{
m_frame = Framestamp;
m_wanted.clear();
auto const level{std::clamp(Global.gfx_lights_shadows, 0, 3)};
auto const count{level > 0 ? Global.gfx_lights_shadows_lamps : 0};
auto const size{count > 0 ? Global.gfx_lights_shadows_lamps_size : 0};
if ((static_cast<std::size_t>(count) != m_slots.size()) || (size != m_lampssize))
{
allocate_lamps(count, size);
}
if (m_slots.empty())
{
return;
}
m_sceneryleft = setup(level).sceneryperframe;
auto const reach{static_cast<double>(Global.gfx_lights_shadows_lamps_range)};
// the lights come the most important first: the first ones within reach get the slots
std::vector<gfx::local_lights::light const *> picked;
for (auto const &light : Lights)
{
if (picked.size() >= m_slots.size())
{
break;
}
if ((light.owner == nullptr) || (light.range < LAMP_RANGE_MIN) || (glm::length(light.position - Camera) > reach))
{
continue;
}
picked.emplace_back(&light);
}
// lamps which already have a slot keep it
std::vector<gfx::local_lights::light const *> unplaced;
for (auto const *light : picked)
{
auto placed{false};
for (std::size_t idx = 0; idx < m_slots.size(); ++idx)
{
auto &slot{m_slots[idx]};
if ((slot.owner == light->owner) && (slot.part == light->part) && (slot.wantedframe != Framestamp))
{
if (glm::length(slot.position - light->position) > LAMP_MOVED)
{
slot.scenery = false;
slot.vehicles = false;
}
slot.position = light->position;
slot.range = light->range;
slot.wantedframe = Framestamp;
m_wanted.emplace_back(idx);
placed = true;
break;
}
}
if (false == placed)
{
unplaced.emplace_back(light);
}
}
// the others take the slots of lamps no longer picked
for (auto const *light : unplaced)
{
for (std::size_t idx = 0; idx < m_slots.size(); ++idx)
{
auto &slot{m_slots[idx]};
if (slot.wantedframe == Framestamp)
{
continue;
}
slot = lamp_slot{};
slot.owner = light->owner;
slot.part = light->part;
slot.position = light->position;
slot.range = light->range;
slot.wantedframe = Framestamp;
m_wanted.emplace_back(idx);
break;
}
}
// the scene changed (events, the editor): the scenery parts are out of date
for (auto &slot : m_slots)
{
if (slot.scenery && (slot.version != Version))
{
slot.scenery = false;
slot.vehicles = false;
}
}
}
void opengl46_local_shadows::reset_lamps()
{
for (auto &slot : m_slots)
{
slot = lamp_slot{};
}
m_wanted.clear();
}
bool opengl46_local_shadows::scenery_wanted(std::size_t const Slot)
{
if (m_slots[Slot].scenery || (m_sceneryleft <= 0))
{
return false;
}
--m_sceneryleft;
return true;
}
bool opengl46_local_shadows::vehicles_wanted(std::size_t const Slot, unsigned int const Framestamp) const
{
auto const &slot{m_slots[Slot]};
return slot.scenery && ((false == slot.vehicles) || (Framestamp - slot.vehiclesframe >= setup(m_level).interval));
}
void opengl46_local_shadows::begin_lamp(std::size_t const Slot, bool const Vehicles, std::uint64_t const Version, unsigned int const Framestamp)
{
auto &slot{m_slots[Slot]};
auto const scenerycube{static_cast<int>(Slot)};
auto const workingcube{static_cast<int>(m_slots.size() + Slot)};
if (Vehicles)
{
// the vehicles go over the scenery
glCopyImageSubData(m_lampstexture, GL_TEXTURE_CUBE_MAP_ARRAY, 0, 0, 0, scenerycube * 6, m_lampstexture, GL_TEXTURE_CUBE_MAP_ARRAY, 0, 0, 0, workingcube * 6, m_lampssize, m_lampssize, 6);
slot.vehicles = true;
slot.vehiclesframe = Framestamp;
++m_lampsvehicles;
glCopyImageSubData(m_lampstexture, GL_TEXTURE_CUBE_MAP_ARRAY, 0, 0, 0, Scenerycube * 6, m_lampstexture, GL_TEXTURE_CUBE_MAP_ARRAY, 0, 0, 0, Workingcube * 6, m_lampssize, m_lampssize, 6);
}
else
{
float const farthest{0.f};
glClearTexSubImage(m_lampstexture, 0, 0, 0, scenerycube * 6, m_lampssize, m_lampssize, 6, GL_DEPTH_COMPONENT, GL_FLOAT, &farthest);
slot.scenery = true;
slot.version = Version;
slot.vehicles = false;
++m_lampsscenery;
glClearTexSubImage(m_lampstexture, 0, 0, 0, Scenerycube * 6, m_lampssize, m_lampssize, 6, GL_DEPTH_COMPONENT, GL_FLOAT, &farthest);
}
set_layer((Vehicles ? workingcube : scenerycube) * 6);
set_layer((Vehicles ? Workingcube : Scenerycube) * 6);
setup_drawing(m_lampsframebuffer, m_lampssize);
}
void opengl46_local_shadows::end_lamp(std::uint32_t const Drawcalls)
void opengl46_local_shadows::gpu_end_lamp()
{
glEnable(GL_CULL_FACE);
gl::framebuffer::unbind();
set_layer(CAB_SLOT * 6);
m_lampsframedrawcalls += Drawcalls;
}
void opengl46_local_shadows::lamps_timer_begin()
void opengl46_local_shadows::gpu_lamps_timer(bool const Begin)
{
m_lampsframedrawcalls = 0;
m_cpustart = std::chrono::steady_clock::now();
m_lampstimer.begin();
m_lampstiming = true;
if (Begin)
m_lampstimer.begin();
else
m_lampstimer.end();
}
void opengl46_local_shadows::lamps_timer_end()
void opengl46_local_shadows::gpu_begin_headlight(std::size_t const Slot)
{
if (false == m_lampstiming)
{
return;
}
m_lampstimer.end();
m_lampstiming = false;
m_lampsdrawcalls = m_lampsframedrawcalls;
auto const elapsed{std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - m_cpustart).count()};
m_lampscputime = (m_lampscputime == 0.0 ? elapsed : m_lampscputime * 0.9 + elapsed * 0.1);
}
opengl46_local_shadows::lamp_slot const *opengl46_local_shadows::lamp(gfx::local_lights::light const &Light, std::size_t &Slot) const
{
if (Light.owner == nullptr)
{
return nullptr;
}
for (auto const idx : m_wanted)
{
auto const &slot{m_slots[idx]};
if ((slot.owner == Light.owner) && (slot.part == Light.part))
{
if (false == slot.scenery)
{
return nullptr;
}
Slot = idx;
return &slot;
}
}
return nullptr;
}
glm::vec4 opengl46_local_shadows::lamp_weights(std::size_t const Slot) const
{
auto const cube{m_slots[Slot].vehicles ? m_slots.size() + Slot : Slot};
return glm::vec4{static_cast<float>(cube), SHADOW_NEAR, 2.f / static_cast<float>(std::max(m_lampssize, 1)), setup(m_level).radius};
}
void opengl46_local_shadows::update_headlights(std::vector<light_array::light_record const *> const &Lights, unsigned int const Framestamp)
{
m_headlightswanted.clear();
auto const level{std::clamp(Global.gfx_lights_shadows, 0, 3)};
auto const count{level > 0 ? std::clamp(Global.gfx_lights_shadows_headlights, 0, static_cast<int>(gl::MAX_HEADLIGHT_SHADOWS)) : 0};
auto const size{count > 0 ? setup(level).headlightsize : 0};
if ((static_cast<std::size_t>(count) != m_headlightslots.size()) || (size != m_headlightssize))
{
allocate_headlights(count, size);
}
if (m_headlightslots.empty())
{
return;
}
// sets which already have a slot keep it
std::vector<light_array::light_record const *> unplaced;
for (auto const *light : Lights)
{
if (m_headlightswanted.size() + unplaced.size() >= m_headlightslots.size())
{
break;
}
auto placed{false};
for (std::size_t idx = 0; idx < m_headlightslots.size(); ++idx)
{
auto &slot{m_headlightslots[idx]};
if ((slot.owner == light->owner) && (slot.index == light->index) && (slot.wantedframe != Framestamp))
{
slot.wantedframe = Framestamp;
m_headlightswanted.emplace_back(idx);
placed = true;
break;
}
}
if (false == placed)
{
unplaced.emplace_back(light);
}
}
// the others take the slots of sets no longer picked
for (auto const *light : unplaced)
{
for (std::size_t idx = 0; idx < m_headlightslots.size(); ++idx)
{
auto &slot{m_headlightslots[idx]};
if (slot.wantedframe == Framestamp)
{
continue;
}
slot = headlight_slot{};
slot.owner = light->owner;
slot.index = light->index;
slot.wantedframe = Framestamp;
m_headlightswanted.emplace_back(idx);
break;
}
}
}
void opengl46_local_shadows::reset_headlights()
{
for (auto &slot : m_headlightslots)
{
slot = headlight_slot{};
}
m_headlightswanted.clear();
}
bool opengl46_local_shadows::headlight_wanted(std::size_t const Slot, bool const Moved, unsigned int const Framestamp) const
{
auto const &slot{m_headlightslots[Slot]};
auto const level{setup(m_level)};
return (false == slot.drawn) || (Framestamp - slot.drawnframe >= (Moved ? level.headlightmoving : level.headlightstill));
}
void opengl46_local_shadows::begin_headlight(std::size_t const Slot, glm::dvec3 const &Projector, glm::mat3 const &Rotation, glm::mat4 const &Projection, glm::vec3 const &Direction, unsigned int const Framestamp)
{
auto &slot{m_headlightslots[Slot]};
slot.drawn = true;
slot.drawnframe = Framestamp;
slot.projector = Projector;
slot.rotation = Rotation;
slot.projection = Projection;
slot.direction = Direction;
++m_headlightsdrawn;
gl::framebuffer::unbind();
glNamedFramebufferTextureLayer(m_headlightsframebuffer, GL_DEPTH_ATTACHMENT, m_headlightstexture, 0, static_cast<GLint>(Slot));
glBindFramebuffer(GL_FRAMEBUFFER, m_headlightsframebuffer);
@@ -649,51 +289,17 @@ void opengl46_local_shadows::begin_headlight(std::size_t const Slot, glm::dvec3
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}
void opengl46_local_shadows::end_headlight(std::uint32_t const Drawcalls)
void opengl46_local_shadows::gpu_end_headlight()
{
gl::framebuffer::unbind();
m_headlightsframedrawcalls += Drawcalls;
}
void opengl46_local_shadows::headlights_timer_begin()
void opengl46_local_shadows::gpu_headlights_timer(bool const Begin)
{
m_headlightsframedrawcalls = 0;
m_headlightscpustart = std::chrono::steady_clock::now();
m_headlightstimer.begin();
m_headlightstiming = true;
}
void opengl46_local_shadows::headlights_timer_end()
{
if (false == m_headlightstiming)
{
return;
}
m_headlightstimer.end();
m_headlightstiming = false;
m_headlightsdrawcalls = m_headlightsframedrawcalls;
auto const elapsed{std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - m_headlightscpustart).count()};
m_headlightscputime = (m_headlightscputime == 0.0 ? elapsed : m_headlightscputime * 0.9 + elapsed * 0.1);
}
int opengl46_local_shadows::headlight_map(TDynamicObject const *Owner, int const Index) const
{
for (auto const idx : m_headlightswanted)
{
auto const &slot{m_headlightslots[idx]};
if ((slot.owner == Owner) && (slot.index == Index))
{
return (slot.drawn ? static_cast<int>(idx) : -1);
}
}
return -1;
}
glm::vec4 opengl46_local_shadows::headlight_params(std::size_t const Slot) const
{
// a map spans 2 tan(fov / 2) one metre from the projector, and the projection holds 1 / tan(fov / 2)
auto const &projection{m_headlightslots[Slot].projection};
return glm::vec4{2.f / (std::max(projection[1][1], 1e-6f) * static_cast<float>(std::max(m_headlightssize, 1))), setup(m_level).radius, 0.f, 0.f};
if (Begin)
m_headlightstimer.begin();
else
m_headlightstimer.end();
}
std::string opengl46_local_shadows::info_times()
@@ -707,67 +313,3 @@ std::string opengl46_local_shadows::info_times()
text += " headlight shadows: " + to_string(static_cast<double>(*time) / 1000000.0, 3) + " ms\n";
return text;
}
std::string opengl46_local_shadows::info() const
{
std::string text;
if (m_size != 0)
{
text += "\nlamp shadows: " + std::to_string(m_size) + " cube, " + (m_valid ? "cab lamp" : "none") + ", " + std::to_string(m_drawcalls) + " drawcalls, cpu " + to_string(m_cputime, 2) +
" ms";
}
if (false == m_slots.empty())
{
auto withvehicles{0};
auto ready{0};
for (auto const idx : m_wanted)
{
ready += (m_slots[idx].scenery ? 1 : 0);
withvehicles += (m_slots[idx].vehicles ? 1 : 0);
}
text += "\nscenery lamp shadows: " + std::to_string(m_wanted.size()) + " of " + std::to_string(m_slots.size()) + " slots (" + std::to_string(ready) + " ready, " +
std::to_string(withvehicles) + " with vehicles), drawn " + std::to_string(m_lampsscenery) + " scenery + " + std::to_string(m_lampsvehicles) + " vehicles, " +
std::to_string(m_lampsdrawcalls) + " drawcalls, cpu " + to_string(m_lampscputime, 2) + " ms";
}
if (false == m_headlightslots.empty())
{
auto ready{0};
for (auto const idx : m_headlightswanted)
{
ready += (m_headlightslots[idx].drawn ? 1 : 0);
}
text += "\nheadlight shadows: " + std::to_string(m_headlightswanted.size()) + " of " + std::to_string(m_headlightslots.size()) + " slots (" + std::to_string(ready) + " ready), " +
std::to_string(m_headlightssize) + " map, drawn " + std::to_string(m_headlightsdrawn) + " times, " + std::to_string(m_headlightsdrawcalls) + " drawcalls, cpu " +
to_string(m_headlightscputime, 2) + " ms";
}
return text;
}
void opengl46_local_shadows::draw_debug_ui()
{
char const *levels[]{"no", "low (512, every 4th frame)", "medium (1024, every 2nd frame)", "high (1024, every frame, softer)"};
ImGui::Combo("lamp shadows (gfx.lights.shadows)", &Global.gfx_lights_shadows, levels, IM_ARRAYSIZE(levels));
if (m_size != 0)
{
ImGui::Text("cab: cube %d x %d, %s, drawn %u times, %u drawcalls, %.2f ms (cpu)", m_size, m_size, m_valid ? "cab lamp" : "no lamp", m_updatecount, m_drawcalls, m_cputime);
}
ImGui::SliderInt("scenery lamps with shadows (gfx.lights.shadows.lamps)", &Global.gfx_lights_shadows_lamps, 0, 32);
ImGui::SliderFloat("their reach from the camera, m (.lamps.range)", &Global.gfx_lights_shadows_lamps_range, 0.f, 500.f);
ImGui::SliderFloat("vehicles casting them, from the camera, m (.vehicles.range)", &Global.gfx_lights_shadows_vehicles_range, 0.f, 500.f);
if (false == m_slots.empty())
{
auto const text{info()};
auto const line{text.find("\nscenery lamp shadows:")};
auto const end{(line == std::string::npos) ? std::string::npos : text.find('\n', line + 1)};
if (line != std::string::npos)
ImGui::TextUnformatted(text.substr(line + 1, end == std::string::npos ? std::string::npos : end - line - 1).c_str());
}
ImGui::SliderInt("headlights with shadows (gfx.lights.shadows.headlights)", &Global.gfx_lights_shadows_headlights, 0, static_cast<int>(gl::MAX_HEADLIGHT_SHADOWS));
ImGui::SliderFloat("their reach ahead of the vehicle, m (.headlights.range)", &Global.gfx_lights_shadows_headlights_range, 10.f, 1000.f);
if (false == m_headlightslots.empty())
{
auto const text{info()};
auto const line{text.rfind('\n')};
ImGui::TextUnformatted(text.c_str() + (line == std::string::npos ? 0 : line + 1));
}
}

View File

@@ -19,37 +19,17 @@ http://mozilla.org/MPL/2.0/.
#include "gl/shader.h"
#include "gl/query.h"
#include "rendering/common/locallights.h"
#include "rendering/common/localshadows.h"
#include "rendering/lightarray.h"
class TDynamicObject;
class TModel3d;
// cube shadow maps of the lamps of the opengl 4.6 renderer (Global.gfx_lights_shadows):
// - the lamp of the occupied cab, with the shadows cast by the cab (the model drawn by Render_cab()). the map is kept in the space
// of the cab model, centred on the lamp: it stays right while the vehicle moves and turns, and is drawn anew only every few frames
// (for the moving parts of the cab: levers, doors, wipers) or when the lamp, the cab or the level change;
// - the lamps of scenery models closest to the camera (Global.gfx_lights_shadows_lamps of them, within
// Global.gfx_lights_shadows_lamps_range). the map of such a lamp is kept in world axes, centred on the lamp, and has two parts:
// the scenery (drawn once, when the lamp gets its slot or the scene changes) and a working copy of it, with the vehicles near
// the camera (within Global.gfx_lights_shadows_vehicles_range) and the occupied cab added, made every few frames while there
// are any by the lamp. the shading reads the working copy then, the scenery part otherwise.
// the six faces of a map are drawn at once, by a geometry shader (shadowcube.vert/.geom), so a model costs one draw call per
// submodel, as in the colour pass. the depth is n / d (d the distance along the axis of the face), see local_light_shadow() in
// light_common.glsl, which reads the map for the light with the layer in its headlight_weights.x, from the array of scenery
// lamps when its linear is 1 and from the one of the cab lamp otherwise.
// - the headlights of the vehicles (Global.gfx_lights_shadows_headlights sets of them, the most important ones): a plain map,
// drawn from the projector of the beam with its projection (the beam is a narrow frustum aimed along the vehicle, see
// opengl46_renderer::Headlight_Projector()), with what is within Global.gfx_lights_shadows_headlights_range ahead of the
// vehicle, but not the vehicle itself. the map keeps the projector it was drawn with, in world space: one a few frames old is
// still right for what doesn't move. read by headlight_beam_shadow() in light_common.glsl, for the headlights with the map in their
// headlight_weights.w (1 + the layer) and the projection to it in gl::scene_ubs::headlight_shadow
class opengl46_local_shadows
// the shadow maps of the lamps of the opengl 4.6 renderer (Global.gfx_lights_shadows): the maps and the drawing to them of
// gfx::local_shadows, which tells what they hold. the six faces of a cube map are drawn at once, by a geometry shader
// (shadowcube.vert/.geom)
class opengl46_local_shadows : public gfx::local_shadows
{
public:
opengl46_local_shadows() = default;
~opengl46_local_shadows();
opengl46_local_shadows(const opengl46_local_shadows &) = delete;
opengl46_local_shadows &operator=(const opengl46_local_shadows &) = delete;
~opengl46_local_shadows() override;
// creates the programs and the stand-in maps. throws gl::shader_exception
void init();
@@ -67,157 +47,26 @@ class opengl46_local_shadows
{
return Alpha ? m_alpha_multidraw_program.get() : m_multidraw_program.get();
}
// the cab lamp
// whether the map of the cab lamp is to be drawn in this frame: Vehicle and Cab the occupied vehicle and its cab model,
// Lamp the position of the lamp in the space of the cab model; Vehicle nullptr when the lamp is off. (re)makes the map to
// the size of the level when the level changed
bool update_wanted(TDynamicObject const *Vehicle, TModel3d const *Cab, glm::vec3 const &Lamp, unsigned int const Framestamp);
// sets up the drawing to the map: the framebuffer, viewport, cleared faces, the state of the faces. the geometry is to be
// drawn with program() and a modelview from the space of the model to the space of the map
void begin();
// ends the drawing to the map, leaves the default framebuffer bound and the culling of faces on
void end(std::uint32_t const Drawcalls);
// whether the map holds the shadows of the lamp of Vehicle, the occupied vehicle
bool ready(TDynamicObject const *Vehicle) const
{
return m_valid && (Vehicle != nullptr) && (Vehicle == m_vehicle);
}
// position of the lamp the map was drawn for, in the space of the cab model
glm::vec3 const &lamp() const
{
return m_lamp;
}
// the headlight_weights of the light which reads the map: layer, near plane, size of a texel one metre from the lamp,
// radius of the filter in texels
glm::vec4 weights() const;
// lamps of scenery models
// a lamp with a slot
struct lamp_slot
{
void const *owner{nullptr}; // the light it is, see gfx::local_lights::light
void const *part{nullptr};
glm::dvec3 position{0.0}; // world space
float range{0.f}; // of the light, metres
unsigned int wantedframe{~0u}; // framestamp it was last among the lamps given shadows
bool scenery{false}; // the scenery part of the map is drawn
std::uint64_t version{0}; // of the scene the scenery part was drawn for
bool vehicles{false}; // the working copy is drawn, with vehicles, and is what the shading reads
unsigned int vehiclesframe{0}; // framestamp the working copy was last drawn in
};
// picks the lamps which get shadows in this frame from the gathered lights (the most important first): the scenery lamps
// within reach of Camera. a lamp keeps its slot while it is picked; one which got a slot or moved, and all of them when the
// scene changes (Version), need their scenery drawn. (re)makes the maps when their count or size changed
void update_lamps(std::vector<gfx::local_lights::light> const &Lights, glm::dvec3 const &Camera, std::uint64_t const Version, unsigned int const Framestamp);
// forgets the lamps: the scene they come from is about to be replaced (a scenery being loaded)
void reset_lamps();
// slots of the lamps picked in this frame
std::vector<std::size_t> const &lamps() const
{
return m_wanted;
}
lamp_slot const &slot(std::size_t const Slot) const
{
return m_slots[Slot];
}
// whether the scenery part of the map of the slot is to be drawn in this frame. a few per frame at most, by the level
bool scenery_wanted(std::size_t const Slot);
// whether the working copy of the slot, with vehicles, is to be drawn in this frame (every few frames, by the level)
bool vehicles_wanted(std::size_t const Slot, unsigned int const Framestamp) const;
// the slot has no vehicles by it: the shading reads the scenery part
void no_vehicles(std::size_t const Slot)
{
m_slots[Slot].vehicles = false;
}
// sets up the drawing to the scenery part (cleared) or to the working copy (a copy of the scenery part) of the map of the slot.
// the geometry is to be drawn with program() and a modelview to world axes centred on the lamp
void begin_lamp(std::size_t const Slot, bool const Vehicles, std::uint64_t const Version, unsigned int const Framestamp);
// ends the drawing to the map of a lamp
void end_lamp(std::uint32_t const Drawcalls);
// gpu timer of the drawing of the maps of the lamps in this frame
void lamps_timer_begin();
void lamps_timer_end();
// the slot of the lamp the light is, if it has its shadows ready, nullptr otherwise
lamp_slot const *lamp(gfx::local_lights::light const &Light, std::size_t &Slot) const;
// the headlight_weights of the light of the lamp in the slot, see weights()
glm::vec4 lamp_weights(std::size_t const Slot) const;
// headlights of vehicles
// a set of headlights (one end of a vehicle) with a slot
struct headlight_slot
{
TDynamicObject const *owner{nullptr}; // light_array::light_record::owner and index
int index{-1};
unsigned int wantedframe{~0u}; // framestamp it was last among the headlights given shadows
bool drawn{false}; // the map holds the shadows of the headlights
unsigned int drawnframe{0};
// the projector the map was drawn with: world space position, world axes to its axes, projection
glm::dvec3 projector{0.0};
glm::mat3 rotation{1.f};
glm::mat4 projection{1.f};
glm::vec3 direction{0.f}; // of the light when the map was drawn
};
// picks the headlights which get shadows in this frame: Lights, the most important first, as many as there are slots
// (Global.gfx_lights_shadows_headlights). a set keeps its slot while it is picked. (re)makes the maps when their count or size changed
void update_headlights(std::vector<light_array::light_record const *> const &Lights, unsigned int const Framestamp);
// forgets the headlights: the vehicles may be about to go (a scenery being loaded)
void reset_headlights();
// slots of the headlights picked in this frame
std::vector<std::size_t> const &headlights() const
{
return m_headlightswanted;
}
headlight_slot const &headlight(std::size_t const Slot) const
{
return m_headlightslots[Slot];
}
// whether the map of the slot is to be drawn in this frame: one never drawn, then every few frames by the level, more often
// while the headlights Moved (with the vehicle)
bool headlight_wanted(std::size_t const Slot, bool const Moved, unsigned int const Framestamp) const;
// sets up the drawing to the map of the slot (cleared), drawn from the projector given (see headlight_slot). the geometry is
// to be drawn by the shadow pass with this projection, back faces only
void begin_headlight(std::size_t const Slot, glm::dvec3 const &Projector, glm::mat3 const &Rotation, glm::mat4 const &Projection, glm::vec3 const &Direction, unsigned int const Framestamp);
// ends the drawing to the map of a set of headlights, leaves the default framebuffer bound
void end_headlight(std::uint32_t const Drawcalls);
// gpu timer of the drawing of the maps of the headlights in this frame
void headlights_timer_begin();
void headlights_timer_end();
// the slot of the headlights with their map ready in this frame, -1 if there is none
int headlight_map(TDynamicObject const *Owner, int const Index) const;
// the headlight_shadow_params of the map of the slot, see gl::scene_ubs
glm::vec4 headlight_params(std::size_t const Slot) const;
// lines for the gpu times and statistics of the debug panel
// lines for the gpu times of the debug panel
std::string info_times();
std::string info() const;
// controls for the debug window
void draw_debug_ui();
protected:
bool gpu_allocate(int const Size) override;
void gpu_release() override;
bool gpu_allocate_lamps(int const Count, int const Size) override;
void gpu_release_lamps() override;
bool gpu_allocate_headlights(int const Count, int const Size) override;
void gpu_release_headlights() override;
void gpu_begin_cab() override;
void gpu_end_cab() override;
void gpu_begin_lamp(int const Scenerycube, int const Workingcube, bool const Vehicles) override;
void gpu_end_lamp() override;
void gpu_begin_headlight(std::size_t const Slot) override;
void gpu_end_headlight() override;
void gpu_lamps_timer(bool const Begin) override;
void gpu_headlights_timer(bool const Begin) override;
private:
// size, refresh interval (frames), filter radius (texels, 0: the 2x2 filter of the hardware) of a level, the number of
// scenery parts of lamp maps drawn per frame, and the size and refresh intervals (frames, while the vehicle moves and while
// it doesn't) of the maps of headlights
struct level_setup
{
int size;
unsigned int interval;
float radius;
int sceneryperframe;
int headlightsize;
unsigned int headlightmoving;
unsigned int headlightstill;
};
static level_setup setup(int const Level);
void allocate(int const Size);
void release();
void allocate_lamps(int const Count, int const Size);
void release_lamps();
void allocate_headlights(int const Count, int const Size);
void release_headlights();
void set_layer(int const Layer);
std::unique_ptr<gl::program> m_program;
@@ -226,48 +75,16 @@ class opengl46_local_shadows
std::unique_ptr<gl::program> m_alpha_multidraw_program;
GLuint m_empty{0}; // stand-in: a cube map array of 1x1 faces with the farthest depth
GLuint m_emptyheadlights{0}; // the same, a 2d array of one 1x1 layer
int m_level{-1}; // level the maps were made for
// cab lamp
GLuint m_texture{0}; // cube map array, one cube
// cab lamp: cube map array, one cube
GLuint m_texture{0};
GLuint m_framebuffer{0};
int m_size{0}; // of a face of m_texture, 0 when there is none
bool m_valid{false}; // m_texture holds the map of m_vehicle
TDynamicObject const *m_vehicle{nullptr};
TModel3d const *m_cab{nullptr};
glm::vec3 m_lamp{0.f};
unsigned int m_drawnframe{0}; // framestamp the map was last drawn in
gl::gpu_timer m_timer;
// scenery lamps: cube map array, the scenery parts of the slots first, then their working copies
GLuint m_lampstexture{0};
GLuint m_lampsframebuffer{0};
int m_lampssize{0};
std::vector<lamp_slot> m_slots;
std::vector<std::size_t> m_wanted;
unsigned int m_frame{0}; // framestamp of the last update_lamps()
int m_sceneryleft{0}; // scenery parts which may still be drawn in this frame
gl::gpu_timer m_lampstimer;
bool m_lampstiming{false};
// headlights: 2d array, a layer per slot
GLuint m_headlightstexture{0};
GLuint m_headlightsframebuffer{0};
int m_headlightssize{0};
std::vector<headlight_slot> m_headlightslots;
std::vector<std::size_t> m_headlightswanted;
gl::gpu_timer m_headlightstimer;
bool m_headlightstiming{false};
std::chrono::steady_clock::time_point m_headlightscpustart;
// statistics
std::uint32_t m_drawcalls{0}; // of the last drawing of the cab map
std::uint32_t m_updatecount{0}; // drawings of the cab map so far
double m_cputime{0.0}; // milliseconds, smoothed
std::chrono::steady_clock::time_point m_cpustart;
std::uint32_t m_lampsscenery{0}; // scenery parts drawn so far
std::uint32_t m_lampsvehicles{0}; // working copies drawn so far
std::uint32_t m_lampsdrawcalls{0}; // in the drawings of the lamp maps in the last frame with any
std::uint32_t m_lampsframedrawcalls{0};
double m_lampscputime{0.0}; // milliseconds per frame, smoothed
std::uint32_t m_headlightsdrawn{0}; // maps of headlights drawn so far
std::uint32_t m_headlightsdrawcalls{0}; // in the drawings of the maps of headlights in the last frame with any
std::uint32_t m_headlightsframedrawcalls{0};
double m_headlightscputime{0.0}; // milliseconds per frame, smoothed
};

File diff suppressed because it is too large Load Diff

View File

@@ -216,14 +216,10 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
// the gpu culling of the instances, models and sleepers, the depth pass of the whole scene
void walk_region_begin() override;
void walk_editor_cursor() override;
bool walk_bucket(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, bool const Bucketrange, scene::bounding_area const &Area) override;
void walk_bucket_culled(scene::basic_cell::instance_bucket const &Bucket) override;
bool walk_queue_model(TAnimModel *Instance, bool const Inrange) override;
bool walk_queue_sleepers(TTrack *Track) override;
void walk_sleepers_drawn(TTrack const &Track, std::vector<float> const &Lodbounds, std::vector<std::pair<float, glm::mat4>> const &Drawn) override;
bool walk_instances_begin() override;
void walk_instances_compare(scene::basic_cell::instance_bucket_key const &Key, std::vector<std::uint32_t> const &Bandcounts) override;
void walk_instances_end() override;
gfx::geometrybank_manager &walk_geometry() override;
bool walk_blending_enabled() const override;
// types
struct viewport_config {
@@ -346,11 +342,11 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
// materials like the main view. no occlusion test, no depth prepass, one phase
void Render_Instances_Indirect_Pass();
// the walk of the submodel trees of the groups culled by Cull, once per lod band, which records their draws
// (m_indirectdraws, m_indirectcommands), puts them in order for multi draws and has Cull fill in their commands
// (m_indirectdraws, m_indirectcommands; Walk_Indirect_Draws()), puts them in order for multi draws and has Cull fill in their
// commands
void Record_Indirect_Draws( opengl46_instance_cull &Cull );
// whether a model with its skins has any part the sun shadows draw: the same test Render_Instanced() makes. the answer
// is kept for the rest of the pass
bool Instances_Cast_Shadow( scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket );
// the culling the pass under way feeds (Begin_Pass_Cull()), always one of this renderer
opengl46_instance_cull &Pass_Cull() { return static_cast<opengl46_instance_cull &>( *m_passcull ); }
// draws of the instances made, and the gl calls which made them
struct indirect_counts {
std::uint32_t draws { 0 };
@@ -369,18 +365,11 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
indirect_counts Render_Instances_Indirect_Colour( opengl46_instance_cull::command_set const Set );
// the recorded draws of a shadow pass with the shadow programs, the way Bind_Material_Shadow() picks them
indirect_counts Render_Instances_Indirect_Shadow( opengl46_instance_cull::command_set const Set );
// the texture Bind_Material_Shadow() binds for the alpha test of a material, or null_handle (no test)
texture_handle Indirect_Shadow_Texture( material_handle const Material );
// multi draws of the recorded draws (Global.gfx_gpucull_multidraw): puts the draws in an order in which the ones with the
// same state follow one another, the draws of one entry kept in the order of the walk, then places their model blocks in
// the buffer of the frame (m_drawblockstream), in the same order
void Order_Indirect_Draws();
// multi draws of the recorded draws (Global.gfx_gpucull_multidraw), put in order by Order_Indirect_Draws(): places their
// model blocks in the buffer of the frame (m_drawblockstream), in the same order
void Upload_Indirect_Blocks();
// binds the model blocks of Count recorded draws from First for the programs of the multi draws (MODEL_DRAW_SSBO_BINDING)
void Bind_Indirect_Blocks( std::size_t const First, std::size_t const Count );
// whether two recorded draws can go in one multi draw of the colour pass: the same material and texture variant, geometry
// bank, primitive and blending
bool Indirect_Mergeable( std::size_t const First, std::size_t const Other ) const;
// the program Bind_Material() binds for the material, nullptr when it binds none
gl::program const *Indirect_Program( material_handle Material ) const;
// the variant of a material program for multi draws (built with MODEL_DRAWS), made the first time it's asked for.
@@ -431,14 +420,6 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
std::array<std::optional<std::array<glm::vec3, 8>>, gl::MAX_CASCADES> &Areas );
// whether the depth pass of the whole scene draws the material bound last (Bind_Material()) and the geometry
bool Scene_Depth_Draws( gfx::geometry_handle const &Geometry ) const;
// queues the sleepers of a track in the gpu culling of the pass (Global.gfx_gpucull_sleepers), with the gates of
// Render_Sleepers(), making their records first when they're missing or out of date
void Queue_Sleepers( TTrack *Track );
// queues a model instance drawn on its own in the gpu culling of the pass (Global.gfx_gpucull_models), as a bucket of
// its own, making its record first when it's missing or out of date. Inrange: the cell of the instance lies within the
// range of the pass. returns true when the instance isn't drawn by the cpu in the pass: it's drawn from the result of the
// culling, or it was culled and recorded by the depth pass of the whole scene, or it lies out of range
bool Queue_Model( TAnimModel *Instance, bool const Inrange );
// releases gl textures used only by vehicles farther than gfx.textures.releasedistance times the draw range
void Update_Texture_Release();
// textures used by materials of the sub-models of specified model, replaceable skins excluded
@@ -566,7 +547,6 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
// when Global.gfx_gpucull is set: the instances of the pass are drawn from its result (1), or the result is compared
// with the one of the cpu (2)
opengl46_instance_cull m_instancecull;
bool m_instancecullpass { false }; // the current pass feeds a culling: m_passcull
unsigned int m_instancecullframe { ~0u }; // frame it last ran in
// the cullings of the cascades of the sun shadows and of the faces of the environment map (Global.gfx_gpucull_shadows,
// _reflections), with records and programs shared with m_instancecull
@@ -578,42 +558,8 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
std::vector<std::unique_ptr<opengl46_instance_cull>> m_lampcull;
opengl46_instance_cull *m_lightshadowcull { nullptr }; // the one of the map of headlights or of a lamp being drawn
std::size_t m_lampscenerydrawn { 0 }; // scenery parts of the maps of lamps drawn in this frame so far
opengl46_instance_cull *m_passcull { nullptr }; // the one the current pass feeds
opengl46_instance_cull *m_indirectcull { &m_instancecull }; // the one the recorded draws (m_indirectdraws) were culled by
int m_shadowcascade { -1 }; // the region is drawn for this cascade of the sun shadows
// Instances_Cast_Shadow() of the groups of the shadow pass under way
std::unordered_map<scene::basic_cell::instance_bucket_key, bool, scene::basic_cell::instance_bucket_key_hash> m_shadowcasters;
// the sleepers of tracks culled on the gpu with the instances of the main view (Global.gfx_gpucull_sleepers): records made
// from TTrack::m_sleeper_local_transforms when the track is queued first, and again when they or the instance records
// (editor, m_instancerecordsversion) went out of date. the records of tracks not queued for a while are let go
struct sleeper_records {
scene::basic_cell::instance_bucket bucket; // the records only, no instances
scene::basic_cell::instance_bucket_key key; // the model, and the skin in the slot Render_Sleepers() sets it in
scene::bounding_area area; // of the locations of the sleepers
TModel3d const *model { nullptr }; // what the records were made from
material_handle skin { null_handle };
glm::mat4 const *transforms { nullptr };
std::size_t count { 0 };
unsigned int frame { 0 }; // queued last
};
std::unordered_map<TTrack const *, sleeper_records> m_sleeperrecords;
// the model instances drawn on their own culled on the gpu (Global.gfx_gpucull_models), each one a bucket of its own with
// a group of its own: the record made when the instance is queued first, and again when the instance records went out of
// date (editor, events: m_instancerecordsversion). the records of instances not queued for a while are let go
struct model_records {
scene::basic_cell::instance_bucket bucket; // the instance alone
scene::basic_cell::instance_bucket_key key; // the model, the skins and the instance
scene::bounding_area area; // the location of the instance
unsigned int frame { 0 }; // queued last
};
std::unordered_map<TAnimModel const *, model_records> m_modelrecords;
bool m_modelspass { false }; // the models drawn on their own of the pass under way go to its gpu culling
bool m_indirectcolourmodels { false }; // with m_indirectcolourpending: the depth pass culled and recorded them too
bool m_sleeperspass { false }; // the sleepers of the pass under way go to its gpu culling (the one of the main view)
bool m_indirectcoloursleepers { false }; // with m_indirectcolourpending: the depth pass culled and recorded the sleepers too
// counts per lod band of the sleepers Render_Sleepers() drew in the pass, by model and skin, for the comparison (they're
// drawn before the culling runs)
std::unordered_map<scene::basic_cell::instance_bucket_key, std::vector<std::uint32_t>, scene::basic_cell::instance_bucket_key_hash> m_sleeperbandcounts;
// depth pyramid for the occlusion test of the gpu culling
opengl46_hiz m_hiz;
// the cascades of the sun shadows fitted to the depth of the main view (Global.gfx_shadow_sdsm)
@@ -621,75 +567,10 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
// with a cascade fitted: the corners of the area its map is to cover, for setup_pass()
std::array<glm::vec3, 8> const *m_shadowfitarea { nullptr };
// what the last shadow pass did, for the debug panel
struct shadow_fit_state {
bool fitted { false };
char const *reason { nullptr }; // why not, with gfx.shadow.sdsm on
unsigned int age { 0 }; // frames since the measurement
float depthmin { 0.f };
float depthmax { 0.f };
float turn { 0.f }; // of the view since the measurement, degrees
std::array<float, gl::MAX_CASCADES + 1> splits {};
// size of the area of the map of each cascade: of the whole slice of the view, and as drawn; metres
std::array<float, gl::MAX_CASCADES> slice {};
std::array<float, gl::MAX_CASCADES> drawn {};
} m_shadowfitstate;
gfx::shadow_fit::fit_state m_shadowfitstate;
// the sizes setup_pass() found for the last shadow pass: of the whole slice of the view and as drawn
float m_shadowslicesize { 0.f };
float m_shadowdrawnsize { 0.f };
// a draw recorded from the walk of the submodel tree of a group in one lod band (Render_Instances_Indirect()), to be
// replayed once the instances of its command are known: the state the walk left for it and what it bound
struct indirect_draw {
gl::model_ubs ubs;
material_handle material;
TSubModel const *submodel;
gfx::geometry_handle geometry;
unsigned int type; // primitive
bool blending; // drawn with the transparency hack of submodels faded out
std::uint32_t group { 0 }; // the buffers of the geometry, which the draws of a multi draw share (geometrybank_manager::draw_group())
};
std::vector<indirect_draw> m_indirectdraws;
std::vector<gl::instance_draw_command> m_indirectcommands; // in the order of m_indirectdraws
// the walks Record_Indirect_Draws() made for the cascades of the sun shadows in the frame, for the other cascades to copy: the
// draws and commands recorded for a group (model and skins) in each of its lod bands, first none where no cascade walked it
struct cascade_band {
std::uint32_t first;
std::uint32_t count;
};
struct cascade_record {
unsigned int frame { 0 };
std::vector<cascade_band> bands;
};
std::unordered_map<scene::basic_cell::instance_bucket_key, cascade_record, scene::basic_cell::instance_bucket_key_hash> m_cascaderecords;
std::vector<indirect_draw> m_cascadedraws;
std::vector<gl::instance_draw_command> m_cascadecommands;
unsigned int m_cascaderecordframe { 0 };
// the walks Record_Indirect_Draws() made in earlier frames (Global.gfx_gpucull_walkcache). what the walk of a group of instances
// in a lod band records depends on the model with its skins, the band and whether the pass draws shadows, not on the camera,
// so it's kept while the shared state of the model stays the same (TModel3d::state_version()) and used instead of walking the
// tree again. a walk meeting an animated submodel, one lit by the luminance or the stars isn't kept. the models drawn on their
// own (with an instance in the key) are walked every time
struct walk_band {
std::vector<indirect_draw> draws; // as recorded
float bandmin { 0.f };
std::uint32_t modelversion { 0 };
bool blending { false }; // the state the walk began in
bool valid { false };
};
struct walk_record {
unsigned int frame { 0 }; // used last
std::array<std::vector<walk_band>, 2> bands; // of the colour passes (the main view, the environment map) and of the shadows
};
std::unordered_map<scene::basic_cell::instance_bucket_key, walk_record, scene::basic_cell::instance_bucket_key_hash> m_walkrecords;
int m_walkrankcutoff { 0 }; // Global.gfx_shadow_rank_cutoff the walks were made with
// what the walks of the frame did, for the panel: walks made, of them not kept, walks taken from earlier frames, and the cpu time
// of the walks and of putting the draws in order with their blocks uploaded
struct walk_counts {
std::uint32_t walked { 0 };
std::uint32_t changing { 0 };
std::uint32_t reused { 0 };
double walktime { 0.0 };
double ordertime { 0.0 };
};
// cpu time of the parts of the culling and the draws of the instances of the main view (Render_Instances_Indirect() and
// _Pending()), for the panel: the depth of the recorded walk of the scene drawn before the culling, the depth pyramids, the
// culling passes, the walks with putting the draws in order, the depth and the colour of the instances
@@ -702,47 +583,13 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
double colour { 0.0 };
};
cull_parts m_cullparts;
walk_counts m_walkcounts;
walk_counts m_lastwalkcounts;
unsigned int m_walkcountsframe { 0 };
// appends the draws of a walk kept from an earlier frame to the recorded draws, with the commands of the entry under way
void Replay_Walk( walk_band const &Walk, bool const Shadows );
// multi draws of the recorded draws: whether they were recorded for them (ordered, their model blocks uploaded), and where
// the blocks are
bool m_indirectmultidraw { false };
GLuint m_indirectblockbuffer { 0 };
GLintptr m_indirectblockoffset { 0 };
std::uint64_t m_indirectblockgeneration { 0 };
// working space of Order_Indirect_Draws() and Upload_Indirect_Blocks(), kept between frames
struct indirect_queue {
std::uint32_t next; // draw
std::uint32_t end;
std::uint32_t link; // next queue waiting for the same state
};
struct indirect_state {
std::uint32_t head;
std::uint32_t tail;
};
// what a multi draw has in common, and the draw which has it
struct indirect_state_key {
material_handle material;
std::uint32_t variant; // of the textures of the material
std::uint32_t bank;
unsigned int type;
std::uint32_t indexed;
bool blending;
std::uint32_t draw;
auto tie() const {
return std::tie( material, variant, bank, type, indexed, blending ); }
};
std::vector<indirect_state_key> m_indirectstatekeys;
std::vector<std::uint32_t> m_indirectdrawstates;
std::vector<indirect_queue> m_indirectqueues;
std::vector<indirect_state> m_indirectstates;
std::vector<std::uint32_t> m_indirectstateorder;
std::vector<std::uint32_t> m_indirectorder;
std::vector<indirect_draw> m_indirectdrawsordered;
std::vector<gl::instance_draw_command> m_indirectcommandsordered;
// working space of Upload_Indirect_Blocks(), kept between frames
std::vector<gl::model_ubs> m_indirectblocks;
// the model blocks of the multi draws, one buffer for each of the last frames like model_ubo (nullptr: no multi draws)
std::unique_ptr<gl::frame_stream> m_drawblockstream;
@@ -754,10 +601,6 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
std::unique_ptr<gl::program> m_shadow_multidraw_shader; // and of the sun shadows
std::unique_ptr<gl::program> m_alpha_shadow_multidraw_shader;
bool m_bindmultidraw { false }; // Bind_Material() binds the variant of the material program for multi draws
bool m_indirectrecording { false }; // draw() and Bind_Material() record instead of drawing
std::uint32_t m_indirectentry { 0 }; // entry (group and lod band) recorded
material_handle m_indirectmaterial { null_handle }; // what the last Bind_Material() bound while recording
TSubModel const *m_indirectsubmodel { nullptr };
// depth prepass: programs and the class of the material programs (see Prepass_Class())
std::unique_ptr<gl::program> m_prepass_shader;
std::unique_ptr<gl::program> m_prepass_alpha_shader;
@@ -768,7 +611,7 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
bool m_scenedepthpass { false }; // inside it: Bind_Material() and draw() work for the depth
bool m_scenedepthskip { true }; // the material bound last isn't drawn in it
bool m_scenedepthdrawn { false }; // it was drawn for the colour pass under way, which skips what was done in it already
bool m_indirectcolourpending { false }; // the instances were culled in it, the colour pass draws them from the recorded draws
// with m_indirectcolourpending: the instances were culled in it, the colour pass draws them from the recorded draws
opengl46_instance_cull::command_set m_indirectcolourset { opengl46_instance_cull::command_set::first }; // with these commands
debug_stats m_scenedepthstats; // of its last run
double m_scenedepthcputime { 0.0 }; // ms
@@ -894,12 +737,6 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
opengl46_local_shadows m_localshadows;
// the shadow pass draws to a cube shadow map, with the programs of m_localshadows
bool m_cubeshadows = false;
// vehicles near the camera which cast the shadows of scenery lamps, and those of them by the lamp being drawn
std::vector<TDynamicObject *> m_lampshadowvehicles;
std::vector<TDynamicObject *> m_lampshadowslot;
// headlights which may get shadow maps in this frame, and their importance
std::vector<std::pair<float, light_array::light_record const *>> m_headlightshadowcandidates;
std::vector<light_array::light_record const *> m_headlightshadowpicked;
double m_precipitationrotation;

View File

@@ -10,27 +10,10 @@ http://mozilla.org/MPL/2.0/.
#include "stdafx.h"
#include "rendering/opengl46/opengl46shadowfit.h"
#include <cstring>
#include "model/Texture.h"
#include "utilities/Globals.h"
#include "utilities/utilities.h"
namespace
{
// the nearest distance the first cascade may start from and the least one the last reaches, metres: the cascades don't
// get any closer to the camera than this, however close the pixels are
float const SPLIT_NEAR_MIN{0.5f};
float const SPLIT_FAR_MIN{16.f};
// how far past its distances a cascade takes the pixels, this many metres and this share of the distance: the camera moves
// in the frame or two before the cascades are drawn from the measurement, and a pixel which came into another cascade since
// has to be within its map
float const MARGIN_METRES{1.f};
float const MARGIN_SHARE{0.05f};
} // namespace
opengl46_shadow_fit::~opengl46_shadow_fit()
{
release();
@@ -97,20 +80,7 @@ void opengl46_shadow_fit::measure(GLuint const Depth, GLuint const Source, int c
depth = m_depth;
}
// numbers start from 1, 0 is no measurement
if (++m_measurement == 0)
++m_measurement;
gl::shadow_fit_data data{};
data.measurement = m_measurement;
data.depthmin = std::numeric_limits<float>::infinity();
for (std::size_t cascade = 0; cascade < gl::MAX_CASCADES; ++cascade)
{
for (std::size_t axis = 0; axis < 4; ++axis)
{
data.bounds[cascade * 8 + axis] = 0xFFFFFFFFu;
data.bounds[cascade * 8 + 4 + axis] = 0u;
}
}
auto const data{begin_measurement(View, Frame)};
m_buffer->update(data);
m_buffer->bind_base();
@@ -124,11 +94,12 @@ void opengl46_shadow_fit::measure(GLuint const Depth, GLuint const Source, int c
m_depthprogram->dispatch(groupsx, groupsy);
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
auto const farmin{std::min(SPLIT_FAR_MIN, View.range)};
auto const bounds{bounds_params(View)};
auto const margins{margin_params()};
glProgramUniformMatrix4fv(*m_boundsprogram, 0, 1, GL_FALSE, glm::value_ptr(inverseprojection));
glProgramUniformMatrix4fv(*m_boundsprogram, 4, 1, GL_FALSE, glm::value_ptr(viewtolight));
glProgramUniform4f(*m_boundsprogram, 8, View.range, std::min(SPLIT_NEAR_MIN, farmin * 0.25f), farmin, std::clamp(Global.gfx_shadow_sdsm_split, 0.f, 1.f));
glProgramUniform4f(*m_boundsprogram, 9, MARGIN_METRES, MARGIN_SHARE, 0.f, 0.f);
glProgramUniform4f(*m_boundsprogram, 8, bounds.x, bounds.y, bounds.z, bounds.w);
glProgramUniform4f(*m_boundsprogram, 9, margins.x, margins.y, margins.z, margins.w);
m_boundsprogram->dispatch(groupsx, groupsy);
// for the copy of the read back
glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);
@@ -139,27 +110,14 @@ void opengl46_shadow_fit::measure(GLuint const Depth, GLuint const Source, int c
m_timer.end();
m_readback->push(*m_buffer);
m_pending[m_measurement % PENDING] = pending{m_measurement, View, Frame};
}
opengl46_shadow_fit::result const *opengl46_shadow_fit::latest()
{
gl::shadow_fit_data data;
if (m_readback && m_readback->pop(&data))
{
auto const &source{m_pending[data.measurement % PENDING]};
if ((data.measurement != 0) && (source.measurement == data.measurement))
m_latest = result{data, source.source, source.frame};
}
return m_latest ? &(*m_latest) : nullptr;
}
float opengl46_shadow_fit::unorder(std::uint32_t const Value)
{
auto const bits{((Value & 0x80000000u) != 0u) ? (Value & 0x7FFFFFFFu) : ~Value};
float result;
std::memcpy(&result, &bits, sizeof(result));
return result;
arrived(data);
return gfx::shadow_fit::latest();
}
std::string opengl46_shadow_fit::info_times()

View File

@@ -19,34 +19,14 @@ http://mozilla.org/MPL/2.0/.
#include "gl/shader.h"
#include "gl/query.h"
#include "gl/readback.h"
#include "rendering/common/shadowfit.h"
// the cascades of the sun shadows fitted to the depth of the main view (sample distribution shadow maps, gfx.shadow.sdsm).
// at the end of the colour pass of the main view two compute passes go over its resolved depth: the nearest and the farthest
// distance of the pixels which aren't the sky (shadow_fit_depth.comp), then the distances split between the cascades and,
// for each cascade, the box of the pixels it takes in the space of the light (shadow_fit_bounds.comp). the result comes back
// to the cpu a frame or two later without waiting (gl::readback), with the camera and the light it was measured with, and
// the renderer makes the cascades of the frame cover the boxes (opengl46_renderer::Shadow_Fit()) instead of the whole slices
// of the view between the fixed distances of shadowtune
class opengl46_shadow_fit
// the cascades of the sun shadows fitted to the depth of the main view (gfx.shadow.sdsm): the passes and the buffers of
// gfx::shadow_fit, which tells what they do. the result comes back to the cpu a frame or two later without waiting
// (gl::readback), and opengl46_renderer::Shadow_Fit() fits the cascades of the frame to it
class opengl46_shadow_fit : public gfx::shadow_fit
{
public:
// the view and the light a measurement was made with
struct view
{
glm::dvec3 camera; // position of the camera
glm::mat4 projection; // projection of the view
glm::mat3 rotation; // world to view space, rotation only
glm::mat3 light; // world to the space of the light, rotation only
float range{0.f}; // the farthest distance the cascades may reach
};
// a measurement which came back
struct result
{
gl::shadow_fit_data data;
view source;
unsigned int frame{0}; // frame of the renderer it was made in
};
opengl46_shadow_fit() = default;
~opengl46_shadow_fit();
opengl46_shadow_fit(const opengl46_shadow_fit &) = delete;
@@ -63,30 +43,15 @@ class opengl46_shadow_fit
// the time of the passes, for the debug panel
std::string info_times();
// the ends of the bounds, as the passes store them (shadow_fit_order() in shadow_fit_bounds.comp)
static float unorder(std::uint32_t const Value);
private:
// the texture the depth is resolved to when there is no resolved depth to measure
void allocate(int const Width, int const Height);
void release();
// a measurement on its way back, kept by its number
struct pending
{
std::uint32_t measurement{0}; // 0: none
view source;
unsigned int frame{0};
};
static constexpr std::size_t PENDING = 8; // more than the copies gl::readback keeps in flight
std::unique_ptr<gl::program> m_depthprogram;
std::unique_ptr<gl::program> m_boundsprogram;
std::unique_ptr<gl::ssbo> m_buffer;
std::unique_ptr<gl::readback> m_readback;
std::array<pending, PENDING> m_pending;
std::uint32_t m_measurement{0}; // number of the last measurement
std::optional<result> m_latest;
GLuint m_depth{0};
GLuint m_depthfb{0};
glm::ivec2 m_depthsize{0};

View File

@@ -12,84 +12,10 @@ http://mozilla.org/MPL/2.0/.
#include <cstring>
#include "environment/skydome.h"
#include "model/Texture.h"
#include "utilities/Globals.h"
#include "utilities/utilities.h"
namespace
{
// irradiance of a radiance given as spherical harmonics, divided by pi (the units of the ambient light of the shading:
// under a uniform sky of radiance L it is L): the coefficients of band l are scaled by A_l / pi (Ramamoorthi, Hanrahan 2001)
float const SH_IRRADIANCE_BAND[3]{1.f, 2.f / 3.f, 0.25f};
int const SH_BAND[gl::SKY_SH_COEFFICIENTS]{0, 1, 1, 1, 2, 2, 2, 2, 2};
float const SH_Y00{0.282095f};
float const SH_Y1{0.488603f};
float const PI{3.14159265359f};
// real spherical harmonics of order 2 in a world space direction, in the order of light_common.glsl
std::array<float, gl::SKY_SH_COEFFICIENTS> sh_basis(glm::vec3 const &n)
{
return {SH_Y00,
SH_Y1 * n.y,
SH_Y1 * n.z,
SH_Y1 * n.x,
1.092548f * n.x * n.y,
1.092548f * n.y * n.z,
0.315392f * (3.f * n.z * n.z - 1.f),
1.092548f * n.x * n.z,
0.546274f * (n.x * n.x - n.y * n.y)};
}
float luminance(glm::vec3 const &Color)
{
return glm::dot(Color, glm::vec3{0.2126f, 0.7152f, 0.0722f});
}
glm::vec3 desaturate(glm::vec3 const &Color, float const Amount)
{
return glm::mix(Color, glm::vec3{luminance(Color)}, Amount);
}
// the response of color.frag to the vertex colours of the dome, so the light taken from the dome is the light it shows
glm::vec3 sky_shoulder(glm::vec3 const &Color)
{
float const knee{0.25f};
float const range{0.95f - knee};
auto const excess{glm::max(Color - glm::vec3{knee}, glm::vec3{0.f})};
return glm::min(Color, glm::vec3{knee}) + range * (glm::vec3{1.f} - glm::exp(-excess / range));
}
// the atmosphere is calibrated against the old sky: these scales make a sky of the atmosphere, with the sun 15 to 60 degrees
// high on a clear day, as bright as the old Perez dome is drawn on average, and the ambient light and the sunlight as strong
// as the old ones, with the default ground albedo (measured on the tables worked out by the same shaders on a software
// renderer, see the notes of the plan, stage S2)
float const ATMOSPHERE_SKY_SCALE{16.f};
float const ATMOSPHERE_AMBIENT_SCALE{44.f};
float const ATMOSPHERE_SUN_SCALE{1.45f};
// sun elevations (degrees) between which the sky goes over from the old model (night, twilight) to the atmosphere. the
// atmosphere darkens at dusk much faster than the old model, which the rest of the lighting was tuned to
float const ATMOSPHERE_FADE_START{-6.f};
float const ATMOSPHERE_FADE_END{10.f};
// the shading scales the sunlight and the ambient light by these (light_common.glsl), so the light the sun gives the
// ground, in the units of the ambient light, is the diffuse colour times their ratio
float const SHADING_SUN_TO_AMBIENT{1.5f / 0.65f};
// parameters of the earth's atmosphere after Hillaire 2020 (km, 1/km)
glm::vec3 const RAYLEIGH_SCATTERING{5.802e-3f, 13.558e-3f, 33.1e-3f};
float const RAYLEIGH_SCALE_HEIGHT{8.f};
float const MIE_SCATTERING{3.996e-3f};
float const MIE_EXTINCTION{4.440e-3f};
float const MIE_SCALE_HEIGHT{1.2f};
float const MIE_ASYMMETRY{0.8f};
glm::vec3 const OZONE_ABSORPTION{0.650e-3f, 1.881e-3f, 0.085e-3f};
float const OZONE_ALTITUDE{25.f};
float const OZONE_HALF_WIDTH{15.f};
float const BOTTOM_RADIUS{6360.f};
float const TOP_RADIUS{6460.f};
} // namespace
opengl46_sky::~opengl46_sky()
{
@@ -137,15 +63,7 @@ void opengl46_sky::init()
m_skyview_table = create_table(gl::ATMOSPHERE_SKYVIEW_WIDTH, gl::ATMOSPHERE_SKYVIEW_HEIGHT);
// a valid atmosphere for the sky shader from the start, the tables stay black until the first update
m_atmosphere.rayleigh = glm::vec4{RAYLEIGH_SCATTERING, RAYLEIGH_SCALE_HEIGHT};
m_atmosphere.mie = glm::vec4{MIE_SCATTERING, MIE_EXTINCTION, MIE_SCALE_HEIGHT, MIE_ASYMMETRY};
m_atmosphere.ozone = glm::vec4{OZONE_ABSORPTION, OZONE_ALTITUDE};
m_atmosphere.ground = glm::vec4{glm::vec3{Global.gfx_ambient_groundalbedo}, OZONE_HALF_WIDTH};
m_atmosphere.sun_direction = glm::vec4{0.f, 1.f, 0.f, 0.f};
m_atmosphere.bottom_radius = BOTTOM_RADIUS;
m_atmosphere.top_radius = TOP_RADIUS;
m_atmosphere.observer_radius = BOTTOM_RADIUS + 0.2f;
m_atmosphere_ubo->update(m_atmosphere);
m_atmosphere_ubo->update(m_light.atmosphere());
}
void opengl46_sky::bind()
@@ -155,75 +73,13 @@ void opengl46_sky::bind()
m_atmosphere_result_ssbo->bind_base();
}
opengl46_sky::sh_coefficients opengl46_sky::dome_directional(CSkyDome const &Skydome, glm::vec3 const &Ambient, glm::vec3 const &Sunlight, float const Sunheight) const
{
sh_coefficients radiance{};
auto const &vertices{Skydome.vertices()};
auto const &colors{Skydome.colors()};
// the vertices make a grid even in the elevation and the azimuth, so the solid angle of a vertex goes with the cosine of
// its elevation. the bottom ring lies a little under the horizon and is taken as the horizon
float totalweight{0.f};
for (std::size_t i = 0; i < vertices.size() && i < colors.size(); ++i)
{
auto direction{glm::normalize(vertices[i])};
// the dome darkens its colours towards the top and brightens the band above the horizon for the looks
// (CSkyDome::RebuildColors), which would make the zenith almost black and the horizon the main source of light.
// both are undone here, so the light comes from the sky the Perez model describes
auto const height{std::max(direction.y, 0.05f)};
auto const horizonband{std::clamp(1.f - height / 0.2f, 0.f, 1.f)};
auto const looks{std::clamp(1.f - height * 0.75f, 0.25f, 1.f) * std::lerp(1.f, 1.5f + 0.75f * m_overcast, horizonband)};
direction.y = std::max(direction.y, 0.f);
direction = glm::normalize(direction);
auto const weight{std::sqrt(std::max(1.f - direction.y * direction.y, 0.f))};
auto const color{sky_shoulder(glm::pow(glm::max(colors[i] / looks, glm::vec3{0.f}), glm::vec3{2.2f}))};
auto const basis{sh_basis(direction)};
for (std::size_t c = 0; c < radiance.size(); ++c)
radiance[c] += color * (basis[c] * weight);
totalweight += weight;
}
if (totalweight <= 0.f)
return {};
for (auto &coefficient : radiance)
coefficient *= 2.f * PI / totalweight;
// the ground, lit by the sky and the sun, fills the lower hemisphere with uniform light (see atmosphere_ambient.comp).
// the dome is in its own units: the light of the sky on an upward facing surface is taken to be the ambient light,
// which converts the sunlight to them
auto const up{sh_basis(glm::vec3{0.f, 1.f, 0.f})};
glm::vec3 skyup{0.f};
for (std::size_t c = 0; c < radiance.size(); ++c)
skyup += radiance[c] * (SH_IRRADIANCE_BAND[SH_BAND[c]] * up[c]);
auto const tosky{luminance(skyup) / std::max(luminance(Ambient), 1e-4f)};
auto const groundradiance{Global.gfx_ambient_groundalbedo * (skyup + Sunlight * SHADING_SUN_TO_AMBIENT * std::max(Sunheight, 0.f) * tosky)};
radiance[0] += groundradiance * (SH_Y00 * 2.f * PI);
radiance[1] += groundradiance * (SH_Y1 * -PI);
// to the irradiance, scaled so its average over all directions is the ambient light, channel by channel
sh_coefficients directional{};
glm::vec3 const average{radiance[0] * SH_Y00};
auto const scale{Ambient / glm::max(average, glm::vec3{1e-4f})};
for (std::size_t c = 1; c < radiance.size(); ++c)
directional[c] = radiance[c] * SH_IRRADIANCE_BAND[SH_BAND[c]] * scale;
return directional;
}
void opengl46_sky::update_atmosphere(glm::vec3 const &Sundirection)
{
// aerosols grow with the turbidity the weather sets (3 clear .. 9 overcast)
auto const turbidity{std::clamp(Global.fTurbidity, 3.f, 9.f)};
auto const miescale{Global.gfx_atmosphere_haze * (1.f + 0.5f * (turbidity - 3.f))};
glm::vec4 const medium{miescale, Global.gfx_ambient_groundalbedo, Global.gfx_atmosphere_altitude, 0.f};
auto const mediumchanged{!m_tables_medium || *m_tables_medium != medium};
// the sky-view table follows the sun when it moved by more than about a twentieth of a degree
auto const sunchanged{!m_tables_sun || glm::dot(*m_tables_sun, Sundirection) < 0.9999995f};
if (!mediumchanged && !sunchanged)
bool mediumchanged{false};
if (false == m_light.atmosphere_changed(Sundirection, mediumchanged))
return;
m_atmosphere.mie = glm::vec4{MIE_SCATTERING * miescale, MIE_EXTINCTION * miescale, MIE_SCALE_HEIGHT, MIE_ASYMMETRY};
m_atmosphere.ground = glm::vec4{glm::vec3{Global.gfx_ambient_groundalbedo}, OZONE_HALF_WIDTH};
m_atmosphere.sun_direction = glm::vec4{Sundirection, 0.f};
m_atmosphere.observer_radius = BOTTOM_RADIUS + std::max(Global.gfx_atmosphere_altitude, 0.f) * 0.001f + 0.001f;
m_atmosphere_ubo->update(m_atmosphere);
m_atmosphere_ubo->update(m_light.atmosphere());
m_atmosphere_ubo->bind_uniform();
m_atmosphere_result_ssbo->bind_base();
@@ -257,89 +113,23 @@ void opengl46_sky::update_atmosphere(glm::vec3 const &Sundirection)
opengl_texture::reset_unit_cache();
m_result_readback.push(*m_atmosphere_result_ssbo);
m_tables_medium = medium;
m_tables_sun = Sundirection;
}
void opengl46_sky::update(opengl46_light &Sunlight, glm::vec3 &Fogcolor, CSkyDome const &Skydome, glm::vec3 const &Sundirection, float const Sunangle)
{
// the light the simulation set, which is kept where the atmosphere is not used
glm::vec3 const oldambient{Sunlight.ambient};
glm::vec3 const olddiffuse{Sunlight.diffuse};
glm::vec3 const oldfog{Fogcolor};
m_overcast = std::clamp(Global.Overcast, 0.f, 1.f);
m_light.begin_update();
// atmosphere
m_weight = 0.f;
if (Global.gfx_atmosphere)
{
update_atmosphere(glm::normalize(Sundirection));
gl::atmosphere_result result;
if (m_result_readback.pop(&result))
m_result = result;
if (m_result)
m_weight = glm::smoothstep(ATMOSPHERE_FADE_START, ATMOSPHERE_FADE_END, Sunangle);
}
sh_coefficients directional{};
if (Global.gfx_ambient_sh)
{
// the dome changes with the sun, the weather and the light the simulation works out from them
std::array<glm::vec4, 3> const key{glm::vec4{Sundirection, Sunlight.direction.y}, glm::vec4{oldambient, m_overcast}, glm::vec4{olddiffuse, Global.gfx_ambient_groundalbedo}};
if (!m_dome_key || *m_dome_key != key)
{
m_dome_sh = dome_directional(Skydome, oldambient, olddiffuse, -Sunlight.direction.y);
m_dome_key = key;
}
directional = m_dome_sh;
}
m_sunlight = olddiffuse;
m_ambient = oldambient;
m_fog = oldfog;
m_sun_disc_color = glm::vec3{1.f};
if (m_weight > 0.f)
{
auto const &result{*m_result};
// the camera's white balance: the sun high in the sky is white
auto const balance{1.f / glm::max(glm::vec3{result.zenith_transmittance}, glm::vec3{1e-3f})};
m_white_balance = balance / std::max(luminance(balance), 1e-4f);
// the atmosphere has no clouds: the overcast takes away the sunlight, greys the sky and evens out its light
// with the shadows of the clouds the sun is held back where they are, not everywhere
auto const overcastsun{m_cloud_shadows ? 1.f : (1.f - 0.65f * m_overcast)};
auto const sun{glm::vec3{result.sun_transmittance} * m_white_balance * (ATMOSPHERE_SUN_SCALE * Global.gfx_atmosphere_sun) * overcastsun};
auto const ambientscale{ATMOSPHERE_AMBIENT_SCALE * Global.gfx_atmosphere_ambient};
auto const ambient{desaturate(glm::vec3{result.radiance_sh[0]} * SH_Y00 * m_white_balance * ambientscale, 0.5f * m_overcast)};
m_sky_scale = ATMOSPHERE_SKY_SCALE * Global.gfx_atmosphere_sky;
auto const fog{desaturate(glm::vec3{result.horizon} * m_white_balance * m_sky_scale, 0.5f * m_overcast)};
m_sky_flat_luminance = luminance(glm::vec3{result.average} * m_white_balance * m_sky_scale);
m_sunlight = glm::mix(olddiffuse, sun, m_weight);
m_ambient = glm::mix(oldambient, ambient, m_weight);
m_fog = glm::mix(oldfog, fog, m_weight);
auto const sundisc{glm::vec3{result.sun_transmittance} * m_white_balance};
m_sun_disc_color = sundisc / std::max({sundisc.r, sundisc.g, sundisc.b, 1e-4f});
if (Global.gfx_ambient_sh)
{
auto const evenness{1.f - 0.75f * m_overcast};
for (std::size_t c = 1; c < directional.size(); ++c)
{
auto const atmosphere{desaturate(glm::vec3{result.radiance_sh[c]} * m_white_balance * (SH_IRRADIANCE_BAND[SH_BAND[c]] * ambientscale * evenness), 0.5f * m_overcast)};
directional[c] = glm::mix(directional[c], atmosphere, m_weight);
}
}
Sunlight.diffuse = glm::vec4{m_sunlight, Sunlight.diffuse.a};
Sunlight.ambient = glm::vec4{m_ambient, Sunlight.ambient.a};
Fogcolor = m_fog;
m_light.set_result(result);
}
m_light.end_update(Sunlight, Fogcolor, Skydome, Sundirection, Sunangle);
// directional part of the ambient light for the shading
gl::sky_data skydata{};
for (std::size_t c = 1; c < directional.size(); ++c)
skydata.ambient_sh[c] = glm::vec4{directional[c] * Global.gfx_ambient_sh_strength, 0.f};
auto const &skydata{m_light.sky_data()};
if (!m_sky_data_uploaded || std::memcmp(&skydata, &m_sky_data, sizeof(skydata)) != 0)
{
m_sky_data = skydata;
@@ -351,14 +141,12 @@ void opengl46_sky::update(opengl46_light &Sunlight, glm::vec3 &Fogcolor, CSkyDom
void opengl46_sky::setup_sky(gl::model_ubs &Model) const
{
glBindTextureUnit(gl::SKY_VIEW_TEX, m_skyview_table);
// the volumetric clouds make the overcast sky themselves
Model.param[0] = glm::vec4{m_weight, m_sky_scale, m_clouds ? 0.f : 0.75f * m_overcast, m_sky_flat_luminance};
Model.param[1] = glm::vec4{m_white_balance, 0.f};
m_light.sky_params(Model.param[0], Model.param[1]);
}
glm::vec3 opengl46_sky::sun_disc_color(glm::vec3 const &Oldcolor) const
{
return glm::mix(Oldcolor, m_sun_disc_color, m_weight);
return m_light.sun_disc_color(Oldcolor);
}
void opengl46_sky::prefilter(gl::cubemap const &Source)
@@ -402,19 +190,5 @@ std::string opengl46_sky::info_times()
void opengl46_sky::draw_debug_ui()
{
ImGui::Checkbox("directional ambient light (gfx.ambient.sh)", &Global.gfx_ambient_sh);
ImGui::SliderFloat("directional strength", &Global.gfx_ambient_sh_strength, 0.f, 2.f);
ImGui::SliderFloat("ground albedo", &Global.gfx_ambient_groundalbedo, 0.f, 1.f);
ImGui::Checkbox("GGX prefiltered reflections (gfx.envmap.ggx)", &Global.gfx_envmap_ggx);
ImGui::Checkbox("physical atmosphere (gfx.atmosphere)", &Global.gfx_atmosphere);
ImGui::SliderFloat("sky", &Global.gfx_atmosphere_sky, 0.f, 4.f);
ImGui::SliderFloat("ambient", &Global.gfx_atmosphere_ambient, 0.f, 4.f);
ImGui::SliderFloat("sun", &Global.gfx_atmosphere_sun, 0.f, 4.f);
ImGui::SliderFloat("haze", &Global.gfx_atmosphere_haze, 0.f, 8.f);
ImGui::SliderFloat("altitude (m)", &Global.gfx_atmosphere_altitude, 0.f, 3000.f);
ImGui::Text("atmosphere share %.2f%s", m_weight, (Global.gfx_atmosphere && !m_result) ? " (waiting for the first read back)" : "");
ImGui::Text("sun %.3f %.3f %.3f", m_sunlight.r, m_sunlight.g, m_sunlight.b);
ImGui::Text("ambient %.3f %.3f %.3f", m_ambient.r, m_ambient.g, m_ambient.b);
ImGui::Text("fog %.3f %.3f %.3f", m_fog.r, m_fog.g, m_fog.b);
ImGui::Text("up / down ambient %.3f / %.3f", luminance(m_ambient + glm::vec3{m_sky_data.ambient_sh[1]} * SH_Y1), luminance(m_ambient - glm::vec3{m_sky_data.ambient_sh[1]} * SH_Y1));
m_light.draw_debug_ui();
}

View File

@@ -21,6 +21,7 @@ http://mozilla.org/MPL/2.0/.
#include "gl/query.h"
#include "gl/readback.h"
#include "rendering/opengl46/opengl46light.h"
#include "rendering/common/skylight.h"
class CSkyDome;
@@ -32,7 +33,8 @@ class CSkyDome;
// them (sky_atmosphere.frag), and the sunlight, ambient light and fog colour read back from them a few frames later.
// these are values for the render only: the simulation (Global.DayLight, fLuminance) keeps its own
// - the environment map prefiltered with the GGX lobe, for rough reflections (envmap_prefilter.comp)
// all of it is switched by Global.gfx_ambient_sh, gfx_atmosphere and gfx_envmap_ggx; off, the renderer works as before
// all of it is switched by Global.gfx_ambient_sh, gfx_atmosphere and gfx_envmap_ggx; off, the renderer works as before.
// what isn't the work of the gpu is the light of the sky shared with the vulkan renderer (gfx::sky_light)
class opengl46_sky
{
public:
@@ -52,7 +54,7 @@ class opengl46_sky
// share of the atmosphere in the sky of the frame; 0 when the old sky is drawn alone
float atmosphere_weight() const
{
return m_weight;
return m_light.atmosphere_weight();
}
// the sky-view table of the atmosphere, its white balance, brightness and the overcast it was drawn with, for the colours of
// the fog (opengl46_weather)
@@ -62,22 +64,21 @@ class opengl46_sky
}
glm::vec3 const &white_balance() const
{
return m_white_balance;
return m_light.white_balance();
}
float sky_scale() const
{
return m_sky_scale;
return m_light.sky_scale();
}
float overcast() const
{
return m_overcast;
return m_light.overcast();
}
// the volumetric clouds are drawn over the sky (it doesn't take the overcast look then), and their shadows hold back the
// sunlight (it isn't dimmed by the overcast then)
void set_clouds(bool const Clouds, bool const Shadows)
{
m_clouds = Clouds;
m_cloud_shadows = Shadows;
m_light.set_clouds(Clouds, Shadows);
}
// sets the shader parameters of the sky drawn with the atmosphere (sky_atmosphere.frag) and binds its table
void setup_sky(gl::model_ubs &Model) const;
@@ -96,11 +97,6 @@ class opengl46_sky
void draw_debug_ui();
private:
// spherical harmonics of order 2, rgb
using sh_coefficients = std::array<glm::vec3, gl::SKY_SH_COEFFICIENTS>;
// directional part of the ambient light from the colours of the dome, scaled so its average is Ambient
sh_coefficients dome_directional(CSkyDome const &Skydome, glm::vec3 const &Ambient, glm::vec3 const &Sunlight, float const Sunheight) const;
// sets up the parameters of the atmosphere for the frame and runs the passes whose input changed
void update_atmosphere(glm::vec3 const &Sundirection);
// creates an immutable 2d texture for a look-up table
@@ -121,31 +117,11 @@ class opengl46_sky
GLuint m_transmittance_table{0};
GLuint m_multiscattering_table{0};
GLuint m_skyview_table{0};
gl::atmosphere_ubs m_atmosphere{};
// inputs the tables were last made for: the transmittance and multiple scattering tables depend on the medium only,
// the sky-view table also on the sun
std::optional<glm::vec4> m_tables_medium;
std::optional<glm::vec3> m_tables_sun;
gl::readback m_result_readback{sizeof(gl::atmosphere_result)};
std::optional<gl::atmosphere_result> m_result;
gl::gpu_timer m_atmosphere_timer;
// what update() made of the frame, for the sky, the sun disc and the debug window
float m_weight{0.f};
glm::vec3 m_white_balance{1.f};
float m_sky_scale{0.f};
float m_overcast{0.f};
bool m_clouds{false};
bool m_cloud_shadows{false};
float m_sky_flat_luminance{0.f};
glm::vec3 m_sun_disc_color{1.f};
glm::vec3 m_sunlight{0.f};
glm::vec3 m_ambient{0.f};
glm::vec3 m_fog{0.f};
// cache of the directional ambient light of the dome, remade when its inputs change
std::optional<std::array<glm::vec4, 3>> m_dome_key;
sh_coefficients m_dome_sh{};
// the light of the sky, shared with the vulkan renderer
gfx::sky_light m_light;
// prefiltered environment map
std::unique_ptr<gl::program> m_prefilter_program;

View File

@@ -17,13 +17,6 @@ http://mozilla.org/MPL/2.0/.
namespace
{
int constexpr initial_capacity{8};
int constexpr copy_attempts{600}; // frames to wait for the textures of a layer to load before it's left with what it has
float constexpr default_repeat{8.f};
// array textures: diffuse, normal maps, specular/gloss maps
std::array<int, 3> constexpr array_sizes{1024, 512, 256};
void bind_for_upload(opengl_texture const &Texture)
{
::glActiveTexture(GL_TEXTURE0);
@@ -38,11 +31,8 @@ void set_anisotropy(GLenum const Target)
} // namespace
bool opengl46_terrain_materials::init()
bool opengl46_terrain_materials::gpu_init()
{
if (m_state != 0)
return m_state > 0;
m_state = -1;
try
{
m_copy = std::make_unique<gl::postfx>(gl::shader("copy.frag"));
@@ -53,151 +43,56 @@ bool opengl46_terrain_materials::init()
return false;
}
m_framebuffer = std::make_unique<gl::framebuffer>();
m_white = make_texture("internal_src:terrain/white", GL_TEXTURE_2D);
{
auto const &white{m_textures.texture(m_white)};
std::uint8_t const texel[4]{255, 255, 255, 255};
bind_for_upload(white);
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, texel);
}
char const *names[]{"internal_src:terrain/layers_diffuse", "internal_src:terrain/layers_normal", "internal_src:terrain/layers_specgloss"};
for (std::size_t i = 0; i < m_arrays.size(); ++i)
{
auto &array{m_arrays[i]};
array.handle = make_texture(names[i], GL_TEXTURE_2D_ARRAY);
array.size = array_sizes[i];
array.srgb = (i == 0);
array.format = (array.srgb ? GL_SRGB8_ALPHA8 : GL_RGBA8);
make_storage(array, initial_capacity);
}
m_capacity = initial_capacity;
opengl_texture::reset_unit_cache();
// the shader is checked now, so a broken one makes the terrain fall back on plain materials instead of the failure indicator
try
{
m_program = GfxRenderer->Fetch_Shader("terrain");
}
catch (gl::shader_exception const &e)
{
ErrorLog("heightmap terrain: can't use the terrain shader: " + std::string(e.what()));
return false;
}
m_state = 1;
return true;
}
texture_handle opengl46_terrain_materials::make_texture(std::string const &Name, GLenum const Target)
void opengl46_terrain_materials::gpu_texture(texture_source &Texture, texture_kind const Kind)
{
auto const handle{m_textures.create(Name, false, GL_RGBA)};
auto &texture{m_textures.texture(handle)};
auto &texture{static_cast<opengl_texture &>(Texture)};
if (texture.id == opengl_texture::invalid_id)
::glGenTextures(1, &texture.id);
texture.target = Target;
// the content is supplied here, the texture manager isn't to load, release or replace it
texture.is_ready = true;
texture.is_static = true;
texture.is_rendertarget = true;
texture.is_texstub = false;
texture.data_state = resource_state::good;
return handle;
texture.target = (Kind == texture_kind::layers ? GL_TEXTURE_2D_ARRAY : GL_TEXTURE_2D);
}
void opengl46_terrain_materials::make_storage(texture_array &Array, int const Layers)
void opengl46_terrain_materials::gpu_white(texture_source &Texture)
{
auto &texture{m_textures.texture(Array.handle)};
auto const &white{static_cast<opengl_texture &>(Texture)};
std::uint8_t const texel[4]{255, 255, 255, 255};
bind_for_upload(white);
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, texel);
}
void opengl46_terrain_materials::gpu_storage(texture_array const &Array, int const Layers, bool const Fresh)
{
auto &texture{m_gltextures.texture(Array.handle)};
if (Fresh)
{
::glDeleteTextures(1, &texture.id);
::glGenTextures(1, &texture.id);
}
auto const format{Array.srgb ? GL_SRGB8_ALPHA8 : GL_RGBA8};
bind_for_upload(texture);
::glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
::glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
::glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_REPEAT);
::glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_REPEAT);
set_anisotropy(GL_TEXTURE_2D_ARRAY);
::glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, Array.format, Array.size, Array.size, Layers, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
::glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, format, Array.size, Array.size, Layers, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
texture.data_width = Array.size;
texture.data_height = Array.size;
texture.layers = Layers;
texture.data_format = Array.format;
texture.data_format = format;
texture.data_components = GL_RGBA;
}
void opengl46_terrain_materials::read_material(layer &Layer)
void opengl46_terrain_materials::gpu_blank(slot const &Slot)
{
auto const &material{m_materials.material(Layer.material)};
Layer.repeat = (Layer.size > 0.f ? Layer.size : (material.size.x > 0.f ? material.size.x : default_repeat));
Layer.normalmap = (material.shader && material.shader->texture_conf.count("normalmap") > 0);
Layer.specgloss = (material.shader && material.shader->texture_conf.count("specgloss") > 0);
Layer.reflection = (std::isfinite(material.params[1].z) ? material.params[1].z : 0.f);
}
int opengl46_terrain_materials::layer_index(gfx::terrain_layer const &Layer)
{
for (auto const &existing : m_layers)
if (existing.material == Layer.material && existing.size == Layer.size)
return existing.index;
layer entry;
entry.material = Layer.material;
entry.size = Layer.size;
entry.index = static_cast<int>(m_layers.size());
read_material(entry);
m_layers.push_back(entry);
m_pending = true;
if (static_cast<int>(m_layers.size()) > m_capacity)
{
// more room: the arrays are made anew, and every layer copied again
auto const capacity{m_capacity * 2};
for (auto &array : m_arrays)
{
auto &texture{m_textures.texture(array.handle)};
::glDeleteTextures(1, &texture.id);
::glGenTextures(1, &texture.id);
make_storage(array, capacity);
}
m_capacity = capacity;
for (auto &existing : m_layers)
{
existing.copied = false;
existing.attempts = 0;
}
opengl_texture::reset_unit_cache();
}
return entry.index;
}
std::size_t opengl46_terrain_materials::acquire_slot(material_handle const Reuse)
{
if (Reuse != null_handle)
{
auto const lookup{m_slotof.find(Reuse)};
if (lookup != m_slotof.end())
{
m_slots[lookup->second].used = true;
return lookup->second;
}
}
if (false == m_free.empty())
{
auto const index{m_free.back()};
m_free.pop_back();
m_slots[index].used = true;
return index;
}
auto const index{m_slots.size()};
auto const suffix{std::to_string(index)};
slot entry;
entry.weights[0] = make_texture("internal_src:terrain/weights0_" + suffix, GL_TEXTURE_2D);
entry.weights[1] = make_texture("internal_src:terrain/weights1_" + suffix, GL_TEXTURE_2D);
entry.table = make_texture("internal_src:terrain/table_" + suffix, GL_TEXTURE_2D);
entry.holes = make_texture("internal_src:terrain/holes_" + suffix, GL_TEXTURE_2D);
// the textures get their content before the material is made of them, which checks they're there
std::uint8_t const blank[4]{0, 0, 0, 0};
for (auto const handle : entry.weights)
for (auto const handle : Slot.weights)
{
bind_for_upload(m_textures.texture(handle));
bind_for_upload(m_gltextures.texture(handle));
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
@@ -206,7 +101,7 @@ std::size_t opengl46_terrain_materials::acquire_slot(material_handle const Reuse
::glGenerateMipmap(GL_TEXTURE_2D);
}
{
bind_for_upload(m_textures.texture(entry.table));
bind_for_upload(m_gltextures.texture(Slot.table));
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
std::array<float, max_layers * 2 * 4> const zeros{};
@@ -214,7 +109,7 @@ std::size_t opengl46_terrain_materials::acquire_slot(material_handle const Reuse
}
{
// led between the samples, so the edges of the holes run smoothly across the cells
bind_for_upload(m_textures.texture(entry.holes));
bind_for_upload(m_gltextures.texture(Slot.holes));
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
@@ -224,281 +119,89 @@ std::size_t opengl46_terrain_materials::acquire_slot(material_handle const Reuse
::glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, 1, 1, 0, GL_RED, GL_UNSIGNED_BYTE, &none);
::glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
}
opengl_texture::reset_unit_cache();
auto const name = [&](texture_handle const Handle) { return "internal_src:" + m_textures.texture(Handle).name; };
std::string const definition{
"shader: terrain\n"
"texture1: " + name(m_white) + "\n"
"texture2: " + name(entry.weights[0]) + "\n"
"texture3: " + name(entry.weights[1]) + "\n"
"texture4: " + name(m_arrays[0].handle) + "\n"
"texture5: " + name(m_arrays[1].handle) + "\n"
"texture6: " + name(m_arrays[2].handle) + "\n"
"texture7: " + name(entry.table) + "\n"
"texture8: " + name(entry.holes) + "\n"};
entry.material = m_materials.create_from_text("internal_src:terrain/chunk_" + suffix, definition);
if (entry.material == null_handle || false == m_materials.material(entry.material).is_good)
{
ErrorLog("heightmap terrain: can't make the material of a chunk");
m_state = -1;
return static_cast<std::size_t>(-1);
}
entry.used = true;
m_slots.push_back(entry);
m_slotof.emplace(entry.material, index);
return index;
}
material_handle opengl46_terrain_materials::make(material_handle const Reuse, std::vector<gfx::terrain_layer> const &Layers, int const Samples, std::uint8_t const *Weights, glm::vec3 const &Placement,
int const HoleSamples, std::uint8_t const *Holes)
void opengl46_terrain_materials::gpu_weights(texture_handle const Texture, int const Side, std::uint8_t const *Texels)
{
if (Layers.empty() || false == init())
return null_handle;
auto const count{std::min(Layers.size(), max_layers)};
std::array<int, max_layers> indices{};
for (std::size_t i = 0; i < count; ++i)
{
if (Layers[i].material == null_handle)
return null_handle;
indices[i] = layer_index(Layers[i]);
}
auto const slotindex{acquire_slot(Reuse)};
if (slotindex == static_cast<std::size_t>(-1))
return null_handle;
auto &entry{m_slots[slotindex]};
// weights, four layers to a texture
auto const painted{Weights != nullptr && Samples > 0 && count > 1};
auto const side{painted ? Samples + 1 : 1};
auto const samples{static_cast<std::size_t>(side) * side};
std::vector<std::uint8_t> texels(samples * 4);
for (std::size_t texture = 0; texture < 2; ++texture)
{
std::fill(texels.begin(), texels.end(), 0);
for (std::size_t channel = 0; channel < 4; ++channel)
{
auto const layer{texture * 4 + channel};
if (layer >= count)
break;
if (false == painted)
{
if (layer == 0)
texels[channel] = 255;
continue;
}
auto const *source{Weights + layer * samples};
for (std::size_t i = 0; i < samples; ++i)
texels[i * 4 + channel] = source[i];
}
bind_for_upload(m_textures.texture(entry.weights[texture]));
::glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, side, side, 0, GL_RGBA, GL_UNSIGNED_BYTE, texels.data());
::glGenerateMipmap(GL_TEXTURE_2D);
}
entry.count = count;
entry.layers = indices;
for (std::size_t i = 0; i < count; ++i)
entry.rotations[i] = Layers[i].rotation;
write_table(entry);
// the holes: the cells cut out entirely are left out of the mesh, the program of the holes cuts the rest of the openings
auto const cut{Holes != nullptr && HoleSamples > 0};
{
auto const holeside{cut ? HoleSamples + 1 : 1};
std::uint8_t const none{0};
bind_for_upload(m_textures.texture(entry.holes));
::glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
::glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, holeside, holeside, 0, GL_RED, GL_UNSIGNED_BYTE, cut ? Holes : &none);
::glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
opengl_texture::reset_unit_cache();
}
auto &material{m_materials.material(entry.material)};
material.params[2] = glm::vec4(Placement.x, Placement.y, 1.f / std::max(1.f, Placement.z), static_cast<float>(side - 1));
if (m_holesprogram == nullptr && cut)
{
try
{
m_holesprogram = GfxRenderer->Fetch_Shader("terrain_holes");
}
catch (gl::shader_exception const &e)
{
ErrorLog("heightmap terrain: can't use the shader of the holes: " + std::string(e.what()));
}
}
// the program of the holes discards fragments, which the depth prepass of the plain one never does
material.shader = (cut && m_holesprogram != nullptr) ? m_holesprogram : m_program;
return entry.material;
bind_for_upload(m_gltextures.texture(Texture));
::glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, Side, Side, 0, GL_RGBA, GL_UNSIGNED_BYTE, Texels);
::glGenerateMipmap(GL_TEXTURE_2D);
}
void opengl46_terrain_materials::write_table(slot const &Slot)
void opengl46_terrain_materials::gpu_table(texture_handle const Texture, float const *Table)
{
bind_for_upload(m_gltextures.texture(Texture));
::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, static_cast<GLsizei>(max_layers), 2, 0, GL_RGBA, GL_FLOAT, Table);
}
void opengl46_terrain_materials::gpu_holes(texture_handle const Texture, int const Side, std::uint8_t const *Samples)
{
bind_for_upload(m_gltextures.texture(Texture));
::glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
::glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, Side, Side, 0, GL_RED, GL_UNSIGNED_BYTE, Samples);
::glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
}
void opengl46_terrain_materials::gpu_changed()
{
// what the layers are: index in the arrays, repeat, which maps they have, reflection
std::array<float, max_layers * 2 * 4> table{};
for (std::size_t i = 0; i < Slot.count; ++i)
{
auto const &layer{m_layers[Slot.layers[i]]};
table[i * 4 + 0] = static_cast<float>(layer.index);
table[i * 4 + 1] = 1.f / std::max(0.01f, layer.repeat);
table[i * 4 + 2] = layer.normalmap ? 1.f : 0.f;
table[i * 4 + 3] = layer.specgloss ? 1.f : 0.f;
table[(max_layers + i) * 4 + 0] = layer.reflection;
// the textures turned by the layer of the chunk (the same material can be turned differently in another layer)
auto const angle{glm::radians(static_cast<double>(Slot.rotations[i]))};
table[(max_layers + i) * 4 + 1] = static_cast<float>(std::cos(angle));
table[(max_layers + i) * 4 + 2] = static_cast<float>(std::sin(angle));
}
bind_for_upload(m_textures.texture(Slot.table));
::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, static_cast<GLsizei>(max_layers), 2, 0, GL_RGBA, GL_FLOAT, table.data());
opengl_texture::reset_unit_cache();
}
void opengl46_terrain_materials::on_environment_change()
void opengl46_terrain_materials::gpu_begin_copies()
{
if (m_state <= 0)
return;
bool changed{false};
for (auto &layer : m_layers)
{
auto const &material{m_materials.material(layer.material)};
if (false == (material.update_on_season_change || material.update_on_weather_change))
continue;
// the reloaded material can have other textures, other maps and another size
read_material(layer);
layer.copied = false;
layer.attempts = 0;
changed = true;
}
if (false == changed)
return;
m_pending = true;
// the tables of the chunks hold what the layers have, freed slots get theirs anew when they're used again
for (auto const &entry : m_slots)
if (entry.used)
write_table(entry);
}
void opengl46_terrain_materials::release(material_handle const Material)
{
auto const lookup{m_slotof.find(Material)};
if (lookup == m_slotof.end() || false == m_slots[lookup->second].used)
return;
m_slots[lookup->second].used = false;
m_free.push_back(lookup->second);
}
bool opengl46_terrain_materials::copy_layer(layer &Layer)
{
auto const &material{m_materials.material(Layer.material)};
auto const slot = [&](char const *Name, int const Fallback) {
if (material.shader)
{
auto const lookup{material.shader->texture_conf.find(Name)};
if (lookup != material.shader->texture_conf.end())
return static_cast<int>(lookup->second.id);
return -1;
}
return Fallback;
};
std::array<int, 3> const slots{slot("diffuse", 0), Layer.normalmap ? slot("normalmap", 1) : -1, Layer.specgloss ? slot("specgloss", 2) : -1};
// all maps of the layer have to be ready, so they go together
std::array<opengl_texture *, 3> sources{};
for (std::size_t i = 0; i < slots.size(); ++i)
{
if (slots[i] < 0 || material.textures[slots[i]] == null_handle)
continue;
auto &texture{m_textures.mark_as_used(material.textures[slots[i]])};
if (false == texture.is_ready)
{
// a texture which is on the card already may have dropped its data after the upload (create() would refuse it then),
// one released by the sweep or never read has to be read first
if (texture.data_state == resource_state::none)
{
texture.reload_on_use = false;
texture.load();
}
if (texture.data_state == resource_state::failed)
continue;
if (false == texture.create())
{
if (++Layer.attempts < copy_attempts)
return false;
WriteLog("heightmap terrain: texture \"" + texture.name + "\" isn't ready, the terrain goes without it");
continue; // waited long enough, the layer goes without it
}
}
sources[i] = &texture;
}
// defaults for maps the layer doesn't have: grey, flat, without specular reflections
std::array<std::array<float, 4>, 3> const defaults{{{0.5f, 0.5f, 0.5f, 1.f}, {0.5f, 0.5f, 1.f, 1.f}, {0.f, 1.f, 0.f, 1.f}}};
for (std::size_t i = 0; i < m_arrays.size(); ++i)
{
auto const &array{m_arrays[i]};
auto &target{m_textures.texture(array.handle)};
m_framebuffer->attach(target, GL_COLOR_ATTACHMENT0, Layer.index);
m_framebuffer->setup_drawing(1);
::glViewport(0, 0, array.size, array.size);
if (sources[i] == nullptr)
{
::glClearBufferfv(GL_COLOR, 0, defaults[i].data());
continue;
}
if (array.srgb)
::glEnable(GL_FRAMEBUFFER_SRGB);
else
::glDisable(GL_FRAMEBUFFER_SRGB);
m_copy->apply(*sources[i], m_framebuffer.get());
}
Layer.copied = true;
return true;
}
void opengl46_terrain_materials::update()
{
if (false == m_pending || m_state <= 0)
return;
auto const blend{::glIsEnabled(GL_BLEND)};
auto const scissor{::glIsEnabled(GL_SCISSOR_TEST)};
auto const cull{::glIsEnabled(GL_CULL_FACE)};
auto const depth{::glIsEnabled(GL_DEPTH_TEST)};
auto const srgb{::glIsEnabled(GL_FRAMEBUFFER_SRGB)};
m_saved.blend = ::glIsEnabled(GL_BLEND);
m_saved.scissor = ::glIsEnabled(GL_SCISSOR_TEST);
m_saved.cull = ::glIsEnabled(GL_CULL_FACE);
m_saved.depth = ::glIsEnabled(GL_DEPTH_TEST);
m_saved.srgb = ::glIsEnabled(GL_FRAMEBUFFER_SRGB);
::glDisable(GL_BLEND);
::glDisable(GL_SCISSOR_TEST);
::glDisable(GL_CULL_FACE);
}
bool copied{false};
m_pending = false;
for (auto &layer : m_layers)
void opengl46_terrain_materials::gpu_copy(texture_array const &Array, int const Layer, texture_source *Source, std::array<float, 4> const &Default)
{
auto &target{m_gltextures.texture(Array.handle)};
m_framebuffer->attach(target, GL_COLOR_ATTACHMENT0, Layer);
m_framebuffer->setup_drawing(1);
::glViewport(0, 0, Array.size, Array.size);
if (Source == nullptr)
{
if (layer.copied)
continue;
if (copy_layer(layer))
copied = true;
else
m_pending = true;
::glClearBufferfv(GL_COLOR, 0, Default.data());
return;
}
if (copied)
if (Array.srgb)
::glEnable(GL_FRAMEBUFFER_SRGB);
else
::glDisable(GL_FRAMEBUFFER_SRGB);
m_copy->apply(static_cast<opengl_texture &>(*Source), m_framebuffer.get());
}
void opengl46_terrain_materials::gpu_end_copies(bool const Copied)
{
if (Copied)
{
for (auto const &array : m_arrays)
for (auto const &array : arrays())
{
bind_for_upload(m_textures.texture(array.handle));
bind_for_upload(m_gltextures.texture(array.handle));
::glGenerateMipmap(GL_TEXTURE_2D_ARRAY);
}
}
// leave the state as the rest of the renderer knows it
gl::framebuffer::unbind();
if (blend)
if (m_saved.blend)
::glEnable(GL_BLEND);
if (scissor)
if (m_saved.scissor)
::glEnable(GL_SCISSOR_TEST);
if (cull)
if (m_saved.cull)
::glEnable(GL_CULL_FACE);
if (depth)
if (m_saved.depth)
::glEnable(GL_DEPTH_TEST);
else
::glDisable(GL_DEPTH_TEST);
if (srgb)
if (m_saved.srgb)
::glEnable(GL_FRAMEBUFFER_SRGB);
else
::glDisable(GL_FRAMEBUFFER_SRGB);

View File

@@ -9,91 +9,44 @@ http://mozilla.org/MPL/2.0/.
#pragma once
#include <array>
#include <memory>
#include <unordered_map>
#include <vector>
#include "rendering/renderer.h"
#include "model/material.h"
#include "model/Texture.h"
#include "rendering/common/terrainmaterials.h"
#include "gl/framebuffer.h"
#include "gl/postfx.h"
// materials of the heightmap terrain for the opengl 4.6 renderer (shader mat_terrain, mat_terrain_holes for chunks with holes).
// the textures of the layer materials are copied into texture arrays shared by all chunks; each chunk gets a material
// of its own, with textures holding the weights of its layers and a table of what its layers are
class opengl46_terrain_materials
// materials of the heightmap terrain for the opengl 4.6 renderer (shader mat_terrain, mat_terrain_holes for chunks with holes):
// the textures of gfx::terrain_materials as opengl textures, the layer maps copied into the arrays by a draw of the copy shader
class opengl46_terrain_materials : public gfx::terrain_materials
{
public:
opengl46_terrain_materials(material_manager &Materials, texture_manager &Textures) : m_materials(Materials), m_textures(Textures) {}
opengl46_terrain_materials(material_manager &Materials, texture_manager &Textures) : gfx::terrain_materials(Materials, Textures), m_gltextures(Textures) {}
// see gfx_renderer::Terrain_Material. returns null_handle if the terrain shader can't be used
material_handle make(material_handle const Reuse, std::vector<gfx::terrain_layer> const &Layers, int const Samples, std::uint8_t const *Weights, glm::vec3 const &Placement,
int const HoleSamples = 0, std::uint8_t const *Holes = nullptr);
void release(material_handle const Material);
// copies the textures of new layers into the arrays; to be called before the frame is drawn
void update();
// the layer materials which change with the season or the weather were reloaded, their textures are copied again
void on_environment_change();
protected:
bool gpu_init() override;
void gpu_texture(texture_source &Texture, texture_kind const Kind) override;
void gpu_white(texture_source &Texture) override;
void gpu_storage(texture_array const &Array, int const Layers, bool const Fresh) override;
void gpu_blank(slot const &Slot) override;
void gpu_weights(texture_handle const Texture, int const Side, std::uint8_t const *Texels) override;
void gpu_table(texture_handle const Texture, float const *Table) override;
void gpu_holes(texture_handle const Texture, int const Side, std::uint8_t const *Samples) override;
void gpu_begin_copies() override;
void gpu_copy(texture_array const &Array, int const Layer, texture_source *Source, std::array<float, 4> const &Default) override;
void gpu_end_copies(bool const Copied) override;
void gpu_changed() override;
private:
static constexpr std::size_t max_layers{8};
struct layer
{
material_handle material{null_handle};
float size{0.f}; // as given by the chunk
float repeat{8.f}; // metres the textures are repeated at
int index{0}; // in the arrays
bool normalmap{false};
bool specgloss{false};
float reflection{0.f};
bool copied{false};
int attempts{0};
};
struct texture_array
{
texture_handle handle{null_handle};
int size{0};
GLint format{0};
bool srgb{false};
};
struct slot
{
material_handle material{null_handle};
std::array<texture_handle, 2> weights{null_handle, null_handle};
texture_handle table{null_handle};
texture_handle holes{null_handle}; // where the ground is cut, R8; a single texel of 0 without holes
bool used{false};
// what the table was made of, kept to make it again when a layer changes
std::size_t count{0};
std::array<int, max_layers> layers{};
std::array<float, max_layers> rotations{};
};
bool init();
texture_handle make_texture(std::string const &Name, GLenum const Target);
void make_storage(texture_array &Array, int const Layers);
void read_material(layer &Layer);
int layer_index(gfx::terrain_layer const &Layer);
void write_table(slot const &Slot);
bool copy_layer(layer &Layer);
std::size_t acquire_slot(material_handle const Reuse);
material_manager &m_materials;
texture_manager &m_textures;
int m_state{0}; // 0: not set up yet, 1: ready, -1: can't be used
texture_handle m_white{null_handle};
std::array<texture_array, 3> m_arrays; // diffuse, normal maps, specular/gloss
int m_capacity{0}; // layers the arrays have room for
std::vector<layer> m_layers;
bool m_pending{false};
std::vector<slot> m_slots;
std::vector<std::size_t> m_free;
std::unordered_map<material_handle, std::size_t> m_slotof;
std::shared_ptr<gfx::shader_program> m_program; // of the chunks, mat_terrain
std::shared_ptr<gfx::shader_program> m_holesprogram; // of the chunks with holes, mat_terrain_holes
texture_manager &m_gltextures;
std::unique_ptr<gl::postfx> m_copy;
std::unique_ptr<gl::framebuffer> m_framebuffer;
// the state of opengl the copies change, given back after them
struct
{
GLboolean blend{GL_FALSE};
GLboolean scissor{GL_FALSE};
GLboolean cull{GL_FALSE};
GLboolean depth{GL_FALSE};
GLboolean srgb{GL_FALSE};
} m_saved;
};

View File

@@ -11,14 +11,11 @@ http://mozilla.org/MPL/2.0/.
#include "rendering/opengl46/opengl46weather.h"
#include "rendering/opengl46/opengl46sky.h"
#include "environment/starcatalog.h"
#include "environment/weather.h"
#include "simulation/simulationenvironment.h"
#include "model/Texture.h"
#include "utilities/Globals.h"
#include "utilities/Timer.h"
#include "utilities/utilities.h"
#include "widgets/weatherpanel.h"
namespace
{
@@ -27,47 +24,6 @@ namespace
int const SHAPE_NOISE_SIZE{128};
int const DETAIL_NOISE_SIZE{32};
int const COVERAGE_NOISE_SIZE{512};
// how many times the cell of a genus fits in the tile of the shape noise, and its spread in the tile of the coverage noise
double const SHAPE_TILE_CELLS{4.0};
double const COVERAGE_TILE_SPREADS{4.0};
// size of the patches of the ground fog
float const FOG_PATCH_SIZE{300.f};
// the haze the atmosphere draws in the sky already, as an extinction: about the haze of a day with 30 km of visibility
float const SKY_HAZE_EXTINCTION{3.f / 30000.f};
// near end of the fog volume (view depth, metres)
float const FOG_VOLUME_NEAR{0.5f};
// bands the panorama is drawn in, one a frame
int const PANORAMA_BANDS{8};
// brightness of a star of magnitude 0 in the units of the scene
float const STAR_BRIGHTNESS{4.f};
// stars dimmer than this aren't drawn
float const STAR_LIMIT{6.5f};
float luminance(glm::vec3 const &Color)
{
return glm::dot(Color, glm::vec3{0.2126f, 0.7152f, 0.0722f});
}
// the position in a tiling noise, of a period in metres, kept small for the single precision of the shaders
double wrap(double const Value, double const Period)
{
return Value - Period * std::floor(Value / Period);
}
// the colour of a star of a colour temperature, linear, of luminance 1, somewhat paler than the black body: the eye sees most
// stars white
glm::vec3 star_color(float const Temperature)
{
auto const t{std::clamp(Temperature, 1500.f, 40000.f) / 100.f};
glm::vec3 color;
color.r = (t <= 66.f) ? 255.f : 329.698727446f * std::pow(t - 60.f, -0.1332047592f);
color.g = (t <= 66.f) ? 99.4708025861f * std::log(t) - 161.1195681661f : 288.1221695283f * std::pow(t - 60.f, -0.0755148492f);
color.b = (t >= 66.f) ? 255.f : (t <= 19.f) ? 0.f : 138.5177312231f * std::log(t - 10.f) - 305.0447927307f;
color = glm::pow(glm::clamp(color / 255.f, glm::vec3{0.f}, glm::vec3{1.f}), glm::vec3{2.2f});
color /= std::max(luminance(color), 1e-3f);
return glm::mix(color, glm::vec3{1.f}, 0.5f);
}
std::unique_ptr<gl::program> make_compute(std::string const &Name)
{
gl::shader shader(Name);
@@ -118,26 +74,6 @@ GLuint opengl46_weather::create_texture(GLenum const Target, GLenum const Format
return texture;
}
opengl46_weather::quality_level opengl46_weather::quality(int const Clouds, int const Fog)
{
quality_level level;
switch (std::clamp(Clouds, 1, 4))
{
case 1: level = {4, 32, 3, false, {512, 128}, {}}; break;
case 2: level = {2, 48, 4, true, {1024, 256}, {}}; break;
case 3: level = {2, 80, 6, true, {1024, 256}, {}}; break;
default: level = {2, 128, 8, true, {2048, 512}, {}}; break;
}
switch (std::clamp(Fog, 1, 4))
{
case 1: level.fog_grid = {80, 45, 48}; break;
case 2: level.fog_grid = {160, 90, 64}; break;
case 3: level.fog_grid = {160, 90, 96}; break;
default: level.fog_grid = {240, 135, 128}; break;
}
return level;
}
void opengl46_weather::init()
{
m_noise_program = make_compute("clouds_noise.comp");
@@ -150,32 +86,12 @@ void opengl46_weather::init()
m_sky_program = make_program("quad.vert", "sky_weather.frag");
m_stars_program = make_program("stars.vert", "stars.frag");
// a grey fog and no clouds until the first update
for (auto &color : m_weather.fog_horizon)
color = glm::vec4{0.5f, 0.5f, 0.5f, 1.f};
m_weather.fog_haze = glm::vec4{3.f / 10000.f, 1.f / 800.f, SKY_HAZE_EXTINCTION, 1.f};
m_weather.fog_light = glm::vec4{0.f, 1.f, 0.6f, 1.f / 5000.f};
m_weather_ubo = std::make_unique<gl::ubo>(sizeof(gl::weather_ubs), gl::WEATHER_UBO_BINDING);
m_weather_ubo->update(m_weather);
m_weather_ubo->update(m_state.block());
make_noise();
// the stars, in the equatorial frame
std::vector<glm::vec4> stars;
auto const *catalogue{star_catalogue::stars()};
for (std::size_t i = 0; i < star_catalogue::size(); ++i)
{
auto const &star{catalogue[i]};
auto const magnitude{static_cast<float>(star.magnitude) * 0.01f};
if (magnitude > STAR_LIMIT)
continue;
auto const ascension{glm::radians(static_cast<float>(star.right_ascension) * 0.01f)};
auto const declination{glm::radians(static_cast<float>(star.declination) * 0.01f)};
stars.emplace_back(std::cos(declination) * std::cos(ascension), std::cos(declination) * std::sin(ascension), std::sin(declination), magnitude);
// a phase of the twinkling which doesn't repeat from star to star
auto const phase{std::fmod(static_cast<float>(i) * 0.6180339887f, 1.f)};
stars.emplace_back(star_color(static_cast<float>(star.temperature) * 10.f), phase);
}
auto const stars{gfx::weather_state::star_vertices()};
m_stars_count = static_cast<int>(stars.size() / 2);
m_stars_vao.emplace();
m_stars_vao->bind();
@@ -249,105 +165,15 @@ void opengl46_weather::bind_noise() const
void opengl46_weather::upload_header()
{
// the colours of the horizon are left alone when the gpu writes them
auto const size{m_horizon_on_gpu ? offsetof(gl::weather_ubs, fog_horizon) : sizeof(gl::weather_ubs)};
m_weather_ubo->update(reinterpret_cast<uint8_t const *>(&m_weather), 0, static_cast<GLsizeiptr>(size));
auto const size{m_state.horizon_on_gpu() ? offsetof(gl::weather_ubs, fog_horizon) : sizeof(gl::weather_ubs)};
m_weather_ubo->update(reinterpret_cast<uint8_t const *>(&m_state.block()), 0, static_cast<GLsizeiptr>(size));
m_weather_ubo->bind_uniform();
}
void opengl46_weather::update(opengl46_sky const &Sky, glm::vec3 const &Keylight, glm::vec3 const &Keycolor, glm::vec3 const &Ambient, glm::vec3 const &Fogcolor, glm::dvec3 const &Camera)
{
auto const &weather{simulation::Environment.weather()};
auto const &conditions{weather.conditions()};
m_fog_volume_valid = false;
m_clouds_view_valid = false;
++m_frame;
auto const height{static_cast<float>(Camera.y) - weather.ground()};
auto const time{weather.time()};
auto const sun{glm::normalize(Keylight)};
// the fog. the visibility is where a dark object fades to 5%: the extinction is 3 over it
auto const haze{3.f / conditions.visibility};
auto const hazerate{1.f / conditions.haze_height};
m_weather.fog_haze = glm::vec4{haze, hazerate, SKY_HAZE_EXTINCTION, 1.f};
if (conditions.groundfog_thickness > 0.f)
{
auto const thickness{conditions.groundfog_thickness};
m_weather.fog_ground = glm::vec4{std::max(3.f / conditions.groundfog_visibility - haze, 0.f), 1.f / (0.25f * thickness), 0.75f * thickness, conditions.fog_noise};
}
else
m_weather.fog_ground = glm::vec4{0.f, 1.f, 0.f, conditions.fog_noise};
// the patches drift with the wind, slower than the clouds
auto const drift{glm::dvec2{weather.cloud_offset()} * 0.3};
auto const patch{static_cast<double>(FOG_PATCH_SIZE)};
m_weather.fog_noise = glm::vec4{static_cast<float>(wrap(Camera.x - drift.x, patch)), static_cast<float>(wrap(Camera.y, patch)), static_cast<float>(wrap(Camera.z - drift.y, patch)), 1.f / FOG_PATCH_SIZE};
// the extinction at the height of the camera tells how far things are hidden
auto const extinction{haze * std::exp(-hazerate * std::max(height, 0.f)) +
m_weather.fog_ground.x * std::exp(-m_weather.fog_ground.y * std::max(height - m_weather.fog_ground.z, 0.f))};
// 2% left at 1.3 visibilities; the draw range is twice this
m_fog_range = std::clamp(3.f / std::max(extinction, 1e-7f) / 1.5f, 5.f, 1e6f);
// the share of the light of the fog the sun gives, which the shadows hold back
auto const sunlight{luminance(Keycolor) * 1.5f * std::max(sun.y, 0.f)};
auto const skylight{luminance(Ambient) * 0.65f};
m_sun_share = (sunlight + skylight > 1e-6f) ? sunlight / (sunlight + skylight) : 0.f;
// clouds
auto const layers{std::min(conditions.clouds.size(), gl::MAX_CLOUD_LAYERS)};
m_clouds_drawn = (Global.gfx_clouds > 0) && (layers > 0);
m_average_transmittance = 1.f;
auto const velocity{weather.cloud_velocity()};
auto const speed{glm::length(velocity)};
auto const wind{speed > 1e-3f ? glm::dvec2{velocity / speed} : glm::dvec2{1.0, 0.0}};
m_weather.cloud_wind = glm::vec4{static_cast<float>(wind.x), static_cast<float>(wind.y), speed, m_cloud_detail_scale};
for (std::size_t i = 0; i < gl::MAX_CLOUD_LAYERS; ++i)
{
auto &target{m_weather.cloud_layers[i]};
if (i >= layers)
{
target = gl::cloud_layer_ubs{};
continue;
}
auto const &layer{conditions.clouds[i]};
auto const &genus{cloud_genus_info(layer.type)};
target.heights = glm::vec4{layer.base, layer.top, 1.f / std::max(layer.top - layer.base, 1.f), static_cast<float>(layer.type)};
auto const shapetile{static_cast<double>(genus.cell) * SHAPE_TILE_CELLS};
auto const coveragetile{static_cast<double>(genus.spread) * COVERAGE_TILE_SPREADS};
target.shape = glm::vec4{layer.coverage, genus.extinction * layer.density, static_cast<float>(1.0 / shapetile), genus.erosion};
target.profile = glm::vec4{genus.bottom, genus.top, genus.top_variation, genus.anvil};
// the higher, the faster the wind carries them
auto const offset{glm::dvec2{weather.cloud_offset()} * (1.0 + static_cast<double>(layer.base) / 8000.0)};
// the camera's place in the noise, in the space stretched along the wind as the shaders take it
auto const place{glm::dvec2{Camera.x, Camera.z} - offset};
auto const along{glm::dot(place, wind)};
auto const stretched{(place - wind * along) + wind * (along / std::max(static_cast<double>(genus.stretch), 1.0))};
target.motion = glm::vec4{static_cast<float>(wrap(stretched.x, shapetile)), static_cast<float>(wrap(stretched.y, shapetile)), static_cast<float>(1.0 / coveragetile), genus.stretch};
// the clouds change their shapes slowly; the seed moves the layers apart in the noise
auto const evolution{std::fmod(static_cast<double>(conditions.seed) * 0.6180339887 + static_cast<double>(i) * 0.37 + time * 0.00002, 1.0)};
target.extra = glm::vec4{genus.flat ? 1.f : 0.f, static_cast<float>(evolution), static_cast<float>(wrap(stretched.x, coveragetile)), static_cast<float>(wrap(stretched.y, coveragetile))};
m_average_transmittance *= 1.f - std::clamp(layer.coverage * genus.opacity * std::min(layer.density, 1.5f), 0.f, 1.f);
}
if (Global.gfx_clouds <= 0)
m_average_transmittance = 1.f;
m_weather.fog_light = glm::vec4{m_sun_share, m_average_transmittance, 0.6f, fog_density()};
m_weather.weather_camera = glm::vec4{height, static_cast<float>(std::fmod(time, 100000.0)), m_clouds_drawn ? 1.f : 0.f, m_clouds_drawn ? static_cast<float>(layers) : 0.f};
m_weather.weather_sun_direction = glm::vec4{sun, sun.y > -0.02f ? 1.f : 0.f};
m_weather.weather_sun_color = glm::vec4{Keycolor, m_cloud_sun_scale};
m_weather.weather_ambient = glm::vec4{Ambient * 1.3f, m_cloud_sky_scale};
m_weather.fog_volume_params = glm::vec4{0.f, FOG_VOLUME_NEAR, 1.f, 1.f};
m_weather.fog_volume_target = glm::vec4{1.f, 1.f, Global.gfx_fog_range, 1.f - Sky.atmosphere_weight()};
// the colours of the horizon: from the atmosphere on the gpu, the old fog colour otherwise
m_horizon_on_gpu = (Sky.atmosphere_weight() > 0.f) && (Sky.skyview_table() != 0);
if (false == m_horizon_on_gpu)
{
for (auto &color : m_weather.fog_horizon)
color = glm::vec4{Fogcolor, 1.f};
}
// the shadows of the clouds, on a map snapped to its texels so it doesn't crawl as the camera moves
auto const shadows{m_clouds_drawn && Global.gfx_clouds_shadows && (sun.y > 0.02f)};
m_weather.cloud_shadow_params = glm::vec4{0.f};
m_state.update(Sky.atmosphere_weight(), Sky.skyview_table() != 0, Keylight, Keycolor, Ambient, Fogcolor, Camera);
auto const shadows{m_state.shadows_wanted()};
if (shadows)
{
if (m_shadow_size != Global.gfx_clouds_shadows_size)
@@ -356,14 +182,11 @@ void opengl46_weather::update(opengl46_sky const &Sky, glm::vec3 const &Keylight
m_shadow_size = Global.gfx_clouds_shadows_size;
m_shadow_map = create_texture(GL_TEXTURE_2D, GL_R16F, glm::ivec3{m_shadow_size, m_shadow_size, 1}, GL_LINEAR, GL_CLAMP_TO_EDGE);
}
auto const extent{static_cast<double>(Global.gfx_clouds_shadows_range)};
auto const texel{extent / static_cast<double>(m_shadow_size)};
auto const centre{glm::dvec2{std::round(Camera.x / texel) * texel - Camera.x, std::round(Camera.z / texel) * texel - Camera.z}};
m_weather.cloud_shadow_params = glm::vec4{static_cast<float>(centre.x), static_cast<float>(centre.y), static_cast<float>(1.0 / extent), 0.f};
m_state.place_shadows(m_shadow_size, Camera);
}
upload_header();
if (m_horizon_on_gpu)
if (m_state.horizon_on_gpu())
{
glBindTextureUnit(gl::SKY_VIEW_TEX, Sky.skyview_table());
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, gl::WEATHER_SCRATCH_SSBO_BINDING, *m_weather_ubo);
@@ -383,35 +206,32 @@ void opengl46_weather::update(opengl46_sky const &Sky, glm::vec3 const &Keylight
bind_noise();
glBindImageTexture(0, m_shadow_map, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R16F);
glProgramUniform1i(*m_shadow_program, 1, m_shadow_size);
glProgramUniform3f(*m_shadow_program, 2, m_weather.cloud_shadow_params.x, m_weather.cloud_shadow_params.y, Global.gfx_clouds_shadows_range);
glProgramUniform3f(*m_shadow_program, 2, m_state.block().cloud_shadow_params.x, m_state.block().cloud_shadow_params.y, Global.gfx_clouds_shadows_range);
glProgramUniform1i(*m_shadow_program, 3, 8);
m_shadow_program->dispatch(groups(m_shadow_size), groups(m_shadow_size), 1);
glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT | GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
glBindImageTexture(0, 0, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R16F);
m_shadow_timer.end();
m_shadows_valid = true;
m_weather.cloud_shadow_params.w = 1.f;
m_state.shadows_drawn(true);
upload_header();
}
else
m_shadows_valid = false;
m_state.shadows_drawn(false);
// a band of the panorama of the clouds, all of it the first time
if (m_clouds_drawn)
if (m_state.clouds_drawn())
{
auto const level{quality(Global.gfx_clouds, Global.gfx_fog_quality)};
auto const level{gfx::weather_state::quality(Global.gfx_clouds, Global.gfx_fog_quality)};
if (m_panorama_size != level.panorama)
{
release(m_panorama);
m_panorama_size = level.panorama;
m_panorama = create_texture(GL_TEXTURE_2D, GL_RGBA16F, glm::ivec3{m_panorama_size, 1}, GL_LINEAR, GL_CLAMP_TO_EDGE);
glTextureParameteri(m_panorama, GL_TEXTURE_WRAP_S, GL_REPEAT);
m_panorama_full = false;
m_panorama_band = 0;
m_state.panorama_resized();
}
auto const bandrows{(m_panorama_size.y + PANORAMA_BANDS - 1) / PANORAMA_BANDS};
auto const first{m_panorama_full ? m_panorama_band * bandrows : 0};
auto const rows{m_panorama_full ? bandrows : m_panorama_size.y};
int first, rows;
m_state.panorama_band(m_panorama_size, first, rows);
m_panorama_timer.begin();
bind_noise();
glBindImageTexture(0, m_panorama, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA16F);
@@ -419,7 +239,7 @@ void opengl46_weather::update(opengl46_sky const &Sky, glm::vec3 const &Keylight
glBindImageTexture(1, m_panorama, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA16F);
glProgramUniform1i(*m_march_program, 0, 1);
glProgramUniform2i(*m_march_program, 1, m_panorama_size.x, m_panorama_size.y);
glProgramUniform1i(*m_march_program, 4, m_frame);
glProgramUniform1i(*m_march_program, 4, m_state.frame());
// the panorama is seen blurred: fewer samples
glProgramUniform4f(*m_march_program, 5, static_cast<float>(std::max(level.steps / 2, 24)), static_cast<float>(std::max(level.light_steps - 1, 2)), Global.gfx_clouds_distance, 0.f);
glProgramUniform2i(*m_march_program, 6, first, rows);
@@ -428,17 +248,14 @@ void opengl46_weather::update(opengl46_sky const &Sky, glm::vec3 const &Keylight
glBindImageTexture(0, 0, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA16F);
glBindImageTexture(1, 0, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA16F);
m_panorama_timer.end();
m_panorama_band = m_panorama_full ? (m_panorama_band + 1) % PANORAMA_BANDS : 0;
m_panorama_full = true;
}
gl::program::unbind();
m_report = to_metar(conditions);
}
void opengl46_weather::prepare_view(glm::ivec2 const &Size, quality_level const &Level)
{
auto const cloudsize{glm::max((Size + Level.divisor - 1) / Level.divisor, glm::ivec2{1})};
if (m_clouds_drawn && (cloudsize != m_view_size || m_clouds_color == 0))
if (m_state.clouds_drawn() && (cloudsize != m_view_size || m_clouds_color == 0))
{
for (auto *texture : {&m_clouds_color, &m_clouds_depth, &m_clouds_history[0], &m_clouds_history[1]})
release(*texture);
@@ -447,7 +264,7 @@ void opengl46_weather::prepare_view(glm::ivec2 const &Size, quality_level const
m_clouds_depth = create_texture(GL_TEXTURE_2D, GL_R32F, glm::ivec3{m_view_size, 1}, GL_NEAREST, GL_CLAMP_TO_EDGE);
for (auto &history : m_clouds_history)
history = create_texture(GL_TEXTURE_2D, GL_RGBA16F, glm::ivec3{m_view_size, 1}, GL_LINEAR, GL_CLAMP_TO_EDGE);
m_previous_clouds.valid = false;
m_state.previous_clouds.valid = false;
}
if ((Global.gfx_fog == 1) && (Level.fog_grid != m_fog_grid || m_fog_integrated == 0))
{
@@ -458,23 +275,23 @@ void opengl46_weather::prepare_view(glm::ivec2 const &Size, quality_level const
scattering = create_texture(GL_TEXTURE_3D, GL_RGBA16F, m_fog_grid, GL_LINEAR, GL_CLAMP_TO_EDGE);
// one slice more: the fog through the whole volume without the clouds, which the sky takes (fog_integrate.comp)
m_fog_integrated = create_texture(GL_TEXTURE_3D, GL_RGBA16F, m_fog_grid + glm::ivec3{0, 0, 1}, GL_LINEAR, GL_CLAMP_TO_EDGE);
m_previous_fog.valid = false;
m_state.previous_fog.valid = false;
}
m_view_level = Level;
}
void opengl46_weather::render_view(glm::mat4 const &Projection, glm::mat4 const &View, glm::dvec3 const &Camera, glm::ivec2 const &Size, int const Cablamp)
{
auto const level{quality(Global.gfx_clouds, Global.gfx_fog_quality)};
auto const level{gfx::weather_state::quality(Global.gfx_clouds, Global.gfx_fog_quality)};
prepare_view(Size, level);
auto const rotation{glm::mat4{glm::mat3{View}}};
auto const inverseprojection{glm::inverse(Projection)};
auto const viewtoworld{glm::transpose(rotation)};
auto const projectionview{Projection * rotation};
// a jump of the camera (another vehicle, the free camera) leaves nothing to blend with
auto const continuity = [&](view_state const &Previous) { return Previous.valid && glm::length(Previous.camera - Camera) < 50.0; };
auto &previousclouds{m_state.previous_clouds};
auto &previousfog{m_state.previous_fog};
if (m_clouds_drawn)
if (m_state.clouds_drawn())
{
m_clouds_timer.begin();
bind_noise();
@@ -484,7 +301,7 @@ void opengl46_weather::render_view(glm::mat4 const &Projection, glm::mat4 const
glProgramUniform2i(*m_march_program, 1, m_view_size.x, m_view_size.y);
glProgramUniformMatrix4fv(*m_march_program, 2, 1, GL_FALSE, glm::value_ptr(inverseprojection));
glProgramUniformMatrix4fv(*m_march_program, 3, 1, GL_FALSE, glm::value_ptr(viewtoworld));
glProgramUniform1i(*m_march_program, 4, m_frame);
glProgramUniform1i(*m_march_program, 4, m_state.frame());
glProgramUniform4f(*m_march_program, 5, static_cast<float>(level.steps), static_cast<float>(level.light_steps), Global.gfx_clouds_distance, level.detail ? 1.f : 0.f);
glProgramUniform2i(*m_march_program, 6, 0, m_view_size.y);
m_march_program->dispatch(groups(m_view_size.x), groups(m_view_size.y), 1);
@@ -499,10 +316,10 @@ void opengl46_weather::render_view(glm::mat4 const &Projection, glm::mat4 const
glProgramUniform2i(*m_temporal_program, 1, m_view_size.x, m_view_size.y);
glProgramUniformMatrix4fv(*m_temporal_program, 2, 1, GL_FALSE, glm::value_ptr(inverseprojection));
glProgramUniformMatrix4fv(*m_temporal_program, 3, 1, GL_FALSE, glm::value_ptr(viewtoworld));
glProgramUniformMatrix4fv(*m_temporal_program, 4, 1, GL_FALSE, glm::value_ptr(m_previous_clouds.projection_view));
auto const previous{glm::vec3{m_previous_clouds.camera - Camera}};
glProgramUniformMatrix4fv(*m_temporal_program, 4, 1, GL_FALSE, glm::value_ptr(previousclouds.projection_view));
auto const previous{glm::vec3{previousclouds.camera - Camera}};
glProgramUniform3f(*m_temporal_program, 5, previous.x, previous.y, previous.z);
glProgramUniform1f(*m_temporal_program, 6, continuity(m_previous_clouds) ? 0.15f : 1.f);
glProgramUniform1f(*m_temporal_program, 6, gfx::weather_state::continuity(previousclouds, Camera) ? 0.15f : 1.f);
m_temporal_program->dispatch(groups(m_view_size.x), groups(m_view_size.y), 1);
glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT | GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
glBindImageTexture(0, 0, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA16F);
@@ -510,31 +327,18 @@ void opengl46_weather::render_view(glm::mat4 const &Projection, glm::mat4 const
glBindTextureUnit(gl::CLOUD_SHAPE_TEX, m_shape_noise);
m_clouds_current = next;
m_clouds_timer.end();
m_previous_clouds = {projectionview, Camera, {}, true};
m_clouds_view_valid = true;
previousclouds = {projectionview, Camera, {}, true};
m_state.clouds_made();
}
else
m_previous_clouds.valid = false;
previousclouds.valid = false;
if (Global.gfx_fog == 1)
{
m_fog_timer.begin();
auto const farthest{std::max(Global.gfx_fog_range, FOG_VOLUME_NEAR * 4.f)};
// the cells are sampled at another point in every frame: along the depth in the Halton sequence of 2, across the view in
// those of 3 and 5 (fog_inject.comp moves the point of every cell by a value of its own)
auto const halton = [](int Index, int const Base) {
float value{0.f}, fraction{1.f};
for (; Index > 0; Index /= Base)
{
fraction /= static_cast<float>(Base);
value += fraction * static_cast<float>(Index % Base);
}
return value;
};
auto const temporal{Global.gfx_fog_temporal && continuity(m_previous_fog)};
auto const sample{(m_frame % 64) + 1};
glm::vec3 const jitter{temporal ? glm::vec3{halton(sample, 3), halton(sample, 5), halton(sample, 2)} : glm::vec3{0.5f}};
glm::vec4 const slicing{FOG_VOLUME_NEAR, farthest, static_cast<float>(m_fog_grid.z) / std::log((farthest + gl::FOG_VOLUME_BIAS) / (FOG_VOLUME_NEAR + gl::FOG_VOLUME_BIAS)), jitter.z};
auto const temporal{Global.gfx_fog_temporal && gfx::weather_state::continuity(previousfog, Camera)};
auto const jitter{m_state.fog_jitter(temporal)};
auto const slicing{gfx::weather_state::fog_slicing(m_fog_grid.z, jitter)};
bind_noise();
auto const next{1 - m_fog_current};
glBindImageTexture(0, m_fog_scattering[next], 0, GL_TRUE, 0, GL_WRITE_ONLY, GL_RGBA16F);
@@ -543,14 +347,14 @@ void opengl46_weather::render_view(glm::mat4 const &Projection, glm::mat4 const
glProgramUniformMatrix4fv(*m_inject_program, 1, 1, GL_FALSE, glm::value_ptr(inverseprojection));
glProgramUniformMatrix4fv(*m_inject_program, 2, 1, GL_FALSE, glm::value_ptr(viewtoworld));
glProgramUniform4fv(*m_inject_program, 3, 1, glm::value_ptr(slicing));
glProgramUniformMatrix4fv(*m_inject_program, 4, 1, GL_FALSE, glm::value_ptr(m_previous_fog.projection_view));
auto const previous{glm::vec3{m_previous_fog.camera - Camera}};
glProgramUniformMatrix4fv(*m_inject_program, 4, 1, GL_FALSE, glm::value_ptr(previousfog.projection_view));
auto const previous{glm::vec3{previousfog.camera - Camera}};
glProgramUniform3f(*m_inject_program, 5, previous.x, previous.y, previous.z);
auto const sunshadows{Global.gfx_fog_shadows && Global.gfx_shadowmap_enabled};
auto const share{temporal ? std::clamp(Global.gfx_fog_temporal_share, 0.02f, 1.f) : 1.f};
glProgramUniform4f(*m_inject_program, 6, share, Global.gfx_fog_lights, sunshadows ? 1.f : 0.f, Global.gfx_fog_shadows ? 1.f : 0.f);
glProgramUniform1i(*m_inject_program, 7, Cablamp);
glProgramUniform2f(*m_inject_program, 8, m_previous_fog.slicing.x, m_previous_fog.slicing.z);
glProgramUniform2f(*m_inject_program, 8, previousfog.slicing.x, previousfog.slicing.z);
glProgramUniform2f(*m_inject_program, 9, jitter.x, jitter.y);
m_inject_program->dispatch(groups(m_fog_grid.x), groups(m_fog_grid.y), static_cast<GLuint>(m_fog_grid.z));
glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT | GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
@@ -565,50 +369,34 @@ void opengl46_weather::render_view(glm::mat4 const &Projection, glm::mat4 const
glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT | GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
glBindImageTexture(0, 0, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA16F);
m_fog_current = next;
m_fog_slicing = slicing;
m_fog_timer.end();
m_previous_fog = {projectionview, Camera, slicing, true};
m_fog_volume_valid = true;
previousfog = {projectionview, Camera, slicing, true};
m_state.fog_made(slicing, m_fog_grid);
}
else
m_previous_fog.valid = false;
previousfog.valid = false;
gl::program::unbind();
}
void opengl46_weather::setup_pass(bool const Volume, glm::ivec2 const &Size)
{
auto const volume{Volume && m_fog_volume_valid && (Global.gfx_fog == 1)};
m_weather.fog_volume_params = volume ? glm::vec4{1.f, m_fog_slicing.x, m_fog_slicing.z, static_cast<float>(m_fog_grid.z)} : glm::vec4{0.f, FOG_VOLUME_NEAR, 1.f, 1.f};
m_weather.fog_volume_target.x = 1.f / static_cast<float>(std::max(Size.x, 1));
m_weather.fog_volume_target.y = 1.f / static_cast<float>(std::max(Size.y, 1));
m_weather.fog_volume_target.z = volume ? m_fog_slicing.y : Global.gfx_fog_range;
m_state.setup_pass(Volume, Size);
upload_header();
bind();
}
void opengl46_weather::draw_stars(glm::mat4 const &View, float const Sunangle, double const Siderealtime, double const Latitude)
{
if (!Global.gfx_stars || !m_stars_vao || m_stars_count == 0)
if (!m_stars_vao || m_stars_count == 0)
return;
// the stars come out between the civil and the nautical twilight
auto const night{1.f - glm::smoothstep(-12.f, -3.f, Sunangle)};
if (night <= 0.f)
glm::mat4 toworld, toview;
glm::vec4 params;
if (false == m_state.stars(Sunangle, Siderealtime, Latitude, View, toworld, toview, params))
return;
// the equatorial frame (z to the pole, x to the vernal equinox) to the sky of the observer (x west, y up, z north)
auto const theta{glm::radians(Siderealtime)};
auto const phi{glm::radians(Latitude)};
auto const st{static_cast<float>(std::sin(theta))}, ct{static_cast<float>(std::cos(theta))};
auto const sp{static_cast<float>(std::sin(phi))}, cp{static_cast<float>(std::cos(phi))};
glm::mat3 equatorial;
equatorial[0] = glm::vec3{st, cp * ct, -sp * ct};
equatorial[1] = glm::vec3{-ct, cp * st, -sp * st};
equatorial[2] = glm::vec3{0.f, sp, cp};
auto const toworld{glm::mat4{equatorial}};
auto const toview{glm::mat4{glm::mat3{View}}};
m_stars_program->bind();
glProgramUniformMatrix4fv(*m_stars_program, 0, 1, GL_FALSE, glm::value_ptr(toworld));
glProgramUniformMatrix4fv(*m_stars_program, 1, 1, GL_FALSE, glm::value_ptr(toview));
glProgramUniform4f(*m_stars_program, 2, STAR_BRIGHTNESS * Global.gfx_stars_brightness * night, static_cast<float>(std::fmod(Timer::GetTime(), 10000.0)), 2.5f, 0.f);
glProgramUniform4fv(*m_stars_program, 2, 1, glm::value_ptr(params));
glEnable(GL_PROGRAM_POINT_SIZE);
glBlendFunc(GL_ONE, GL_ONE);
m_stars_vao->bind();
@@ -620,9 +408,9 @@ void opengl46_weather::draw_stars(glm::mat4 const &View, float const Sunangle, d
void opengl46_weather::draw_sky(glm::mat4 const &Projection, glm::mat4 const &View, bool const Mainview)
{
int mode{0};
if (m_clouds_drawn)
if (m_state.clouds_drawn())
{
if (Mainview && m_clouds_view_valid)
if (Mainview && m_state.clouds_view_valid())
{
mode = 1;
glBindTextureUnit(gl::CLOUD_TEX, m_clouds_history[m_clouds_current]);
@@ -674,28 +462,5 @@ std::string opengl46_weather::info_times()
void opengl46_weather::draw_debug_ui()
{
char const *fogmodes[]{"analytic (along the ray, as the old fog)", "volumetric"};
ImGui::Combo("fog (gfx.fog)", &Global.gfx_fog, fogmodes, IM_ARRAYSIZE(fogmodes));
char const *levels[]{"low", "medium", "high", "ultra"};
auto fogquality{std::clamp(Global.gfx_fog_quality, 1, 4) - 1};
if (ImGui::Combo("fog volume quality (gfx.fog.quality)", &fogquality, levels, IM_ARRAYSIZE(levels)))
Global.gfx_fog_quality = fogquality + 1;
ImGui::SliderFloat("fog volume depth, m (gfx.fog.range)", &Global.gfx_fog_range, 100.f, 10000.f, "%.0f", ImGuiSliderFlags_Logarithmic);
ImGui::SliderFloat("lamps in the fog (gfx.fog.lights)", &Global.gfx_fog_lights, 0.f, 4.f);
ImGui::Checkbox("shadows of the sun and headlights in the fog (gfx.fog.shadows)", &Global.gfx_fog_shadows);
ImGui::Checkbox("fog blended over frames (gfx.fog.temporal)", &Global.gfx_fog_temporal);
ImGui::SliderFloat("share of a new frame in the fog (gfx.fog.temporal.share)", &Global.gfx_fog_temporal_share, 0.02f, 1.f);
char const *cloudlevels[]{"none", "low", "medium", "high", "ultra"};
ImGui::Combo("clouds (gfx.clouds)", &Global.gfx_clouds, cloudlevels, IM_ARRAYSIZE(cloudlevels));
ImGui::SliderFloat("cloud distance, m (gfx.clouds.distance)", &Global.gfx_clouds_distance, 5000.f, 200000.f, "%.0f", ImGuiSliderFlags_Logarithmic);
ImGui::Checkbox("cloud shadows (gfx.clouds.shadows)", &Global.gfx_clouds_shadows);
ImGui::SliderFloat("cloud shadow map, m (gfx.clouds.shadows.range)", &Global.gfx_clouds_shadows_range, 1000.f, 100000.f, "%.0f", ImGuiSliderFlags_Logarithmic);
ImGui::SliderFloat("cloud sunlight (tuning, not saved)", &m_cloud_sun_scale, 0.25f, 3.f);
ImGui::SliderFloat("cloud sky light (tuning, not saved)", &m_cloud_sky_scale, 0.f, 3.f);
ImGui::SliderFloat("cloud edge detail (tuning, not saved)", &m_cloud_detail_scale, 0.f, 3.f);
ImGui::Checkbox("stars (gfx.stars)", &Global.gfx_stars);
ImGui::SliderFloat("star brightness", &Global.gfx_stars_brightness, 0.f, 8.f);
ImGui::Text("fog range %.0f m, sun share of the fog light %.2f, clouds let through %.2f on average", m_fog_range, m_sun_share, m_average_transmittance);
ImGui::Separator();
ui::weather_controls(simulation::Environment.weather());
m_state.draw_debug_ui();
}

View File

@@ -19,6 +19,7 @@ http://mozilla.org/MPL/2.0/.
#include "gl/vao.h"
#include "gl/buffer.h"
#include "gl/query.h"
#include "rendering/common/weatherstate.h"
class opengl46_sky;
@@ -64,22 +65,22 @@ class opengl46_weather
// true when the volumetric clouds are drawn (the sky leaves its own overcast look then)
bool clouds_drawn() const
{
return m_clouds_drawn;
return m_state.clouds_drawn();
}
// true when the clouds cast shadows (the sunlight isn't dimmed by the overcast then, the shadows do it)
bool cloud_shadows() const
{
return m_shadows_valid;
return m_state.cloud_shadows();
}
// fog density of a regular draw (model_ubs.fog_density): 1 / the fog range
float fog_density() const
{
return 1.f / m_fog_range;
return m_state.fog_density();
}
// distance the fog hides things at, for the draw range: a dark object fades to about 2% there
float fog_range() const
{
return m_fog_range;
return m_state.fog_range();
}
// lines for the gpu times of the debug panel
std::string info_times();
@@ -87,16 +88,7 @@ class opengl46_weather
void draw_debug_ui();
private:
struct quality_level
{
int divisor; // of the resolution of the clouds of the main view
int steps; // samples through a layer at most
int light_steps; // samples towards the sun
bool detail; // with the small noise
glm::ivec2 panorama; // size of the panorama
glm::ivec3 fog_grid; // cells of the fog volume
};
static quality_level quality(int const Clouds, int const Fog);
using quality_level = gfx::weather_state::quality_level;
static GLuint create_texture(GLenum Target, GLenum Format, glm::ivec3 const &Size, GLint Filter, GLint Wrap);
static void release(GLuint &Texture);
@@ -119,8 +111,8 @@ class opengl46_weather
std::unique_ptr<gl::program> m_stars_program;
std::unique_ptr<gl::ubo> m_weather_ubo;
gl::weather_ubs m_weather{};
bool m_horizon_on_gpu{false};
// the block and the decisions of the frame, shared with the vulkan renderer
gfx::weather_state m_state;
// noise
GLuint m_shape_noise{0};
@@ -130,7 +122,6 @@ class opengl46_weather
// shadows of the clouds
GLuint m_shadow_map{0};
int m_shadow_size{0};
bool m_shadows_valid{false};
gl::gpu_timer m_shadow_timer;
// clouds of the main view
@@ -140,51 +131,21 @@ class opengl46_weather
GLuint m_clouds_depth{0};
std::array<GLuint, 2> m_clouds_history{0, 0};
int m_clouds_current{0}; // the history holding the latest result
bool m_clouds_view_valid{false}; // render_view() made the clouds of the frame
gl::gpu_timer m_clouds_timer;
// panorama
GLuint m_panorama{0};
glm::ivec2 m_panorama_size{0};
int m_panorama_band{0};
bool m_panorama_full{false}; // every band was drawn once since it was made
gl::gpu_timer m_panorama_timer;
bool m_clouds_drawn{false};
// fog volume
glm::ivec3 m_fog_grid{0};
std::array<GLuint, 2> m_fog_scattering{0, 0};
int m_fog_current{0};
GLuint m_fog_integrated{0};
bool m_fog_volume_valid{false};
float m_fog_range{5000.f};
glm::vec4 m_fog_slicing{0.f};
gl::gpu_timer m_fog_timer;
// the camera of the previous frame of the main view, for the blending over frames
struct view_state
{
glm::mat4 projection_view{1.f}; // world axes to clip space, camera at the origin
glm::dvec3 camera{0.0};
glm::vec4 slicing{0.f};
bool valid{false};
};
view_state m_previous_clouds;
view_state m_previous_fog;
int m_frame{0};
// stars
std::optional<gl::vao> m_stars_vao;
std::optional<gl::buffer> m_stars_buffer;
int m_stars_count{0};
// tuning of the look of the clouds in the debug window, not saved: scales of their sunlight, of their sky light and of the
// erosion of their edges by the detail noise (weather_sun_color.w, weather_ambient.w, cloud_wind.w)
float m_cloud_sun_scale{1.f};
float m_cloud_sky_scale{1.f};
float m_cloud_detail_scale{1.f};
// what the debug window shows
float m_sun_share{0.f};
float m_average_transmittance{1.f};
std::string m_report;
char m_report_input[256]{};
};

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,20 +1,21 @@
// fog between the camera and a fragment, the api of shaders/ogl46/apply_fog.glsl. the fog volume and the weather of the opengl
// renderer come with the stage P2 of the plan; until then the fog is analytic: exponential with the distance, of the density of
// the draw (fog_density of the model block, a draw with 0 is not fogged), of the colour fog_color of the light buffer, turned
// towards the colour of the sun around its direction
// fog between the camera and a fragment, see fog_volume.glsl and weather_common.glsl. a draw with a fog_density of 0 is not
// fogged; one with another density than the regular one (the glare of lamps) gets the optical depth scaled by the ratio
#include "fog_volume.glsl"
// rgb: light the fog adds, a: what it lets through
vec4 fog_at_fragment()
{
if (fog_density <= 0.0)
return vec4(0.0, 0.0, 0.0, 1.0);
float amount = 1.0 - clamp(exp(-length(f_pos.xyz) * fog_density), 0.0, 1.0);
vec3 color = fog_color;
if ((lights_count >= 1U) && (lights[0].type == LIGHT_DIR))
vec4 fog = fog_along_view(f_pos.xyz, gl_FragCoord.xy);
float scale = fog_density / max(fog_light.w, 1e-12);
if (abs(scale - 1.0) > 1e-3)
{
float sunward = pow(max(dot(normalize(f_pos.xyz), normalize(-lights[0].dir)), 0.0), 30.0);
color = mix(color, lights[0].color, sunward);
float transmittance = exp(log(max(fog.a, 1e-8)) * scale);
fog.rgb *= (1.0 - transmittance) / max(1.0 - fog.a, 1e-4);
fog.a = transmittance;
}
return vec4(color * amount, 1.0 - amount);
return fog;
}
vec3 get_fog_color()

View File

@@ -0,0 +1,130 @@
#version 460 core
#define STAGE_COMPUTE 1
// what the cpu needs from the atmosphere, worked out from the sky-view table in one work group: the light arriving at the
// observer from every direction as spherical harmonics (the ground below the horizon lit by the sun and the sky), the
// transmittance towards the sun, and the average light of the sky above the horizon and over the whole sky.
// run after the sky-view table; the result is read back by the cpu a few frames later
#define LAYOUT_COMPUTE 1
#include "common.glsl"
#include "atmosphere_common.glsl"
layout(set = COMPUTE_SET, binding = 0) uniform sampler2D atmo_transmittance_lut;
layout(set = COMPUTE_SET, binding = 1) uniform sampler2D atmo_skyview_lut;
layout(local_size_x = ATMOSPHERE_AMBIENT_GROUP_SIZE) in;
layout(std430, set = COMPUTE_SET, binding = 2) writeonly buffer atmosphere_result_ssbo
{
vec4 result_radiance_sh[9];
vec4 result_sun_transmittance;
vec4 result_horizon;
vec4 result_average;
vec4 result_zenith_transmittance;
};
// directions of the upper hemisphere each invocation takes
const uint DIRECTIONS_PER_INVOCATION = 4U;
const uint DIRECTIONS = ATMOSPHERE_AMBIENT_GROUP_SIZE * DIRECTIONS_PER_INVOCATION;
// the sky up to this sine of the elevation is the horizon (about 6 degrees)
const float HORIZON_BAND = 0.1;
// partial sums: the 9 coefficients, the light on an upward facing surface, the horizon and its weight, the sky average
const int SUMS = 12;
shared vec4 shared_sums[ATMOSPHERE_AMBIENT_GROUP_SIZE];
void sh_basis(vec3 n, out float basis[9])
{
basis[0] = 0.282095;
basis[1] = 0.488603 * n.y;
basis[2] = 0.488603 * n.z;
basis[3] = 0.488603 * n.x;
basis[4] = 1.092548 * n.x * n.y;
basis[5] = 1.092548 * n.y * n.z;
basis[6] = 0.315392 * (3.0 * n.z * n.z - 1.0);
basis[7] = 1.092548 * n.x * n.z;
basis[8] = 0.546274 * (n.x * n.x - n.y * n.y);
}
// sum of the value of every invocation, valid in invocation 0
vec4 reduce(vec4 value)
{
uint index = gl_LocalInvocationID.x;
barrier();
shared_sums[index] = value;
barrier();
for (uint stride = ATMOSPHERE_AMBIENT_GROUP_SIZE / 2U; stride > 0U; stride >>= 1)
{
if (index < stride)
shared_sums[index] += shared_sums[index + stride];
barrier();
}
return shared_sums[0];
}
void main()
{
uint index = gl_LocalInvocationID.x;
vec3 sundirection = normalize(atmo_sun_direction.xyz);
vec3 sums[SUMS];
for (int i = 0; i < SUMS; ++i)
sums[i] = vec3(0.0);
// even spread over the upper hemisphere: a fibonacci spiral in the sine of the elevation
const float goldenangle = 2.39996323;
const float solidangle = 2.0 * ATMO_PI / float(DIRECTIONS);
for (uint k = 0U; k < DIRECTIONS_PER_INVOCATION; ++k)
{
uint i = index * DIRECTIONS_PER_INVOCATION + k;
float y = (float(i) + 0.5) / float(DIRECTIONS);
float r = sqrt(max(1.0 - y * y, 0.0));
float azimuth = goldenangle * float(i);
vec3 direction = vec3(r * cos(azimuth), y, r * sin(azimuth));
vec3 radiance = textureLod(atmo_skyview_lut, atmo_skyview_uv_world(direction, sundirection), 0.0).rgb;
float basis[9];
sh_basis(direction, basis);
for (int c = 0; c < 9; ++c)
sums[c] += radiance * (basis[c] * solidangle);
sums[9] += radiance * (y * solidangle);
if (y < HORIZON_BAND)
{
sums[10] += radiance;
sums[11] += vec3(1.0);
}
}
vec3 total[SUMS];
for (int i = 0; i < SUMS; ++i)
total[i] = reduce(vec4(sums[i], 0.0)).rgb;
if (index != 0U)
return;
// the transmittance towards the sun, zero once the planet is in the way
float radius = atmo_observer_radius;
vec3 origin = vec3(0.0, radius, 0.0);
vec3 suntransmittance = texture(atmo_transmittance_lut, atmo_transmittance_uv(radius, sundirection.y)).rgb;
if (atmo_ray_sphere_nearest(origin, sundirection, atmo_bottom_radius) >= 0.0)
suntransmittance = vec3(0.0);
// the ground, a lambertian surface lit by the sun and the sky, fills the lower hemisphere with uniform light. its
// spherical harmonics have only the constant term and the vertical one: for a uniform radiance L over the lower
// hemisphere, 0.282095 * 2 pi * L and 0.488603 * (-pi) * L
// the band 0 coefficient of the sky alone over the solid angle of the hemisphere
vec3 skyaverage = total[0] / (0.282095 * 2.0 * ATMO_PI);
vec3 irradiance = suntransmittance * max(sundirection.y, 0.0) + total[9];
vec3 groundradiance = atmo_ground.rgb / ATMO_PI * irradiance;
total[0] += groundradiance * (0.282095 * 2.0 * ATMO_PI);
total[1] += groundradiance * (0.488603 * -ATMO_PI);
for (int c = 0; c < 9; ++c)
result_radiance_sh[c] = vec4(total[c], 0.0);
result_sun_transmittance = vec4(suntransmittance, 0.0);
result_horizon = vec4(total[10] / max(total[11].x, 1.0), 0.0);
result_average = vec4(skyaverage, 0.0);
// what the sun high in the sky looks like at the observer, for the white balance
result_zenith_transmittance = vec4(texture(atmo_transmittance_lut, atmo_transmittance_uv(radius, 1.0)).rgb, 0.0);
}

View File

@@ -0,0 +1,194 @@
// physically based sky after S. Hillaire, "A Scalable and Production Ready Sky and Atmosphere Rendering Technique"
// (EGSR 2020): transmittance, multiple scattering and sky-view look-up tables. distances in kilometres, coefficients per
// kilometre. the planet is centred at the origin; the look-up tables are worked out in a frame with z up at the observer,
// the world (y up) is mapped to it by the callers. radiance is that of a sun of illuminance 1
layout(std140, set = PASS_SET, binding = ATMOSPHERE_UBO) uniform atmosphere_ubo
{
// rgb: rayleigh scattering, w: rayleigh scale height
vec4 atmo_rayleigh;
// x: mie scattering, y: mie extinction, z: mie scale height, w: mie phase asymmetry
vec4 atmo_mie;
// rgb: ozone absorption, w: altitude of the middle of the ozone layer
vec4 atmo_ozone;
// rgb: albedo of the ground, w: half width of the ozone layer
vec4 atmo_ground;
// world space direction towards the sun (y up)
vec4 atmo_sun_direction;
float atmo_bottom_radius;
float atmo_top_radius;
// distance of the observer from the centre of the planet
float atmo_observer_radius;
};
const float ATMO_PI = 3.14159265359;
struct atmo_medium_s
{
vec3 scattering;
vec3 extinction;
vec3 rayleigh_scattering;
vec3 mie_scattering;
};
atmo_medium_s atmo_sample_medium(float radius)
{
float height = max(radius - atmo_bottom_radius, 0.0);
float rayleighdensity = exp(-height / atmo_rayleigh.w);
float miedensity = exp(-height / atmo_mie.z);
float ozonedensity = max(0.0, 1.0 - abs(height - atmo_ozone.w) / atmo_ground.w);
atmo_medium_s medium;
medium.rayleigh_scattering = atmo_rayleigh.rgb * rayleighdensity;
medium.mie_scattering = vec3(atmo_mie.x * miedensity);
medium.scattering = medium.rayleigh_scattering + medium.mie_scattering;
medium.extinction = medium.rayleigh_scattering + vec3(atmo_mie.y * miedensity) + atmo_ozone.rgb * ozonedensity;
return medium;
}
// distance along a ray (unit direction) to the nearest point ahead of the origin where it meets the sphere, -1 if it does not
float atmo_ray_sphere_nearest(vec3 origin, vec3 direction, float radius)
{
float b = dot(origin, direction);
float c = dot(origin, origin) - radius * radius;
float discriminant = b * b - c;
if (discriminant < 0.0)
return -1.0;
float root = sqrt(discriminant);
float t0 = -b - root;
float t1 = -b + root;
if (t0 < 0.0 && t1 < 0.0)
return -1.0;
if (t0 < 0.0)
return t1;
if (t1 < 0.0)
return t0;
return min(t0, t1);
}
float atmo_rayleigh_phase(float cosangle)
{
return 3.0 / (16.0 * ATMO_PI) * (1.0 + cosangle * cosangle);
}
// Cornette-Shanks
float atmo_mie_phase(float cosangle)
{
float g = atmo_mie.w;
float k = 3.0 / (8.0 * ATMO_PI) * (1.0 - g * g) / (2.0 + g * g);
return k * (1.0 + cosangle * cosangle) / pow(max(1.0 + g * g - 2.0 * g * cosangle, 1e-4), 1.5);
}
// texel centres of a table of the given resolution span [0, 1] of the parameter
float atmo_unit_to_sub_uv(float u, float resolution)
{
return (u + 0.5 / resolution) * (resolution / (resolution + 1.0));
}
float atmo_sub_uv_to_unit(float u, float resolution)
{
return (u - 0.5 / resolution) * (resolution / (resolution - 1.0));
}
// transmittance table: x the distance to the top of the atmosphere along the view, y the height
vec2 atmo_transmittance_uv(float radius, float cosviewzenith)
{
float H = sqrt(max(0.0, atmo_top_radius * atmo_top_radius - atmo_bottom_radius * atmo_bottom_radius));
float rho = sqrt(max(0.0, radius * radius - atmo_bottom_radius * atmo_bottom_radius));
float discriminant = radius * radius * (cosviewzenith * cosviewzenith - 1.0) + atmo_top_radius * atmo_top_radius;
float d = max(0.0, -radius * cosviewzenith + sqrt(max(discriminant, 0.0)));
float dmin = atmo_top_radius - radius;
float dmax = rho + H;
return vec2((d - dmin) / max(dmax - dmin, 1e-6), rho / H);
}
void atmo_transmittance_params(vec2 uv, out float radius, out float cosviewzenith)
{
float H = sqrt(max(0.0, atmo_top_radius * atmo_top_radius - atmo_bottom_radius * atmo_bottom_radius));
float rho = H * uv.y;
radius = sqrt(rho * rho + atmo_bottom_radius * atmo_bottom_radius);
float dmin = atmo_top_radius - radius;
float dmax = rho + H;
float d = dmin + uv.x * (dmax - dmin);
cosviewzenith = (d == 0.0) ? 1.0 : (H * H - rho * rho - d * d) / (2.0 * radius * d);
cosviewzenith = clamp(cosviewzenith, -1.0, 1.0);
}
// multiple scattering table: x the cosine of the sun zenith angle, y the height
vec2 atmo_multiscattering_uv(float radius, float cossunzenith)
{
vec2 uv = vec2(cossunzenith * 0.5 + 0.5, (radius - atmo_bottom_radius) / (atmo_top_radius - atmo_bottom_radius));
return clamp(vec2(atmo_unit_to_sub_uv(uv.x, float(ATMOSPHERE_MULTISCATTERING_SIZE)), atmo_unit_to_sub_uv(uv.y, float(ATMOSPHERE_MULTISCATTERING_SIZE))), 0.0, 1.0);
}
// sky-view table: x the angle between the view and the sun around the zenith (sqrt spaced), y the angle of the view from
// the zenith, packed towards the horizon, which splits the table in the sky (lower half) and the ground (upper half)
vec2 atmo_skyview_uv(bool intersectsground, float cosviewzenith, float coslightview, float radius)
{
float vhorizon = sqrt(max(radius * radius - atmo_bottom_radius * atmo_bottom_radius, 0.0));
float cosbeta = vhorizon / radius;
float beta = acos(clamp(cosbeta, -1.0, 1.0));
float zenithhorizonangle = ATMO_PI - beta;
vec2 uv;
if (!intersectsground)
{
float coord = acos(clamp(cosviewzenith, -1.0, 1.0)) / zenithhorizonangle;
coord = 1.0 - coord;
coord = sqrt(max(coord, 0.0));
coord = 1.0 - coord;
uv.y = coord * 0.5;
}
else
{
float coord = (acos(clamp(cosviewzenith, -1.0, 1.0)) - zenithhorizonangle) / max(beta, 1e-6);
coord = sqrt(max(coord, 0.0));
uv.y = coord * 0.5 + 0.5;
}
uv.x = sqrt(clamp(-coslightview * 0.5 + 0.5, 0.0, 1.0));
return vec2(atmo_unit_to_sub_uv(uv.x, float(ATMOSPHERE_SKYVIEW_WIDTH)), atmo_unit_to_sub_uv(uv.y, float(ATMOSPHERE_SKYVIEW_HEIGHT)));
}
void atmo_skyview_params(vec2 uv, float radius, out float cosviewzenith, out float coslightview)
{
uv = vec2(atmo_sub_uv_to_unit(uv.x, float(ATMOSPHERE_SKYVIEW_WIDTH)), atmo_sub_uv_to_unit(uv.y, float(ATMOSPHERE_SKYVIEW_HEIGHT)));
uv = clamp(uv, 0.0, 1.0);
float vhorizon = sqrt(max(radius * radius - atmo_bottom_radius * atmo_bottom_radius, 0.0));
float cosbeta = vhorizon / radius;
float beta = acos(clamp(cosbeta, -1.0, 1.0));
float zenithhorizonangle = ATMO_PI - beta;
if (uv.y < 0.5)
{
float coord = 2.0 * uv.y;
coord = 1.0 - coord;
coord *= coord;
coord = 1.0 - coord;
cosviewzenith = cos(zenithhorizonangle * coord);
}
else
{
float coord = uv.y * 2.0 - 1.0;
coord *= coord;
cosviewzenith = cos(zenithhorizonangle + beta * coord);
}
float coord = uv.x * uv.x;
coslightview = -(coord * 2.0 - 1.0);
}
// the same mapping for a world space view direction and sun direction (y up), as seen by the observer
vec2 atmo_skyview_uv_world(vec3 viewdirection, vec3 sundirection)
{
float radius = atmo_observer_radius;
vec3 origin = vec3(0.0, 0.0, radius);
float cosviewzenith = viewdirection.y;
// angle between the view and the sun around the zenith
vec2 viewflat = viewdirection.xz;
vec2 sunflat = sundirection.xz;
float lengths = length(viewflat) * length(sunflat);
float coslightview = (lengths > 1e-6) ? clamp(dot(viewflat, sunflat) / lengths, -1.0, 1.0) : 1.0;
vec3 localview = vec3(sqrt(max(1.0 - cosviewzenith * cosviewzenith, 0.0)), 0.0, cosviewzenith);
bool intersectsground = atmo_ray_sphere_nearest(origin, localview, atmo_bottom_radius) >= 0.0;
return atmo_skyview_uv(intersectsground, cosviewzenith, coslightview, radius);
}

View File

@@ -0,0 +1,105 @@
// scattering along a ray through the atmosphere, see atmosphere_common.glsl. the includer declares the samplers
// atmo_transmittance_lut and (when multiple scattering is used) atmo_multiscattering_lut
// above the ground by this much (km), so the test for the shadow of the planet does not hit the point itself
const float ATMO_PLANET_RADIUS_OFFSET = 0.01;
struct atmo_scattering_s
{
// light scattered towards the origin
vec3 luminance;
// transmittance from the origin to the end of the ray
vec3 transmittance;
// light scattered once more, as if the light arriving at every point was 1 (multiple scattering table)
vec3 multiscattering_as_1;
};
vec3 atmo_transmittance_to_top(float radius, float cossunzenith)
{
return texture(atmo_transmittance_lut, atmo_transmittance_uv(radius, cossunzenith)).rgb;
}
// Origin and Direction (unit) in the frame of the planet. with Ground the light the ground reflects at the end of the ray
// is included. Physicalphase: rayleigh and mie phase functions, otherwise isotropic (multiple scattering table).
// Multiscattering: light scattered more than once comes from the multiple scattering table
atmo_scattering_s atmo_integrate(vec3 origin, vec3 direction, vec3 sundirection, bool ground, int samples, bool physicalphase, bool multiscattering)
{
atmo_scattering_s result;
result.luminance = vec3(0.0);
result.transmittance = vec3(1.0);
result.multiscattering_as_1 = vec3(0.0);
float tbottom = atmo_ray_sphere_nearest(origin, direction, atmo_bottom_radius);
float ttop = atmo_ray_sphere_nearest(origin, direction, atmo_top_radius);
float tmax;
if (tbottom < 0.0)
{
if (ttop < 0.0)
return result;
tmax = ttop;
}
else
{
tmax = (ttop > 0.0) ? min(ttop, tbottom) : tbottom;
}
float cosangle = dot(direction, sundirection);
const float uniformphase = 1.0 / (4.0 * ATMO_PI);
float miephase = physicalphase ? atmo_mie_phase(cosangle) : uniformphase;
float rayleighphase = physicalphase ? atmo_rayleigh_phase(cosangle) : uniformphase;
vec3 throughput = vec3(1.0);
float invsamples = 1.0 / float(samples);
for (int s = 0; s < samples; ++s)
{
// the steps grow with the distance: the medium near the observer matters most
float t0 = float(s) * invsamples;
float t1 = float(s + 1) * invsamples;
t0 = t0 * t0 * tmax;
t1 = t1 * t1 * tmax;
float dt = t1 - t0;
float t = t0 + dt * 0.3;
vec3 position = origin + t * direction;
float radius = length(position);
vec3 up = position / radius;
atmo_medium_s medium = atmo_sample_medium(radius);
vec3 extinction = max(medium.extinction, vec3(1e-7));
vec3 sampletransmittance = exp(-extinction * dt);
float cossunzenith = dot(sundirection, up);
vec3 suntransmittance = atmo_transmittance_to_top(radius, cossunzenith);
float earthshadow = (atmo_ray_sphere_nearest(position + up * ATMO_PLANET_RADIUS_OFFSET, sundirection, atmo_bottom_radius) >= 0.0) ? 0.0 : 1.0;
vec3 multiscatteredluminance = vec3(0.0);
#ifdef ATMO_MULTISCATTERING_LUT
if (multiscattering)
multiscatteredluminance = texture(atmo_multiscattering_lut, atmo_multiscattering_uv(radius, cossunzenith)).rgb;
#endif
vec3 phasescattering = medium.mie_scattering * miephase + medium.rayleigh_scattering * rayleighphase;
vec3 S = earthshadow * suntransmittance * phasescattering + multiscatteredluminance * medium.scattering;
// analytic integral over the step of the light scattered at the sample, attenuated on the way to its start
vec3 Sint = (S - S * sampletransmittance) / extinction;
result.luminance += throughput * Sint;
vec3 MSint = (medium.scattering - medium.scattering * sampletransmittance) / extinction;
result.multiscattering_as_1 += throughput * MSint;
throughput *= sampletransmittance;
}
if (ground && tbottom > 0.0 && tmax == tbottom)
{
vec3 position = origin + tbottom * direction;
float radius = length(position);
vec3 up = position / radius;
float cossunzenith = dot(sundirection, up);
vec3 suntransmittance = atmo_transmittance_to_top(radius, cossunzenith);
float NdotL = clamp(dot(up, sundirection), 0.0, 1.0);
result.luminance += suntransmittance * throughput * NdotL * atmo_ground.rgb / ATMO_PI;
}
result.transmittance = throughput;
return result;
}

View File

@@ -0,0 +1,72 @@
#version 460 core
#define STAGE_COMPUTE 1
// light scattered more than once, for the multiple scattering table (Hillaire 2020, section 5.5): for a point at a height
// lit by the sun at an angle, the second order scattering from 64 directions around it, summed as a geometric series of
// all the orders. one work group per texel, one invocation per direction. run after the transmittance table
#define LAYOUT_COMPUTE 1
#include "common.glsl"
#include "atmosphere_common.glsl"
layout(set = COMPUTE_SET, binding = 0) uniform sampler2D atmo_transmittance_lut;
#include "atmosphere_integrate.glsl"
layout(local_size_x = 64) in;
layout(set = COMPUTE_SET, binding = 1, rgba16f) writeonly uniform image2D multiscattering_target;
const int MULTISCATTERING_SAMPLES = 20;
const int SQRT_DIRECTIONS = 8;
shared vec3 shared_luminance[64];
shared vec3 shared_multiscattering[64];
void main()
{
ivec2 texel = ivec2(gl_WorkGroupID.xy);
uint index = gl_LocalInvocationID.x;
vec2 uv = (vec2(texel) + 0.5) / float(ATMOSPHERE_MULTISCATTERING_SIZE);
uv = vec2(atmo_sub_uv_to_unit(uv.x, float(ATMOSPHERE_MULTISCATTERING_SIZE)), atmo_sub_uv_to_unit(uv.y, float(ATMOSPHERE_MULTISCATTERING_SIZE)));
uv = clamp(uv, 0.0, 1.0);
float cossunzenith = uv.x * 2.0 - 1.0;
vec3 sundirection = vec3(0.0, sqrt(max(1.0 - cossunzenith * cossunzenith, 0.0)), cossunzenith);
float radius = atmo_bottom_radius + clamp(uv.y + 1e-4, 0.0, 1.0) * (atmo_top_radius - atmo_bottom_radius - 1e-3);
vec3 origin = vec3(0.0, 0.0, radius);
// directions spread evenly over the sphere
float a = (0.5 + float(index / uint(SQRT_DIRECTIONS))) / float(SQRT_DIRECTIONS);
float b = (0.5 + float(index % uint(SQRT_DIRECTIONS))) / float(SQRT_DIRECTIONS);
float theta = 2.0 * ATMO_PI * a;
float phi = acos(clamp(1.0 - 2.0 * b, -1.0, 1.0));
vec3 direction = vec3(cos(theta) * sin(phi), sin(theta) * sin(phi), cos(phi));
atmo_scattering_s result = atmo_integrate(origin, direction, sundirection, true, MULTISCATTERING_SAMPLES, false, false);
const float spheresolidangle = 4.0 * ATMO_PI;
const float directions = float(SQRT_DIRECTIONS * SQRT_DIRECTIONS);
shared_luminance[index] = result.luminance * spheresolidangle / directions;
shared_multiscattering[index] = result.multiscattering_as_1 * spheresolidangle / directions;
barrier();
for (uint stride = 32U; stride > 0U; stride >>= 1)
{
if (index < stride)
{
shared_luminance[index] += shared_luminance[index + stride];
shared_multiscattering[index] += shared_multiscattering[index + stride];
}
barrier();
}
if (index != 0U)
return;
const float isotropicphase = 1.0 / spheresolidangle;
vec3 inscatteredluminance = shared_luminance[0] * isotropicphase;
vec3 multiscatteringas1 = shared_multiscattering[0] * isotropicphase;
// every further order of scattering is the previous one times multiscatteringas1
vec3 luminance = inscatteredluminance / max(vec3(1.0) - multiscatteringas1, vec3(1e-3));
imageStore(multiscattering_target, texel, vec4(luminance, 1.0));
}

View File

@@ -0,0 +1,42 @@
#version 460 core
#define STAGE_COMPUTE 1
// light of the sky seen by the observer in every direction, for the sky-view table (atmosphere_common.glsl), which the
// sky, the ambient light and the fog colour are read from. one invocation per texel. run every frame the sky is drawn with
// the atmosphere, after the transmittance and multiple scattering tables
#define LAYOUT_COMPUTE 1
#include "common.glsl"
#include "atmosphere_common.glsl"
layout(set = COMPUTE_SET, binding = 0) uniform sampler2D atmo_transmittance_lut;
layout(set = COMPUTE_SET, binding = 1) uniform sampler2D atmo_multiscattering_lut;
#define ATMO_MULTISCATTERING_LUT
#include "atmosphere_integrate.glsl"
layout(local_size_x = 8, local_size_y = 8) in;
layout(set = COMPUTE_SET, binding = 2, rgba16f) writeonly uniform image2D skyview_target;
const int SKYVIEW_SAMPLES = 32;
void main()
{
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
if (texel.x >= int(ATMOSPHERE_SKYVIEW_WIDTH) || texel.y >= int(ATMOSPHERE_SKYVIEW_HEIGHT))
return;
vec2 uv = (vec2(texel) + 0.5) / vec2(ATMOSPHERE_SKYVIEW_WIDTH, ATMOSPHERE_SKYVIEW_HEIGHT);
float radius = atmo_observer_radius;
float cosviewzenith;
float coslightview;
atmo_skyview_params(uv, radius, cosviewzenith, coslightview);
float cossunzenith = clamp(atmo_sun_direction.y, -1.0, 1.0);
vec3 sundirection = normalize(vec3(sqrt(max(1.0 - cossunzenith * cossunzenith, 0.0)), 0.0, cossunzenith));
float sinviewzenith = sqrt(max(1.0 - cosviewzenith * cosviewzenith, 0.0));
vec3 direction = vec3(sinviewzenith * coslightview, sinviewzenith * sqrt(max(1.0 - coslightview * coslightview, 0.0)), cosviewzenith);
vec3 origin = vec3(0.0, 0.0, radius);
atmo_scattering_s result = atmo_integrate(origin, direction, sundirection, false, SKYVIEW_SAMPLES, true, true);
imageStore(skyview_target, texel, vec4(result.luminance, 1.0));
}

View File

@@ -0,0 +1,43 @@
#version 460 core
#define STAGE_COMPUTE 1
// transmittance from a point in the atmosphere to its top, for the transmittance table (atmosphere_common.glsl).
// one invocation per texel. run when the parameters of the atmosphere change
#define LAYOUT_COMPUTE 1
#include "common.glsl"
#include "atmosphere_common.glsl"
layout(local_size_x = 8, local_size_y = 8) in;
layout(set = COMPUTE_SET, binding = 0, rgba16f) writeonly uniform image2D transmittance_target;
const int TRANSMITTANCE_SAMPLES = 40;
void main()
{
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
if (texel.x >= int(ATMOSPHERE_TRANSMITTANCE_WIDTH) || texel.y >= int(ATMOSPHERE_TRANSMITTANCE_HEIGHT))
return;
vec2 uv = (vec2(texel) + 0.5) / vec2(ATMOSPHERE_TRANSMITTANCE_WIDTH, ATMOSPHERE_TRANSMITTANCE_HEIGHT);
float radius;
float cosviewzenith;
atmo_transmittance_params(uv, radius, cosviewzenith);
vec3 origin = vec3(0.0, 0.0, radius);
vec3 direction = vec3(sqrt(max(1.0 - cosviewzenith * cosviewzenith, 0.0)), 0.0, cosviewzenith);
float tbottom = atmo_ray_sphere_nearest(origin, direction, atmo_bottom_radius);
float ttop = atmo_ray_sphere_nearest(origin, direction, atmo_top_radius);
float tmax = (tbottom > 0.0) ? ((ttop > 0.0) ? min(ttop, tbottom) : tbottom) : max(ttop, 0.0);
vec3 opticaldepth = vec3(0.0);
float dt = tmax / float(TRANSMITTANCE_SAMPLES);
for (int s = 0; s < TRANSMITTANCE_SAMPLES; ++s)
{
vec3 position = origin + (float(s) + 0.5) * dt * direction;
opticaldepth += atmo_sample_medium(length(position)).extinction * dt;
}
imageStore(transmittance_target, texel, vec4(exp(-opticaldepth), 1.0));
}

View File

@@ -0,0 +1,291 @@
// clouds of the weather (opengl46_weather): density of the layers of weather_common.glsl, made of three tiling noises.
// the shape noise (3d, clouds_noise.comp) gives the lumps of a cloud, the detail noise (3d) eats into their edges, the coverage
// noise (2d) spreads the clouds of a layer over the sky so they take up its coverage. positions are world offsets from the
// camera, in metres, y up; the earth curves away from the camera, so far clouds sink below the horizon
#include "weather_common.glsl"
layout(set = PASS_SET, binding = CLOUD_SHAPE_TEX) uniform sampler3D cloud_shape_noise;
layout(set = PASS_SET, binding = CLOUD_DETAIL_TEX) uniform sampler3D cloud_detail_noise;
layout(set = PASS_SET, binding = CLOUD_COVERAGE_TEX) uniform sampler2D cloud_coverage_noise;
const float CLOUD_EARTH_RADIUS = 6360000.0;
// how much of the shape noise is left standing at the edge and in the core of a cloud area (cloud_layer_density)
const float CLOUD_FILL_EDGE = 0.45;
const float CLOUD_FILL_CORE = 0.85;
// how fast the density rises inside the edge of a cloud: above 1 the edges are crisper, the inside keeps its density
const float CLOUD_EDGE_SHARPNESS = 3.0;
// how far the base and the top of the single clouds stand off those of their layer, up or down, as shares of the depth of the layer
// and at most in metres: the clouds of a layer don't all end at one height, some reach a little out of it
// how high the base of a cloud curves up at its edge and how far into the cloud area (cloud_coverage: y) it does, and how much higher
// the base of a billow is between the cells of the shape noise than in their middles; shares of the height of the cloud
const float CLOUD_BASE_ROUND = 0.35;
const float CLOUD_BASE_ROUND_DEPTH = 0.3;
const float CLOUD_BASE_LUMPS = 0.08;
const float CLOUD_BASE_SHIFT = 0.07;
const float CLOUD_BASE_SHIFT_MAX = 100.0;
const float CLOUD_TOP_SHIFT = 0.15;
const float CLOUD_TOP_SHIFT_MAX = 400.0;
// the detail noise: its tile against the tile of the shape noise and how deep it eats into the edges (times the erosion of the
// genus and the tuning of the debug window, cloud_wind.w); the finer octave of the thin edges: its tile against the detail tile,
// how far the coarser octave pushes its lookup about (in its tiles), and the density below which it is used
const float CLOUD_DETAIL_SCALE = 6.0;
const float CLOUD_EROSION = 2.0;
const float CLOUD_FINE_SCALE = 3.7;
const float CLOUD_FINE_PUSH = 0.6;
const float CLOUD_FINE_BELOW = 0.6;
float cloud_remap(float value, float low, float high, float newlow, float newhigh)
{
return newlow + (value - low) / max(high - low, 1e-5) * (newhigh - newlow);
}
// height of a point above the ground reference, the earth curving down with the square of the distance
float cloud_height(vec3 position)
{
return weather_camera.x + position.y - dot(position.xz, position.xz) * (0.5 / CLOUD_EARTH_RADIUS);
}
// distances along a ray (unit Direction from the camera) where it is at Height: the height goes as
// camera + up * t - (1 - up^2) / (2 R) * t^2. returns the number of crossings ahead (0-2), sorted into T
int cloud_ray_height(float up, float height, out vec2 t)
{
t = vec2(-1.0);
float a = (1.0 - up * up) * (0.5 / CLOUD_EARTH_RADIUS);
float b = -up;
float c = height - weather_camera.x;
// a t^2 + b t + c = 0
if (a < 1e-12)
{
if (abs(b) < 1e-8)
return 0;
float root = -c / b;
t = vec2(root, -1.0);
return root > 0.0 ? 1 : 0;
}
float discriminant = b * b - 4.0 * a * c;
if (discriminant < 0.0)
return 0;
float q = -0.5 * (b + (b >= 0.0 ? 1.0 : -1.0) * sqrt(discriminant));
float r0 = q / a;
float r1 = (abs(q) > 1e-12) ? c / q : r0;
vec2 roots = vec2(min(r0, r1), max(r0, r1));
if (roots.y <= 0.0)
return 0;
if (roots.x <= 0.0)
{
t = vec2(roots.y, -1.0);
return 1;
}
t = roots;
return 2;
}
// the first stretch of a ray within the heights of a layer, x to y; y <= x when it never gets there
vec2 cloud_layer_segment(float up, float base, float top)
{
vec2 tb, tt;
int nb = cloud_ray_height(up, base, tb);
int nt = cloud_ray_height(up, top, tt);
// the crossings in order; the ray goes in and out of the layer at each
float crossings[4];
int count = 0;
for (int i = 0; i < nb; i++)
crossings[count++] = tb[i];
for (int i = 0; i < nt; i++)
crossings[count++] = tt[i];
for (int i = 1; i < count; i++)
for (int j = i; j > 0 && crossings[j - 1] > crossings[j]; j--)
{
float swap = crossings[j];
crossings[j] = crossings[j - 1];
crossings[j - 1] = swap;
}
bool inside = (weather_camera.x >= base) && (weather_camera.x <= top);
float start = 0.0;
for (int i = 0; i < count; i++)
{
if (inside)
return vec2(start, crossings[i]);
inside = true;
start = crossings[i];
}
return inside ? vec2(start, 1e9) : vec2(0.0, -1.0);
}
// the position in the noise of a layer: the camera's place in it and the offset from the camera, stretched along the wind
vec2 cloud_noise_plane(cloud_layer_s layer, vec2 offset)
{
vec2 wind = cloud_wind.xy;
float along = dot(offset, wind);
vec2 across = offset - wind * along;
return across + wind * (along / max(layer.motion.w, 1.0));
}
// how far the base (x) and the top (y) of the clouds of a layer may stand off those of the layer, metres
vec2 cloud_layer_shift(cloud_layer_s layer)
{
float thickness = layer.heights.y - layer.heights.x;
return vec2(min(thickness * CLOUD_BASE_SHIFT, CLOUD_BASE_SHIFT_MAX), min(thickness * CLOUD_TOP_SHIFT, CLOUD_TOP_SHIFT_MAX));
}
// the lowest base (x) and the highest top (y) the clouds of a layer may reach
vec2 cloud_layer_bounds(cloud_layer_s layer)
{
vec2 shift = cloud_layer_shift(layer);
return vec2(layer.heights.x - shift.x, layer.heights.y + shift.y);
}
// share of the layer filled at a place, x: the soft mask of the coverage, y: how far into a cloud area (0 at its edge). Shift: how
// far the base (x) and the top (y) of the cloud there stand off those of the layer, from the second and the third noise of the
// coverage texture, so the clouds which reach lower don't all reach higher too
vec2 cloud_coverage(cloud_layer_s layer, vec2 offset, out vec2 shift)
{
vec2 coords = (layer.extra.zw + cloud_noise_plane(layer, offset)) * layer.motion.z;
vec3 noise = textureLod(cloud_coverage_noise, coords + layer.extra.y, 0.0).rgb;
shift = (noise.yz * 2.0 - 1.0) * cloud_layer_shift(layer);
float coverage = layer.shape.x;
float threshold = 1.0 - coverage;
float mask = smoothstep(threshold - 0.06, threshold + 0.06, noise.x);
float depth = clamp((noise.x - threshold) / max(coverage, 0.05), 0.0, 1.0);
return vec2(mask, depth);
}
vec2 cloud_coverage(cloud_layer_s layer, vec2 offset)
{
vec2 shift;
return cloud_coverage(layer, offset, shift);
}
// extinction (1/m) of a volumetric layer at a point; Detail eats the edges with the small noise
float cloud_layer_density(int index, vec3 position, float height, bool detail)
{
cloud_layer_s layer = cloud_layers[index];
vec2 bounds = cloud_layer_bounds(layer);
if (height <= bounds.x || height >= bounds.y)
return 0.0;
vec2 shift;
vec2 coverage = cloud_coverage(layer, position.xz, shift);
float bottom = layer.heights.x + shift.x;
float h = (height - bottom) / max(layer.heights.y + shift.y - bottom, 1.0);
if (h <= 0.0 || h >= 1.0)
return 0.0;
// the anvil spreads the top of the storm clouds out of their area
if (layer.profile.w > 0.0)
{
float anvil = layer.profile.w * smoothstep(0.7, 0.92, h) * (1.0 - smoothstep(0.97, 1.0, h));
coverage.x = max(coverage.x, anvil * smoothstep(0.0, 0.5, coverage.y + 0.35));
coverage.y = max(coverage.y, anvil * 0.6);
}
if (coverage.x <= 0.0)
return 0.0;
// the tops of the single clouds are of different heights, the larger ones (deeper in their area) the taller
float top = mix(1.0 - layer.profile.z, 1.0, coverage.y);
float fall = 1.0 - smoothstep(top * layer.profile.y, top, h);
if (fall <= 0.0)
return 0.0;
vec2 plane = layer.motion.xy + cloud_noise_plane(layer, position.xz);
vec3 coords = vec3(plane.x, height + layer.extra.y * 4096.0, plane.y) * layer.shape.z;
vec4 shape = textureLod(cloud_shape_noise, coords, 0.0);
// the smaller cells of the worley octaves bite billows into the heaps of the perlin-worley noise; the octaves keep to a narrow
// band (clouds_noise.comp: WORLEY_FBM_LOW, WORLEY_FBM_HIGH), spread over 0-1 first
float worley = clamp((shape.g * 0.625 + shape.b * 0.25 + shape.a * 0.125 - 0.30) / 0.35, 0.0, 1.0);
// the base is not cut flat: it curves up towards the edge of the cloud into its sides, and every billow starts a little higher or
// lower; both as shares of the height of the cloud, so the small ones are rounded too and not cut away. the sheets (little
// erosion) keep a flatter base
float lift = (CLOUD_BASE_ROUND * (1.0 - smoothstep(0.0, CLOUD_BASE_ROUND_DEPTH, coverage.y)) + CLOUD_BASE_LUMPS * (1.0 - worley)) * top * clamp(layer.shape.w * 2.5, 0.0, 1.0);
float gradient = smoothstep(lift, lift + layer.profile.x, h) * fall;
if (gradient <= 0.0)
return 0.0;
float base = clamp(cloud_remap(shape.r, (1.0 - worley) * 0.35, 1.0, 0.0, 1.0), 0.0, 1.0);
// the sheets (little erosion) keep few holes, the heaps are made of lumps
base = mix(1.0, base, clamp(layer.shape.w * 2.5, 0.25, 1.0));
// inside its area a cloud is not a solid block: the lumps of the shape noise are cut out of it, the more the nearer its edge,
// so it breaks up into single heaps with rounded tops and gaps between them. the sheets keep their base above the cut, and a
// layer closing up (broken to overcast) keeps its lumps whole, so it has no more gaps than its coverage
float fill = coverage.x * mix(mix(CLOUD_FILL_EDGE, CLOUD_FILL_CORE, coverage.y), 1.0, smoothstep(0.5, 1.0, layer.shape.x));
float density = clamp(cloud_remap(base * gradient, 1.0 - fill, 1.0, 0.0, 1.0), 0.0, 1.0) * coverage.x;
if (detail && density > 0.0 && layer.shape.w > 0.0)
{
vec3 detailcoords = coords * CLOUD_DETAIL_SCALE;
vec4 small = textureLod(cloud_detail_noise, detailcoords, 0.0);
// spread over 0-1 as the worley octaves of the shape; left in their narrow band they would only thin the cloud evenly
float fbm = clamp((small.r * 0.625 + small.g * 0.25 + small.b * 0.125 - 0.30) / 0.35, 0.0, 1.0);
// the thin outer part of a cloud gets a finer octave too, its lookup pushed about by the coarser one, so the edges are frayed
// into small billows and torn bits rather than evenly rounded. the dense inside is never eaten into, so it is skipped there
float fineshare = 1.0 - smoothstep(CLOUD_FINE_BELOW * 0.5, CLOUD_FINE_BELOW, density);
if (fineshare > 0.0)
{
vec3 push = (small.gbr - 0.5) * CLOUD_FINE_PUSH;
vec4 fine = textureLod(cloud_detail_noise, detailcoords * CLOUD_FINE_SCALE + push, 0.0);
float finefbm = clamp((fine.r * 0.625 + fine.g * 0.25 + fine.b * 0.125 - 0.30) / 0.35, 0.0, 1.0);
fbm = clamp(mix(fbm, fbm * 0.65 + finefbm * 0.35, fineshare), 0.0, 1.0);
}
// wispy at the base, billowing at the top
float erosion = mix(fbm, 1.0 - fbm, clamp(h * 4.0, 0.0, 1.0));
density = clamp(cloud_remap(density, erosion * layer.shape.w * CLOUD_EROSION * cloud_wind.w, 1.0, 0.0, 1.0), 0.0, 1.0);
}
return min(density * CLOUD_EDGE_SHARPNESS, 1.0) * layer.shape.y;
}
// the same without the detail, cheaper, for the shadows and the fog
float cloud_density_coarse(vec3 position)
{
float height = cloud_height(position);
float density = 0.0;
for (int i = 0; i < int(weather_camera.w); i++)
{
if (cloud_layers[i].extra.x > 0.5)
continue;
density += cloud_layer_density(i, position, height, false);
}
return density;
}
// optical depth of a flat layer (cirrus and the like) where a ray crosses its middle, Up the vertical part of the ray there
float cloud_flat_depth(int index, vec3 position)
{
cloud_layer_s layer = cloud_layers[index];
vec2 coverage = cloud_coverage(layer, position.xz);
if (coverage.x <= 0.0)
return 0.0;
vec2 plane = layer.motion.xy + cloud_noise_plane(layer, position.xz);
vec2 coords = plane * layer.shape.z;
// strands and ripples out of slices of the noise
vec4 shape = textureLod(cloud_shape_noise, vec3(coords, 0.37 + layer.extra.y), 0.0);
vec4 small = textureLod(cloud_detail_noise, vec3(coords * 4.0, 0.61), 0.0);
float strands = clamp(cloud_remap(shape.r * 0.7 + small.r * 0.3, 0.35 * layer.shape.w + (1.0 - coverage.x) * 0.5, 1.0, 0.0, 1.0), 0.0, 1.0);
return strands * coverage.x * layer.shape.y;
}
// the shadow of the clouds at a point: what they let through towards the key light. Steps per layer
float cloud_transmittance_to_sun(vec3 position, int steps)
{
vec3 sun = weather_sun_direction.xyz;
if (sun.y <= 0.01)
return fog_light.y;
float height = cloud_height(position);
float depth = 0.0;
for (int i = 0; i < int(weather_camera.w); i++)
{
cloud_layer_s layer = cloud_layers[i];
if (layer.extra.x > 0.5)
{
float middle = 0.5 * (layer.heights.x + layer.heights.y);
if (middle > height)
depth += cloud_flat_depth(i, position + sun * ((middle - height) / sun.y)) / sun.y;
continue;
}
vec2 bounds = cloud_layer_bounds(layer);
float enter = max(bounds.x - height, 0.0) / sun.y;
float leave = (bounds.y - height) / sun.y;
if (leave <= 0.0)
continue;
float step = (leave - enter) / float(steps);
for (int s = 0; s < steps; s++)
{
vec3 point = position + sun * (enter + step * (float(s) + 0.5));
depth += cloud_layer_density(i, point, cloud_height(point), false) * step;
}
}
return exp(-depth);
}

View File

@@ -0,0 +1,295 @@
#version 460 core
#define STAGE_COMPUTE 1
// clouds of the weather ray marched through their layers (clouds_common.glsl), into an image the sky pass lays over the sky
// (sky_weather.frag). mode 0: the view of the main camera at a part of the resolution, smoothed over frames by
// clouds_temporal.comp; mode 1: a panorama around the camera (azimuth across, elevation up), a band of rows per frame, for the
// environment map and the other views. rgb: the light of the clouds with the fog in front of them, a: what they let through
// of the sky behind; the depth image gets the distance of the clouds, for the reprojection
#define LAYOUT_COMPUTE 1
#include "common.glsl"
#include "clouds_common.glsl"
layout(local_size_x = WEATHER_GROUP_SIZE, local_size_y = WEATHER_GROUP_SIZE, local_size_z = 1) in;
layout(rgba16f, set = COMPUTE_SET, binding = 0) uniform writeonly image2D clouds_color;
layout(r32f, set = COMPUTE_SET, binding = 1) uniform writeonly image2D clouds_depth;
// the parameters of the kernel (push constants)
layout(push_constant) uniform kernel_constants
{
int mode;
ivec2 size;
// mode 0: clip space to view space of the camera, and view space to world axes
mat4 inverse_projection;
mat4 view_to_world;
int frame;
// x: samples through a layer at most, y: samples towards the sun, z: farthest distance (m), w: 1 with the detail noise
vec4 quality;
// mode 1: first row of the band and the rows in it
ivec2 rows;
};
// lowest elevation of the panorama (radians); its rows go with the square of the height, denser at the horizon
const float PANORAMA_LOW = -0.1;
// scales of the light of the clouds against the shading of the surfaces (light_common.glsl: SUN_DIFFUSE_SCALE, AMBIENT_SCALE),
// times the tuning of the debug window (weather_sun_color.w, weather_ambient.w). the sunlight three times that of the sunlit faces,
// set by eye in the simulator: the sunlit sides of real cumulus are several times brighter than the sky around them. the sky light
// about as the shaded faces get it: more of it greys the shaded sides of the clouds into a flat pastel
const float CLOUD_SUN_SCALE = 4.5;
const float CLOUD_AMBIENT_SCALE = 0.6;
// the ground under the clouds lights their bases back with this share of the light falling on it
const float CLOUD_GROUND_ALBEDO = 0.2;
// how fast the light of the sky fades into a cloud, per optical depth of the cloud below a point, or above it past CLOUD_SKY_REACH
const float CLOUD_AMBIENT_FADE = 0.05;
// the part of the cloud over a point looked at for the sky light held back (cloud_sky_depth), metres, how fast the light fades
// per optical depth there (less than the extinction: the light scattered many times gets through further), and the share left
// in the darkest places
const float CLOUD_SKY_REACH = 400.0;
const float CLOUD_SKY_FADE = 0.25;
const float CLOUD_SKY_FLOOR = 0.15;
// how dense a cloud has to be to scatter the sunlight back at full: the thin edges and wisps of the side lit by the sun, seen with the
// sun behind, are darker than its bulk (the "powder" effect: the light takes a few scatterings to turn back towards the view)
const float CLOUD_POWDER = 6.0;
// how many of the nearest steps towards the sun see the detail noise
const int CLOUD_LIGHT_DETAIL_STEPS = 1;
// orders of the scattering the sunlight is followed through (cloud_sunlight): the higher ones reach deep into a thick cloud, so its
// shaded sides and its inside are lit through the cloud too, not only by the sky, and a big cloud close by is not a flat grey
const int CLOUD_SCATTER_OCTAVES = 6;
// how much longer every next step towards the sun is than the one before: 1 + this / steps², so the first step is short where the
// light falls off fastest (about a 30th of the reach at 4 steps) and the steps get close to even when there are many of them
const float CLOUD_LIGHT_GROWTH = 24.0;
float henyey_greenstein(float cosangle, float g)
{
// times 4 pi: 1 for an even scattering
float g2 = g * g;
return (1.0 - g2) / pow(max(1.0 + g2 - 2.0 * g * cosangle, 1e-4), 1.5);
}
float cloud_phase(float cosangle, float scale)
{
return mix(henyey_greenstein(cosangle, 0.8 * scale), henyey_greenstein(cosangle, -0.25 * scale), 0.3);
}
// the light of the sky a face looking straight up (Up 1) or down (-1) gets: the uniform ambient light and the directional part of
// the sky light (sky_ssbo, light_common.glsl: sky_ambient_directional) along the vertical. with the physical atmosphere it is bluer
// and brighter from above than from the sides, so the shaded sides of the clouds take the colour of the sky above them
vec3 cloud_sky_light(float up)
{
vec3 directional = sky_ambient_sh[1].rgb * (0.488603 * up) - sky_ambient_sh[6].rgb * 0.315392 - sky_ambient_sh[8].rgb * 0.546274;
return max(weather_ambient.rgb + directional * 1.3, vec3(0.0)) * (CLOUD_AMBIENT_SCALE * weather_ambient.w);
}
// light of the sun reaching a point inside a layer through the cloud towards it, with the light scattered many times
// (octaves after Wrenninge), and the phase of the view
float cloud_sunlight(int index, vec3 position, float cosangle, int steps, float powder, bool detail)
{
vec3 sun = weather_sun_direction.xyz;
cloud_layer_s layer = cloud_layers[index];
float thickness = layer.heights.y - layer.heights.x;
float reach = min(thickness, 1500.0);
float depth = 0.0;
float travelled = 0.0;
// steps growing geometrically: near the point the shape matters, far only the bulk. with steps growing evenly the first one was a
// tenth of the reach at 4 steps, and the light fell off the smooth sides of the clouds in visible bands
float growth = 1.0 + CLOUD_LIGHT_GROWTH / float(steps * steps);
float step = reach * (growth - 1.0) / (pow(growth, float(steps)) - 1.0);
for (int s = 0; s < steps; s++)
{
vec3 point = position + sun * (travelled + 0.5 * step);
travelled += step;
// the nearest steps with the detail noise: the small billows shade one another, lit on top and grey beneath
depth += cloud_layer_density(index, point, cloud_height(point), detail && (s < CLOUD_LIGHT_DETAIL_STEPS)) * step;
step *= growth;
}
float light = 0.0;
float amount = 1.0;
float fade = 1.0;
float spread = 1.0;
for (int octave = 0; octave < CLOUD_SCATTER_OCTAVES; octave++)
{
light += amount * exp(-depth * fade) * cloud_phase(cosangle, spread) * (octave == 0 ? powder : 1.0);
amount *= 0.5;
fade *= 0.5;
spread *= 0.5;
}
return light;
}
// optical depth of the cloud over a point up to Reach above it, from Samples of the coarse density: under a lump and deep in a
// thick cloud the sky light is held back, at the tops and the thin edges it is not, so the shaded sides take the form of the cloud
float cloud_sky_depth(int index, vec3 position, float height, float reach, int samples)
{
float depth = 0.0;
for (int s = 0; s < samples; s++)
{
// up along the vertical: the height grows by the same distance (cloud_height bends only with the horizontal distance)
float up = reach * (float(s) + 0.5) / float(samples);
depth += cloud_layer_density(index, position + vec3(0.0, up, 0.0), height + up, false);
}
return depth * reach / float(samples);
}
float interleaved_noise(vec2 pixel)
{
return fract(52.9829189 * fract(dot(pixel, vec2(0.06711056, 0.00583715))));
}
void main()
{
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
vec3 direction;
if (mode == 0)
{
if (any(greaterThanEqual(texel, size)))
return;
vec2 ndc = (vec2(texel) + 0.5) / vec2(size) * 2.0 - 1.0;
// reverse z: the near plane at 1
vec4 near = inverse_projection * vec4(ndc, 1.0, 1.0);
direction = normalize(mat3(view_to_world) * normalize(near.xyz / near.w));
}
else
{
texel.y += rows.x;
if (texel.x >= size.x || texel.y >= min(size.y, rows.x + rows.y))
return;
vec2 coords = (vec2(texel) + 0.5) / vec2(size);
float elevation = PANORAMA_LOW + (0.5 * WEATHER_PI - PANORAMA_LOW) * coords.y * coords.y;
float azimuth = coords.x * 2.0 * WEATHER_PI;
direction = vec3(cos(elevation) * sin(azimuth), sin(elevation), cos(elevation) * cos(azimuth));
}
vec3 sun = weather_sun_direction.xyz;
float cosangle = dot(direction, sun);
float sunup = weather_sun_direction.w;
float maxdistance = quality.z;
int maxsteps = int(quality.x);
int lightsteps = int(quality.y);
int skysteps = (lightsteps >= 4) ? 2 : 1;
bool detail = quality.w > 0.5;
float jitter = fract(interleaved_noise(vec2(texel)) + float(frame) * 0.618034);
vec3 scattered = vec3(0.0);
float transmittance = 1.0;
float depthsum = 0.0;
float weightsum = 0.0;
int layers = int(weather_camera.w);
// the stretches of the volumetric layers along the ray, nearest first
vec2 segments[MAX_CLOUD_LAYERS];
int order[MAX_CLOUD_LAYERS];
int count = 0;
for (int i = 0; i < layers; i++)
{
if (cloud_layers[i].extra.x > 0.5)
continue;
vec2 bounds = cloud_layer_bounds(cloud_layers[i]);
vec2 segment = cloud_layer_segment(direction.y, bounds.x, bounds.y);
segment.y = min(segment.y, maxdistance);
if (segment.y <= segment.x)
continue;
int at = count++;
segments[at] = segment;
order[at] = i;
for (int j = at; j > 0 && segments[j - 1].x > segments[j].x; j--)
{
vec2 swapsegment = segments[j];
segments[j] = segments[j - 1];
segments[j - 1] = swapsegment;
int swaporder = order[j];
order[j] = order[j - 1];
order[j - 1] = swaporder;
}
}
vec3 ambienttop = cloud_sky_light(1.0);
vec3 sunlight = weather_sun_color.rgb * (CLOUD_SUN_SCALE * weather_sun_color.w) * sunup;
// the bases are lit from below by the ground, which the sun lights through the gaps between the clouds
vec3 ambientbottom = (weather_sun_color.rgb * max(weather_sun_direction.y, 0.0) * sunup * fog_light.y + ambienttop) * CLOUD_GROUND_ALBEDO;
// seen against the sun the thin edges glow (the forward scattering); seen with the sun behind, they are dark
float powderview = clamp(1.0 - cosangle, 0.0, 1.0);
for (int k = 0; k < count && transmittance > 0.01; k++)
{
int index = order[k];
cloud_layer_s layer = cloud_layers[index];
vec2 segment = segments[k];
float span = segment.y - segment.x;
float thickness = layer.heights.y - layer.heights.x;
int steps = clamp(int(float(maxsteps) * span / (thickness * 4.0)), 8, maxsteps);
float step = span / float(steps);
for (int s = 0; s < steps; s++)
{
float t = segment.x + (float(s) + jitter) * step;
vec3 position = direction * t;
float height = cloud_height(position);
float density = cloud_layer_density(index, position, height, detail);
if (density <= 0.0)
continue;
float powder = mix(1.0, 1.0 - exp(-density / max(layer.shape.y, 1e-6) * CLOUD_POWDER), powderview);
float sunshare = (sunup > 0.0) ? cloud_sunlight(index, position, cosangle, lightsteps, powder, detail) : 0.0;
// the light of the sky reaches inside through the cloud above, that of the ground through the cloud below: the base of a
// thick cloud is darker than its top, and greyer, lit only by the ground and the sky at its sides
float column = layer.shape.y * layer.shape.x;
float top = cloud_layer_bounds(layer).y;
float reach = clamp(top - height, 0.0, CLOUD_SKY_REACH);
float sky = exp(-CLOUD_SKY_FADE * cloud_sky_depth(index, position, height, reach, skysteps) - CLOUD_AMBIENT_FADE * column * max(top - height - reach, 0.0));
float below = column * max(height - layer.heights.x, 0.0);
vec3 light = sunlight * sunshare + (ambienttop * mix(CLOUD_SKY_FLOOR, 1.0, sky) + ambientbottom * exp(-CLOUD_AMBIENT_FADE * below));
float through = exp(-density * step);
vec3 inscattered = light * (1.0 - through);
scattered += transmittance * inscattered;
float weight = transmittance * (1.0 - through);
depthsum += t * weight;
weightsum += weight;
transmittance *= through;
if (transmittance < 0.01)
break;
}
}
// the thin high layers, past the others
for (int i = 0; i < layers && transmittance > 0.01; i++)
{
cloud_layer_s layer = cloud_layers[i];
if (layer.extra.x < 0.5)
continue;
vec2 crossing;
if (cloud_ray_height(direction.y, 0.5 * (layer.heights.x + layer.heights.y), crossing) == 0)
continue;
float t = crossing.x;
if (t > maxdistance * 2.0)
continue;
vec3 position = direction * t;
// the slant of the ray through the sheet where it crosses it, the earth curving away
float slope = max(abs(direction.y - (1.0 - direction.y * direction.y) * t / CLOUD_EARTH_RADIUS), 0.03);
float depth = cloud_flat_depth(i, position) / slope;
if (depth <= 0.0)
continue;
float through = exp(-depth);
// ice crystals scatter forwards strongly
vec3 light = sunlight * henyey_greenstein(cosangle, 0.6) * 0.8 + ambienttop;
scattered += transmittance * light * (1.0 - through);
float weight = transmittance * (1.0 - through);
depthsum += t * weight;
weightsum += weight;
transmittance *= through;
}
// the haze the atmosphere has in the sky already lies between the camera and the clouds too; the rest of the fog goes over
// the clouds and the sky alike when they are laid over it (sky_weather.frag)
float span_distance = (weightsum > 1e-5) ? depthsum / weightsum : maxdistance;
if (weightsum > 1e-5)
{
vec4 fog = fog_analytic_with(direction, 0.0, span_distance, min(fog_haze.x, fog_haze.z), 0.0);
scattered = scattered * fog.a + fog.rgb * (1.0 - transmittance);
}
if (any(isnan(scattered)) || isnan(transmittance))
{
scattered = vec3(0.0);
transmittance = 1.0;
}
imageStore(clouds_color, texel, vec4(scattered, transmittance));
if (mode == 0)
imageStore(clouds_depth, texel, vec4(span_distance));
}

View File

@@ -0,0 +1,141 @@
#version 460 core
#define STAGE_COMPUTE 1
// the tiling noises the clouds and the fog are made of, made once at the start (opengl46_weather::init).
// kind 0, the shape (3d): r perlin-worley (perlin fbm with the worley cells carved in), g b a worley fbm of rising frequency;
// kind 1, the detail (3d): r g b worley fbm of rising frequency;
// kind 2, the coverage (2d): r g b three perlin fbm, spread evenly over 0-1 so a threshold of 1 - c leaves a share c above it
#define LAYOUT_COMPUTE 1
#include "common.glsl"
#include "random.glsl"
layout(local_size_x = WEATHER_GROUP_SIZE, local_size_y = WEATHER_GROUP_SIZE, local_size_z = 1) in;
layout(rgba8, set = COMPUTE_SET, binding = 0) uniform writeonly image3D noise_volume;
layout(rgba8, set = COMPUTE_SET, binding = 1) uniform writeonly image2D noise_plane;
// the parameters of the kernel (push constants)
layout(push_constant) uniform kernel_constants
{
int kind;
int size;
};
vec3 hash3(ivec3 cell, int period, uint seed)
{
uvec3 wrapped = uvec3((cell % period + period) % period);
uint h = Hash(uvec3(wrapped.x + seed * 977U, wrapped.y, wrapped.z));
uint h2 = Hash(h + 0x68bc21ebU);
uint h3 = Hash(h2 + 0x02e5be93U);
return vec3(float(h & 0xffffU), float(h2 & 0xffffU), float(h3 & 0xffffU)) / 65535.0;
}
// gradient noise repeating every Period cells, -1..1 about
float perlin(vec3 position, int period, uint seed)
{
ivec3 cell = ivec3(floor(position));
vec3 local = fract(position);
vec3 fade = local * local * local * (local * (local * 6.0 - 15.0) + 10.0);
float corners[8];
for (int i = 0; i < 8; i++)
{
ivec3 offset = ivec3(i & 1, (i >> 1) & 1, (i >> 2) & 1);
vec3 gradient = normalize(hash3(cell + offset, period, seed) * 2.0 - 1.0 + 1e-4);
corners[i] = dot(gradient, local - vec3(offset));
}
float x0 = mix(corners[0], corners[1], fade.x);
float x1 = mix(corners[2], corners[3], fade.x);
float x2 = mix(corners[4], corners[5], fade.x);
float x3 = mix(corners[6], corners[7], fade.x);
return mix(mix(x0, x1, fade.y), mix(x2, x3, fade.y), fade.z) * 1.6;
}
// inverted distance to the nearest of the points scattered one per cell, 0-1, repeating every Period cells
float worley(vec3 position, int period, uint seed)
{
ivec3 cell = ivec3(floor(position));
vec3 local = fract(position);
float nearest = 1.0;
for (int z = -1; z <= 1; z++)
for (int y = -1; y <= 1; y++)
for (int x = -1; x <= 1; x++)
{
ivec3 offset = ivec3(x, y, z);
vec3 point = vec3(offset) + hash3(cell + offset, period, seed);
nearest = min(nearest, length(point - local));
}
return 1.0 - clamp(nearest, 0.0, 1.0);
}
float perlin_fbm(vec3 position, int frequency, int octaves, uint seed)
{
float sum = 0.0;
float amplitude = 0.5;
float total = 0.0;
for (int i = 0; i < octaves; i++)
{
sum += perlin(position * float(frequency), frequency, seed + uint(i)) * amplitude;
total += amplitude;
amplitude *= 0.5;
frequency *= 2;
}
return sum / total;
}
float worley_fbm(vec3 position, int frequency, uint seed)
{
return worley(position * float(frequency), frequency, seed) * 0.625
+ worley(position * float(frequency * 2), frequency * 2, seed + 1U) * 0.25
+ worley(position * float(frequency * 4), frequency * 4, seed + 2U) * 0.125;
}
// octaves of the coverage noise: it only places the clouds and gives their outline. it is the same at every height, so its finer
// octaves would stand as upright grooves down the sides of the clouds; their small shapes come from the 3d noises
const int COVERAGE_OCTAVES = 4;
// the worley fbm keeps about to this range (5th and 95th percentile 0.35 and 0.61)
const float WORLEY_FBM_LOW = 0.30;
const float WORLEY_FBM_HIGH = 0.65;
float remap(float value, float low, float high, float newlow, float newhigh)
{
return newlow + (value - low) / (high - low) * (newhigh - newlow);
}
// a sum of noises is about normally distributed; this spreads it about evenly over 0-1
float even(float value, float spread)
{
return clamp(0.5 + 0.5 * tanh(value / spread * 1.1), 0.0, 1.0);
}
void main()
{
ivec3 texel = ivec3(gl_GlobalInvocationID);
if (any(greaterThanEqual(texel.xy, ivec2(size))))
return;
if (kind == 2)
{
vec3 position = vec3((vec2(texel.xy) + 0.5) / float(size), 0.0);
float first = perlin_fbm(position, 4, COVERAGE_OCTAVES, 11U);
float second = perlin_fbm(position + vec3(0.0, 0.0, 0.5), 8, 5, 23U);
float third = perlin_fbm(position + vec3(0.0, 0.0, 0.25), 8, 5, 29U);
imageStore(noise_plane, texel.xy, vec4(even(first, 0.22), even(second, 0.22), even(third, 0.22), 1.0));
return;
}
if (texel.z >= size)
return;
vec3 position = (vec3(texel) + 0.5) / float(size);
if (kind == 0)
{
// both spread over 0-1 first: as sums of octaves they keep to a narrow band about the middle, and a cloud made of them
// would be an even block with no lumps. the cells then cut the perlin noise into rounded heaps: whole in their middles,
// only the highest perlin left between them
float low = even(perlin_fbm(position, 4, 4, 3U), 0.22);
float cells = worley_fbm(position, 4, 5U);
float spreadcells = clamp((cells - WORLEY_FBM_LOW) / (WORLEY_FBM_HIGH - WORLEY_FBM_LOW), 0.0, 1.0);
float perlinworley = clamp(remap(low, (1.0 - spreadcells) * 0.6, 1.0, 0.0, 1.0), 0.0, 1.0);
imageStore(noise_volume, texel, vec4(perlinworley, cells, worley_fbm(position, 8, 7U), worley_fbm(position, 16, 9U)));
}
else
imageStore(noise_volume, texel, vec4(worley_fbm(position, 4, 13U), worley_fbm(position, 8, 15U), worley_fbm(position, 16, 17U), 1.0));
}

View File

@@ -0,0 +1,34 @@
#version 460 core
#define STAGE_COMPUTE 1
// the shadow the clouds cast on the ground: for points of the ground reference around the camera, what the clouds let through
// towards the key light. the shading (light_common.glsl) and the fog (fog_inject.comp) look it up along the ray of the sun,
// see cloud_shadow() in weather_common.glsl
#define LAYOUT_COMPUTE 1
#include "common.glsl"
#include "clouds_common.glsl"
layout(local_size_x = WEATHER_GROUP_SIZE, local_size_y = WEATHER_GROUP_SIZE, local_size_z = 1) in;
layout(r16f, set = COMPUTE_SET, binding = 0) uniform writeonly image2D shadow;
// the parameters of the kernel (push constants)
layout(push_constant) uniform kernel_constants
{
int size;
// world x and z of the centre of the map relative to the camera, size of the map (metres)
vec3 placement;
// samples through a layer
int steps;
};
void main()
{
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
if (any(greaterThanEqual(texel, ivec2(size))))
return;
vec2 offset = placement.xy + ((vec2(texel) + 0.5) / float(size) - 0.5) * placement.z;
// the ground reference, the earth curving away
vec3 ground = vec3(offset.x, -weather_camera.x + dot(offset, offset) * (0.5 / CLOUD_EARTH_RADIUS), offset.y);
imageStore(shadow, texel, vec4(cloud_transmittance_to_sun(ground, steps)));
}

View File

@@ -0,0 +1,72 @@
#version 460 core
#define STAGE_COMPUTE 1
// the clouds of the main view smoothed over frames: the image of this frame (clouds_march.comp, ray started at another
// distance every frame) blended into the one of the previous frames, found where the clouds were by the distance of the
// clouds, the rotation and the move of the camera. the history is held to the range of the neighbourhood of this frame,
// so moving clouds and a turning camera don't leave trails
#define LAYOUT_COMPUTE 1
#include "common.glsl"
layout(local_size_x = WEATHER_GROUP_SIZE, local_size_y = WEATHER_GROUP_SIZE, local_size_z = 1) in;
layout(set = COMPUTE_SET, binding = 0) uniform sampler2D current_color;
layout(set = COMPUTE_SET, binding = 1) uniform sampler2D current_depth;
layout(set = COMPUTE_SET, binding = 2) uniform sampler2D history_color;
layout(rgba16f, set = COMPUTE_SET, binding = 3) uniform writeonly image2D result;
// the parameters of the kernel (push constants)
layout(push_constant) uniform kernel_constants
{
ivec2 size;
mat4 inverse_projection;
mat4 view_to_world;
// world axes to the clip space of the previous frame, the camera of the previous frame at the origin
mat4 previous_projection_view;
// where the camera was in the previous frame, relative to where it is (world, metres)
vec3 previous_camera;
// share of this frame in the result; 1 when there is no history
float blend;
};
void main()
{
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
if (any(greaterThanEqual(texel, size)))
return;
vec4 current = texelFetch(current_color, texel, 0);
if (blend >= 1.0)
{
imageStore(result, texel, current);
return;
}
// the range of the neighbourhood
vec4 low = current;
vec4 high = current;
for (int y = -1; y <= 1; y++)
for (int x = -1; x <= 1; x++)
{
vec4 neighbour = texelFetch(current_color, clamp(texel + ivec2(x, y), ivec2(0), size - 1), 0);
low = min(low, neighbour);
high = max(high, neighbour);
}
vec2 ndc = (vec2(texel) + 0.5) / vec2(size) * 2.0 - 1.0;
vec4 near = inverse_projection * vec4(ndc, 1.0, 1.0);
vec3 direction = normalize(mat3(view_to_world) * normalize(near.xyz / near.w));
float span_distance = texelFetch(current_depth, texel, 0).r;
vec3 position = direction * span_distance - previous_camera;
vec4 clip = previous_projection_view * vec4(position, 1.0);
float share = blend;
vec4 history = current;
if (clip.w > 0.0)
{
vec2 coords = clip.xy / clip.w * 0.5 + 0.5;
if (all(greaterThanEqual(coords, vec2(0.0))) && all(lessThanEqual(coords, vec2(1.0))))
history = clamp(textureLod(history_color, coords, 0.0), low, high);
else
share = 1.0;
}
else
share = 1.0;
imageStore(result, texel, mix(history, current, share));
}

View File

@@ -0,0 +1,48 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
// debug view of the light clusters: tints every pixel of the scene with the colour of the number of lights which
// were assigned to its cluster (see gl/cluster_grid.h), pixels of clusters without lights stay as they are.
// drawn over the output of the viewport, with the layout of the scene (the clusters of the pass set; the depth as the texture of
// a material)
layout(location = 14) in vec2 f_coords;
#include "common.glsl"
#pragma texture(depth_tex, 0, R)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D depth_tex;
layout(location = 0) out vec4 out_color;
const float TINT = 0.55;
// blue (few) through green to red (many)
vec3 ramp(float t)
{
return clamp(vec3(1.5) - abs(4.0 * t - vec3(3.0, 2.0, 1.0)), 0.0, 1.0);
}
void main()
{
// reverse z: 0 is the far plane at infinity, i.e. the sky, which has no cluster
float depth = texelFetch(depth_tex, ivec2(f_coords * vec2(textureSize(depth_tex, 0))), 0).r;
if (depth <= 0.0)
discard;
// depth is znear / w
float w = cluster_znear / depth;
int slice = clamp(int(floor(log(w) * cluster_slice_scale + cluster_slice_bias)), 0, CLUSTER_GRID_Z - 1);
ivec2 tile = clamp(ivec2(floor(f_coords * vec2(CLUSTER_GRID_X, CLUSTER_GRID_Y))), ivec2(0), ivec2(CLUSTER_GRID_X - 1, CLUSTER_GRID_Y - 1));
uint count = cluster_ranges[tile.x + CLUSTER_GRID_X * (tile.y + CLUSTER_GRID_Y * slice)].count;
if (count == 0U)
discard;
// a full cluster (lights beyond its capacity are dropped) is white; the rest goes by the logarithm of the count
vec3 color = (count >= uint(MAX_LIGHTS_PER_CLUSTER))
? vec3(1.0)
: ramp(log2(1.0 + float(count)) / log2(1.0 + float(MAX_LIGHTS_PER_CLUSTER)));
out_color = vec4(color, TINT);
}

View File

@@ -6,10 +6,14 @@
//
// the pipeline layout is picked by a define before the include of this file:
// (none) the passes of the scene (materials, shadows, picking, lines, sprites, sky, particles): set PASS_SET with
// the blocks of the scene, of the draws, of the instances, the lights, the clusters and the shadow,
// environment and headlight maps; set MATERIAL_SET with the textures of the material
// LAYOUT_POST the post-processing and the copies of whole images: set POST_SET pushed with the images read, the
// parameters in the push constants (param stands for pc.data)
// the blocks of the scene, of the draws, of the instances, the lights, the clusters, the shadow,
// environment and headlight maps, the light of the sky, the weather, the shadows of the lamps and the depth
// of the scene; set MATERIAL_SET with the textures of the material
// LAYOUT_COMPUTE the compute kernels (vk14::compute_kernel): set PASS_SET as above (the block of the scene, the lights, the
// clusters, the light of the sky; the weather and the atmosphere with their includes), set COMPUTE_SET
// pushed with the bindings the kernel declares from 0 on, its parameters in push constants of its own
// LAYOUT_POST the post-processing and the copies of whole images: set POST_SET pushed with the images read and the
// exposure, the parameters in the push constants (param stands for pc.data)
// LAYOUT_CLUSTER the compute passes of the light clusters: set 0 pushed at the CL_* bindings
// LAYOUT_UI the user interface: none of the above, its own push constants
//
@@ -40,7 +44,22 @@
#define SHADOW_TEX 7
#define ENV_TEX 8
#define HEADLIGHT_TEX 9
#define PASS_BINDING_COUNT 10
#define SKY_SSBO 10
#define ATMOSPHERE_UBO 11
#define SKY_VIEW_TEX 12
#define ENV_GGX_TEX 13
#define WEATHER_UBO 14
#define WEATHER_HORIZON_UBO 15
#define FOG_VOLUME_TEX 16
#define CLOUD_SHADOW_TEX 17
#define CLOUD_SHAPE_TEX 18
#define CLOUD_DETAIL_TEX 19
#define CLOUD_COVERAGE_TEX 20
#define LOCAL_SHADOW_TEX 21
#define LOCAL_SHADOW_LAMPS_TEX 22
#define HEADLIGHT_SHADOW_TEX 23
#define SCENE_DEPTH_TEX 24
#define PASS_BINDING_COUNT 25
#define MATERIAL_TEXTURE_FIRST 0
#define CL_CLUSTER_UBO 0
#define CL_AABB_SSBO 1
@@ -50,6 +69,9 @@
#define CL_LIGHT_SSBO 5
#define POST_SET 0
#define POST_TEXTURE_FIRST 0
#define POST_TEXTURES 4
#define POST_EXPOSURE_SSBO 4
#define COMPUTE_SET 1
#define PUSH_CONSTANTS_SIZE 112
#define SPEC_OUTPUT_GAMMA 0
#define SPEC_SHADOWMAP_ENABLED 1
@@ -69,11 +91,35 @@
#define MAX_LIGHTS_PER_CLUSTER 64
#define CLUSTER_BUILD_GROUP_SIZE 64
#define CLUSTER_CULL_GROUP_SIZE 64
#define ENVMAP_GGX_LEVELS 7U
#define ENVMAP_GGX_ROUGHNESS_MIN 0.200000
#define ENVMAP_GGX_GROUP_SIZE 8
#define EXPOSURE_HISTOGRAM_BINS 256
#define EXPOSURE_HISTOGRAM_GROUP_SIZE 16
#define ATMOSPHERE_TRANSMITTANCE_WIDTH 256
#define ATMOSPHERE_TRANSMITTANCE_HEIGHT 64
#define ATMOSPHERE_MULTISCATTERING_SIZE 32
#define ATMOSPHERE_SKYVIEW_WIDTH 192
#define ATMOSPHERE_SKYVIEW_HEIGHT 108
#define ATMOSPHERE_AMBIENT_GROUP_SIZE 256
#define MAX_CLOUD_LAYERS 4
#define FOG_HORIZON_SAMPLES 32
#define WEATHER_GROUP_SIZE 8
#define FOG_VOLUME_BIAS 10.000000
#define INSTANCE_CULL_GROUP_SIZE 64
#define INSTANCE_OFFSETS_GROUP_SIZE 1024
#define INSTANCE_COMMAND_GROUP_SIZE 64
#define INSTANCE_COUNTER_HEADER 8
#define INSTANCE_JOB_SLEEPERS 1U
#define HIZ_GROUP_SIZE 8
#define SHADOW_FIT_GROUP_SIZE 16
#define LOCAL_SHADOW_NEAR 0.150000
#define PARTICLE_GROUP_SIZE 64
#endif
#include "settings.glsl"
#ifndef LAYOUT_UI
#if !defined(LAYOUT_UI) && !defined(LAYOUT_COMPUTE)
// vk14::draw_constants, PUSH_CONSTANTS_SIZE bytes, visible in every stage. draw: the element of model_draws[] the draw reads
// (the passes of the scene); flags and data: the parameters of the special shaders, each says what it takes from them
layout(push_constant) uniform draw_constants_block
@@ -171,8 +217,9 @@ layout(std430, set = 0, binding = CL_LIGHT_SSBO) readonly buffer light_ssbo
uint lights_count;
// 1 if the shading takes the lights from the list of the cluster of a fragment, see cluster_ubo
uint lights_clustered;
// not used by the shaders of this renderer yet (the sky ambient light and the prefiltered environment map come later)
// scale of the directional part of the sky ambient light, see sky_ssbo
float sky_ambient_factor;
// 1 if rough reflections come from the prefiltered environment map
uint env_ggx;
light_s lights[];
@@ -183,6 +230,16 @@ layout(std430, set = 0, binding = CL_LIGHT_SSBO) readonly buffer light_ssbo
// the post-processing reads the parameters the opengl renderer gives it in the model block from the push constants
#define param pc.data
// exposure worked out by exposure_average.comp, gl::exposure_data
layout(std430, set = POST_SET, binding = POST_EXPOSURE_SSBO) readonly buffer exposure_ssbo
{
uint histogram[EXPOSURE_HISTOGRAM_BINS];
float adapted_luminance;
float exposure;
float average_luminance;
uint valid;
};
#elif !defined(LAYOUT_UI)
// the block of the scene, gl::scene_ubs (every member kept, the layout is the one of the c++ struct)
@@ -204,7 +261,8 @@ layout(std140, set = PASS_SET, binding = SCENE_UBO) uniform scene_ubo
vec4 shadow_fit;
};
// the model block of a draw, gl::model_ubs (256 bytes), the one of the draw picked by pc.draw. the names below stand for its
#ifndef LAYOUT_COMPUTE
// the model block of a draw, gl::model_ubs (256 bytes), the one of the draw picked by pc.draw (MODEL_DRAW). the names below stand for its
// fields, so the shaders read them as the uniform block of the opengl renderer
struct model_draw_s
{
@@ -225,15 +283,32 @@ layout(std430, set = PASS_SET, binding = DRAW_SSBO) readonly buffer model_draw_s
model_draw_s model_draws[];
};
#define modelview model_draws[pc.draw].modelview
#define modelviewnormal model_draws[pc.draw].modelviewnormal
#define param model_draws[pc.draw].param
#define future model_draws[pc.draw].future
#define opacity model_draws[pc.draw].opacity
#define emission model_draws[pc.draw].emission
#define fog_density model_draws[pc.draw].fog_density
#define alpha_mult model_draws[pc.draw].alpha_mult
#define shadow_tone model_draws[pc.draw].shadow_tone
#ifdef MODEL_DRAWS
// programs of the multi draws of the instances drawn from the gpu culling: the blocks of the draws of a multi draw follow one
// another from the one of pc.draw, picked by the number of the draw in it. the fragment stage gets the block from the vertex
// stage (which writes f_model_draw)
#if defined(STAGE_VERTEX)
layout(location = 11) flat out uint f_model_draw;
#define MODEL_DRAW (pc.draw + uint(gl_DrawID))
#elif defined(STAGE_FRAGMENT)
layout(location = 11) flat in uint f_model_draw;
#define MODEL_DRAW f_model_draw
#else
#define MODEL_DRAW pc.draw
#endif
#else
#define MODEL_DRAW pc.draw
#endif
#define modelview model_draws[MODEL_DRAW].modelview
#define modelviewnormal model_draws[MODEL_DRAW].modelviewnormal
#define param model_draws[MODEL_DRAW].param
#define future model_draws[MODEL_DRAW].future
#define opacity model_draws[MODEL_DRAW].opacity
#define emission model_draws[MODEL_DRAW].emission
#define fog_density model_draws[MODEL_DRAW].fog_density
#define alpha_mult model_draws[MODEL_DRAW].alpha_mult
#define shadow_tone model_draws[MODEL_DRAW].shadow_tone
// per-instance modelview matrices of the instanced draws, element 0 the identity. a draw which isn't instanced is drawn with
// firstInstance 0, an instanced one from its base instance on, with modelview set to the submodel-local chain (computed from
@@ -242,6 +317,7 @@ layout(std430, set = PASS_SET, binding = INSTANCE_SSBO) readonly buffer instance
{
mat4 instance_modelview[];
};
#endif
// the light buffer of the pass, gl::light_data
layout(std430, set = PASS_SET, binding = LIGHT_SSBO) readonly buffer light_ssbo
@@ -255,13 +331,21 @@ layout(std430, set = PASS_SET, binding = LIGHT_SSBO) readonly buffer light_ssbo
uint lights_count;
// 1 if the shading takes the lights from the list of the cluster of a fragment, see cluster_ubo
uint lights_clustered;
// not used by the shaders of this renderer yet (the sky ambient light and the prefiltered environment map come later)
// scale of the directional part of the sky ambient light, see sky_ssbo
float sky_ambient_factor;
// 1 if rough reflections come from the prefiltered environment map
uint env_ggx;
light_s lights[];
};
// directional part of the sky ambient light: real spherical harmonics (bands 1 and 2, band 0 is ambient) of the light
// a surface facing a world space direction gets, in units of ambient. see gl::sky_data
layout(std430, set = PASS_SET, binding = SKY_SSBO) readonly buffer sky_ssbo
{
vec4 sky_ambient_sh[9];
};
// the grid of the light clusters of the pass, gl::cluster_ubs; the compute passes fill the lists, shading only reads them
layout(std140, set = PASS_SET, binding = CLUSTER_UBO) uniform cluster_ubo
{

View File

@@ -0,0 +1,10 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
// depth prepass of materials which never discard a fragment (no alpha test)
layout(location = 2) in vec2 f_coord;
void main()
{
}

View File

@@ -0,0 +1,26 @@
#version 460 core
#define STAGE_VERTEX 1
#include "common.glsl"
// depth prepass of the instances drawn from the gpu culling (opengl46_renderer::Render_Instances_Indirect()).
// the position is worked out the way vertex.vert of the materials does it, and declared invariant in both, so the colour
// pass which follows gets the same depth for the same surface
layout(location = 0) in vec3 v_vert;
layout(location = 2) in vec2 v_coord;
invariant gl_Position;
layout(location = 2) out vec2 f_coord;
void main()
{
mat4 mv = effective_modelview();
vec4 position = mv * vec4(v_vert, 1.0);
gl_Position = projection * position;
f_coord = v_coord;
#ifdef MODEL_DRAWS
// which block of the multi draw the fragments read (common.glsl)
f_model_draw = MODEL_DRAW;
#endif
}

View File

@@ -0,0 +1,18 @@
#version 460 core
#define STAGE_FRAGMENT 1
#include "common.glsl"
// depth prepass of materials whose colour shader tests the alpha of the diffuse texture against the opacity of the
// material (mat_default.frag and the like). the test here is stricter by one step of 8-bit alpha: a fragment which writes
// depth here is never discarded by the colour pass, which would leave a hole with nothing drawn behind it
layout(location = 2) in vec2 f_coord;
#pragma texture(diffuse, 0, sRGB_A)
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D diffuse;
void main()
{
if (texture(diffuse, f_coord).a < opacity + 1.0 / 255.0)
discard;
}

View File

@@ -0,0 +1,107 @@
#version 460 core
#define STAGE_COMPUTE 1
// prefilters the environment map with the GGX lobe for rough reflections (split sum approximation, Karis 2013), one level
// of the prefiltered map per dispatch. filtered importance sampling (Krivanek, Colbert 2008): each sample reads the mipmap
// of the source whose texels are about as large as the solid angle the sample stands for, so a few dozen samples give a
// smooth result. run when an update of the environment map is complete
#define LAYOUT_COMPUTE 1
#include "common.glsl"
layout(local_size_x = ENVMAP_GGX_GROUP_SIZE, local_size_y = ENVMAP_GGX_GROUP_SIZE) in;
layout(set = COMPUTE_SET, binding = 0, rgba16f) writeonly uniform imageCube prefilter_target;
// the plain map with its box filtered mipmaps, on the unit gl::program binds envmap to
layout(set = COMPUTE_SET, binding = 1) uniform samplerCube envmap;
// the parameters of the kernel (push constants)
layout(push_constant) uniform kernel_constants
{
// perceptual roughness of the level being written
float roughness;
// size of the level being written, and of the first level of the source
int target_size;
float source_size;
int sample_count;
};
const float PI = 3.14159265359;
// direction through the centre of a texel of a cube map face, in the order and orientation of GL_TEXTURE_CUBE_MAP_POSITIVE_X..
vec3 cube_direction(ivec3 texel, int size)
{
vec2 uv = (vec2(texel.xy) + 0.5) / float(size) * 2.0 - 1.0;
switch (texel.z)
{
case 0: return vec3(1.0, -uv.y, -uv.x);
case 1: return vec3(-1.0, -uv.y, uv.x);
case 2: return vec3(uv.x, 1.0, uv.y);
case 3: return vec3(uv.x, -1.0, -uv.y);
case 4: return vec3(uv.x, -uv.y, 1.0);
default: return vec3(-uv.x, -uv.y, -1.0);
}
}
vec2 hammersley(uint i, uint n)
{
uint bits = bitfieldReverse(i);
return vec2(float(i) / float(n), float(bits) * 2.3283064365386963e-10);
}
// half vector around N for the GGX distribution of linear roughness a
vec3 importance_sample_ggx(vec2 xi, float a, vec3 N)
{
float phi = 2.0 * PI * xi.x;
float costheta = sqrt((1.0 - xi.y) / (1.0 + (a * a - 1.0) * xi.y));
float sintheta = sqrt(max(1.0 - costheta * costheta, 0.0));
vec3 H = vec3(sintheta * cos(phi), sintheta * sin(phi), costheta);
vec3 up = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0);
vec3 tangentx = normalize(cross(up, N));
vec3 tangenty = cross(N, tangentx);
return tangentx * H.x + tangenty * H.y + N * H.z;
}
float distribution_ggx(float NdotH, float a)
{
float a2 = a * a;
float d = NdotH * NdotH * (a2 - 1.0) + 1.0;
return a2 / (PI * d * d);
}
void main()
{
ivec3 texel = ivec3(gl_GlobalInvocationID);
if (texel.x >= target_size || texel.y >= target_size)
return;
vec3 N = normalize(cube_direction(texel, target_size));
// the view is taken along the normal, which is what the split sum approximation assumes
vec3 V = N;
float a = max(roughness * roughness, 1e-3);
float texelsolidangle = 4.0 * PI / (6.0 * source_size * source_size);
vec3 sum = vec3(0.0);
float weight = 0.0;
uint count = uint(sample_count);
for (uint i = 0U; i < count; ++i)
{
vec3 H = importance_sample_ggx(hammersley(i, count), a, N);
float VdotH = dot(V, H);
vec3 L = 2.0 * VdotH * H - V;
float NdotL = dot(N, L);
if (NdotL <= 0.0)
continue;
float NdotH = max(dot(N, H), 0.0);
// pdf of L: D * NdotH / (4 * VdotH)
float pdf = distribution_ggx(NdotH, a) * NdotH / max(4.0 * VdotH, 1e-4);
float samplesolidangle = 1.0 / (float(count) * max(pdf, 1e-6));
float level = max(0.5 * log2(samplesolidangle / texelsolidangle) + 1.0, 0.0);
vec3 color = textureLod(envmap, L, level).rgb;
if (any(isnan(color)) || any(isinf(color)))
color = vec3(0.0);
sum += max(color, vec3(0.0)) * NdotL;
weight += NdotL;
}
imageStore(prefilter_target, texel, vec4(sum / max(weight, 1e-4), 1.0));
}

Some files were not shown because too many files have changed in this diff Show More