17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-07 08:48:01 +01:00

Improve rain shader

This commit is contained in:
2026-10-26 15:51:56 +01:00
parent a6bdeeaa2b
commit 719c729929
43 changed files with 5620 additions and 355 deletions

View File

@@ -0,0 +1,447 @@
/*
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/windscreenwater.h"
#include "model/Model3d.h"
#include "model/material.h"
#include "rendering/renderer.h"
#include "simulation/simulation.h"
#include "simulation/simulationenvironment.h"
#include "utilities/Globals.h"
#include "utilities/Logs.h"
#include "vehicle/DynObj.h"
#include "vehicle/Train.h"
namespace gfx
{
namespace
{
// the param of mat_rain_windscreen the slot of a glass goes to (water_slot: param[2].z) and the one of the size of its drops
// (rain_scale: param[2].y)
std::size_t constexpr SLOT_PARAM{2};
// how fast the rain and the snow fall, m/s (as the particles of the rain and the snow, gfx::gpu_particles)
float constexpr RAIN_FALL{7.f};
float constexpr SNOW_FALL{1.1f};
// the share of the wind the rain drifts with (as the particles of the rain and the snow)
double constexpr WIND_SHARE{0.5};
// the smallest raindrop landing as a drop of its own, mm: the smaller ones are the mist of the wet glass
float constexpr LEAST_DROP{0.5f};
// the water a flake of snow brings, mm3 on average (water_spawn.comp: 0.3 .. 2.5)
float constexpr FLAKE_WATER{1.4f};
// the most landings tried on a glass in a step
std::uint32_t constexpr MOST_TRIES{1024};
// the longest step, seconds: a longer frame slows the water down rather than let it jump
double constexpr LONGEST_STEP{0.05};
// whether the material is a glass: one of mat_rain_windscreen, with a mask of the wipers
bool is_glass(opengl_material const *Material)
{
return (Material != nullptr) && (Material->shader != nullptr) && (Material->shader->texture_conf.count("wipermask") > 0) && (Material->shader->param_conf.count("water_slot") > 0);
}
// calls Visit for every submodel of the branch, its children and its siblings
template <typename Visit> void visit_submodels(TSubModel *Submodel, Visit &&Visitor)
{
while (Submodel != nullptr)
{
Visitor(Submodel);
visit_submodels(Submodel->ChildGet(), Visitor);
Submodel = Submodel->NextGet();
}
}
// the vertices of a submodel, wherever they are kept
gfx::vertex_array const &submodel_vertices(TSubModel const *Submodel)
{
return (Submodel->m_geometry.handle != null_handle ? GfxRenderer->Vertices(Submodel->m_geometry.handle) : Submodel->Vertices);
}
// the transformation of a submodel to the space of the model
glm::mat4 submodel_transform(TSubModel const *Submodel)
{
float4x4 matrix;
Submodel->ParentMatrix(&matrix);
return glm::make_mat4(matrix.readArray());
}
} // namespace
bool windscreen_water::available()
{
if (m_state == 0)
{
m_state = (gpu_init() ? 1 : -1);
if (m_state < 0)
{
WriteLog("windscreen water: the simulation can't be used, the windscreens make their water up");
}
}
return m_state > 0;
}
void windscreen_water::release()
{
assign(false);
m_glasses = {};
m_glasscount = 0;
m_vehicle = nullptr;
m_cab = nullptr;
}
void windscreen_water::reset()
{
release();
m_state = 0;
}
void windscreen_water::assign(bool const Assigned)
{
if (false == bool(m_host.material))
{
return;
}
for (std::size_t slot = 0; slot < m_glasscount; ++slot)
{
auto const &glass{m_glasses[slot]};
if (auto *material{m_host.material(glass.material)}; is_glass(material))
{
material->params[SLOT_PARAM].z = (Assigned && glass.baked ? static_cast<float>(slot + 1) : 0.f);
}
}
}
void windscreen_water::rasterize(TSubModel *Submodel, material_handle const Material, glm::vec3 const &Inside, std::vector<glm::vec4> &Axes) const
{
if ((Submodel->m_material != Material) || (Submodel->eType != GL_TRIANGLES))
{
return;
}
auto const &vertices{submodel_vertices(Submodel)};
if (vertices.size() < 3)
{
return;
}
auto const transform{submodel_transform(Submodel)};
gfx::index_array const *indices{nullptr};
if ((Submodel->m_geometry.handle != null_handle) && (Submodel->m_geometry.index_count > 0))
{
indices = &GfxRenderer->Indices(Submodel->m_geometry.handle);
}
else if (false == Submodel->Indices.empty())
{
indices = &Submodel->Indices;
}
auto const count{indices != nullptr ? indices->size() : vertices.size()};
auto const size{static_cast<int>(gl::WATER_MAP)};
for (std::size_t first = 0; first + 2 < count; first += 3)
{
std::array<glm::vec3, 3> position;
std::array<glm::vec2, 3> uv;
bool valid{true};
for (std::size_t corner = 0; corner < 3; ++corner)
{
auto const index{indices != nullptr ? static_cast<std::size_t>((*indices)[first + corner]) : first + corner};
if (index >= vertices.size())
{
valid = false;
break;
}
position[corner] = glm::vec3{transform * glm::vec4{vertices[index].position, 1.f}};
uv[corner] = vertices[index].texture;
}
if (false == valid)
{
continue;
}
// the steps along the glass for the steps of u and v: the edges of the triangle in both spaces
glm::mat2 const uvedges{uv[1] - uv[0], uv[2] - uv[0]};
auto const det{glm::determinant(uvedges)};
if (std::abs(det) < 1e-12f)
{
continue;
}
auto const inverse{glm::inverse(uvedges)};
auto const e1{position[1] - position[0]};
auto const e2{position[2] - position[0]};
// [tu tv] = [e1 e2] * inverse
auto const tu{e1 * inverse[0][0] + e2 * inverse[0][1]};
auto const tv{e1 * inverse[1][0] + e2 * inverse[1][1]};
auto const centre{(position[0] + position[1] + position[2]) / 3.f};
auto const out{glm::dot(glm::cross(tu, tv), centre - Inside) >= 0.f ? 1.f : -1.f};
// a triangle laid on a repeat of the texture is taken to the first one (the mask of the wipers repeats with it)
auto const shift{glm::floor((uv[0] + uv[1] + uv[2]) / 3.f)};
for (auto &corner : uv)
{
corner -= shift;
}
auto const lower{glm::max(glm::ivec2{glm::floor(glm::min(glm::min(uv[0], uv[1]), uv[2]) * static_cast<float>(size))}, glm::ivec2{0})};
auto const upper{glm::min(glm::ivec2{glm::ceil(glm::max(glm::max(uv[0], uv[1]), uv[2]) * static_cast<float>(size))}, glm::ivec2{size - 1})};
for (int y = lower.y; y <= upper.y; ++y)
{
for (int x = lower.x; x <= upper.x; ++x)
{
// the middle of the texel inside the triangle (barycentric)
auto const point{(glm::vec2{static_cast<float>(x), static_cast<float>(y)} + 0.5f) / static_cast<float>(size)};
auto const weights{inverse * (point - uv[0])};
if ((weights.x < -1e-4f) || (weights.y < -1e-4f) || (weights.x + weights.y > 1.f + 1e-4f))
{
continue;
}
auto const texel{static_cast<std::size_t>(y) * gl::WATER_MAP + static_cast<std::size_t>(x)};
Axes[texel * 2] = glm::vec4{tu, 1.f};
Axes[texel * 2 + 1] = glm::vec4{tv, out};
}
}
}
}
void windscreen_water::build(TModel3d &Cab)
{
auto *root{Cab.GetSMRoot()};
if ((root == nullptr) || (false == bool(m_host.material)))
{
return;
}
// the materials of the glasses, in the order they come in the model; and a point inside the cab, the middle of the box
// around the whole model (the glasses are around it)
std::vector<material_handle> materials;
glm::vec3 lower{std::numeric_limits<float>::max()};
glm::vec3 upper{std::numeric_limits<float>::lowest()};
visit_submodels(root, [&](TSubModel *Submodel) {
auto const &vertices{submodel_vertices(Submodel)};
if (false == vertices.empty())
{
auto const transform{submodel_transform(Submodel)};
for (auto const &vertex : vertices)
{
auto const position{glm::vec3{transform * glm::vec4{vertex.position, 1.f}}};
lower = glm::min(lower, position);
upper = glm::max(upper, position);
}
}
auto const material{Submodel->m_material};
if ((material != null_handle) && (material > 0) && (Submodel->eType == GL_TRIANGLES) && (std::find(materials.begin(), materials.end(), material) == materials.end()) && is_glass(m_host.material(material)))
{
materials.push_back(material);
}
});
if (materials.empty())
{
return;
}
if (materials.size() > gl::WATER_SLOTS)
{
WriteLog("windscreen water: the cab has " + std::to_string(materials.size()) + " glasses, the water of the first " + std::to_string(gl::WATER_SLOTS) + " is simulated");
materials.resize(gl::WATER_SLOTS);
}
auto const inside{(lower + upper) * 0.5f};
auto const texels{static_cast<std::size_t>(gl::WATER_MAP) * gl::WATER_MAP};
auto const size{static_cast<int>(gl::WATER_MAP)};
for (auto const material : materials)
{
std::vector<glm::vec4> axes(texels * 2, glm::vec4{0.f});
visit_submodels(root, [&](TSubModel *Submodel) { rasterize(Submodel, material, inside, axes); });
auto &glass{m_glasses[m_glasscount]};
glass = windscreen_water::glass{};
glass.material = material;
auto const *opengl{m_host.material(material)};
glass.mask = opengl->textures[opengl->shader->texture_conf.at("wipermask").id];
for (std::size_t texel = 0; texel < texels; ++texel)
{
if (axes[texel * 2].w > 0.f)
{
auto const density{glm::length(glm::cross(glm::vec3{axes[texel * 2]}, glm::vec3{axes[texel * 2 + 1]}))};
glass.densest = std::max(glass.densest, density);
glass.area += density / static_cast<float>(texels);
}
}
if (glass.densest <= 0.f)
{
continue;
}
// the edges spread out by two texels, so the filtering of the map near them takes the glass along
for (int pass = 0; pass < 2; ++pass)
{
auto const source{axes};
for (int y = 0; y < size; ++y)
{
for (int x = 0; x < size; ++x)
{
auto const texel{static_cast<std::size_t>(y * size + x)};
if (source[texel * 2].w > 0.f)
{
continue;
}
for (int k = 0; k < 8; ++k)
{
static int const dx[]{1, -1, 0, 0, 1, 1, -1, -1};
static int const dy[]{0, 0, 1, -1, 1, -1, 1, -1};
auto const nx{x + dx[k]};
auto const ny{y + dy[k]};
if ((nx < 0) || (ny < 0) || (nx >= size) || (ny >= size))
{
continue;
}
auto const neighbour{static_cast<std::size_t>(ny * size + nx)};
if (source[neighbour * 2].w > 0.f)
{
axes[texel * 2] = glm::vec4{glm::vec3{source[neighbour * 2]}, 0.5f};
axes[texel * 2 + 1] = source[neighbour * 2 + 1];
break;
}
}
}
}
}
gpu_setup(m_glasscount, axes);
WriteLog("windscreen water: glass " + std::to_string(m_glasscount + 1) + " of the cab, material " + opengl->GetName() + ", " + std::to_string(glass.area) + " m2");
++m_glasscount;
}
}
void windscreen_water::update(double const Seconds)
{
if (false == available())
{
return;
}
auto const *vehicle{simulation::Train != nullptr ? simulation::Train->Dynamic() : nullptr};
auto *cab{vehicle != nullptr ? vehicle->mdKabina : nullptr};
if ((vehicle != m_vehicle) || (cab != m_cab))
{
// another cab: its glasses dry
release();
m_vehicle = vehicle;
m_cab = cab;
m_bladesbefore = glm::vec4{-1.f};
if (cab != nullptr)
{
build(*cab);
}
}
if (m_glasscount == 0)
{
return;
}
gl::water_ubs block{};
// the blades of the wipers of the vehicle, as the glasses read them (the channels of the mask follow the wipers of the cab
// the vehicle is driven from)
glm::vec4 blades{-1.f};
if (auto const cabactive{vehicle->MoverParameters->CabActive}; cabactive != 0)
{
auto const count{static_cast<int>(vehicle->dWiperPos.size())};
for (int i = 0; i < 4; ++i)
{
if (i < count)
{
auto const index{cabactive > 0 ? i : count - 1 - i};
blades[i] = static_cast<float>(std::clamp(vehicle->dWiperPos[index], 0.0, 1.0));
}
}
}
// the space of the model of the cab, drawn with the matrix of the vehicle
auto const toworld{glm::dmat3{vehicle->mMatrix}};
auto const tomodel{glm::inverse(toworld)};
auto const up{glm::normalize(glm::vec3{tomodel * glm::dvec3{0.0, 1.0, 0.0}})};
auto const air{glm::vec3{tomodel * (glm::dvec3{simulation::Environment.wind()} * WIND_SHARE - vehicle->VectorFront() * vehicle->MoverParameters->V)}};
auto const heavy{std::clamp(Global.Overcast - 1.f, 0.f, 1.f)};
auto const snow{Global.Weather == "snow:"};
auto const fall{snow ? SNOW_FALL : RAIN_FALL};
// the masks of the wipers baked into the maps once the textures are there
for (std::size_t slot = 0; slot < m_glasscount; ++slot)
{
auto &glass{m_glasses[slot]};
if (false == glass.baked)
{
block.counts = glm::uvec4{0u, 0u, static_cast<std::uint32_t>(slot), 0u};
glass.baked = gpu_bake(slot, glass.mask, block);
}
}
assign(true);
auto const seconds{std::clamp(Seconds, 0.0, LONGEST_STEP)};
if (seconds <= 0.0)
{
return;
}
m_time += seconds;
++m_step;
// the drops landing: as many raindrops a second come to a square metre of the ground as the rain gives water (from light rain at
// 1 mm/h to a downpour at 30 mm/h) in the drops of the Marshall-Palmer distribution above the least landing as a drop of its own
// (exponential, the mean diameter falling with the rain); a glass gets them in the share it faces the rain coming at it
// against the cab (water_spawn.comp)
float perground{0.f}; // the drops coming to a square metre of the ground in a second
auto const rate{1.f + 29.f * std::pow(heavy, 1.5f)}; // mm/h
auto const lambda{4.1f * std::pow(rate, -0.21f)}; // 1 / mm
if (heavy > 0.f)
{
auto const water{rate / 3600.f * 1e6f}; // mm3 a second on a square metre
if (snow)
{
perground = water * 0.25f / FLAKE_WATER;
}
else
{
// the share of the water in the drops above the least one, and their mean volume
auto const x{lambda * LEAST_DROP};
auto const share{std::exp(-x) * (1.f + x + x * x / 2.f + x * x * x / 6.f)};
auto const d0{LEAST_DROP};
auto const mean{0.5236f * (d0 * d0 * d0 + 3.f * d0 * d0 / lambda + 6.f * d0 / (lambda * lambda) + 6.f / (lambda * lambda * lambda))};
perground = water * share / mean;
}
}
// the landings tried on a glass: as if the whole of its uv held the glass at its densest, the ones at the less dense places and
// facing the rain less are left out. the share of a landing left over goes to the next step
auto const coming{glm::length(air - up * fall)};
std::uint32_t most{0};
m_tries = 0;
for (std::size_t slot = 0; slot < m_glasscount; ++slot)
{
auto &glass{m_glasses[slot]};
auto const *material{m_host.material(glass.material)};
auto const simulated{glass.baked && is_glass(material)};
glass.landings += static_cast<double>(perground / fall * coming * glass.densest) * seconds;
auto const whole{std::floor(glass.landings)};
glass.landings -= whole;
auto const tries{simulated ? static_cast<std::uint32_t>(std::min<double>(whole, MOST_TRIES)) : 0u};
most = std::max(most, tries);
m_tries += tries;
auto const scale{(material != nullptr) && std::isfinite(material->params[SLOT_PARAM].y) ? std::max(material->params[SLOT_PARAM].y, 0.05f) : 1.f};
block.slots[slot] = glm::vec4{static_cast<float>(tries), 1.f / glass.densest, simulated ? 1.f : 0.f, scale};
}
block.step = glm::vec4{static_cast<float>(seconds), static_cast<float>(m_time), heavy, snow ? 1.f : 0.f};
block.up = glm::vec4{up, Global.AirTemperature};
block.air = glm::vec4{air, fall};
block.blades = blades;
block.blades_before = glm::vec4{
m_bladesbefore.x < 0.f ? blades.x : m_bladesbefore.x, m_bladesbefore.y < 0.f ? blades.y : m_bladesbefore.y, m_bladesbefore.z < 0.f ? blades.z : m_bladesbefore.z, m_bladesbefore.w < 0.f ? blades.w : m_bladesbefore.w};
block.rain = glm::vec4{LEAST_DROP, lambda, 1.f, 0.f};
block.counts = glm::uvec4{m_step * 2654435761u, most, 0u, 0u};
m_bladesbefore = blades;
gpu_step(block, most);
}
std::string windscreen_water::info() const
{
if ((m_state <= 0) || (m_glasscount == 0))
{
return {};
}
return "windscreen water: " + std::to_string(m_glasscount) + " glasses, " + std::to_string(m_tries) + " drops landing tried in the frame\n";
}
} // namespace gfx

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/.
*/
#pragma once
#include <array>
#include <cstdint>
#include <functional>
#include <string>
#include <vector>
#include <glm/glm.hpp>
#include "gl/ubo.h"
#include "interfaces/ITexture.h"
#include "utilities/Classes.h"
struct opengl_material;
class TModel3d;
class TSubModel;
class TDynamicObject;
namespace gfx
{
// the water on the windscreens of the cab of the train, simulated on the gpu, whichever api runs it.
//
// a glass is a material of mat_rain_windscreen (one with a mask of the wipers) in the model of the cab of the train, up to
// gl::WATER_SLOTS of them; the material is told its slot in its param water_slot (1 + the slot, the drawing of the glass reads
// the water of the slot where it's there, mat_rain_windscreen.frag). the water lies in the coordinates of the texture of the glass
// (uv), as the mask does. the map of a glass is made here from the triangles of the model with its material: for the texels of
// the uv on the glass, the step along the glass (metres, the space of the model of the cab) for a step of u and of v, and which
// way is out of the cab; the passes of the gpu bake the mask of the wipers into it. once a frame the step of the simulation is
// worked out here (the time, the rain or snow, gravity and the air against the cab in the space of its model, the blades) and
// the passes run it (water_*.comp: the landing drops, the drops moving and carried by the blades, the grids, the drops flowing
// together); what they keep and the glasses read is the storage of the water (water.glsl, gl::WATER_*_OFFSET)
class windscreen_water
{
public:
// what the renderer gives: the materials of its manager, which get their slots
struct host
{
std::function<opengl_material *(material_handle)> material;
};
windscreen_water() = default;
virtual ~windscreen_water() = default;
windscreen_water(windscreen_water const &) = delete;
windscreen_water &operator=(windscreen_water const &) = delete;
void set_host(host Host)
{
m_host = std::move(Host);
}
// whether the passes could be made
bool available();
// once a frame, before the colour passes: the cab of the train followed, the step of the simulation run. Seconds: of the
// simulation since the frame before (0 when it's paused)
void update(double Seconds);
// the materials let go of their slots, the glasses forgotten (the storage stays as it is, unused)
void release();
std::string info() const;
protected:
// the glasses forgotten and the programs taken as not made yet (the renderer let go of them), made again when next needed
void reset();
// the programs and the storage; false: the simulation can't be used, the glasses make their water up
virtual bool gpu_init() = 0;
// the slot set up for a glass: its drops, its grid and its paths cleared, its header saying it isn't simulated (until the
// first step), the axes of its map (two vec4 a texel, gl::WATER_MAP x WATER_MAP texels, see water.glsl) put in the storage
virtual void gpu_setup(std::size_t Slot, std::vector<glm::vec4> const &Axes) = 0;
// the mask of the wipers of a glass (a texture of the renderer) baked into its map; false when the texture isn't ready yet
// (tried again with the next frame)
virtual bool gpu_bake(std::size_t Slot, texture_handle Mask, gl::water_ubs const &Block) = 0;
// the step of the simulation of the glasses Block says are simulated; Tries: the most landings tried on a glass
virtual void gpu_step(gl::water_ubs const &Block, std::uint32_t Tries) = 0;
private:
struct glass
{
material_handle material{null_handle};
texture_handle mask{null_handle};
float densest{0.f}; // the most metres squared a unit of the uv holds
float area{0.f}; // metres squared of the glass
double landings{0.0}; // the share of a landing left over from the steps before
bool baked{false};
};
// the glasses of the model of a cab found and set up
void build(TModel3d &Cab);
// the triangles of a glass laid into its map; Inside: a point inside the cab
void rasterize(TSubModel *Submodel, material_handle Material, glm::vec3 const &Inside, std::vector<glm::vec4> &Axes) const;
// the slots given to the materials of the glasses (every frame: a material made anew takes its default params)
void assign(bool Assigned);
host m_host;
int m_state{0}; // 0: not set up yet, 1: ready, -1: can't be used
TDynamicObject const *m_vehicle{nullptr};
TModel3d const *m_cab{nullptr};
std::array<glass, gl::WATER_SLOTS> m_glasses{};
std::size_t m_glasscount{0};
glm::vec4 m_bladesbefore{-1.f};
double m_time{0.0}; // seconds the simulation runs
std::uint32_t m_step{0};
std::uint32_t m_tries{0}; // landings tried in the last step, all glasses
};
} // namespace gfx

