mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-05 19:29:11 +01:00
feat(loading): optional background loading: sound files decoded by worker threads (sound.asyncload), data of dds textures read by worker threads (gfx.textures.streaming), gl textures of vehicles farther than a multiple of the draw range released (gfx.textures.releasedistance); all off by default
This commit is contained in:
@@ -19,6 +19,7 @@ http://mozilla.org/MPL/2.0/.
|
|||||||
#include "rendering/renderer.h"
|
#include "rendering/renderer.h"
|
||||||
#include "utilities/Logs.h"
|
#include "utilities/Logs.h"
|
||||||
#include "utilities/translation.h"
|
#include "utilities/translation.h"
|
||||||
|
#include "audio/audiorenderer.h"
|
||||||
|
|
||||||
scenarioloader_mode::scenarioloader_mode() {
|
scenarioloader_mode::scenarioloader_mode() {
|
||||||
m_userinterface = std::make_shared<scenarioloader_ui>();
|
m_userinterface = std::make_shared<scenarioloader_ui>();
|
||||||
@@ -58,6 +59,8 @@ bool scenarioloader_mode::update() {
|
|||||||
// the loading state holds the scenario file open, which on some systems keeps the file from being replaced
|
// the loading state holds the scenario file open, which on some systems keeps the file from being replaced
|
||||||
// when the scenery is saved in the editor. it'd also get in the way of another load
|
// when the scenery is saved in the editor. it'd also get in the way of another load
|
||||||
state.reset();
|
state.reset();
|
||||||
|
// sound files decoded in the background are all ready before the simulation starts
|
||||||
|
audio::renderer.complete_buffers();
|
||||||
|
|
||||||
WriteLog( "Scenario loading time: " + std::to_string( std::chrono::duration_cast<std::chrono::seconds>( std::chrono::system_clock::now() - timestart ).count() ) + " seconds" );
|
WriteLog( "Scenario loading time: " + std::to_string( std::chrono::duration_cast<std::chrono::seconds>( std::chrono::system_clock::now() - timestart ).count() ) + " seconds" );
|
||||||
// TODO: implement and use next mode cue
|
// TODO: implement and use next mode cue
|
||||||
|
|||||||
242
audio/audio.cpp
242
audio/audio.cpp
@@ -19,35 +19,42 @@ http://mozilla.org/MPL/2.0/.
|
|||||||
|
|
||||||
namespace audio {
|
namespace audio {
|
||||||
|
|
||||||
openal_buffer::openal_buffer( std::string const &Filename ) :
|
namespace {
|
||||||
name( Filename ) {
|
|
||||||
|
// content of a sound file, mixed down to 16 bit mono
|
||||||
|
struct decoded_sound {
|
||||||
|
std::vector<std::int16_t> samples;
|
||||||
|
unsigned int rate { 0 };
|
||||||
|
bool mixed { false }; // the file had more than one channel
|
||||||
|
};
|
||||||
|
|
||||||
|
// reads and decodes whole sound file. throws std::runtime_error on failure
|
||||||
|
// NOTE: uses no simulator state, it's also run by the background decoder threads
|
||||||
|
decoded_sound decode_sound( std::string const &File ) {
|
||||||
|
|
||||||
SF_INFO si;
|
SF_INFO si;
|
||||||
si.format = 0;
|
si.format = 0;
|
||||||
|
|
||||||
std::string file = Filename;
|
SNDFILE *sf = sf_open(File.c_str(), SFM_READ, &si);
|
||||||
|
|
||||||
WriteLog("sound: loading file: " + file);
|
|
||||||
|
|
||||||
SNDFILE *sf = sf_open(file.c_str(), SFM_READ, &si);
|
|
||||||
|
|
||||||
if (sf == nullptr)
|
if (sf == nullptr)
|
||||||
throw std::runtime_error("sound: sf_open failed");
|
throw std::runtime_error("sound: sf_open failed");
|
||||||
|
|
||||||
sf_command(sf, SFC_SET_NORM_FLOAT, nullptr, SF_TRUE);
|
sf_command(sf, SFC_SET_NORM_FLOAT, nullptr, SF_TRUE);
|
||||||
|
|
||||||
float *fbuf = new float[si.frames * si.channels];
|
std::unique_ptr<float[]> fbuf { new float[si.frames * si.channels] };
|
||||||
if (sf_readf_float(sf, fbuf, si.frames) != si.frames)
|
if (sf_readf_float(sf, fbuf.get(), si.frames) != si.frames) {
|
||||||
|
sf_close(sf);
|
||||||
throw std::runtime_error("sound: incomplete file");
|
throw std::runtime_error("sound: incomplete file");
|
||||||
|
}
|
||||||
|
|
||||||
sf_close(sf);
|
sf_close(sf);
|
||||||
|
|
||||||
rate = si.samplerate;
|
decoded_sound sound;
|
||||||
|
sound.rate = si.samplerate;
|
||||||
|
sound.mixed = ( si.channels != 1 );
|
||||||
|
sound.samples.resize( si.frames );
|
||||||
|
|
||||||
if (si.channels != 1)
|
|
||||||
WriteLog("sound: warning: mixing multichannel file to mono");
|
|
||||||
|
|
||||||
int16_t *buf = new int16_t[si.frames];
|
|
||||||
for (size_t i = 0; i < si.frames; i++)
|
for (size_t i = 0; i < si.frames; i++)
|
||||||
{
|
{
|
||||||
float accum = 0;
|
float accum = 0;
|
||||||
@@ -59,26 +66,149 @@ openal_buffer::openal_buffer( std::string const &Filename ) :
|
|||||||
val = 32767;
|
val = 32767;
|
||||||
if (val < -32767)
|
if (val < -32767)
|
||||||
val = -32767;
|
val = -32767;
|
||||||
buf[i] = val;
|
sound.samples[i] = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return sound;
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates AL buffer holding provided sound. returns: id of the buffer, or null_resource on failure
|
||||||
|
ALuint upload_sound( decoded_sound const &Sound ) {
|
||||||
|
|
||||||
|
ALuint id { null_resource };
|
||||||
alGenBuffers(1, &id);
|
alGenBuffers(1, &id);
|
||||||
if (id != null_resource && alIsBuffer(id)) {
|
if (id != null_resource && alIsBuffer(id)) {
|
||||||
alGetError();
|
alGetError();
|
||||||
alBufferData(id, AL_FORMAT_MONO16, buf, si.frames * 2, rate);
|
alBufferData(id, AL_FORMAT_MONO16, Sound.samples.data(), static_cast<ALsizei>(Sound.samples.size() * 2), Sound.rate);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
id = null_resource;
|
id = null_resource;
|
||||||
const char *str = alGetString(alGetError());
|
const char *str = alGetString(alGetError());
|
||||||
ErrorLog("sound: failed to create AL buffer: " + (str != nullptr ? std::string(str) : ""));
|
ErrorLog("sound: failed to create AL buffer: " + (str != nullptr ? std::string(str) : ""));
|
||||||
}
|
}
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
delete[] buf;
|
} // namespace
|
||||||
delete[] fbuf;
|
|
||||||
|
openal_buffer::openal_buffer( std::string const &Filename ) :
|
||||||
|
name( Filename ) {
|
||||||
|
|
||||||
|
WriteLog("sound: loading file: " + Filename);
|
||||||
|
|
||||||
|
auto const sound { decode_sound( Filename ) };
|
||||||
|
|
||||||
|
rate = sound.rate;
|
||||||
|
|
||||||
|
if (sound.mixed)
|
||||||
|
WriteLog("sound: warning: mixing multichannel file to mono");
|
||||||
|
|
||||||
|
id = upload_sound( sound );
|
||||||
|
|
||||||
fetch_caption();
|
fetch_caption();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sound files decoded by worker threads. AL buffers for them are made on the main thread
|
||||||
|
struct buffer_manager::decoder {
|
||||||
|
|
||||||
|
struct job {
|
||||||
|
audio::buffer_handle handle;
|
||||||
|
std::string file;
|
||||||
|
decoded_sound sound; // written by the thread decoding the file
|
||||||
|
std::string error;
|
||||||
|
bool done { false }; // guarded by the mutex
|
||||||
|
};
|
||||||
|
|
||||||
|
std::mutex mutex;
|
||||||
|
std::condition_variable queued; // wakes workers when files are added
|
||||||
|
std::condition_variable decoded; // wakes the main thread waiting for a file
|
||||||
|
std::deque<std::shared_ptr<job>> queue; // files no worker has taken yet
|
||||||
|
std::vector<std::shared_ptr<job>> pending; // files without AL buffer yet, in order of request. main thread only
|
||||||
|
bool stop { false };
|
||||||
|
std::vector<std::thread> workers;
|
||||||
|
|
||||||
|
decoder() {
|
||||||
|
auto const count { std::clamp<unsigned int>( std::thread::hardware_concurrency(), 2, 9 ) - 1 };
|
||||||
|
for( unsigned int idx = 0; idx < count; ++idx ) {
|
||||||
|
workers.emplace_back( [ this ]() { work(); } );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
~decoder() {
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock( mutex );
|
||||||
|
stop = true;
|
||||||
|
}
|
||||||
|
queued.notify_all();
|
||||||
|
for( auto &worker : workers ) {
|
||||||
|
worker.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void decode( job &Item ) {
|
||||||
|
try {
|
||||||
|
Item.sound = decode_sound( Item.file );
|
||||||
|
}
|
||||||
|
catch( std::exception const &Error ) {
|
||||||
|
Item.error = Error.what();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void work() {
|
||||||
|
std::unique_lock<std::mutex> lock( mutex );
|
||||||
|
while( true ) {
|
||||||
|
queued.wait( lock, [ this ]() { return stop || ( false == queue.empty() ); } );
|
||||||
|
if( stop ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto item { queue.front() };
|
||||||
|
queue.pop_front();
|
||||||
|
lock.unlock();
|
||||||
|
decode( *item );
|
||||||
|
lock.lock();
|
||||||
|
item->done = true;
|
||||||
|
decoded.notify_all();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// waits until the specified file is decoded. a file no worker has taken yet is decoded right away
|
||||||
|
void wait( std::shared_ptr<job> const &Item ) {
|
||||||
|
std::unique_lock<std::mutex> lock( mutex );
|
||||||
|
if( Item->done ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto const lookup { std::find( std::begin( queue ), std::end( queue ), Item ) };
|
||||||
|
if( lookup != std::end( queue ) ) {
|
||||||
|
queue.erase( lookup );
|
||||||
|
lock.unlock();
|
||||||
|
decode( *Item );
|
||||||
|
lock.lock();
|
||||||
|
Item->done = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
decoded.wait( lock, [ &Item ]() { return Item->done; } );
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_done( job const &Item ) {
|
||||||
|
std::lock_guard<std::mutex> lock( mutex );
|
||||||
|
return Item.done;
|
||||||
|
}
|
||||||
|
|
||||||
|
// makes the AL buffer out of the decoded file
|
||||||
|
static void finish( job &Item, openal_buffer &Buffer ) {
|
||||||
|
if( false == Item.error.empty() ) {
|
||||||
|
ErrorLog( "Bad file: failed to load audio file \"" + Item.file + "\" (" + Item.error + ")", logtype::file );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Buffer.rate = Item.sound.rate;
|
||||||
|
if( Item.sound.mixed ) {
|
||||||
|
WriteLog( "sound: warning: mixing multichannel file \"" + Item.file + "\" to mono" );
|
||||||
|
}
|
||||||
|
Buffer.id = upload_sound( Item.sound );
|
||||||
|
Item.sound = {};
|
||||||
|
Buffer.fetch_caption();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// retrieves sound caption in currently set language
|
// retrieves sound caption in currently set language
|
||||||
void
|
void
|
||||||
openal_buffer::fetch_caption() {
|
openal_buffer::fetch_caption() {
|
||||||
@@ -93,8 +223,16 @@ openal_buffer::fetch_caption() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buffer_manager::buffer_manager() {
|
||||||
|
|
||||||
|
m_buffers.emplace_back( openal_buffer() ); // empty bindings for null buffer
|
||||||
|
}
|
||||||
|
|
||||||
buffer_manager::~buffer_manager() {
|
buffer_manager::~buffer_manager() {
|
||||||
|
|
||||||
|
// workers are stopped before the buffers go
|
||||||
|
m_decoder.reset();
|
||||||
|
|
||||||
for( auto &buffer : m_buffers ) {
|
for( auto &buffer : m_buffers ) {
|
||||||
if( buffer.id != null_resource ) {
|
if( buffer.id != null_resource ) {
|
||||||
::alDeleteBuffers( 1, &buffer.id );
|
::alDeleteBuffers( 1, &buffer.id );
|
||||||
@@ -152,15 +290,81 @@ buffer_manager::create( std::string const &Filename ) {
|
|||||||
audio::openal_buffer const &
|
audio::openal_buffer const &
|
||||||
buffer_manager::buffer( audio::buffer_handle const Buffer ) const {
|
buffer_manager::buffer( audio::buffer_handle const Buffer ) const {
|
||||||
|
|
||||||
|
if( ( m_decoder != nullptr )
|
||||||
|
&& ( false == m_decoder->pending.empty() )
|
||||||
|
&& ( m_buffers[ Buffer ].id == null_resource ) ) {
|
||||||
|
complete( Buffer );
|
||||||
|
}
|
||||||
return m_buffers[ Buffer ];
|
return m_buffers[ Buffer ];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// creates AL buffers for files decoded in the background so far, or for all of them if requested
|
||||||
|
void
|
||||||
|
buffer_manager::update( bool const Wait ) {
|
||||||
|
|
||||||
|
if( ( m_decoder == nullptr )
|
||||||
|
|| ( m_decoder->pending.empty() ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::vector<std::shared_ptr<decoder::job>> undone;
|
||||||
|
for( auto &item : m_decoder->pending ) {
|
||||||
|
if( Wait ) {
|
||||||
|
m_decoder->wait( item );
|
||||||
|
}
|
||||||
|
else if( false == m_decoder->is_done( *item ) ) {
|
||||||
|
undone.emplace_back( item );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
decoder::finish( *item, m_buffers[ item->handle ] );
|
||||||
|
}
|
||||||
|
m_decoder->pending.swap( undone );
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates AL buffer for a file decoded in the background, waiting for the decoder if needed
|
||||||
|
void
|
||||||
|
buffer_manager::complete( audio::buffer_handle const Buffer ) const {
|
||||||
|
|
||||||
|
auto &pending { m_decoder->pending };
|
||||||
|
auto const lookup {
|
||||||
|
std::find_if(
|
||||||
|
std::begin( pending ), std::end( pending ),
|
||||||
|
[ Buffer ]( std::shared_ptr<decoder::job> const &Item ) { return Item->handle == Buffer; } ) };
|
||||||
|
if( lookup == std::end( pending ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto const item { *lookup };
|
||||||
|
pending.erase( lookup );
|
||||||
|
m_decoder->wait( item );
|
||||||
|
decoder::finish( *item, m_buffers[ Buffer ] );
|
||||||
|
}
|
||||||
|
|
||||||
// places in the bank a buffer containing data stored in specified file. returns: handle to the buffer
|
// places in the bank a buffer containing data stored in specified file. returns: handle to the buffer
|
||||||
audio::buffer_handle
|
audio::buffer_handle
|
||||||
buffer_manager::emplace( std::string Filename ) {
|
buffer_manager::emplace( std::string Filename ) {
|
||||||
|
|
||||||
buffer_handle const handle { m_buffers.size() };
|
buffer_handle const handle { m_buffers.size() };
|
||||||
m_buffers.emplace_back( Filename );
|
if( true == Global.AudioAsyncLoad ) {
|
||||||
|
// the file is decoded in the background, the AL buffer is made once the data is ready or when the buffer is needed
|
||||||
|
WriteLog( "sound: loading file: " + Filename );
|
||||||
|
openal_buffer buffer;
|
||||||
|
buffer.name = Filename;
|
||||||
|
m_buffers.emplace_back( std::move( buffer ) );
|
||||||
|
if( m_decoder == nullptr ) {
|
||||||
|
m_decoder = std::make_unique<decoder>();
|
||||||
|
}
|
||||||
|
auto item { std::make_shared<decoder::job>() };
|
||||||
|
item->handle = handle;
|
||||||
|
item->file = Filename;
|
||||||
|
m_decoder->pending.emplace_back( item );
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock( m_decoder->mutex );
|
||||||
|
m_decoder->queue.emplace_back( item );
|
||||||
|
}
|
||||||
|
m_decoder->queued.notify_one();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_buffers.emplace_back( Filename );
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: we store mapping without file type extension, to simplify lookups
|
// NOTE: we store mapping without file type extension, to simplify lookups
|
||||||
erase_extension( Filename );
|
erase_extension( Filename );
|
||||||
|
|||||||
@@ -47,21 +47,25 @@ class buffer_manager {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
// constructors
|
// constructors
|
||||||
buffer_manager() { m_buffers.emplace_back( openal_buffer() ); } // empty bindings for null buffer
|
buffer_manager(); // makes empty bindings for null buffer
|
||||||
// destructor
|
// destructor
|
||||||
~buffer_manager();
|
~buffer_manager();
|
||||||
// methods
|
// methods
|
||||||
// creates buffer object out of data stored in specified file. returns: handle to the buffer or null_handle if creation failed
|
// creates buffer object out of data stored in specified file. returns: handle to the buffer or null_handle if creation failed
|
||||||
buffer_handle
|
buffer_handle
|
||||||
create( std::string const &Filename );
|
create( std::string const &Filename );
|
||||||
// provides direct access to a specified buffer
|
// provides direct access to a specified buffer. a buffer still decoded in the background is completed first
|
||||||
audio::openal_buffer const &
|
audio::openal_buffer const &
|
||||||
buffer( audio::buffer_handle const Buffer ) const;
|
buffer( audio::buffer_handle const Buffer ) const;
|
||||||
|
// creates AL buffers for files decoded in the background so far, or for all of them if requested
|
||||||
|
void
|
||||||
|
update( bool const Wait = false );
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// types
|
// types
|
||||||
using buffer_sequence = std::vector<openal_buffer>;
|
using buffer_sequence = std::vector<openal_buffer>;
|
||||||
using index_map = std::unordered_map<std::string, std::size_t>;
|
using index_map = std::unordered_map<std::string, std::size_t>;
|
||||||
|
struct decoder; // background decoding of sound files, used with sound.asyncload
|
||||||
// methods
|
// methods
|
||||||
// places in the bank a buffer containing data stored in specified file. returns: handle to the buffer
|
// places in the bank a buffer containing data stored in specified file. returns: handle to the buffer
|
||||||
buffer_handle
|
buffer_handle
|
||||||
@@ -72,9 +76,13 @@ private:
|
|||||||
// checks whether specified file exists. returns: name of the located file, or empty string.
|
// checks whether specified file exists. returns: name of the located file, or empty string.
|
||||||
std::string
|
std::string
|
||||||
find_file( std::string const &Filename ) const;
|
find_file( std::string const &Filename ) const;
|
||||||
|
// creates AL buffer for a file decoded in the background, waiting for the decoder if needed
|
||||||
|
void
|
||||||
|
complete( audio::buffer_handle const Buffer ) const;
|
||||||
// members
|
// members
|
||||||
buffer_sequence m_buffers;
|
mutable buffer_sequence m_buffers; // buffers decoded in the background are completed on first access
|
||||||
index_map m_buffermappings;
|
index_map m_buffermappings;
|
||||||
|
mutable std::unique_ptr<decoder> m_decoder;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // audio
|
} // audio
|
||||||
|
|||||||
@@ -305,6 +305,13 @@ openal_renderer::buffer( audio::buffer_handle const Buffer ) const {
|
|||||||
return m_buffers.buffer( Buffer );
|
return m_buffers.buffer( Buffer );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// makes buffers of all sound files still decoded in the background, waiting for them
|
||||||
|
void
|
||||||
|
openal_renderer::complete_buffers() {
|
||||||
|
|
||||||
|
m_buffers.update( true );
|
||||||
|
}
|
||||||
|
|
||||||
// initializes the service
|
// initializes the service
|
||||||
bool
|
bool
|
||||||
openal_renderer::init() {
|
openal_renderer::init() {
|
||||||
@@ -350,6 +357,9 @@ openal_renderer::erase( sound_source const *Controller ) {
|
|||||||
void
|
void
|
||||||
openal_renderer::update( double const Deltatime ) {
|
openal_renderer::update( double const Deltatime ) {
|
||||||
|
|
||||||
|
// buffers of sound files decoded in the background since the last update
|
||||||
|
m_buffers.update();
|
||||||
|
|
||||||
ALenum err = alGetError();
|
ALenum err = alGetError();
|
||||||
if (err != AL_NO_ERROR)
|
if (err != AL_NO_ERROR)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -109,6 +109,8 @@ public:
|
|||||||
|
|
||||||
audio::buffer_handle fetch_buffer( std::string const &Filename );
|
audio::buffer_handle fetch_buffer( std::string const &Filename );
|
||||||
audio::openal_buffer const &buffer( audio::buffer_handle const Buffer ) const;
|
audio::openal_buffer const &buffer( audio::buffer_handle const Buffer ) const;
|
||||||
|
// makes buffers of all sound files still decoded in the background (sound.asyncload), waiting for them
|
||||||
|
void complete_buffers();
|
||||||
|
|
||||||
bool init();
|
bool init();
|
||||||
|
|
||||||
|
|||||||
@@ -67,6 +67,10 @@ public:
|
|||||||
std::chrono::steady_clock::time_point
|
std::chrono::steady_clock::time_point
|
||||||
timestamp() const {
|
timestamp() const {
|
||||||
return m_resourcetimestamp; }
|
return m_resourcetimestamp; }
|
||||||
|
// updates the time given to resources marked as used, without a sweep
|
||||||
|
void
|
||||||
|
refresh() {
|
||||||
|
m_resourcetimestamp = std::chrono::steady_clock::now(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// members:
|
// members:
|
||||||
|
|||||||
@@ -30,6 +30,134 @@ http://mozilla.org/MPL/2.0/.
|
|||||||
|
|
||||||
#define EU07_DEFERRED_TEXTURE_UPLOAD
|
#define EU07_DEFERRED_TEXTURE_UPLOAD
|
||||||
|
|
||||||
|
// texture data read by a worker thread. the texture itself is only touched by the main thread,
|
||||||
|
// which takes the data over when the texture is created on the gl side
|
||||||
|
struct texture_request {
|
||||||
|
std::string file;
|
||||||
|
std::size_t offset { 0 }; // start of the texture data in the file
|
||||||
|
std::size_t size { 0 }; // size of the texture data
|
||||||
|
// dds data with top-left origin is flipped by the worker
|
||||||
|
bool flip { false };
|
||||||
|
std::uint32_t fourcc { 0 };
|
||||||
|
int width { 0 };
|
||||||
|
int height { 0 };
|
||||||
|
int mapcount { 0 };
|
||||||
|
int blocksize { 0 };
|
||||||
|
// results, written by the thread reading the data
|
||||||
|
std::vector<unsigned char> data;
|
||||||
|
bool failed { false };
|
||||||
|
bool done { false }; // guarded by the mutex of the loader
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// worker threads reading texture data from disk
|
||||||
|
class texture_loader {
|
||||||
|
|
||||||
|
public:
|
||||||
|
static texture_loader &instance() {
|
||||||
|
static texture_loader loader;
|
||||||
|
return loader; }
|
||||||
|
|
||||||
|
void submit( std::shared_ptr<texture_request> Request ) {
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock( m_mutex );
|
||||||
|
if( m_workers.empty() ) {
|
||||||
|
// reading the data is mostly waiting for the disk, a few threads are enough
|
||||||
|
auto const count { std::clamp<unsigned int>( std::thread::hardware_concurrency(), 2, 5 ) - 1 };
|
||||||
|
for( unsigned int idx = 0; idx < count; ++idx ) {
|
||||||
|
m_workers.emplace_back( [ this ]() { work(); } );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_queue.emplace_back( std::move( Request ) );
|
||||||
|
}
|
||||||
|
m_queued.notify_one(); }
|
||||||
|
|
||||||
|
// waits until data of the specified request is read. a request no worker has taken yet is carried out right away
|
||||||
|
void wait( std::shared_ptr<texture_request> const &Request ) {
|
||||||
|
std::unique_lock<std::mutex> lock( m_mutex );
|
||||||
|
if( Request->done ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto const lookup { std::find( std::begin( m_queue ), std::end( m_queue ), Request ) };
|
||||||
|
if( lookup != std::end( m_queue ) ) {
|
||||||
|
m_queue.erase( lookup );
|
||||||
|
lock.unlock();
|
||||||
|
read( *Request );
|
||||||
|
lock.lock();
|
||||||
|
Request->done = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_read.wait( lock, [ &Request ]() { return Request->done; } ); }
|
||||||
|
|
||||||
|
~texture_loader() {
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock( m_mutex );
|
||||||
|
m_stop = true;
|
||||||
|
}
|
||||||
|
m_queued.notify_all();
|
||||||
|
for( auto &worker : m_workers ) {
|
||||||
|
worker.join();
|
||||||
|
} }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void work() {
|
||||||
|
std::unique_lock<std::mutex> lock( m_mutex );
|
||||||
|
while( true ) {
|
||||||
|
m_queued.wait( lock, [ this ]() { return m_stop || ( false == m_queue.empty() ); } );
|
||||||
|
if( m_stop ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto request { m_queue.front() };
|
||||||
|
m_queue.pop_front();
|
||||||
|
lock.unlock();
|
||||||
|
read( *request );
|
||||||
|
lock.lock();
|
||||||
|
request->done = true;
|
||||||
|
m_read.notify_all();
|
||||||
|
} }
|
||||||
|
|
||||||
|
// same as the reading part of opengl_texture::load_DDS()
|
||||||
|
static void read( texture_request &Request ) {
|
||||||
|
std::ifstream file( Request.file, std::ios::binary );
|
||||||
|
if( false == file.is_open() ) {
|
||||||
|
Request.failed = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
file.seekg( Request.offset );
|
||||||
|
Request.data.resize( Request.size );
|
||||||
|
file.read( reinterpret_cast<char *>( Request.data.data() ), Request.size );
|
||||||
|
|
||||||
|
if( Request.flip ) {
|
||||||
|
char *mipmap = (char *)&Request.data[ 0 ];
|
||||||
|
int mapcount = Request.mapcount,
|
||||||
|
width = Request.width,
|
||||||
|
height = Request.height;
|
||||||
|
while( mapcount ) {
|
||||||
|
if( Request.fourcc == FOURCC_DXT1 )
|
||||||
|
flip_s3tc::flip_dxt1_image( mipmap, width, height );
|
||||||
|
else if( Request.fourcc == FOURCC_DXT3 )
|
||||||
|
flip_s3tc::flip_dxt23_image( mipmap, width, height );
|
||||||
|
else if( Request.fourcc == FOURCC_DXT5 )
|
||||||
|
flip_s3tc::flip_dxt45_image( mipmap, width, height );
|
||||||
|
|
||||||
|
mipmap += ( width + 3 ) / 4 * ( ( height + 3 ) / 4 ) * Request.blocksize;
|
||||||
|
width = std::max( width / 2, 4 );
|
||||||
|
height = std::max( height / 2, 4 );
|
||||||
|
--mapcount;
|
||||||
|
}
|
||||||
|
} }
|
||||||
|
|
||||||
|
std::mutex m_mutex;
|
||||||
|
std::condition_variable m_queued; // wakes workers when requests are added
|
||||||
|
std::condition_variable m_read; // wakes the main thread waiting for a request
|
||||||
|
std::deque<std::shared_ptr<texture_request>> m_queue;
|
||||||
|
std::vector<std::thread> m_workers;
|
||||||
|
bool m_stop { false };
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
std::array<GLuint, gl::MAX_TEXTURES + gl::HELPER_TEXTURES> opengl_texture::units = { 0 };
|
std::array<GLuint, gl::MAX_TEXTURES + gl::HELPER_TEXTURES> opengl_texture::units = { 0 };
|
||||||
GLint opengl_texture::m_activeunit = -1;
|
GLint opengl_texture::m_activeunit = -1;
|
||||||
|
|
||||||
@@ -261,7 +389,9 @@ opengl_texture::load() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// data state will be set by called loader, so we're all done here
|
// data state will be set by called loader, so we're all done here
|
||||||
if( data_state == resource_state::good ) {
|
// (a streamed texture has its description ready while the data is still being read)
|
||||||
|
if( ( data_state == resource_state::good )
|
||||||
|
|| ( ( data_state == resource_state::loading ) && ( request != nullptr ) ) ) {
|
||||||
|
|
||||||
// verify texture size
|
// verify texture size
|
||||||
if( clamp_power_of_two(data_width) != data_width || clamp_power_of_two(data_height) != data_height ) {
|
if( clamp_power_of_two(data_width) != data_width || clamp_power_of_two(data_height) != data_height ) {
|
||||||
@@ -579,6 +709,28 @@ opengl_texture::load_DDS() {
|
|||||||
data_state = resource_state::failed;
|
data_state = resource_state::failed;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if( true == Global.gfx_texture_streaming ) {
|
||||||
|
// the data is read by a worker thread, everything else about the texture is known already
|
||||||
|
request = std::make_shared<texture_request>();
|
||||||
|
request->file = name + type;
|
||||||
|
request->offset = static_cast<std::size_t>( file.tellg() ) + offset;
|
||||||
|
request->size = datasize;
|
||||||
|
request->flip = Global.dds_upper_origin;
|
||||||
|
request->fourcc = ddsd.ddpfPixelFormat.dwFourCC;
|
||||||
|
request->width = data_width;
|
||||||
|
request->height = data_height;
|
||||||
|
request->mapcount = data_mapcount;
|
||||||
|
request->blocksize = blockSize;
|
||||||
|
texture_loader::instance().submit( request );
|
||||||
|
|
||||||
|
data_components =
|
||||||
|
ddsd.ddpfPixelFormat.dwFourCC == FOURCC_DXT1 ? GL_RGB : GL_RGBA;
|
||||||
|
|
||||||
|
data_state = resource_state::loading;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// reserve space and load texture data
|
// reserve space and load texture data
|
||||||
data.resize( datasize );
|
data.resize( datasize );
|
||||||
|
|
||||||
@@ -930,6 +1082,17 @@ opengl_texture::unbind(size_t unit)
|
|||||||
bool
|
bool
|
||||||
opengl_texture::create( bool const Static ) {
|
opengl_texture::create( bool const Static ) {
|
||||||
|
|
||||||
|
if( ( data_state == resource_state::none )
|
||||||
|
&& ( true == reload_on_use ) ) {
|
||||||
|
// the data was dropped along with the gl texture when the texture was far away
|
||||||
|
reload_on_use = false;
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
if( ( data_state == resource_state::loading )
|
||||||
|
&& ( request != nullptr ) ) {
|
||||||
|
complete_data();
|
||||||
|
}
|
||||||
|
|
||||||
if( data_state != resource_state::good && !is_rendertarget ) {
|
if( data_state != resource_state::good && !is_rendertarget ) {
|
||||||
// don't bother until we have useful texture data
|
// don't bother until we have useful texture data
|
||||||
// and it isn't rendertarget texture without loaded data
|
// and it isn't rendertarget texture without loaded data
|
||||||
@@ -1087,6 +1250,27 @@ opengl_texture::create( bool const Static ) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// takes over the data read by a worker thread, waiting for it if needed
|
||||||
|
void
|
||||||
|
opengl_texture::complete_data() {
|
||||||
|
|
||||||
|
if( request == nullptr ) { return; }
|
||||||
|
|
||||||
|
texture_loader::instance().wait( request );
|
||||||
|
auto const completed { std::move( request ) };
|
||||||
|
|
||||||
|
if( true == completed->failed ) {
|
||||||
|
data_state = resource_state::failed;
|
||||||
|
ErrorLog( "Bad texture: failed to load texture \"" + name + "\"" );
|
||||||
|
// NOTE: temporary workaround for texture assignment errors, same as in load()
|
||||||
|
id = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data.swap( completed->data );
|
||||||
|
data_state = resource_state::good;
|
||||||
|
size = data.size() / 1024;
|
||||||
|
}
|
||||||
|
|
||||||
// releases resources allocated on the opengl end, storing local copy if requested
|
// releases resources allocated on the opengl end, storing local copy if requested
|
||||||
void
|
void
|
||||||
opengl_texture::release() {
|
opengl_texture::release() {
|
||||||
@@ -1397,6 +1581,84 @@ texture_manager::delete_textures() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// starts a scan for gl textures used only by vehicles far away
|
||||||
|
void
|
||||||
|
texture_manager::begin_release_scan() {
|
||||||
|
|
||||||
|
m_releasescantime = std::chrono::steady_clock::now();
|
||||||
|
// textures bound from now on are stamped with the time of this scan, the sweep may not be running
|
||||||
|
m_garbagecollector.refresh();
|
||||||
|
if( m_neededtimes.size() < m_textures.size() ) {
|
||||||
|
m_neededtimes.resize( m_textures.size() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// marks texture of a vehicle as needed if the vehicle is near, otherwise only as one used by vehicles
|
||||||
|
void
|
||||||
|
texture_manager::mark_as_needed( texture_handle const Texture, bool const Near ) {
|
||||||
|
|
||||||
|
if( ( Texture <= 0 )
|
||||||
|
|| ( Texture >= static_cast<texture_handle>( m_neededtimes.size() ) ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto &needed { m_neededtimes[ Texture ] };
|
||||||
|
if( true == Near ) {
|
||||||
|
needed = m_releasescantime;
|
||||||
|
auto &texture { *m_textures[ Texture ].first };
|
||||||
|
if( ( true == texture.reload_on_use )
|
||||||
|
&& ( texture.data_state == resource_state::none ) ) {
|
||||||
|
// the vehicle came back, the texture is read again before it's drawn (in the background with gfx.textures.streaming)
|
||||||
|
texture.reload_on_use = false;
|
||||||
|
texture.load();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if( needed == resource_timestamp() ) {
|
||||||
|
// from now on the texture can be released, if no vehicle near enough needs it for a while
|
||||||
|
needed = m_releasescantime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// releases gl textures of vehicles not needed nor drawn for a while
|
||||||
|
void
|
||||||
|
texture_manager::release_unneeded() {
|
||||||
|
|
||||||
|
// the delay keeps textures of a vehicle moving back and forth around the release distance from going in and out
|
||||||
|
auto const delay { std::chrono::seconds{ 10 } };
|
||||||
|
|
||||||
|
auto releasecount { 0 };
|
||||||
|
for( std::size_t index = 1; index < m_neededtimes.size(); ++index ) {
|
||||||
|
auto const needed { m_neededtimes[ index ] };
|
||||||
|
if( ( needed == resource_timestamp() )
|
||||||
|
|| ( m_releasescantime - needed < delay ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto &record { m_textures[ index ] };
|
||||||
|
auto &texture { *record.first };
|
||||||
|
if( ( texture.id == static_cast<GLuint>( -1 ) )
|
||||||
|
|| ( texture.id == 0 )
|
||||||
|
|| ( true == texture.is_static )
|
||||||
|
|| ( true == texture.is_rendertarget )
|
||||||
|
|| ( texture.type == "make:" )
|
||||||
|
|| ( texture.type == "internalsrc:" ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if( m_releasescantime - record.second < delay ) {
|
||||||
|
// drawn lately, the texture is also used by something else than far vehicles
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
texture.release();
|
||||||
|
// without the resource sweep the data isn't kept after upload, it'll have to be read again
|
||||||
|
texture.reload_on_use = ( texture.data_state == resource_state::none );
|
||||||
|
++releasecount;
|
||||||
|
}
|
||||||
|
if( releasecount > 0 ) {
|
||||||
|
// released gl names can be given to new textures, the unit cache can't trust them
|
||||||
|
for( auto &unit : opengl_texture::units ) {
|
||||||
|
unit = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// performs a resource sweep
|
// performs a resource sweep
|
||||||
void
|
void
|
||||||
texture_manager::update() {
|
texture_manager::update() {
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ http://mozilla.org/MPL/2.0/.
|
|||||||
#include "gl/ubo.h"
|
#include "gl/ubo.h"
|
||||||
#include "interfaces/ITexture.h"
|
#include "interfaces/ITexture.h"
|
||||||
|
|
||||||
|
struct texture_request; // texture data read by a worker thread, with gfx.textures.streaming
|
||||||
|
|
||||||
struct opengl_texture : public ITexture {
|
struct opengl_texture : public ITexture {
|
||||||
static DDSURFACEDESC2 deserialize_ddsd(std::istream&);
|
static DDSURFACEDESC2 deserialize_ddsd(std::istream&);
|
||||||
static DDCOLORKEY deserialize_ddck(std::istream&);
|
static DDCOLORKEY deserialize_ddck(std::istream&);
|
||||||
@@ -111,7 +113,12 @@ public:
|
|||||||
void flip_vertical();
|
void flip_vertical();
|
||||||
void gles_match_internalformat(GLuint format);
|
void gles_match_internalformat(GLuint format);
|
||||||
|
|
||||||
|
// takes over the data read by a worker thread, waiting for it if needed
|
||||||
|
void complete_data();
|
||||||
|
|
||||||
// members
|
// members
|
||||||
|
std::shared_ptr<texture_request> request; // data being read by a worker thread
|
||||||
|
bool reload_on_use = false; // the data was dropped along with the gl texture of a far texture, it's read again when the texture is needed
|
||||||
bool is_static = false; // is excluded from garbage collection
|
bool is_static = false; // is excluded from garbage collection
|
||||||
bool is_rendertarget = false; // is used as postfx rendertarget, without loaded data
|
bool is_rendertarget = false; // is used as postfx rendertarget, without loaded data
|
||||||
int samples = 1;
|
int samples = 1;
|
||||||
@@ -159,6 +166,16 @@ public:
|
|||||||
// performs a resource sweep
|
// performs a resource sweep
|
||||||
void
|
void
|
||||||
update();
|
update();
|
||||||
|
// release of gl textures used only far away (gfx.textures.releasedistance). a scan is made of
|
||||||
|
// begin_release_scan(), mark_as_needed() for textures of each vehicle and release_unneeded()
|
||||||
|
void
|
||||||
|
begin_release_scan();
|
||||||
|
// marks texture of a vehicle as needed if the vehicle is near, otherwise only as one used by vehicles
|
||||||
|
void
|
||||||
|
mark_as_needed( texture_handle const Texture, bool const Near );
|
||||||
|
// releases gl textures of vehicles not needed nor drawn for a while
|
||||||
|
void
|
||||||
|
release_unneeded();
|
||||||
// debug performance string
|
// debug performance string
|
||||||
std::string
|
std::string
|
||||||
info() const;
|
info() const;
|
||||||
@@ -191,6 +208,8 @@ private:
|
|||||||
texture_handle const npos { 0 }; // should be -1, but the rest of the code uses -1 for something else
|
texture_handle const npos { 0 }; // should be -1, but the rest of the code uses -1 for something else
|
||||||
texturetimepointpair_sequence m_textures;
|
texturetimepointpair_sequence m_textures;
|
||||||
index_map m_texturemappings;
|
index_map m_texturemappings;
|
||||||
|
std::vector<resource_timestamp> m_neededtimes; // last time a vehicle near enough needed the texture, blank for textures not used by vehicles
|
||||||
|
resource_timestamp m_releasescantime; // time of the current release scan
|
||||||
garbage_collector<texturetimepointpair_sequence> m_garbagecollector { m_textures, 600, 60, "texture" };
|
garbage_collector<texturetimepointpair_sequence> m_garbagecollector { m_textures, 600, 60, "texture" };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3351,6 +3351,119 @@ void opengl33_renderer::Render_Instanced( TModel3d *Model, std::vector<TAnimMode
|
|||||||
// - Global.SleeperDistance == 0 (sleeper rendering globally disabled)
|
// - Global.SleeperDistance == 0 (sleeper rendering globally disabled)
|
||||||
// - the track has no sleepermodel
|
// - the track has no sleepermodel
|
||||||
// - the track is farther than Global.SleeperDistance meters from the camera
|
// - the track is farther than Global.SleeperDistance meters from the camera
|
||||||
|
// releases gl textures used only by vehicles farther than gfx.textures.releasedistance times the draw range
|
||||||
|
void opengl33_renderer::Update_Texture_Release()
|
||||||
|
{
|
||||||
|
if (m_colorpass.draw_range <= 0.f)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto const range{static_cast<double>(Global.gfx_texture_releasedistance) * m_colorpass.draw_range};
|
||||||
|
auto const camera{m_colorpass.pass_camera.position()};
|
||||||
|
|
||||||
|
m_textures.begin_release_scan();
|
||||||
|
for (auto *vehicle : simulation::Vehicles.sequence())
|
||||||
|
{
|
||||||
|
if (vehicle == nullptr)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
bool const isnear{glm::length(vehicle->vPosition - camera) - vehicle->radius() <= range};
|
||||||
|
auto const mark_model = [&](TModel3d *Model) {
|
||||||
|
if (Model == nullptr)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (auto const texture : Model_Textures(Model))
|
||||||
|
{
|
||||||
|
m_textures.mark_as_needed(texture, isnear);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mark_model(vehicle->mdModel);
|
||||||
|
mark_model(vehicle->mdLowPolyInt);
|
||||||
|
mark_model(vehicle->mdLoad);
|
||||||
|
for (auto *attachment : vehicle->mdAttachments)
|
||||||
|
{
|
||||||
|
mark_model(attachment);
|
||||||
|
}
|
||||||
|
for (auto const skin : vehicle->m_materialdata.replacable_skins)
|
||||||
|
{
|
||||||
|
if (skin <= 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto const &material{m_materials.material(skin)};
|
||||||
|
for (auto const texture : material.textures)
|
||||||
|
{
|
||||||
|
m_textures.mark_as_needed(texture, isnear);
|
||||||
|
}
|
||||||
|
for (auto const &variant : material.texture_variants)
|
||||||
|
{
|
||||||
|
for (auto const texture : variant)
|
||||||
|
{
|
||||||
|
m_textures.mark_as_needed(texture, isnear);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_textures.release_unneeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
// textures used by materials of the sub-models of specified model, replaceable skins excluded
|
||||||
|
std::vector<texture_handle> const &opengl33_renderer::Model_Textures(TModel3d *Model)
|
||||||
|
{
|
||||||
|
auto const lookup{m_modeltextures.find(Model)};
|
||||||
|
if (lookup != m_modeltextures.end())
|
||||||
|
{
|
||||||
|
return lookup->second;
|
||||||
|
}
|
||||||
|
auto &textures{m_modeltextures[Model]};
|
||||||
|
std::vector<TSubModel *> submodels;
|
||||||
|
if (Model->GetSMRoot() != nullptr)
|
||||||
|
{
|
||||||
|
submodels.emplace_back(Model->GetSMRoot());
|
||||||
|
}
|
||||||
|
while (false == submodels.empty())
|
||||||
|
{
|
||||||
|
auto *submodel{submodels.back()};
|
||||||
|
submodels.pop_back();
|
||||||
|
if (submodel->NextGet() != nullptr)
|
||||||
|
{
|
||||||
|
submodels.emplace_back(submodel->NextGet());
|
||||||
|
}
|
||||||
|
if (submodel->ChildGet() != nullptr)
|
||||||
|
{
|
||||||
|
submodels.emplace_back(submodel->ChildGet());
|
||||||
|
}
|
||||||
|
// negative material is one of replaceable skins of the vehicle, these are handled with the vehicle
|
||||||
|
if (submodel->GetMaterial() <= 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto const &material{m_materials.material(submodel->GetMaterial())};
|
||||||
|
for (auto const texture : material.textures)
|
||||||
|
{
|
||||||
|
if (texture > 0)
|
||||||
|
{
|
||||||
|
textures.emplace_back(texture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto const &variant : material.texture_variants)
|
||||||
|
{
|
||||||
|
for (auto const texture : variant)
|
||||||
|
{
|
||||||
|
if (texture > 0)
|
||||||
|
{
|
||||||
|
textures.emplace_back(texture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::sort(std::begin(textures), std::end(textures));
|
||||||
|
textures.erase(std::unique(std::begin(textures), std::end(textures)), std::end(textures));
|
||||||
|
return textures;
|
||||||
|
}
|
||||||
|
|
||||||
void opengl33_renderer::Render_Sleepers( TTrack *Track )
|
void opengl33_renderer::Render_Sleepers( TTrack *Track )
|
||||||
{
|
{
|
||||||
if( Track == nullptr ) { return; }
|
if( Track == nullptr ) { return; }
|
||||||
@@ -5379,6 +5492,11 @@ void opengl33_renderer::Update(double const Deltatime)
|
|||||||
m_textures.update();
|
m_textures.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((Global.gfx_texture_releasedistance > 0.f) && (true == simulation::is_ready))
|
||||||
|
{
|
||||||
|
Update_Texture_Release();
|
||||||
|
}
|
||||||
|
|
||||||
if ((true == Global.ControlPicking) && (false == FreeFlyModeFlag))
|
if ((true == Global.ControlPicking) && (false == FreeFlyModeFlag))
|
||||||
Pick_Control_Callback([](const TSubModel *, const glm::vec2) {});
|
Pick_Control_Callback([](const TSubModel *, const glm::vec2) {});
|
||||||
// temporary conditions for testing. eventually will be coupled with editor mode
|
// temporary conditions for testing. eventually will be coupled with editor mode
|
||||||
|
|||||||
@@ -304,6 +304,10 @@ class opengl33_renderer : public gfx_renderer {
|
|||||||
bool Render_interior( bool const Alpha = false );
|
bool Render_interior( bool const Alpha = false );
|
||||||
bool Render_lowpoly( TDynamicObject *Dynamic, float const Squaredistance, bool const Setup, bool const Alpha = false );
|
bool Render_lowpoly( TDynamicObject *Dynamic, float const Squaredistance, bool const Setup, bool const Alpha = false );
|
||||||
bool Render_coupler_adapter( TDynamicObject *Dynamic, float const Squaredistance, int const End, bool const Alpha = false );
|
bool Render_coupler_adapter( TDynamicObject *Dynamic, float const Squaredistance, int const End, bool const Alpha = false );
|
||||||
|
// releases gl textures used only by vehicles farther than gfx.textures.releasedistance times the draw range
|
||||||
|
void Update_Texture_Release();
|
||||||
|
// textures used by materials of the sub-models of specified model, replaceable skins excluded
|
||||||
|
std::vector<texture_handle> const &Model_Textures( TModel3d *Model );
|
||||||
void Render(TMemCell *Memcell);
|
void Render(TMemCell *Memcell);
|
||||||
void Render_particles();
|
void Render_particles();
|
||||||
void Render_precipitation();
|
void Render_precipitation();
|
||||||
@@ -388,6 +392,8 @@ class opengl33_renderer : public gfx_renderer {
|
|||||||
// of once per cell -- collapsing many tiny instanced draws into a few large
|
// of once per cell -- collapsing many tiny instanced draws into a few large
|
||||||
// batches. Reused every pass; the vectors are emptied (not freed) at the top of Render(scene::basic_region*).
|
// batches. Reused every pass; the vectors are emptied (not freed) at the top of Render(scene::basic_region*).
|
||||||
scene::basic_cell::instance_bucket_map m_frame_instance_buckets;
|
scene::basic_cell::instance_bucket_map m_frame_instance_buckets;
|
||||||
|
// textures used by vehicle models, for the release of far textures
|
||||||
|
std::unordered_map<TModel3d const *, std::vector<texture_handle>> m_modeltextures;
|
||||||
// geometry of neighbouring tracks of a cell which share the material, gathered to be drawn together. reused by each Render(path_sequence)
|
// geometry of neighbouring tracks of a cell which share the material, gathered to be drawn together. reused by each Render(path_sequence)
|
||||||
std::vector<gfx::geometrybank_handle> m_pathbatch;
|
std::vector<gfx::geometrybank_handle> m_pathbatch;
|
||||||
// copies of instance bounds kept by the buckets of scene cells are good as long as they carry this number.
|
// copies of instance bounds kept by the buckets of scene cells are good as long as they carry this number.
|
||||||
|
|||||||
@@ -187,6 +187,12 @@ bool global_settings::ConfigParseAudio(cParser& Parser, const std::string& token
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (token == "sound.asyncload")
|
||||||
|
{
|
||||||
|
ParseOne(Parser, AudioAsyncLoad);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (token == "sound.volume.vehicle")
|
if (token == "sound.volume.vehicle")
|
||||||
{
|
{
|
||||||
ParseOneClamped(Parser, VehicleVolume, 0.f, 1.f);
|
ParseOneClamped(Parser, VehicleVolume, 0.f, 1.f);
|
||||||
@@ -1347,6 +1353,18 @@ global_settings::ConfigParse_gfx( cParser &Parser, std::string_view const Token
|
|||||||
Parser.getTokens();
|
Parser.getTokens();
|
||||||
Parser >> ResourceMove;
|
Parser >> ResourceMove;
|
||||||
}
|
}
|
||||||
|
else if (Token == "gfx.textures.streaming")
|
||||||
|
{
|
||||||
|
Parser.getTokens();
|
||||||
|
Parser >> gfx_texture_streaming;
|
||||||
|
}
|
||||||
|
else if (Token == "gfx.textures.releasedistance")
|
||||||
|
{
|
||||||
|
// multiplier of the draw range, 0 turns the release off. textures within the draw range are never released
|
||||||
|
Parser.getTokens();
|
||||||
|
Parser >> gfx_texture_releasedistance;
|
||||||
|
gfx_texture_releasedistance = (gfx_texture_releasedistance > 0.f ? std::max(gfx_texture_releasedistance, 1.f) : 0.f);
|
||||||
|
}
|
||||||
else if (Token == "gfx.reflections.framerate")
|
else if (Token == "gfx.reflections.framerate")
|
||||||
{
|
{
|
||||||
auto const updatespersecond{std::abs(Parser.getToken<double>())};
|
auto const updatespersecond{std::abs(Parser.getToken<double>())};
|
||||||
@@ -1530,6 +1548,7 @@ global_settings::export_as_text( std::ostream &Output ) const {
|
|||||||
export_as_text( Output, "sound.volume.vehicle", VehicleVolume );
|
export_as_text( Output, "sound.volume.vehicle", VehicleVolume );
|
||||||
export_as_text( Output, "sound.volume.positional", EnvironmentPositionalVolume );
|
export_as_text( Output, "sound.volume.positional", EnvironmentPositionalVolume );
|
||||||
export_as_text( Output, "sound.volume.ambient", EnvironmentAmbientVolume );
|
export_as_text( Output, "sound.volume.ambient", EnvironmentAmbientVolume );
|
||||||
|
export_as_text( Output, "sound.asyncload", AudioAsyncLoad );
|
||||||
export_as_text( Output, "physicslog", WriteLogFlag );
|
export_as_text( Output, "physicslog", WriteLogFlag );
|
||||||
export_as_text( Output, "fullphysics", FullPhysics );
|
export_as_text( Output, "fullphysics", FullPhysics );
|
||||||
export_as_text( Output, "debuglog", iWriteLogEnabled );
|
export_as_text( Output, "debuglog", iWriteLogEnabled );
|
||||||
@@ -1584,6 +1603,8 @@ global_settings::export_as_text( std::ostream &Output ) const {
|
|||||||
export_as_text( Output, "createswitchtrackbeds", CreateSwitchTrackbeds );
|
export_as_text( Output, "createswitchtrackbeds", CreateSwitchTrackbeds );
|
||||||
export_as_text( Output, "gfx.resource.sweep", ResourceSweep );
|
export_as_text( Output, "gfx.resource.sweep", ResourceSweep );
|
||||||
export_as_text( Output, "gfx.resource.move", ResourceMove );
|
export_as_text( Output, "gfx.resource.move", ResourceMove );
|
||||||
|
export_as_text( Output, "gfx.textures.streaming", gfx_texture_streaming );
|
||||||
|
export_as_text( Output, "gfx.textures.releasedistance", gfx_texture_releasedistance );
|
||||||
export_as_text( Output, "gfx.reflections.framerate", 1.0 / reflectiontune.update_interval );
|
export_as_text( Output, "gfx.reflections.framerate", 1.0 / reflectiontune.update_interval );
|
||||||
export_as_text( Output, "gfx.reflections.fidelity", reflectiontune.fidelity );
|
export_as_text( Output, "gfx.reflections.fidelity", reflectiontune.fidelity );
|
||||||
export_as_text( Output, "timespeed", fTimeSpeed );
|
export_as_text( Output, "timespeed", fTimeSpeed );
|
||||||
|
|||||||
@@ -182,6 +182,8 @@ struct global_settings {
|
|||||||
float SmokeFidelity{ 1.f }; // determines amount of generated smoke particles
|
float SmokeFidelity{ 1.f }; // determines amount of generated smoke particles
|
||||||
bool ResourceSweep{ true }; // gfx resource garbage collection
|
bool ResourceSweep{ true }; // gfx resource garbage collection
|
||||||
bool ResourceMove{ false }; // gfx resources are moved between cpu and gpu side instead of sending a copy
|
bool ResourceMove{ false }; // gfx resources are moved between cpu and gpu side instead of sending a copy
|
||||||
|
bool gfx_texture_streaming{ false }; // data of dds textures is read by worker threads, the rest of the loading doesn't wait for it
|
||||||
|
float gfx_texture_releasedistance{ 0.f }; // gl textures of vehicles farther than this many draw ranges are released. 0 = off
|
||||||
bool compress_tex{ true }; // all textures are compressed on gpu side
|
bool compress_tex{ true }; // all textures are compressed on gpu side
|
||||||
std::string asSky{ "1" };
|
std::string asSky{ "1" };
|
||||||
float fFpsAverage{ 0.f }; // oczekiwana wartosć FPS
|
float fFpsAverage{ 0.f }; // oczekiwana wartosć FPS
|
||||||
@@ -197,6 +199,7 @@ struct global_settings {
|
|||||||
float EnvironmentAmbientVolume{ 1.0f };
|
float EnvironmentAmbientVolume{ 1.0f };
|
||||||
int audio_max_sources = 30;
|
int audio_max_sources = 30;
|
||||||
std::string AudioRenderer;
|
std::string AudioRenderer;
|
||||||
|
bool AudioAsyncLoad{ false }; // sound files are decoded by worker threads
|
||||||
// input
|
// input
|
||||||
float fMouseXScale{ 1.5f };
|
float fMouseXScale{ 1.5f };
|
||||||
float fMouseYScale{ 0.2f };
|
float fMouseYScale{ 0.2f };
|
||||||
|
|||||||
Reference in New Issue
Block a user