18
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-15 21:04:13 +01:00

Some optimalisations

This commit is contained in:
2026-11-09 20:58:02 +01:00
parent d89b7dddf0
commit ff2ff308ed
20 changed files with 645 additions and 31 deletions

View File

@@ -342,6 +342,7 @@ set(SOURCES
"rendering/common/localshadows.cpp"
"rendering/common/cabprobe.cpp"
"rendering/common/skyocclusion.cpp"
"rendering/common/texturealpha.cpp"
"rendering/common/terrainmaterials.cpp"
"rendering/common/grassfield.cpp"
"rendering/common/gpuparticles.cpp"

View File

@@ -23,6 +23,7 @@ http://mozilla.org/MPL/2.0/.
#include "utilities/utilities.h"
#include "scene/sn_utils.h"
#include "rendering/flip-s3tc.h"
#include "rendering/common/texturealpha.h"
#include "stb/stb_image.h"
//#include <png.h>
#include "dds-ktx/dds-ktx.h"
@@ -54,12 +55,25 @@ struct texture_request {
glm::dvec3 origin { 0.0 };
// results, written by the thread reading the data
std::vector<unsigned char> data;
// every texel passes the alpha test of the shadow maps: checked by the worker threads, not by a thread waiting for the data
std::optional<bool> alphakept;
bool failed { false };
bool done { false }; // guarded by the mutex of the loader
};
namespace {
// the alpha test of the shadow maps checked on the data of a dds file, block by block of its format
std::optional<bool> dds_alpha_kept( texture_request const &Request ) {
switch( Request.fourcc ) {
case FOURCC_DXT1: { return gfx::texels_pass_alpha_test( gfx::alpha_layout::bc1, Request.data ); }
case FOURCC_DXT3: { return gfx::texels_pass_alpha_test( gfx::alpha_layout::bc2, Request.data ); }
case FOURCC_DXT5: { return gfx::texels_pass_alpha_test( gfx::alpha_layout::bc3, Request.data ); }
// the only one load_DDS() takes of the dx10 formats
case FOURCC_DX10: { return gfx::texels_pass_alpha_test( gfx::alpha_layout::bc7, Request.data ); }
default: { return std::nullopt; }
} }
// worker threads reading texture data from disk
class texture_loader {
@@ -92,7 +106,8 @@ public:
if( lookup != std::end( m_queue ) ) {
m_queue.erase( lookup );
lock.unlock();
read( *Request );
// the thread waiting for the data doesn't check its alpha: the one which makes the texture does it, if it can wait
read( *Request, false );
lock.lock();
Request->done = true;
return;
@@ -111,6 +126,7 @@ public:
return false;
}
Request.data = std::vector<unsigned char>();
Request.alphakept.reset();
Request.done = false;
Request.failed = false;
return true; }
@@ -136,14 +152,14 @@ private:
auto request { m_queue.front() };
m_queue.pop_front();
lock.unlock();
read( *request );
read( *request, true );
lock.lock();
request->done = true;
m_read.notify_all();
} }
// same as the reading part of texture_source::load_DDS()
static void read( texture_request &Request ) {
// same as the reading part of texture_source::load_DDS(). Check: the alpha of the texels checked too (on a worker thread)
static void read( texture_request &Request, bool const Check ) {
std::ifstream file( Request.file, std::ios::binary );
if( false == file.is_open() ) {
Request.failed = true;
@@ -171,6 +187,10 @@ private:
height = std::max( height / 2, 4 );
--mapcount;
}
}
Request.alphakept.reset();
if( Check && file.good() ) {
Request.alphakept = dds_alpha_kept( Request );
} }
std::mutex m_mutex;
@@ -438,6 +458,8 @@ texture_source::load() {
WriteLog( "Loading texture data from \"" + name + "\"", logtype::texture );
data_state = resource_state::loading;
// known only of the data a worker thread reads (complete_data())
data_alphakept.reset();
if( type == ".dds" ) { load_DDS(); }
else if( type == ".tga" ) { load_TGA(); }
@@ -1343,6 +1365,7 @@ texture_source::complete_data() {
return;
}
data.swap( completed->data );
data_alphakept = completed->alphakept;
data_state = resource_state::good;
size = data.size() / 1024;
}

View File

@@ -12,6 +12,7 @@ http://mozilla.org/MPL/2.0/.
#include <istream>
#include <chrono>
#include <limits>
#include <optional>
#include "winheaders.h"
#include "utilities/memorystats.h"
#include <string>
@@ -133,6 +134,9 @@ public:
bool is_rendertarget = false; // is used as postfx rendertarget, without loaded data
bool is_texstub = false; // for make_from_memory internal_src: functionality
std::vector<unsigned char> data; // texture data (stored GL-style, bottom-left origin)
// whether every texel of the data passes the alpha test of the shadow maps (gfx::texels_pass_alpha_test()), when the worker
// thread which read it checked that; unknown otherwise
std::optional<bool> data_alphakept;
resource_state data_state{ resource_state::none }; // current state of texture data
unsigned int data_width{ 0 },
data_height{ 0 },

View File