View File

@@ -86,6 +86,7 @@ bool opengl46_renderer::Init(GLFWwindow *Window)
m_window = Window;
gl::glsl_common_setup();
m_windscreenwater.set_host({[this](material_handle const Material) { return &m_materials.material(Material); }});
if (true == Global.ScaleSpecularValues)
{
@@ -1086,7 +1087,8 @@ void opengl46_renderer::SwapBuffers()
+ ( Global.gfx_lights_clustered ? "clustered" : "first " + std::to_string( std::max( Global.DynamicLightCount, 0 ) ) )
+ "\n" + m_locallights.info()
+ m_localshadows.info()
+ m_gpuparticles.info();
+ m_gpuparticles.info()
+ m_windscreenwater.info();
if( auto const cullstats = ( m_viewports.empty() || !( Global.gfx_lights_clustered || Global.gfx_lights_clusterheatmap ) ? std::nullopt : m_viewports.front()->clusters->stats() ) ) {
statstext +=
"\ncluster lists: " + std::to_string( cullstats->index_count )
@@ -1599,6 +1601,16 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode)
float percipitation_intensity = glm::saturate(Global.Overcast - 1.);
scene_ubs.rain_params.x = percipitation_intensity; // % amount of droplets
scene_ubs.rain_params.y = glm::mix(15., 1., percipitation_intensity); // Regeneration time
scene_ubs.rain_params.z = (Global.Weather == "snow:" ? 1.f : 0.f); // flakes on the glass in place of the drops
scene_ubs.rain_params.w = Global.AirTemperature; // how fast the flakes melt
{
// the air the drops on the glass are blown with: the wind as the particles drift with it, less the velocity of the
// vehicle (V is along the front of the vehicle)
glm::dvec3 air{glm::dvec3{simulation::Environment.wind()} * 0.5};
if (TDynamicObject const *owner = Global.pCamera.m_owner)
air -= owner->VectorFront() * owner->MoverParameters->V;
scene_ubs.rain_wind = glm::vec4{glm::vec3{air}, 0.f};
}
if (TDynamicObject const *owner = Global.pCamera.m_owner; owner && !!owner->MoverParameters->CabActive)
{
for (int i = 0; i < 4; ++i)
@@ -1643,6 +1655,10 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode)
scene_ubs.inv_view = glm::inverse( glm::mat4{ glm::mat3{ m_colorpass.pass_camera.modelview() } } );
scene_ubo->update(scene_ubs);
scene_ubo->bind_uniform();
// the water on the windscreens of the cab: a step of its simulation once a frame, its storage for the glasses of every view
if (vp.main)
m_windscreenwater.update(Timer::GetDeltaTime());
m_windscreenwater.bind();
if (Global.bWireFrame)
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

View File

@@ -14,6 +14,7 @@ http://mozilla.org/MPL/2.0/.
#include "rendering/opengl46/opengl46light.h"
#include "rendering/opengl46/opengl46particles.h"
#include "rendering/opengl46/opengl46gpuparticles.h"
#include "rendering/opengl46/opengl46water.h"
#include "rendering/opengl46/opengl46skydome.h"
#include "rendering/opengl46/opengl46precipitation.h"
#include "simulation/simulationenvironment.h"
@@ -533,6 +534,7 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
opengl46_precipitation m_precipitationrenderer;
opengl46_particles m_particlerenderer; // particle visualization subsystem
opengl46_gpu_particles m_gpuparticles; // the smoke, rain and snow of the gpu (gfx.smoke.gpu, gfx.precipitation.gpu)
opengl46_windscreen_water m_windscreenwater; // the water on the windscreens of the cab of the train
bool m_particlesoft{false}; // the depth for the soft particles is bound in the colour pass being drawn
float m_framerate;

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/.
*/
#include "stdafx.h"
#include "rendering/opengl46/opengl46water.h"
#include "rendering/renderer.h"
#include "utilities/Logs.h"
namespace
{
GLuint groups(std::size_t const Count)
{
return static_cast<GLuint>((Count + gl::WATER_GROUP_SIZE - 1) / gl::WATER_GROUP_SIZE);
}
} // namespace
bool opengl46_windscreen_water::gpu_init()
{
try
{
std::array<char const *, pass::count> const names{"water_trail.comp", "water_spawn.comp", "water_move.comp", "water_clear.comp", "water_grid.comp",
"water_merge.comp", "water_gather.comp", "water_settle.comp", "water_bake.comp"};
for (std::size_t i = 0; i < names.size(); ++i)
{
gl::shader shader(names[i]);
m_programs[i] = std::make_unique<gl::program>(std::vector<std::reference_wrapper<const gl::shader>>{shader});
}
}
catch (gl::shader_exception const &e)
{
ErrorLog("windscreen water: can't use the programs of the simulation: " + std::string(e.what()));
return false;
}
m_storage = std::make_unique<gl::ssbo>(static_cast<GLsizeiptr>(gl::WATER_STORAGE_SIZE), gl::WATER_SSBO_BINDING);
::glClearNamedBufferData(*m_storage, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, nullptr);
m_block = std::make_unique<gl::ubo>(sizeof(gl::water_ubs), gl::WATER_UBO_BINDING);
return true;
}
void opengl46_windscreen_water::bind() const
{
if (m_storage)
m_storage->bind_base();
}
void opengl46_windscreen_water::gpu_setup(std::size_t const Slot, std::vector<glm::vec4> const &Axes)
{
if (!m_storage)
return;
// the frames before may still draw with the storage
::glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT);
// the drops, the grid, the paths, the header and the counts of the slot cleared, the axes of the map put in
for (auto const &part : gl::water_slot_parts(Slot))
::glClearNamedBufferSubData(*m_storage, GL_R32UI, static_cast<GLintptr>(part.first), static_cast<GLsizeiptr>(part.second), GL_RED_INTEGER, GL_UNSIGNED_INT, nullptr);
auto const axes{gl::water_slot_parts(Slot)[1]};
m_storage->update(Axes.data(), static_cast<GLintptr>(axes.first), static_cast<GLsizeiptr>(std::min(axes.second, Axes.size() * sizeof(glm::vec4))));
::glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT);
}
bool opengl46_windscreen_water::gpu_bake(std::size_t const Slot, texture_handle const Mask, gl::water_ubs const &Block)
{
if ((!m_storage) || (Mask == null_handle))
return false;
auto &texture{GfxRenderer->Texture(Mask)};
if ((false == texture.create()) || (false == texture.get_is_ready()) || (texture.get_id() == 0))
return false;
m_storage->bind_base();
m_block->update(Block);
m_block->bind_uniform();
::glBindTextureUnit(static_cast<GLuint>(gl::WATER_MASK_TEX), static_cast<GLuint>(texture.get_id()));
m_programs[pass::bake]->dispatch(groups(static_cast<std::size_t>(gl::WATER_MAP) * gl::WATER_MAP));
::glBindTextureUnit(static_cast<GLuint>(gl::WATER_MASK_TEX), 0);
::glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
gl::program::unbind();
return true;
}
void opengl46_windscreen_water::gpu_step(gl::water_ubs const &Block, std::uint32_t const Tries)
{
if (!m_storage)
return;
m_storage->bind_base();
m_block->update(Block);
m_block->bind_uniform();
auto const slots{static_cast<GLuint>(gl::WATER_SLOTS)};
auto const drops{groups(gl::WATER_DROPS)};
auto const cells{groups(static_cast<std::size_t>(gl::WATER_GRID) * gl::WATER_GRID)};
auto const run = [&](pass const Pass, GLuint const Groups) {
if (Groups == 0)
return;
m_programs[Pass]->dispatch(Groups, slots);
::glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
};
// the wetted paths the blades went over in the step wiped
if (Block.blades != Block.blades_before)
run(pass::trail, groups(static_cast<std::size_t>(gl::WATER_TRAIL) * gl::WATER_TRAIL));
run(pass::spawn, groups(Tries));
run(pass::move, drops);
run(pass::clear, cells);
run(pass::grid, drops);
run(pass::merge, drops);
run(pass::gather, drops);
run(pass::settle, drops);
run(pass::clear, cells);
run(pass::grid, drops);
gl::program::unbind();
}

