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:
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)};
|
||||
|
||||
@@ -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
|
||||
|
||||
219
rendering/common/texturealpha.cpp
Normal file
219
rendering/common/texturealpha.cpp
Normal 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
|
||||
38
rendering/common/texturealpha.h
Normal file
38
rendering/common/texturealpha.h
Normal 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
|
||||
@@ -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};
|
||||
|
||||
@@ -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)");
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
101
tests/unit/test_texture_alpha.cpp
Normal file
101
tests/unit/test_texture_alpha.cpp
Normal 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)));
|
||||
}
|
||||
Reference in New Issue
Block a user