@@ -643,9 +643,9 @@ void scene_walk::Render(cell_sequence::iterator First, cell_sequence::iterator L
}
// remaining (non-instanceable) opaque instance nodes go through the per-node path, or with the instances
// to the gpu culling, each on its own
for (auto *instance : cell->m_instancesopaque)
// (the instanceable ones are handled via bucket)
for (auto *instance : Single_Instances(*cell))
{
if( instance->m_instanceable ) { continue; } // already handled via bucket
// the model of a lamp would shadow everything in the map of the lamp
if( instance == m_cubeskipinstance ) { continue; }
if( walk_queue_model( instance, cellinrange ) ) { continue; }
@@ -680,8 +680,7 @@ void scene_walk::Render(cell_sequence::iterator First, cell_sequence::iterator L
// opaque parts of instanced models -- accumulate into the
// frame-level map; flushed once per unique model after the loop.
Queue_Instances( cell );
for( auto *instance : cell->m_instancesopaque ) {
if( instance->m_instanceable ) { continue; }
for( auto *instance : Single_Instances( *cell ) ) {
if( walk_queue_model( instance, true ) ) { continue; }
Render( instance );
m_modelcomparekey = nullptr;
@@ -1074,6 +1073,32 @@ void scene_walk::Update_Instance_Records( TModel3d const *Model, scene::basic_ce
Bucket.gpustale = true;
}
// the opaque instances of a scene cell drawn each on its own. an instance stops being drawn with the others of its model by an event
// (TAnimModel::AddContainer()), which changes scene::node_state_version and with it the version of the instance records; the editor
// makes the records anew in every frame. the list is made once for a version, before any walk of the frame reads it, so a walk going
// through it doesn't see it change
scene::basic_cell::instance_sequence const &scene_walk::Single_Instances( scene::basic_cell const &Cell ) const
{
auto const stale = [ this, &Cell ]() {
return ( Cell.m_instancessingleversion != m_instancerecordsversion )
|| ( Cell.m_instancessinglesource != Cell.m_instancesopaque.size() ); };
if( stale() ) {
// the lists the passes share are made by one walk at a time (walk_sync)
gfx::walk_sync::change change;
if( stale() ) {
Cell.m_instancessingle.clear();
for( auto *instance : Cell.m_instancesopaque ) {
if( false == instance->m_instanceable ) {
Cell.m_instancessingle.emplace_back( instance );
}
}
Cell.m_instancessinglesource = Cell.m_instancesopaque.size();
Cell.m_instancessingleversion = m_instancerecordsversion;
}
}
return Cell.m_instancessingle;
}
// whether none of the instances of a bucket can pass the tests of the culling of the pass (Render_Instanced(), on the gpu
// instance_cull.comp): by the bounds of their records the visible ones lie wholly out of the frustum of the pass, beyond their
// range or the draw range, or are too small on the screen. the tests are those of the culling with the nearest location and

View File

@@ -153,6 +153,8 @@ class scene_walk
void Queue_Instances(scene::basic_cell const *Cell);
// makes the records of the instances held by a bucket of a scene cell, with locations relative to Origin
void Update_Instance_Records(TModel3d const *Model, scene::basic_cell::instance_bucket const &Bucket, glm::dvec3 const &Origin);
// the opaque instances of a scene cell which aren't drawn with the others of their model, made anew with the instance records
scene::basic_cell::instance_sequence const &Single_Instances(scene::basic_cell const &Cell) const;
// whether none of the instances of a bucket can pass the culling of the current pass, by the bounds of their records
bool Bucket_Culled(scene::basic_cell::instance_bucket const &Bucket) const;
// the sphere around the locations of the visible instances of a bucket, with a margin for the precision of the records
@@ -344,8 +346,9 @@ class scene_walk
// 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 texture the shadow material binds for the alpha test of a material, or null_handle (no test). a renderer which knows a
// texture passes the test everywhere draws its shadows without one
virtual 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

View File

@@ -314,6 +314,8 @@ void sky_occlusion::stop()
void sky_occlusion::clear()
{
// the cells go with the scenery
m_cellsignatures.clear();
// once, until a scenery is there again
if ((m_side <= 0) || (m_region == nullptr))
return;
@@ -628,20 +630,39 @@ std::uint64_t sky_occlusion::signature(scene::basic_region *Region, glm::ivec2 c
if (overlaps(data.area, shape_reach(shape)))
hash = hash_mix(hash, static_cast<std::uint64_t>(data.geometry) ^ static_cast<std::uint64_t>(data.visible ? 1 : 0));
}
for (auto const *instance : cell.m_instancesopaque)
{
hash = hash_mix(hash, static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(instance)) ^ static_cast<std::uint64_t>(instance->visible() ? 1 : 0));
auto const location{instance->location()};
hash = hash_mix_double(hash, location.x);
hash = hash_mix_double(hash, location.y);
hash = hash_mix_double(hash, location.z);
hash = hash_mix_double(hash, static_cast<double>(instance->vAngle.y));
}
hash = hash_mix(hash, instances_signature(cell));
}
}
return hash;
}
std::uint64_t sky_occlusion::instances_signature(scene::basic_cell const &Cell)
{
auto const make = [&Cell]() {
std::uint64_t hash{0x1a57a2ce};
for (auto const *instance : Cell.m_instancesopaque)
{
hash = hash_mix(hash, static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(instance)) ^ static_cast<std::uint64_t>(instance->visible() ? 1 : 0));
auto const location{instance->location()};
hash = hash_mix_double(hash, location.x);
hash = hash_mix_double(hash, location.y);
hash = hash_mix_double(hash, location.z);
hash = hash_mix_double(hash, static_cast<double>(instance->vAngle.y));
}
return hash;
};
// the editor moves the instances and hides them with their layers without the version changing: read anew each time there, and
// kept for when it's left
auto &kept{m_cellsignatures[&Cell]};
if (EditorModeFlag || (kept.version != scene::node_state_version) || (kept.count != Cell.m_instancesopaque.size()))
{
kept.hash = make();
kept.version = scene::node_state_version;
kept.count = Cell.m_instancesopaque.size();
}
return kept.hash;
}
void sky_occlusion::gather(scene::basic_region *Region, glm::ivec2 const &Tile, std::vector<triangle> &Triangles)
{
auto const tilesize{static_cast<double>(m_layout.tile)};

View File

@@ -18,6 +18,7 @@ http://mozilla.org/MPL/2.0/.
#include <mutex>
#include <string>
#include <thread>
#include <unordered_map>
#include <vector>
#include "gl/ubo.h"
@@ -29,7 +30,8 @@ class TSubModel;
namespace scene
{
class basic_region;
}
class basic_cell;
} // namespace scene
namespace gfx
{
@@ -181,6 +183,8 @@ class sky_occlusion
void gather_tunnels(scene::basic_region *Region, glm::ivec2 const &Tile, std::vector<glm::vec2> &Points);
// what the scenery over a tile is made of, cheap to work out: a change of it means the tile is gathered again
std::uint64_t signature(scene::basic_region *Region, glm::ivec2 const &Tile);
// the part of the signature made of the opaque instances of a cell: their places and whether they're shown
std::uint64_t instances_signature(scene::basic_cell const &Cell);
// the values of a tile nothing is known of yet, in the textures and in the copy of the cpu
void neutral(int const Slot);
void apply(tile_result &Result);
@@ -208,6 +212,16 @@ class sky_occlusion
std::vector<glm::vec4> m_neutralheights;
std::vector<std::uint16_t> m_neutralocclusion;
std::size_t m_checkslot{0}; // the slot whose signature is checked next
// the part of the signatures made of the instances of a cell, worked out for a scene::node_state_version and a count of its
// instances. outside the editor an instance is shown or hidden only with the version changing, and isn't moved; reading every
// instance of the cells around for each check took a large part of the frame in the forests
struct cell_signature
{
unsigned int version{0};
std::size_t count{0};
std::uint64_t hash{0};
};
std::unordered_map<scene::basic_cell const *, cell_signature> m_cellsignatures;
scene::basic_region const *m_region{nullptr};
// shared with the thread, under m_mutex

View File

@@ -0,0 +1,219 @@
/*
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/texturealpha.h"
#include <array>
#include <cstddef>
namespace gfx
{
namespace
{
using block_check = bool (*)(std::uint8_t const *Block);
bool rgba8_kept(std::span<std::uint8_t const> const Data)
{
for (std::size_t alpha = 3; alpha < Data.size(); alpha += 4)
{
if (Data[alpha] < alpha_test_kept)
{
return false;
}
}
return true;
}
// bc1: with the first colour above the second the block has four colours and no alpha; otherwise the index 3 is transparent black
bool bc1_kept(std::uint8_t const *Block)
{
auto const colour0{static_cast<unsigned>(Block[0]) | (static_cast<unsigned>(Block[1]) << 8)};
auto const colour1{static_cast<unsigned>(Block[2]) | (static_cast<unsigned>(Block[3]) << 8)};
if (colour0 > colour1)
{
return true;
}
for (std::size_t row = 4; row < 8; ++row)
{
// both bits of a 2 bit index set: the index 3
if ((Block[row] & (Block[row] >> 1) & 0x55u) != 0)
{
return false;
}
}
return true;
}
// bc2: 4 bits of alpha per texel, kept from 8 up (the top bit of each half of a byte)
bool bc2_kept(std::uint8_t const *Block)
{
for (std::size_t byte = 0; byte < 8; ++byte)
{
if ((Block[byte] & 0x88u) != 0x88u)
{
return false;
}
}
return true;
}
// bc3: two alpha endpoints and 3 bit indices into the palette they make
bool bc3_kept(std::uint8_t const *Block)
{
auto const alpha0{static_cast<unsigned>(Block[0])};
auto const alpha1{static_cast<unsigned>(Block[1])};
std::array<unsigned, 8> palette{alpha0, alpha1, 0u, 0u, 0u, 0u, 0u, 0u};
if (alpha0 > alpha1)
{
for (unsigned index = 2; index < 8; ++index)
{
palette[index] = ((8 - index) * alpha0 + (index - 1) * alpha1) / 7;
}
}
else
{
for (unsigned index = 2; index < 6; ++index)
{
palette[index] = ((6 - index) * alpha0 + (index - 1) * alpha1) / 5;
}
palette[6] = 0;
palette[7] = 255;
}
std::uint64_t indices{0};
for (std::size_t byte = 0; byte < 6; ++byte)
{
indices |= static_cast<std::uint64_t>(Block[2 + byte]) << (8 * byte);
}
for (unsigned texel = 0; texel < 16; ++texel)
{
if (palette[(indices >> (3 * texel)) & 7u] < alpha_test_kept)
{
return false;
}
}
return true;
}
// Count bits of a bc7 block from bit Offset, the lowest first
unsigned bc7_bits(std::uint8_t const *Block, unsigned const Offset, unsigned const Count)
{
unsigned result{0};
for (unsigned bit = 0; bit < Count; ++bit)
{
auto const index{Offset + bit};
result |= ((static_cast<unsigned>(Block[index >> 3]) >> (index & 7u)) & 1u) << bit;
}
return result;
}
// an endpoint of Bits bits as the decoder makes it 8 bits wide
unsigned bc7_expand(unsigned const Value, unsigned const Bits)
{
return (Value << (8 - Bits)) | (Value >> (2 * Bits - 8));
}
// modes 4 and 5: one subset with alpha endpoints of their own, which the rotation swaps with one of the colour channels
bool bc7_rotated_kept(std::uint8_t const *Block, unsigned const Mode)
{
auto const rotation{bc7_bits(Block, Mode + 1, 2)};
auto const colourbits{Mode == 4 ? 5u : 7u};
auto const alphabits{Mode == 4 ? 6u : 8u};
auto const bits{rotation == 0 ? alphabits : colourbits};
auto const start{rotation == 0 ? 8 + 6 * colourbits : 8 + 2 * colourbits * (rotation - 1)};
for (unsigned endpoint = 0; endpoint < 2; ++endpoint)
{
if (bc7_expand(bc7_bits(Block, start + endpoint * bits, bits), bits) < alpha_test_kept)
{
return false;
}
}
return true;
}
// bc7: the alpha of a texel lies between the alpha endpoints of its subset, so the endpoints decide
bool bc7_kept(std::uint8_t const *Block)
{
if (Block[0] == 0)
{
// the reserved mode decodes to transparent black
return false;
}
unsigned mode{0};
while ((Block[0] & (1u << mode)) == 0)
{
++mode;
}
switch (mode)
{
case 4:
case 5:
return bc7_rotated_kept(Block, mode);
case 6:
// 7 bit alpha endpoints, each with its own p-bit
for (unsigned endpoint = 0; endpoint < 2; ++endpoint)
{
if (((bc7_bits(Block, 49 + endpoint * 7, 7) << 1) | bc7_bits(Block, 63 + endpoint, 1)) < alpha_test_kept)
{
return false;
}
}
return true;
case 7:
// two subsets, 5 bit alpha endpoints made 6 bits by their p-bits
for (unsigned endpoint = 0; endpoint < 4; ++endpoint)
{
if (bc7_expand((bc7_bits(Block, 74 + endpoint * 5, 5) << 1) | bc7_bits(Block, 94 + endpoint, 1), 6) < alpha_test_kept)
{
return false;
}
}
return true;
default:
// modes 0-3 hold no alpha: opaque
return true;
}
}
bool blocks_kept(std::span<std::uint8_t const> const Data, std::size_t const Size, block_check const Check)
{
for (std::size_t offset = 0; offset + Size <= Data.size(); offset += Size)
{
if (false == Check(Data.data() + offset))
{
return false;
}
}
return true;
}
} // namespace
bool texels_pass_alpha_test(alpha_layout const Layout, std::span<std::uint8_t const> const Data)
{
switch (Layout)
{
case alpha_layout::rgba8:
return rgba8_kept(Data);
case alpha_layout::bc1:
return blocks_kept(Data, 8, bc1_kept);
case alpha_layout::bc2:
return blocks_kept(Data, 16, bc2_kept);
case alpha_layout::bc3:
return blocks_kept(Data, 16, bc3_kept);
case alpha_layout::bc7:
return blocks_kept(Data, 16, bc7_kept);
default:
return false;
}
}
} // namespace gfx

View File

@@ -0,0 +1,38 @@
/*
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 <span>
namespace gfx
{
// how the texels of a texture's data are laid out: rgba (or bgra) bytes, or the 4x4 blocks of the compressed formats
enum class alpha_layout
{
rgba8,
bc1,
bc2,
bc3,
bc7
};
// the alpha the alpha test of the shadow maps (alphashadowmap.frag) keeps for sure: it discards below a half, and this leaves a
// margin for the decoders and the filtering of the gpus (136/255, the 8/15 of the 4 bit alpha of bc2)
constexpr std::uint8_t alpha_test_kept{136};
// whether every texel of Data (the levels of the texture one after another, whole blocks) decodes to an alpha of at least
// alpha_test_kept. the filtering only mixes texels, so the alpha test then keeps every fragment, and a program without the test
// draws the same depth. the compressed blocks are checked by what they can decode to, so the answer errs on the side of a
// translucent texture
bool texels_pass_alpha_test(alpha_layout Layout, std::span<std::uint8_t const> Data);
} // namespace gfx

View File

@@ -730,6 +730,12 @@ struct vulkan14_renderer::draw_state
bool cullface{true};
bool glare{false};
bool depthmask_off{false}; // the editor overlay: no depth written whatever the blending
// the colour pass of the opaque scene after its depth (render_scene_depth()), keepdepth: the draws whose depth the prepass drew with
// the alpha test (prepassed, set by walk_material()) find it in the depth buffer and don't write it again. without the write the
// depth is tested before their fragment shaders, which discard, and the hidden leaves aren't lit. the solid surfaces write theirs
// as they did, so what's in front of what stays as it was
bool keepdepth{false};
bool prepassed{false};
// the pipelines and the textures a draw needs first may be made by the background threads, the draw left out till then (the walks
// of the scene, gfx.vulkan.asyncresources); the draws outside of them (the cab, the sky, the particles) wait for what they need
bool async{false};

View File

@@ -243,6 +243,7 @@ void vulkan14_renderer::draw_debug_ui()
if (ImGui::Begin("Sun shadows"))
{
ImGui::Checkbox("cascades fitted to the depth of the view (gfx.shadow.sdsm)", &Global.gfx_shadow_sdsm);
ImGui::Checkbox("the textures without translucent texels drawn without the alpha test", &m_shadowskipalphatest);
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)
@@ -386,6 +387,7 @@ void vulkan14_renderer::draw_debug_ui()
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);
ImGui::Checkbox("the colour pass leaves the depth of the alpha tested surfaces as the depth of the scene drew it", &m_keepprepassdepth);
if (Global.gfx_contactshadows > 0)
{
ImGui::TextUnformatted("(drawn anyway for the contact shadows, gfx.contactshadows)");

View File

@@ -1680,7 +1680,8 @@ void vulkan14_renderer::render_viewport(std::size_t const Index)
auto const reflections{m_ssrframe && (false == Global.bWireFrame)};
// and the ambient occlusion in the light of the surfaces
auto const aolighting{ao_lighting_wanted(vp)};
if ((Global.gfx_depthprepass_scene || contactshadows || reflections || aolighting) && vp.main && (false == Global.bWireFrame))
auto const scenedepth{(Global.gfx_depthprepass_scene || contactshadows || reflections || aolighting) && vp.main && (false == Global.bWireFrame)};
if (scenedepth)
{
render_scene_depth(Index, future);
if (contactshadows)
@@ -1697,8 +1698,9 @@ void vulkan14_renderer::render_viewport(std::size_t const Index)
}
}
// opaque parts...
// opaque parts... what the prepass drew with the alpha test keeps its depth (draw_state::keepdepth)
ctx.passes.begin_pass("colour, opaque");
m_draw->keepdepth = scenedepth && m_keepprepassdepth && (false == m_prepassincomplete);
walk_blending(false);
// without rain/snow we can render the cab early to limit the overdraw
if (!FreeFlyModeFlag && Global.Overcast <= 1.0f && Global.render_cab && simulation::Train)
@@ -1716,6 +1718,8 @@ void vulkan14_renderer::render_viewport(std::size_t const Index)
model_ubs.future = future;
walk_region(simulation::Region, false);
m_indirectcolourpending = false;
m_draw->keepdepth = false;
m_draw->prepassed = false;
// the grass of the terrain, opaque, over the ground drawn
if (vp.main && (false == Global.bWireFrame))
@@ -1860,6 +1864,9 @@ void vulkan14_renderer::render_scene_depth(std::size_t const Index, glm::mat4 co
draw.weather_offset = offsets[3];
draw.depthpass = true;
draw.depthskip = true;
// what the prepass of this frame leaves out (keepdepth)
m_prepassincomplete = false;
m_prepassunready.clear();
commands->set_depth_bias_enable(VK_TRUE);
commands->set_depth_bias(-1.f, 0.f, -1.f);
auto const colourstats{m_renderpass.draw_stats};

View File

@@ -326,17 +326,22 @@ bool vulkan14_renderer::draw_indirect(vk14::instance_cull &Cull, command_set con
{
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)
// the buffers of the bank of the chunks, with their data on the gpu (the chunks of a multi draw are of one bank). what the prepass
// of the scene leaves out has its depth written by the colour pass (keepdepth)
vk14::chunk_source chunk;
if (false == chunk_of(geometry, chunk))
{
m_prepassincomplete = (m_prepassincomplete || draw.depthpass);
return false;
}
if (false == bind_scene_pipeline(Program, *draw.targets, Blend, static_cast<int>(chunk.topology)))
{
m_prepassincomplete = (m_prepassincomplete || draw.depthpass);
return false;
}
apply_draw_state();
// the prepass draws the triangles of the draws (draw_indirect_depth())
auto const triangles{(first.type == GL_TRIANGLES) || (first.type == GL_TRIANGLE_STRIP) || (first.type == GL_TRIANGLE_FAN)};
apply_draw_state(triangles);
// 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)
@@ -377,6 +382,7 @@ bool vulkan14_renderer::draw_indirect(vk14::instance_cull &Cull, command_set con
draw.block = ~0u;
if (block == ~0u)
{
m_prepassincomplete = (m_prepassincomplete || draw.depthpass);
return false;
}
if (draw.set_dirty)

View File

@@ -173,6 +173,9 @@ class vulkan14_renderer : public gfx_renderer, public gfx::scene_walk
// the draws of the shared walk of the scene, recorded
void walk_material(material_handle const Material, TSubModel const *Submodel = nullptr, lighting_data const *Lighting = nullptr) override;
void walk_shadow_material(material_handle const Material) override;
// none for a texture made from a file of which every texel passes the alpha test (vk14::vulkan14_texture::passes_alpha_test()):
// the program without the test draws the same depth, without reading the texture and with the early depth test
texture_handle Indirect_Shadow_Texture(material_handle const Material) const override;
opengl_material const &walk_material_data(material_handle const Material) const override;
opengl_material const &walk_material_data(TSubModel const *Submodel) const override;
void walk_program(walk_shader const Program) override;
@@ -410,6 +413,17 @@ class vulkan14_renderer : public gfx_renderer, public gfx::scene_walk
// 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);
// the colour pass doesn't write the depth the prepass of the scene drew with the alpha test (draw_state::keepdepth); off in the debug
// window, to compare. what the prepass of the frame left out: a draw it had no pipeline or geometry for yet (the colour pass writes
// all of its depth then), the textures of the alpha test it found not loaded yet (their draws write it)
bool m_keepprepassdepth{true};
// the shadows of the textures which pass the alpha test everywhere drawn without it (Indirect_Shadow_Texture()), read from the
// main renderer by the walkers
bool m_shadowskipalphatest{true};
bool m_prepassincomplete{false};
std::vector<texture_handle> m_prepassunready;
// whether the prepass drew the depth of the draw of the material of the colour pass with the alpha test (walk_material())
bool prepassed_alpha(material_handle const Material, texture_handle const Texture);
bool contact_shadows_wanted(viewport_config const &Viewport) const;
void setup_contact_shadows(std::size_t const Viewport);
bool m_contactshadows{false};
@@ -493,7 +507,8 @@ class vulkan14_renderer : public gfx_renderer, public gfx::scene_walk
// 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();
// Triangles: the draw is of triangles, which the prepass draws (keepdepth)
void apply_draw_state(bool const Triangles = false);
void push_textures();
bool texture_ready(texture_source &Texture);
texture_source &used_texture(texture_handle const Texture);

View File

@@ -25,6 +25,7 @@ http://mozilla.org/MPL/2.0/.
#include "utilities/color.h"
#include "utilities/utilities.h"
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <unordered_set>
@@ -1456,6 +1457,7 @@ void vulkan14_renderer::walk_material(material_handle const Material, TSubModel
{
return;
}
m_draw->prepassed = false;
if (Material == null_handle)
{
if (Material != m_invalid_material)
@@ -1600,6 +1602,11 @@ void vulkan14_renderer::walk_material(material_handle const Material, TSubModel
auto &texture{m_context->textures->mark_as_used(materialtextures[0])};
if ((false == texture_ready(texture)) || (m_context->images.find(texture.get_id()) == nullptr))
{
// the colour pass writes the depth of the draws of the texture itself (prepassed_alpha())
if (std::ranges::find(m_prepassunready, materialtextures[0]) == m_prepassunready.end())
{
m_prepassunready.emplace_back(materialtextures[0]);
}
return;
}
walk_texture(0, materialtextures[0]);
@@ -1612,6 +1619,7 @@ void vulkan14_renderer::walk_material(material_handle const Material, TSubModel
draw.depthskip = false;
return;
}
draw.prepassed = draw.keepdepth && prepassed_alpha(Material, materialtextures[0]);
for (std::size_t unit = 0; unit < gl::MAX_TEXTURES; ++unit)
{
if (draw.textures[unit] != materialtextures[unit])
@@ -1635,9 +1643,9 @@ void vulkan14_renderer::walk_shadow_material(material_handle const Material)
auto &draw{*m_draw};
auto &scene{m_context->scene};
// the cube maps of the lamps with programs of their own (the faces drawn by multiview)
if ((Material != null_handle) && (m_materials.material(Material).textures[0] != null_handle))
if (auto const texture{Indirect_Shadow_Texture(Material)}; texture != null_handle)
{
walk_texture(0, m_materials.material(Material).textures[0]);
walk_texture(0, texture);
draw.program = (m_cubeshadows ? scene.cube_alpha_shadow.get() : scene.alpha_shadow.get());
}
else
@@ -1646,6 +1654,19 @@ void vulkan14_renderer::walk_shadow_material(material_handle const Material)
}
}
texture_handle vulkan14_renderer::Indirect_Shadow_Texture(material_handle const Material) const
{
auto const texture{gfx::scene_walk::Indirect_Shadow_Texture(Material)};
auto const &main{m_main != nullptr ? *m_main : *this};
if ((texture == null_handle) || (false == main.m_shadowskipalphatest))
{
return texture;
}
// a texture not made yet keeps the test: what it holds isn't known
auto const &source{static_cast<vk14::vulkan14_texture const &>(m_context->textures->texture(texture))};
return ((source.resident() && source.passes_alpha_test()) ? null_handle : texture);
}
opengl_material const &vulkan14_renderer::walk_material_data(material_handle const Material) const
{
return m_materials.material(Material);
@@ -2003,10 +2024,13 @@ void vulkan14_renderer::draw_chunk(gfx::geometry_handle const &Geometry, bool co
vk14::chunk_source chunk;
if (false == chunk_of(Geometry, chunk))
{
// what the prepass of the scene leaves out has its depth written by the colour pass (keepdepth)
m_prepassincomplete = (m_prepassincomplete || draw.depthpass);
return;
}
if ((draw.depthpass || draw.probepass) && (draw.depthskip || (topology_class(chunk.topology) != VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)))
auto const triangles{topology_class(chunk.topology) == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST};
if ((draw.depthpass || draw.probepass) && (draw.depthskip || (false == triangles)))
{
// the depth of the scene: what the programs of the prepass don't draw, and no points or lines
return;
@@ -2018,14 +2042,16 @@ void vulkan14_renderer::draw_chunk(gfx::geometry_handle const &Geometry, bool co
auto const blend{draw.glare ? vk14::blend_mode::scene_additive : (draw.blending ? vk14::blend_mode::scene_alpha : vk14::blend_mode::none)};
if (false == bind_scene_pipeline(*draw.program, *draw.targets, blend, static_cast<int>(chunk.topology)))
{
m_prepassincomplete = (m_prepassincomplete || draw.depthpass);
return;
}
apply_draw_state();
apply_draw_state(triangles);
if (Newblock || (draw.block == ~0u))
{
draw.block = push_draw_block();
if (draw.block == ~0u)
{
m_prepassincomplete = (m_prepassincomplete || draw.depthpass);
return;
}
}
@@ -2507,7 +2533,7 @@ void vulkan14_renderer::context::save_scene_pipelines()
}
// the dynamic state of the draws for the state of the walk: the faces culled, the depth tested and written
void vulkan14_renderer::apply_draw_state()
void vulkan14_renderer::apply_draw_state(bool const Triangles)
{
auto &draw{*m_draw};
auto const cull{draw.cullface ? (draw.shadows ? VK_CULL_MODE_FRONT_BIT : VK_CULL_MODE_BACK_BIT) : VK_CULL_MODE_NONE};
@@ -2522,7 +2548,9 @@ void vulkan14_renderer::apply_draw_state()
draw.commands->set_depth_test_enable(depthtest ? VK_TRUE : VK_FALSE);
draw.depthtest = depthtest;
}
auto const depthwrite{(draw.blending || draw.depthmask_off) ? 0 : 1};
// the depth the prepass of the scene drew already isn't written again (draw_state::keepdepth)
auto const kept{draw.keepdepth && draw.prepassed && Triangles};
auto const depthwrite{(draw.blending || draw.depthmask_off || kept) ? 0 : 1};
if (depthwrite != draw.depthwrite)
{
draw.commands->set_depth_write_enable(depthwrite ? VK_TRUE : VK_FALSE);
@@ -2609,6 +2637,17 @@ int vulkan14_renderer::prepass_class(material_handle const Material)
return prepassclass;
}
// what the prepass draws with the alpha test, as walk_material() and draw_indirect_depth() choose it: not blended, the alpha test of
// the opaque pass, the program of the prepass with the test, the diffuse texture there and found loaded by the prepass
bool vulkan14_renderer::prepassed_alpha(material_handle const Material, texture_handle const Texture)
{
if (m_draw->blending || (model_ubs.opacity < 0.f) || (Texture == null_handle) || (prepass_class(Material) != 2))
{
return false;
}
return std::ranges::find(m_prepassunready, Texture) == m_prepassunready.end();
}
// textures: the files read and decoded as for the opengl renderer, made into images when they are first used
texture_handle vulkan14_renderer::Fetch_Texture(std::string const &Filename, bool const Loadnow, GLint format_hint)

View File

@@ -9,6 +9,7 @@ http://mozilla.org/MPL/2.0/.
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14texture.h"
#include "rendering/common/texturealpha.h"
#include "rendering/vulkan14/vulkan14commands.h"
#include "rendering/vulkan14/vulkan14device.h"
#include "rendering/vulkan14/vulkan14tasks.h"
@@ -16,10 +17,74 @@ http://mozilla.org/MPL/2.0/.
#include "utilities/Logs.h"
#include <cstring>
#include <optional>
#include <span>
namespace vk14
{
namespace
{
// the data a texture is made of, for the alpha test of the shadow maps
struct alpha_source
{
std::uint8_t const *data{nullptr};
VkDeviceSize size{0};
// what the worker thread which read the data found (texture_source::data_alphakept)
std::optional<bool> known;
// the texels may be checked here: a background thread makes the texture. the thread of the renderer doesn't wait for that, the
// texture keeps the test
bool scan{false};
};
// whether the alpha test of the shadow maps keeps every texel of the data of the levels: the alpha the material reads is one, or
// that of each texel passes the test (the mipmaps the gpu makes of a single level only mix its texels)
bool texels_pass_alpha_test(texture_format const &Format, alpha_source const &Source)
{
if ((Format.components.a == VK_COMPONENT_SWIZZLE_ONE) || Format.expand_rgb)
{
return true;
}
if (Format.components.a != VK_COMPONENT_SWIZZLE_IDENTITY)
{
return false;
}
if (Source.known.has_value())
{
return *Source.known;
}
if (false == Source.scan)
{
return false;
}
std::span const data{Source.data, static_cast<std::size_t>(Source.size)};
switch (Format.format)
{
case VK_FORMAT_BC1_RGBA_SRGB_BLOCK:
case VK_FORMAT_BC1_RGBA_UNORM_BLOCK:
return gfx::texels_pass_alpha_test(gfx::alpha_layout::bc1, data);
case VK_FORMAT_BC2_SRGB_BLOCK:
case VK_FORMAT_BC2_UNORM_BLOCK:
return gfx::texels_pass_alpha_test(gfx::alpha_layout::bc2, data);
case VK_FORMAT_BC3_SRGB_BLOCK:
case VK_FORMAT_BC3_UNORM_BLOCK:
return gfx::texels_pass_alpha_test(gfx::alpha_layout::bc3, data);
case VK_FORMAT_BC7_SRGB_BLOCK:
case VK_FORMAT_BC7_UNORM_BLOCK:
return gfx::texels_pass_alpha_test(gfx::alpha_layout::bc7, data);
case VK_FORMAT_R8G8B8A8_SRGB:
case VK_FORMAT_R8G8B8A8_UNORM:
case VK_FORMAT_B8G8R8A8_SRGB:
case VK_FORMAT_B8G8R8A8_UNORM:
return gfx::texels_pass_alpha_test(gfx::alpha_layout::rgba8, data);
default:
return false;
}
}
} // namespace
// uploader
void uploader::init(allocator const &Allocator, deletion_queue &Deletions, VkPhysicalDevice Physical)
@@ -751,6 +816,9 @@ vulkan14_texture::make_result vulkan14_texture::make(bool const Background)
return failed();
}
// published by m_made (made now, or by publish() of the thread of the renderer)
m_passesalphatest = texels_pass_alpha_test(format, alpha_source{source, size, data_alphakept, Background});
// a single level is made into the whole chain on the gpu, as glGenerateMipmap() does for the opengl renderer
auto const generate{(levels.size() == 1) && (false == format.compressed) && m_resources.uploads->can_blit(format.format)};
// the levels the file gives are filled by the host where it can, by a background thread (what the gpu makes goes through the
@@ -878,6 +946,7 @@ void vulkan14_texture::release()
m_image.reset();
m_id = 0;
m_made = false;
m_passesalphatest = false;
is_ready = false;
}
@@ -904,6 +973,7 @@ void vulkan14_texture::destroy()
m_image.reset();
m_id = 0;
m_made = false;
m_passesalphatest = false;
}
void vulkan14_texture::on_failed()
@@ -915,6 +985,7 @@ void vulkan14_texture::on_failed()
}
m_image.reset();
m_id = 0;
m_passesalphatest = false;
m_made = true;
}
@@ -972,6 +1043,8 @@ void vulkan14_texture::adopt(std::shared_ptr<image> const &Image, VkSampler cons
m_id = m_resources.images->add(Image, Sampler);
}
m_image = Image;
// filled by the renderer, not from a file
m_passesalphatest = false;
m_made = true;
if (Image)
{
@@ -1027,6 +1100,8 @@ void vulkan14_texture::upload_rgba(std::uint8_t const *Data, std::size_t Width,
data_height = height;
data_format = GL_SRGB8_ALPHA8;
data_components = GL_RGBA;
// content given from memory, not checked
m_passesalphatest = false;
m_made = true;
// the content given straight to the gpu can't be read again: released by the garbage sweep, the texture would come back as
// the grey stub

View File

@@ -226,6 +226,12 @@ struct vulkan14_texture : public texture_source
return m_job != job_none;
}
void destroy() override;
// made from the data of a file of which every texel passes the alpha test of the shadow maps (gfx::texels_pass_alpha_test()):
// its shadows need no test. read after resident()
bool passes_alpha_test() const
{
return m_passesalphatest;
}
// the content given by update_from_memory() from another thread, uploaded by the thread of the renderer
void apply_pending();
@@ -284,6 +290,7 @@ struct vulkan14_texture : public texture_source
std::vector<std::uint8_t> m_pending;
std::size_t m_pendingwidth{0};
std::size_t m_pendingheight{0};
std::atomic<bool> m_passesalphatest{false};
};
} // namespace vk14

View File

@@ -305,6 +305,12 @@ public:
linesnode_sequence m_lines;
path_sequence m_paths; // path pieces
instance_sequence m_instancesopaque;
// the instances of m_instancesopaque drawn each on its own (TAnimModel::m_instanceable false), in the same order: the walks
// go through these instead of reading every instance of the cell in each pass. made by the walks for the version of the
// instance records they go by and the count of the instances they were made from (gfx::scene_walk::Single_Instances())
mutable instance_sequence m_instancessingle;
mutable unsigned int m_instancessingleversion { 0 };
mutable std::size_t m_instancessinglesource { 0 };
instance_sequence m_instancetranslucent;
// batched instance buckets keyed by shared TModel3d*; populated alongside
// m_instancesopaque for nodes whose TAnimModel::m_instanceable == true.

View File

@@ -29,6 +29,8 @@ add_executable(${UNIT_TESTS_NAME}
"${CMAKE_SOURCE_DIR}/editor/editorProfile.cpp"
# and the switches laid into the curves
"${CMAKE_SOURCE_DIR}/editor/editorSwitchCurve.cpp"
# which textures the shadow maps draw without the alpha test
"${CMAKE_SOURCE_DIR}/rendering/common/texturealpha.cpp"
${UNIT_TEST_CASES})
# the core library brings the game's build settings along

View File

@@ -0,0 +1,101 @@
#include "stdafx.h"
#include "doctest.h"
#include "rendering/common/texturealpha.h"
#include <algorithm>
#include <vector>
// which textures the shadow maps draw without the alpha test: every texel has to pass it, so a block which can decode to a
// translucent texel makes the whole texture keep the test
namespace
{
using gfx::alpha_layout;
using gfx::texels_pass_alpha_test;
bool passes(alpha_layout const Layout, std::vector<std::uint8_t> const &Data)
{
return texels_pass_alpha_test(Layout, Data);
}
// a bc7 block of mode 6 (7 bit colour and alpha endpoints, a p-bit each) with the given alpha endpoints before the p-bits
std::vector<std::uint8_t> bc7_mode6(unsigned const Alpha0, unsigned const Alpha1, unsigned const Pbits)
{
std::vector<std::uint8_t> block(16, 0);
auto const put = [&block](unsigned const Offset, unsigned const Count, unsigned const Value) {
for (unsigned bit = 0; bit < Count; ++bit)
{
if (((Value >> bit) & 1u) != 0)
{
block[(Offset + bit) >> 3] |= static_cast<std::uint8_t>(1u << ((Offset + bit) & 7u));
}
}
};
put(6, 1, 1);
put(49, 7, Alpha0);
put(56, 7, Alpha1);
put(63, 2, Pbits);
return block;
}
} // namespace
TEST_CASE("texture alpha: rgba texels pass when every alpha is from 136 up")
{
CHECK(passes(alpha_layout::rgba8, {10, 20, 30, 255, 0, 0, 0, 136}));
CHECK_FALSE(passes(alpha_layout::rgba8, {10, 20, 30, 255, 0, 0, 0, 135}));
CHECK_FALSE(passes(alpha_layout::rgba8, {255, 255, 255, 0}));
}
TEST_CASE("texture alpha: bc1 is opaque unless a three colour block uses the index 3")
{
// the first colour above the second: four colours, the index 3 opaque
CHECK(passes(alpha_layout::bc1, {0x00, 0xf8, 0x1f, 0x00, 0xff, 0xff, 0xff, 0xff}));
// three colours, no index 3
CHECK(passes(alpha_layout::bc1, {0x1f, 0x00, 0x00, 0xf8, 0x00, 0x55, 0xaa, 0x66}));
// three colours, one texel of the index 3: transparent black
CHECK_FALSE(passes(alpha_layout::bc1, {0x1f, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x30, 0x00}));
// equal colours count as three
CHECK_FALSE(passes(alpha_layout::bc1, {0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00}));
// one translucent block among opaque ones
CHECK_FALSE(passes(alpha_layout::bc1, {0x00, 0xf8, 0x1f, 0x00, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x00, 0x00, 0xf8, 0xc0, 0x00, 0x00, 0x00}));
}
TEST_CASE("texture alpha: bc2 passes when every 4 bit alpha is from 8 up")
{
std::vector<std::uint8_t> block(16, 0);
std::fill_n(block.begin(), 8, std::uint8_t{0x88});
CHECK(passes(alpha_layout::bc2, block));
block[5] = 0x87;
CHECK_FALSE(passes(alpha_layout::bc2, block));
}
TEST_CASE("texture alpha: bc3 is checked by the palette entries its texels use")
{
// both endpoints opaque
std::vector<std::uint8_t> block{255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
CHECK(passes(alpha_layout::bc3, block));
// a transparent second endpoint which no texel uses (all indices 0)
block[1] = 0;
CHECK(passes(alpha_layout::bc3, block));
// one texel on the index 1, the transparent endpoint
block[2] = 0x01;
CHECK_FALSE(passes(alpha_layout::bc3, block));
// six interpolated values: the index 6 is 0, the index 7 is 255
std::vector<std::uint8_t> six{200, 220, 0x07, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
CHECK(passes(alpha_layout::bc3, six));
six[2] = 0x06;
CHECK_FALSE(passes(alpha_layout::bc3, six));
}
TEST_CASE("texture alpha: bc7 is checked by its alpha endpoints")
{
// mode 6, alpha endpoints 254 and 255
CHECK(passes(alpha_layout::bc7, bc7_mode6(127, 127, 2)));
// one endpoint at 100
CHECK_FALSE(passes(alpha_layout::bc7, bc7_mode6(50, 127, 3)));
// mode 1 holds no alpha
std::vector<std::uint8_t> mode1(16, 0);
mode1[0] = 0x02;
CHECK(passes(alpha_layout::bc7, mode1));
// the reserved mode decodes to transparent black
CHECK_FALSE(passes(alpha_layout::bc7, std::vector<std::uint8_t>(16, 0)));
}