View File

@@ -0,0 +1,55 @@
/*
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 "rendering/common/windscreenwater.h"
#include "gl/shader.h"
#include "gl/ssbo.h"
#include "gl/ubo.h"
// the water on the windscreens for the opengl 4.6 renderer (gfx::windscreen_water): the storage of the water, the block of the
// step and the compute programs
class opengl46_windscreen_water : public gfx::windscreen_water
{
public:
opengl46_windscreen_water() = default;
~opengl46_windscreen_water() override = default;
// the storage bound to its point for the glasses drawn next (the bindings are kept by every context of its own)
void bind() const;
protected:
bool gpu_init() override;
void gpu_setup(std::size_t Slot, std::vector<glm::vec4> const &Axes) override;
bool gpu_bake(std::size_t Slot, texture_handle Mask, gl::water_ubs const &Block) override;
void gpu_step(gl::water_ubs const &Block, std::uint32_t Tries) override;
private:
// the passes of the step, in their order
enum pass
{
trail,
spawn,
move,
clear,
grid,
merge,
gather,
settle,
bake,
count
};
std::array<std::unique_ptr<gl::program>, pass::count> m_programs;
std::unique_ptr<gl::ssbo> m_storage;
std::unique_ptr<gl::ubo> m_block;
};

View File

@@ -71,7 +71,9 @@ constexpr std::uint32_t HEADLIGHT_SHADOW_TEX = 23;
constexpr std::uint32_t SCENE_DEPTH_TEX = 24;
// the image of the last frame of the main view with its mipmaps, which the screen space reflections take their colour from
constexpr std::uint32_t SSR_HISTORY_TEX = 25;
constexpr std::uint32_t PASS_BINDING_COUNT = 26;
// the water on the windscreens (gfx::windscreen_water): the drops, their grids and the wetted paths the glasses read
constexpr std::uint32_t WATER_SSBO = 26;
constexpr std::uint32_t PASS_BINDING_COUNT = 27;
// 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

View File

@@ -31,6 +31,7 @@ http://mozilla.org/MPL/2.0/.
#include "rendering/vulkan14/vulkan14terrain.h"
#include "rendering/vulkan14/vulkan14grass.h"
#include "rendering/vulkan14/vulkan14particles.h"
#include "rendering/vulkan14/vulkan14water.h"
#include "rendering/vulkan14/vulkan14commands.h"
#include "rendering/vulkan14/vulkan14raytracing.h"
#include "rendering/vulkan14/vulkan14dlss.h"
@@ -397,6 +398,8 @@ struct scene_state
bool grass_tried{false};
// the smoke and the rain or snow of the gpu
gpu_particles particles;
// the water on the windscreens of the cab of the train; its storage is held by the pass sets (WATER_SSBO)
windscreen_water water;
// the ray traced reflections (gfx.raytracing), on a device with ray queries; its set is set RT_SET of the pipelines of the scene
std::unique_ptr<ray_tracing> raytracing;
// the heatmap of the light clusters (gfx.lights.clusterheatmap), made when it's first drawn

View File

@@ -168,7 +168,7 @@ std::string vulkan14_renderer::scene_info()
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();
scene.particles.info() + scene.water.info();
auto const &stats{scene.clusters.stats};
if (stats.valid && (Global.gfx_lights_clustered || Global.gfx_lights_clusterheatmap))
{

View File

@@ -990,6 +990,8 @@ void vulkan14_renderer::render_scene()
{
auto &ctx{*m_context};
auto &scene{ctx.scene};
// the water on the windscreens of the cab: a step of its simulation once a frame, before the passes which draw the glasses
scene.water.update(Timer::GetDeltaTime());
if (scene.targets.size() < m_viewports.size())
{
scene.targets.resize(m_viewports.size());
@@ -1406,6 +1408,16 @@ void vulkan14_renderer::render_viewport(std::size_t const Index)
float percipitation_intensity = glm::saturate(Global.Overcast - 1.);
scene_ubs.rain_params.x = percipitation_intensity;
scene_ubs.rain_params.y = glm::mix(15., 1., percipitation_intensity);
scene_ubs.rain_params.z = (Global.Weather == "snow:" ? 1.f : 0.f); // flakes on the glass in place of the drops
scene_ubs.rain_params.w = Global.AirTemperature; // how fast the flakes melt
{
// the air the drops on the glass are blown with: the wind as the particles drift with it, less the velocity of the
// vehicle (V is along the front of the vehicle)
glm::dvec3 air{glm::dvec3{simulation::Environment.wind()} * 0.5};
if (TDynamicObject const *owner = Global.pCamera.m_owner)
air -= owner->VectorFront() * owner->MoverParameters->V;
scene_ubs.rain_wind = glm::vec4{glm::vec3{air}, 0.f};
}
if (TDynamicObject const *owner = Global.pCamera.m_owner; owner && !!owner->MoverParameters->CabActive)
{
for (int i = 0; i < 4; ++i)

View File

@@ -150,6 +150,7 @@ VkDescriptorType pass_binding_type(std::uint32_t const Binding)
case CLUSTER_RANGE_SSBO:
case CLUSTER_INDEX_SSBO:
case SKY_SSBO:
case WATER_SSBO:
return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
case ATMOSPHERE_UBO:
case WEATHER_HORIZON_UBO:
@@ -708,6 +709,40 @@ bool vulkan14_renderer::init_scene(std::string &Reason)
scene.particles.init(device, ctx.pipelines, ctx.shaders, scene.pass_layout, ctx.memory, ctx.uploads, ctx.deletions, ctx.ring, ctx.device.caps().limits.minUniformBufferOffsetAlignment, frames,
std::move(host));
}
{
vk14::windscreen_water::host host;
host.commands = [this]() {
suspend_rendering();
return m_context->commands;
};
host.texture = [this](texture_handle const Texture, VkImageView &View, VkSampler &Sampler) {
auto &ctx{*m_context};
if ((Texture == null_handle) || (false == bool(ctx.textures)))
{
return false;
}
auto &source{ctx.textures->mark_as_used(Texture)};
if (false == source.create())
{
return false;
}
auto const *entry{ctx.images.find(source.get_id())};
if (entry == nullptr)
{
return false;
}
View = entry->target->view();
Sampler = entry->sampler;
return true;
};
if (false == scene.water.init(device, ctx.pipelines, ctx.shaders, scene.pass_layout, ctx.memory, ctx.uploads, ctx.deletions, ctx.ring, ctx.device.caps().limits.minUniformBufferOffsetAlignment,
std::move(host)))
{
Reason = "no storage of the water on the windscreens";
return false;
}
scene.water.set_host({[this](material_handle const Material) { return &m_materials.material(Material); }});
}
scene.terrain->init(ctx.resources, [this](vk14::command_stream *const Commands, VkImageView const Target, VkFormat const Format, VkExtent2D const Extent, VkImageView const Source, VkSampler const Sampler) {
auto &scene{m_context->scene};
auto const pipeline{post_pipeline("copy.frag", static_cast<int>(Format))};
@@ -779,6 +814,7 @@ void vulkan14_renderer::destroy_scene()
scene.terrain.reset();
scene.grass.destroy();
scene.particles.destroy();
scene.water.destroy();
scene.grass_program.reset();
scene.grass_tried = false;
scene.cluster_heatmap.reset();
@@ -931,6 +967,7 @@ void vulkan14_renderer::write_pass_sets(vk14::instance_cull *Cull)
VkDescriptorBufferInfo const atmosphereblock{scene.sky.atmosphere.handle(), 0, sizeof(gl::atmosphere_ubs)};
VkDescriptorBufferInfo const weatherblock{ctx.ring.handle(), 0, offsetof(gl::weather_ubs, fog_horizon)};
VkDescriptorBufferInfo const horizonblock{scene.weather_horizon.handle(), 0, sizeof(glm::vec4) * gl::FOG_HORIZON_SAMPLES};
VkDescriptorBufferInfo const water{scene.water.storage(), 0, VK_WHOLE_SIZE};
VkDescriptorImageInfo const headlightsinfo{scene.headlights_sampler, scene.headlights_view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL};
std::vector<VkWriteDescriptorSet> writes;
std::vector<std::unique_ptr<VkDescriptorImageInfo>> images;
@@ -964,6 +1001,7 @@ void vulkan14_renderer::write_pass_sets(vk14::instance_cull *Cull)
write(handle, ATMOSPHERE_UBO, &atmosphereblock, nullptr);
write(handle, WEATHER_UBO, &weatherblock, nullptr);
write(handle, WEATHER_HORIZON_UBO, &horizonblock, nullptr);
write(handle, WATER_SSBO, &water, nullptr);
write(handle, HEADLIGHT_TEX, nullptr, &headlightsinfo);
write(handle, SHADOW_TEX, nullptr, image(ctx.sampling.shadow, views[SHADOW_TEX]));
write(handle, ENV_TEX, nullptr, image(ctx.sampling.clamp_linear, views[ENV_TEX]));

View File

@@ -802,6 +802,7 @@ std::string binding_preamble(bool const Raytracing)
define("HEADLIGHT_SHADOW_TEX", HEADLIGHT_SHADOW_TEX);
define("SCENE_DEPTH_TEX", SCENE_DEPTH_TEX);
define("SSR_HISTORY_TEX", SSR_HISTORY_TEX);
define("WATER_SSBO", WATER_SSBO);
define("PASS_BINDING_COUNT", PASS_BINDING_COUNT);
define("MATERIAL_TEXTURE_FIRST", MATERIAL_TEXTURE_FIRST);
define("CL_CLUSTER_UBO", CL_CLUSTER_UBO);
@@ -865,6 +866,13 @@ std::string binding_preamble(bool const Raytracing)
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);
define("WATER_SLOTS", gl::WATER_SLOTS);
define("WATER_DROPS", gl::WATER_DROPS);
define("WATER_GRID", gl::WATER_GRID);
define("WATER_CELL_DROPS", gl::WATER_CELL_DROPS);
define("WATER_TRAIL", gl::WATER_TRAIL);
define("WATER_MAP", gl::WATER_MAP);
define("WATER_GROUP_SIZE", gl::WATER_GROUP_SIZE);
return preamble;
}

View File

@@ -0,0 +1,244 @@
/*
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/vulkan14water.h"
#include "rendering/vulkan14/vulkan14commands.h"
#include "utilities/Logs.h"
#include <cstring>
namespace vk14
{
namespace
{
std::uint32_t groups(std::size_t const Count)
{
return static_cast<std::uint32_t>((Count + gl::WATER_GROUP_SIZE - 1) / gl::WATER_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
windscreen_water::~windscreen_water()
{
// the materials may be gone already
set_host({});
destroy();
}
bool windscreen_water::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, 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_host = std::move(Host);
m_storage = std::make_shared<buffer>();
m_full = m_storage->create(Memory, static_cast<VkDeviceSize>(gl::WATER_STORAGE_SIZE), storage_usage, memory_kind::device, "windscreen water");
if (false == m_full)
{
// the pass sets need a buffer to hold: a small one, which no glass reads (none gets a slot)
ErrorLog("windscreen water: no storage of " + std::to_string(gl::WATER_STORAGE_SIZE / (1024 * 1024)) + " MiB, the windscreens make their water up");
m_storage = std::make_shared<buffer>();
if (false == m_storage->create(Memory, 65536, storage_usage, memory_kind::device, "windscreen water"))
{
m_storage.reset();
return false;
}
}
return true;
}
void windscreen_water::destroy()
{
if (m_device == VK_NULL_HANDLE)
{
return;
}
for (auto &kernel : m_kernels)
{
kernel.destroy(m_device);
}
m_storage.reset();
m_device = VK_NULL_HANDLE;
m_cleared = false;
reset();
}
bool windscreen_water::gpu_init()
{
if ((m_device == VK_NULL_HANDLE) || (false == m_full) || (false == bool(m_host.commands)) || (false == bool(m_host.texture)))
{
return false;
}
try
{
std::array<char const *, pass::count> const names{"water_trail.comp", "water_spawn.comp", "water_move.comp", "water_clear.comp", "water_grid.comp",
"water_merge.comp", "water_gather.comp", "water_settle.comp", "water_bake.comp"};
for (std::size_t i = 0; i < names.size(); ++i)
{
if (false == m_kernels[i].create(m_device, *m_pipelines, m_shaders->get(names[i]), m_passlayout, {}, names[i]))
{
throw gfx::shader_exception(std::string("no compute pipeline of ") + names[i]);
}
}
}
catch (gfx::shader_exception const &e)
{
ErrorLog("windscreen water: can't use the programs of the simulation: " + std::string(e.what()));
return false;
}
return true;
}
void windscreen_water::begin(command_stream *const Commands)
{
// the glasses of the frames before read the storage, the fills and the copies write it
memory_barrier(Commands, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_SHADER_STORAGE_READ_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_CLEAR_BIT, VK_ACCESS_2_SHADER_STORAGE_READ_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT);
if (false == m_cleared)
{
// nothing in the storage yet
m_cleared = true;
Commands->fill_buffer(m_storage->handle(), 0, VK_WHOLE_SIZE, 0);
compute_barrier(Commands);
}
}
void windscreen_water::gpu_setup(std::size_t const Slot, std::vector<glm::vec4> const &Axes)
{
auto const commands{m_host.commands()};
if ((commands == nullptr) || (false == m_full))
{
return;
}
begin(commands);
// the drops, the grid, the paths, the header and the counts of the slot cleared with the commands of the frame
auto const parts{gl::water_slot_parts(Slot)};
for (std::size_t part = 0; part < parts.size(); ++part)
{
if (part != 1)
{
commands->fill_buffer(m_storage->handle(), static_cast<VkDeviceSize>(parts[part].first), static_cast<VkDeviceSize>(parts[part].second), 0);
}
}
// the axes of the map copied after that, from a staging buffer kept until the gpu is done with the frame
auto const size{std::min<VkDeviceSize>(static_cast<VkDeviceSize>(parts[1].second), static_cast<VkDeviceSize>(Axes.size() * sizeof(glm::vec4)))};
auto staging{std::make_shared<buffer>()};
if ((false == staging->create(*m_memory, size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, memory_kind::upload, "windscreen water map")) || (staging->mapped() == nullptr))
{
ErrorLog("windscreen water: no staging buffer for the map of a glass");
compute_barrier(commands);
return;
}
std::memcpy(staging->mapped(), Axes.data(), static_cast<std::size_t>(size));
staging->flush(0, size);
VkBufferCopy2 region{};
region.sType = VK_STRUCTURE_TYPE_BUFFER_COPY_2;
region.dstOffset = static_cast<VkDeviceSize>(parts[1].first);
region.size = size;
VkCopyBufferInfo2 copy{};
copy.sType = VK_STRUCTURE_TYPE_COPY_BUFFER_INFO_2;
copy.srcBuffer = staging->handle();
copy.dstBuffer = m_storage->handle();
copy.regionCount = 1;
copy.pRegions = &region;
commands->copy_buffer2(&copy);
compute_barrier(commands);
m_deletions->push(m_uploads->current_value(), [staging]() {});
}
ring_slice windscreen_water::block(gl::water_ubs const &Block)
{
auto const slice{m_ring->allocate(sizeof(gl::water_ubs), m_alignment)};
if (slice)
{
std::memcpy(slice.data, &Block, sizeof(gl::water_ubs));
}
return slice;
}
bool windscreen_water::gpu_bake(std::size_t const Slot, texture_handle const Mask, gl::water_ubs const &Block)
{
auto const commands{m_host.commands()};
if ((commands == nullptr) || (false == m_full) || (Mask == null_handle))
{
return false;
}
VkImageView view{VK_NULL_HANDLE};
VkSampler sampler{VK_NULL_HANDLE};
if ((false == m_host.texture(Mask, view, sampler)) || (view == VK_NULL_HANDLE))
{
return false;
}
auto const slice{block(Block)};
if (false == bool(slice))
{
return false;
}
begin(commands);
m_kernels[pass::bake].run(commands,
{buffer_descriptor(m_storage->handle(), 0, VK_WHOLE_SIZE), buffer_descriptor(slice.buffer, slice.offset, sizeof(gl::water_ubs)),
image_descriptor(sampler, view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)},
groups(static_cast<std::size_t>(gl::WATER_MAP) * gl::WATER_MAP));
compute_barrier(commands);
return true;
}
void windscreen_water::gpu_step(gl::water_ubs const &Block, std::uint32_t const Tries)
{
auto const commands{m_host.commands()};
if ((commands == nullptr) || (false == m_full))
{
return;
}
auto const slice{block(Block)};
if (false == bool(slice))
{
return;
}
begin(commands);
std::vector<descriptor> const descriptors{buffer_descriptor(m_storage->handle(), 0, VK_WHOLE_SIZE), buffer_descriptor(slice.buffer, slice.offset, sizeof(gl::water_ubs))};
auto const run = [&](pass const Pass, std::uint32_t const Groups) {
if (Groups == 0)
{
return;
}
m_kernels[Pass].run(commands, descriptors, Groups, gl::WATER_SLOTS);
compute_barrier(commands);
};
auto const drops{groups(gl::WATER_DROPS)};
auto const cells{groups(static_cast<std::size_t>(gl::WATER_GRID) * gl::WATER_GRID)};
// the wetted paths the blades went over in the step wiped
if (Block.blades != Block.blades_before)
{
run(pass::trail, groups(static_cast<std::size_t>(gl::WATER_TRAIL) * gl::WATER_TRAIL));
}
run(pass::spawn, groups(Tries));
run(pass::move, drops);
run(pass::clear, cells);
run(pass::grid, drops);
run(pass::merge, drops);
run(pass::gather, drops);
run(pass::settle, drops);
run(pass::clear, cells);
run(pass::grid, drops);
}
} // namespace vk14

View File

@@ -0,0 +1,96 @@
/*
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 water on the windscreens in the vulkan renderer (gfx::windscreen_water, opengl46_windscreen_water): the storage of the water,
// which the pass sets hold for the glasses (WATER_SSBO), and the compute kernels of the simulation, run with the commands of the
// frame before its passes (outside a rendering). the kernels push everything they read (set COMPUTE_SET: the storage, the block of
// the step in the ring of the frame, the mask of the wipers being baked)
#pragma once
#include "rendering/common/windscreenwater.h"
#include "rendering/vulkan14/vulkan14compute.h"
#include "rendering/vulkan14/vulkan14texture.h"
#include <array>
#include <functional>
#include <memory>
namespace vk14
{
class windscreen_water : public gfx::windscreen_water
{
public:
// what the renderer does for the water
struct host
{
// the commands of the frame, outside a rendering
std::function<command_stream *()> commands;
// the view and the sampler of a texture of the renderer, made when needed; false when it isn't ready
std::function<bool(texture_handle, VkImageView &, VkSampler &)> texture;
};
windscreen_water() = default;
~windscreen_water() override;
// called once, before the pass sets are made: the storage made (the pass sets hold it from then on). false: no storage
bool init(VkDevice Device, pipeline_library &Pipelines, shader_library &Shaders, VkDescriptorSetLayout Passlayout, allocator &Memory, uploader &Uploads, deletion_queue &Deletions, frame_ring &Ring,
VkDeviceSize Alignment, host Host);
// everything goes (the device is idle)
void destroy();
// the storage the pass sets hold
VkBuffer storage() const
{
return m_storage ? m_storage->handle() : VK_NULL_HANDLE;
}
protected:
bool gpu_init() override;
void gpu_setup(std::size_t Slot, std::vector<glm::vec4> const &Axes) override;
bool gpu_bake(std::size_t Slot, texture_handle Mask, gl::water_ubs const &Block) override;
void gpu_step(gl::water_ubs const &Block, std::uint32_t Tries) override;
private:
// the passes of the step, in their order
enum pass
{
trail,
spawn,
move,
clear,
grid,
merge,
gather,
settle,
bake,
count
};
// the block of the step in the ring of the frame; empty when the ring is full
ring_slice block(gl::water_ubs const &Block);
// the frames before are done with the storage before the commands of this one write it
void begin(command_stream *Commands);
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};
host m_host;
std::array<compute_kernel, pass::count> m_kernels;
std::shared_ptr<buffer> m_storage;
bool m_full{false}; // the storage is as large as the simulation needs (else a stand-in, the glasses make their water up)
bool m_cleared{false};
};
} // namespace vk14