16
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-07-22 18:39:18 +02:00

basic texture generation support, python multithreading fixes

This commit is contained in:
tmj-fstate
2018-10-21 01:29:27 +02:00
parent b51d5c3d2b
commit b36ed8db6c
11 changed files with 298 additions and 130 deletions

View File

@@ -453,7 +453,7 @@ bool TAnimModel::Init(std::string const &asName, std::string const &asReplacable
bool TAnimModel::Load(cParser *parser, bool ter) bool TAnimModel::Load(cParser *parser, bool ter)
{ // rozpoznanie wpisu modelu i ustawienie świateł { // rozpoznanie wpisu modelu i ustawienie świateł
std::string name = parser->getToken<std::string>(); std::string name = parser->getToken<std::string>();
std::string texture = parser->getToken<std::string>(); // tekstura (zmienia na małe) std::string texture = parser->getToken<std::string>( false ); // tekstura (zmienia na małe)
replace_slashes( name ); replace_slashes( name );
if (!Init( name, texture )) if (!Init( name, texture ))
{ {

View File

@@ -27,7 +27,7 @@ void render_task::run() {
if( ( outputwidth != nullptr ) if( ( outputwidth != nullptr )
&& ( outputheight != nullptr ) ) { && ( outputheight != nullptr ) ) {
::glBindTexture( GL_TEXTURE_2D, GfxRenderer.Texture( m_target ).id ); ::glBindTexture( GL_TEXTURE_2D, m_target );
// setup texture parameters // setup texture parameters
::glTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE ); ::glTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE );
::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); ::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
@@ -74,8 +74,6 @@ auto python_taskqueue::init() -> bool {
#endif #endif
Py_Initialize(); Py_Initialize();
PyEval_InitThreads(); PyEval_InitThreads();
// save a pointer to the main PyThreadState object
m_mainthread = PyThreadState_Get();
// do the setup work while we hold the lock // do the setup work while we hold the lock
m_main = PyImport_ImportModule("__main__"); m_main = PyImport_ImportModule("__main__");
if (m_main == nullptr) { if (m_main == nullptr) {
@@ -101,8 +99,8 @@ auto python_taskqueue::init() -> bool {
if( false == run_file( "abstractscreenrenderer" ) ) { goto release_and_exit; } if( false == run_file( "abstractscreenrenderer" ) ) { goto release_and_exit; }
// release the lock // release the lock, save the state for future use
PyEval_ReleaseLock(); m_mainthread = PyEval_SaveThread();
WriteLog( "Python Interpreter setup complete" ); WriteLog( "Python Interpreter setup complete" );
@@ -110,12 +108,11 @@ auto python_taskqueue::init() -> bool {
for( auto &worker : m_workers ) { for( auto &worker : m_workers ) {
auto *openglcontextwindow { Application.window( -1 ) }; auto *openglcontextwindow { Application.window( -1 ) };
worker = worker = std::thread(
std::make_unique<std::thread>( &python_taskqueue::run, this,
&python_taskqueue::run, this, openglcontextwindow, std::ref( m_tasks ), std::ref( m_condition ), std::ref( m_exit ) );
openglcontextwindow, std::ref( m_tasks ), std::ref( m_condition ), std::ref( m_exit ) );
if( worker == nullptr ) { return false; } if( false == worker.joinable() ) { return false; }
} }
return true; return true;
@@ -131,8 +128,8 @@ void python_taskqueue::exit() {
m_exit = true; m_exit = true;
m_condition.notify_all(); m_condition.notify_all();
// let them free up their shit before we proceed // let them free up their shit before we proceed
for( auto const &worker : m_workers ) { for( auto &worker : m_workers ) {
worker->join(); worker.join();
} }
// get rid of the leftover tasks // get rid of the leftover tasks
// with the workers dead we don't have to worry about concurrent access anymore // with the workers dead we don't have to worry about concurrent access anymore
@@ -140,8 +137,7 @@ void python_taskqueue::exit() {
task->cancel(); task->cancel();
} }
// take a bow // take a bow
PyEval_AcquireLock(); acquire_lock();
PyThreadState_Swap( m_mainthread );
Py_Finalize(); Py_Finalize();
} }
@@ -150,7 +146,7 @@ auto python_taskqueue::insert( task_request const &Task ) -> bool {
if( ( Task.renderer.empty() ) if( ( Task.renderer.empty() )
|| ( Task.input == nullptr ) || ( Task.input == nullptr )
|| ( Task.target == null_handle ) ) { return false; } || ( Task.target == 0 ) ) { return false; }
auto *renderer { fetch_renderer( Task.renderer ) }; auto *renderer { fetch_renderer( Task.renderer ) };
if( renderer == nullptr ) { return false; } if( renderer == nullptr ) { return false; }
@@ -164,11 +160,11 @@ auto python_taskqueue::insert( task_request const &Task ) -> bool {
for( auto &task : m_tasks.data ) { for( auto &task : m_tasks.data ) {
if( task->target() == Task.target ) { if( task->target() == Task.target ) {
// replace pending task in the slot with the more recent one // replace pending task in the slot with the more recent one
PyEval_AcquireLock(); acquire_lock();
{ {
task->cancel(); task->cancel();
} }
PyEval_ReleaseLock(); release_lock();
task = newtask; task = newtask;
newtaskinserted = true; newtaskinserted = true;
break; break;
@@ -202,6 +198,18 @@ auto python_taskqueue::run_file( std::string const &File, std::string const &Pat
return true; return true;
} }
// acquires the python gil and sets the main thread as current
void python_taskqueue::acquire_lock() {
PyEval_RestoreThread( m_mainthread );
}
// releases the python gil and swaps the main thread out
void python_taskqueue::release_lock() {
PyEval_SaveThread();
}
auto python_taskqueue::fetch_renderer( std::string const Renderer ) ->PyObject * { auto python_taskqueue::fetch_renderer( std::string const Renderer ) ->PyObject * {
auto const lookup { m_renderers.find( Renderer ) }; auto const lookup { m_renderers.find( Renderer ) };
@@ -213,7 +221,7 @@ auto python_taskqueue::fetch_renderer( std::string const Renderer ) ->PyObject *
auto const file { Renderer.substr( path.size() ) }; auto const file { Renderer.substr( path.size() ) };
PyObject *renderer { nullptr }; PyObject *renderer { nullptr };
PyObject *rendererarguments { nullptr }; PyObject *rendererarguments { nullptr };
PyEval_AcquireLock(); acquire_lock();
{ {
if( m_main == nullptr ) { if( m_main == nullptr ) {
ErrorLog( "Python Renderer: __main__ module is missing" ); ErrorLog( "Python Renderer: __main__ module is missing" );
@@ -246,7 +254,7 @@ cache_and_return:
Py_DECREF( rendererarguments ); Py_DECREF( rendererarguments );
} }
} }
PyEval_ReleaseLock(); release_lock();
// cache the failures as well so we don't try again on subsequent requests // cache the failures as well so we don't try again on subsequent requests
m_renderers.emplace( Renderer, renderer ); m_renderers.emplace( Renderer, renderer );
return renderer; return renderer;
@@ -279,14 +287,14 @@ void python_taskqueue::run( GLFWwindow *Context, rendertask_sequence &Tasks, thr
} }
if( task != nullptr ) { if( task != nullptr ) {
// swap in my thread state // swap in my thread state
PyEval_AcquireLock(); PyEval_RestoreThread( threadstate );
PyThreadState_Swap( threadstate ); {
// execute python code // execute python code
task->run(); task->run();
error(); error();
}
// clear the thread state // clear the thread state
PyThreadState_Swap( nullptr ); PyEval_SaveThread();
PyEval_ReleaseLock();
} }
// TBD, TODO: add some idle time between tasks in case we're on a single thread cpu? // TBD, TODO: add some idle time between tasks in case we're on a single thread cpu?
} while( task != nullptr ); } while( task != nullptr );

14
PyInt.h
View File

@@ -29,7 +29,7 @@ class render_task {
public: public:
// constructors // constructors
render_task( PyObject *Renderer, PyObject *Input, texture_handle Target ) : render_task( PyObject *Renderer, PyObject *Input, GLuint Target ) :
m_renderer( Renderer ), m_input( Input ), m_target( Target ) m_renderer( Renderer ), m_input( Input ), m_target( Target )
{} {}
// methods // methods
@@ -41,9 +41,11 @@ private:
// members // members
PyObject *m_renderer {nullptr}; PyObject *m_renderer {nullptr};
PyObject *m_input { nullptr }; PyObject *m_input { nullptr };
texture_handle m_target { null_handle }; GLuint m_target { 0 };
}; };
class python_taskqueue { class python_taskqueue {
public: public:
@@ -52,7 +54,7 @@ public:
std::string const &renderer; std::string const &renderer;
PyObject *input; PyObject *input;
texture_handle target; GLuint target;
}; };
// constructors // constructors
python_taskqueue() = default; python_taskqueue() = default;
@@ -65,11 +67,15 @@ public:
auto insert( task_request const &Task ) -> bool; auto insert( task_request const &Task ) -> bool;
// executes python script stored in specified file. returns true on success // executes python script stored in specified file. returns true on success
auto run_file( std::string const &File, std::string const &Path = "" ) -> bool; auto run_file( std::string const &File, std::string const &Path = "" ) -> bool;
// acquires the python gil and sets the main thread as current
void acquire_lock();
// releases the python gil and swaps the main thread out
void release_lock();
private: private:
// types // types
static int const WORKERCOUNT { 1 }; static int const WORKERCOUNT { 1 };
using worker_array = std::array<std::unique_ptr<std::thread>, WORKERCOUNT >; using worker_array = std::array<std::thread, WORKERCOUNT >;
using rendertask_sequence = threading::lockable< std::deque<render_task *> >; using rendertask_sequence = threading::lockable< std::deque<render_task *> >;
// methods // methods
auto fetch_renderer( std::string const Renderer ) -> PyObject *; auto fetch_renderer( std::string const Renderer ) -> PyObject *;

View File

@@ -19,6 +19,7 @@ http://mozilla.org/MPL/2.0/.
#include <ddraw.h> #include <ddraw.h>
#include "GL/glew.h" #include "GL/glew.h"
#include "application.h"
#include "utilities.h" #include "utilities.h"
#include "Globals.h" #include "Globals.h"
#include "Logs.h" #include "Logs.h"
@@ -37,18 +38,23 @@ texture_manager::texture_manager() {
void void
opengl_texture::load() { opengl_texture::load() {
if( name.size() < 3 ) { goto fail; } if( type == "make:" ) {
// for generated texture we delay data creation until texture is bound
// this ensures the script will receive all simulation data it might potentially want
// as any binding will happen after simulation is loaded, initialized and running
// until then we supply data for a tiny 2x2 grey stub
make_stub();
}
else {
WriteLog( "Loading texture data from \"" + name + "\"", logtype::texture ); WriteLog( "Loading texture data from \"" + name + type + "\"", logtype::texture );
data_state = resource_state::loading; data_state = resource_state::loading;
{
std::string const extension = name.substr( name.size() - 3, 3 );
if( extension == "dds" ) { load_DDS(); } if( type == ".dds" ) { load_DDS(); }
else if( extension == "tga" ) { load_TGA(); } else if( type == ".tga" ) { load_TGA(); }
else if( extension == "bmp" ) { load_BMP(); } else if( type == ".bmp" ) { load_BMP(); }
else if( extension == "tex" ) { load_TEX(); } else if( type == ".tex" ) { load_TEX(); }
else { goto fail; } else { goto fail; }
} }
@@ -67,16 +73,54 @@ opengl_texture::load() {
fail: fail:
data_state = resource_state::failed; data_state = resource_state::failed;
ErrorLog( "Bad texture: failed to load texture \"" + name + "\"" ); ErrorLog( "Bad texture: failed to load texture \"" + name + type + "\"" );
// NOTE: temporary workaround for texture assignment errors // NOTE: temporary workaround for texture assignment errors
id = 0; id = 0;
return; return;
} }
void
opengl_texture::make_stub() {
data_width = 2;
data_height = 2;
data.resize( data_width * data_height * 3 );
std::fill( std::begin( data ), std::end( data ), static_cast<char>( 0xc0 ) );
data_mapcount = 1;
data_format = GL_RGB;
data_components = GL_RGB;
data_state = resource_state::good;
}
void
opengl_texture::make_request() {
auto const components { Split( name, '?' ) };
auto const query { Split( components.back(), '&' ) };
PyObject *pythondictionary { nullptr };
Application.acquire_python_lock();
{
pythondictionary = PyDict_New();
if( pythondictionary != nullptr ) {
for( auto const &querypair : query ) {
auto const valuepos { querypair.find( '=' ) };
PyDict_SetItemString(
pythondictionary,
ToLower( querypair.substr( 0, valuepos ) ).c_str(),
PyGetString( querypair.substr( valuepos + 1 ).c_str() ) );
}
}
}
Application.release_python_lock();
Application.request( { ToLower( components.front() ), pythondictionary, id } );
}
void void
opengl_texture::load_BMP() { opengl_texture::load_BMP() {
std::ifstream file( name, std::ios::binary ); file.unsetf( std::ios::skipws ); std::ifstream file( name + type, std::ios::binary ); file.unsetf( std::ios::skipws );
BITMAPFILEHEADER header; BITMAPFILEHEADER header;
@@ -198,7 +242,7 @@ DDSURFACEDESC2 opengl_texture::deserialize_ddsd(std::istream &s)
void void
opengl_texture::load_DDS() { opengl_texture::load_DDS() {
std::ifstream file( name, std::ios::binary | std::ios::ate ); file.unsetf( std::ios::skipws ); std::ifstream file( name + type, std::ios::binary | std::ios::ate ); file.unsetf( std::ios::skipws );
std::size_t filesize = static_cast<size_t>(file.tellg()); // ios::ate already positioned us at the end of the file std::size_t filesize = static_cast<size_t>(file.tellg()); // ios::ate already positioned us at the end of the file
file.seekg( 0, std::ios::beg ); // rewind the caret afterwards file.seekg( 0, std::ios::beg ); // rewind the caret afterwards
@@ -256,12 +300,12 @@ opengl_texture::load_DDS() {
data_width /= 2; data_width /= 2;
data_height /= 2; data_height /= 2;
--data_mapcount; --data_mapcount;
WriteLog( "Texture size exceeds specified limits, skipping mipmap level" ); WriteLog( "Texture pixelcount exceeds specified limits, skipping mipmap level" );
}; };
if( data_mapcount <= 0 ) { if( data_mapcount <= 0 ) {
// there's a chance we've discarded the provided mipmap(s) as too large // there's a chance we've discarded the provided mipmap(s) as too large
WriteLog( "Texture \"" + name + "\" has no mipmaps which can fit currently set texture size limits." ); WriteLog( "Texture \"" + name + "\" has no mipmaps which can fit currently set texture pixelcount limits." );
data_state = resource_state::failed; data_state = resource_state::failed;
return; return;
} }
@@ -316,7 +360,7 @@ opengl_texture::load_DDS() {
void void
opengl_texture::load_TEX() { opengl_texture::load_TEX() {
std::ifstream file( name, std::ios::binary ); file.unsetf( std::ios::skipws ); std::ifstream file( name + type, std::ios::binary ); file.unsetf( std::ios::skipws );
char head[ 5 ]; char head[ 5 ];
file.read( head, 4 ); file.read( head, 4 );
@@ -363,7 +407,7 @@ opengl_texture::load_TEX() {
void void
opengl_texture::load_TGA() { opengl_texture::load_TGA() {
std::ifstream file( name, std::ios::binary ); file.unsetf( std::ios::skipws ); std::ifstream file( name + type, std::ios::binary ); file.unsetf( std::ios::skipws );
// Read the header of the TGA, compare it with the known headers for compressed and uncompressed TGAs // Read the header of the TGA, compare it with the known headers for compressed and uncompressed TGAs
unsigned char tgaheader[ 18 ]; unsigned char tgaheader[ 18 ];
@@ -604,6 +648,12 @@ opengl_texture::create() {
data = std::vector<char>(); data = std::vector<char>();
data_state = resource_state::none; data_state = resource_state::none;
} }
if( type == "make:" ) {
// for generated textures send a request to have the actual content of the texture generated
make_request();
}
is_ready = true; is_ready = true;
} }
@@ -620,30 +670,36 @@ opengl_texture::release() {
// if resource move is enabled we don't keep a cpu side copy after upload // if resource move is enabled we don't keep a cpu side copy after upload
// so need to re-acquire the data before release // so need to re-acquire the data before release
// TBD, TODO: instead of vram-ram transfer fetch the data 'normally' from the disk using worker thread // TBD, TODO: instead of vram-ram transfer fetch the data 'normally' from the disk using worker thread
::glBindTexture( GL_TEXTURE_2D, id ); if( type == "make:" ) {
GLint datasize {}; // auto generated textures only store a stub
GLint iscompressed {}; make_stub();
::glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED, &iscompressed );
if( iscompressed == GL_TRUE ) {
// texture is compressed on the gpu side
// query texture details needed to perform the backup...
::glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &data_format );
::glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &datasize );
data.resize( datasize );
// ...fetch the data...
::glGetCompressedTexImage( GL_TEXTURE_2D, 0, &data[ 0 ] );
} }
else { else {
// for whatever reason texture didn't get compressed during upload ::glBindTexture( GL_TEXTURE_2D, id );
// fallback on plain rgba storage... GLint datasize{};
data_format = GL_RGBA; GLint iscompressed{};
data.resize( data_width * data_height * 4 ); ::glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED, &iscompressed );
// ...fetch the data... if( iscompressed == GL_TRUE ) {
::glGetTexImage( GL_TEXTURE_2D, 0, data_format, GL_UNSIGNED_BYTE, &data[ 0 ] ); // texture is compressed on the gpu side
// query texture details needed to perform the backup...
::glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &data_format );
::glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &datasize );
data.resize( datasize );
// ...fetch the data...
::glGetCompressedTexImage( GL_TEXTURE_2D, 0, &data[ 0 ] );
}
else {
// for whatever reason texture didn't get compressed during upload
// fallback on plain rgba storage...
data_format = GL_RGBA;
data.resize( data_width * data_height * 4 );
// ...fetch the data...
::glGetTexImage( GL_TEXTURE_2D, 0, data_format, GL_UNSIGNED_BYTE, &data[ 0 ] );
}
// ...and update texture object state
data_mapcount = 1; // we keep copy of only top mipmap level
data_state = resource_state::good;
} }
// ...and update texture object state
data_mapcount = 1; // we keep copy of only top mipmap level
data_state = resource_state::good;
} }
// release opengl resources // release opengl resources
::glDeleteTextures( 1, &id ); ::glDeleteTextures( 1, &id );
@@ -697,7 +753,7 @@ opengl_texture::downsize( GLuint const Format ) {
break; break;
} }
WriteLog( "Texture size exceeds specified limits, downsampling data" ); WriteLog( "Texture pixelcount exceeds specified limits, downsampling data" );
// trim potential odd texture sizes // trim potential odd texture sizes
data_width -= ( data_width % 2 ); data_width -= ( data_width % 2 );
data_height -= ( data_height % 2 ); data_height -= ( data_height % 2 );
@@ -754,48 +810,79 @@ texture_manager::create( std::string Filename, bool const Loadnow ) {
if( Filename.find( '|' ) != std::string::npos ) if( Filename.find( '|' ) != std::string::npos )
Filename.erase( Filename.find( '|' ) ); // po | może być nazwa kolejnej tekstury Filename.erase( Filename.find( '|' ) ); // po | może być nazwa kolejnej tekstury
std::pair<std::string, std::string> locator; // resource name, resource type
std::string traits; std::string traits;
auto const traitpos = Filename.find( ':' );
if( traitpos != std::string::npos ) {
// po dwukropku mogą być podane dodatkowe informacje niebędące nazwą tekstury
if( Filename.size() > traitpos + 1 )
traits = Filename.substr( traitpos + 1 );
Filename.erase( traitpos );
}
erase_extension( Filename ); // discern textures generated by a script
replace_slashes( Filename ); // TBD: support file: for file resources?
if( Filename[ 0 ] == '/' ) { auto const isgenerated { Filename.find( "make:" ) == 0 };
// filename can potentially begin with a slash, and we don't need it // process supplied resource name
Filename.erase( 0, 1 ); if( isgenerated ) {
} // generated resource
// scheme:(user@)path?query
// remove scheme indicator
Filename.erase( 0, Filename.find( ':' ) + 1 );
// TODO: extract traits specification from the query
// clean up slashes
replace_slashes( Filename );
erase_leading_slashes( Filename );
}
else {
// regular file resource
// (filepath/)(#)filename.extension(:traits)
// extract trait specifications
auto const traitpos = Filename.rfind( ':' );
if( traitpos != std::string::npos ) {
// po dwukropku mogą być podane dodatkowe informacje niebędące nazwą tekstury
if( Filename.size() > traitpos + 1 ) {
traits = Filename.substr( traitpos + 1 );
}
Filename.erase( traitpos );
}
// potentially trim file type indicator since we check for multiple types
erase_extension( Filename );
// clean up slashes
replace_slashes( Filename );
erase_leading_slashes( Filename );
// temporary code for legacy assets -- textures with names beginning with # are to be sharpened
if( ( Filename[ 0 ] == '#' )
|| ( Filename.find( "/#" ) != std::string::npos ) ) {
traits += '#';
}
}
// try to locate requested texture in the databank // try to locate requested texture in the databank
auto lookup { find_in_databank( Filename ) }; // TBD, TODO: include trait specification in the resource id in the databank?
auto const lookup { find_in_databank( Filename ) };
if( lookup != npos ) { if( lookup != npos ) {
return lookup; return lookup;
} }
// if we don't have the texture in the databank, check if it's on disk // if the lookup fails...
auto const disklookup { find_on_disk( Filename ) }; if( isgenerated ) {
// TODO: verify presence of the generator script
if( true == disklookup.first.empty() ) { locator.first = Filename;
// there's nothing matching in the databank nor on the disk, report failure locator.second = "make:";
ErrorLog( "Bad file: failed do locate texture file \"" + Filename + "\"", logtype::file ); }
return npos; else {
// ...for file resources check if it's on disk
locator = find_on_disk( Filename );
if( true == locator.first.empty() ) {
// there's nothing matching in the databank nor on the disk, report failure
ErrorLog( "Bad file: failed do locate texture file \"" + Filename + "\"", logtype::file );
return npos;
}
} }
auto texture = new opengl_texture(); auto texture = new opengl_texture();
texture->name = disklookup.first + disklookup.second; texture->name = locator.first;
if( Filename.find('#') != std::string::npos ) { texture->type = locator.second;
// temporary code for legacy assets -- textures with names beginning with # are to be sharpened
traits += '#';
}
texture->traits = traits; texture->traits = traits;
auto const textureindex = (texture_handle)m_textures.size(); auto const textureindex = (texture_handle)m_textures.size();
m_textures.emplace_back( texture, std::chrono::steady_clock::time_point() ); m_textures.emplace_back( texture, std::chrono::steady_clock::time_point() );
m_texturemappings.emplace( disklookup.first, textureindex ); m_texturemappings.emplace( locator.first, textureindex );
WriteLog( "Created texture object for \"" + disklookup.first + disklookup.second + "\"", logtype::texture ); WriteLog( "Created texture object for \"" + locator.first + "\"", logtype::texture );
if( true == Loadnow ) { if( true == Loadnow ) {

View File

@@ -47,10 +47,13 @@ struct opengl_texture {
bool is_ready{ false }; // indicates the texture was processed and is ready for use bool is_ready{ false }; // indicates the texture was processed and is ready for use
std::string traits; // requested texture attributes: wrapping modes etc std::string traits; // requested texture attributes: wrapping modes etc
std::string name; // name of the texture source file std::string name; // name of the texture source file
std::string type; // type of the texture source file
std::size_t size{ 0 }; // size of the texture data, in kb std::size_t size{ 0 }; // size of the texture data, in kb
private: private:
// methods // methods
void make_stub();
void make_request();
void load_BMP(); void load_BMP();
void load_DDS(); void load_DDS();
void load_TEX(); void load_TEX();

View File

@@ -448,11 +448,11 @@ bool TTrain::Init(TDynamicObject *NewDynamicObject, bool e3d)
PyObject *TTrain::GetTrainState() { PyObject *TTrain::GetTrainState() {
auto const *mover = DynamicObject->MoverParameters; auto const *mover = DynamicObject->MoverParameters;
PyEval_AcquireLock(); Application.acquire_python_lock();
auto *dict = PyDict_New(); auto *dict = PyDict_New();
if( ( dict == nullptr ) if( ( dict == nullptr )
|| ( mover == nullptr ) ) { || ( mover == nullptr ) ) {
PyEval_ReleaseLock(); Application.release_python_lock();
return nullptr; return nullptr;
} }
@@ -582,7 +582,7 @@ PyObject *TTrain::GetTrainState() {
PyDict_SetItemString( dict, "seconds", PyGetInt( simulation::Time.second() ) ); PyDict_SetItemString( dict, "seconds", PyGetInt( simulation::Time.second() ) );
PyDict_SetItemString( dict, "air_temperature", PyGetInt( Global.AirTemperature ) ); PyDict_SetItemString( dict, "air_temperature", PyGetInt( Global.AirTemperature ) );
PyEval_ReleaseLock(); Application.release_python_lock();
return dict; return dict;
} }
@@ -5993,7 +5993,7 @@ bool TTrain::Update( double const Deltatime )
&& ( false == FreeFlyModeFlag ) ) { // don't bother if we're outside && ( false == FreeFlyModeFlag ) ) { // don't bother if we're outside
fScreenTimer = 0.f; fScreenTimer = 0.f;
for( auto const &screen : m_screens ) { for( auto const &screen : m_screens ) {
Application.request( { screen.first, GetTrainState(), screen.second } ); Application.request( { screen.first, GetTrainState(), GfxRenderer.Texture( screen.second ).id } );
} }
} }
// sounds // sounds

View File

@@ -151,12 +151,27 @@ eu07_application::run() {
return 0; return 0;
} }
// issues request for a worker thread to perform specified task. returns: true if task was scheduled
bool bool
eu07_application::request( python_taskqueue::task_request const &Task ) { eu07_application::request( python_taskqueue::task_request const &Task ) {
return m_taskqueue.insert( Task ); return m_taskqueue.insert( Task );
} }
// ensures the main thread holds the python gil and can safely execute python calls
void
eu07_application::acquire_python_lock() {
m_taskqueue.acquire_lock();
}
// frees the python gil and swaps out the main thread
void
eu07_application::release_python_lock() {
m_taskqueue.release_lock();
}
void void
eu07_application::exit() { eu07_application::exit() {

View File

@@ -30,8 +30,15 @@ public:
init( int Argc, char *Argv[] ); init( int Argc, char *Argv[] );
int int
run(); run();
// issues request for a worker thread to perform specified task. returns: true if task was scheduled
bool bool
request( python_taskqueue::task_request const &Task ); request( python_taskqueue::task_request const &Task );
// ensures the main thread holds the python gil and can safely execute python calls
void
acquire_python_lock();
// frees the python gil and swaps out the main thread
void
release_python_lock();
void void
exit(); exit();
void void

View File

@@ -117,47 +117,75 @@ material_manager::create( std::string const &Filename, bool const Loadnow ) {
if( filename.find( '|' ) != std::string::npos ) if( filename.find( '|' ) != std::string::npos )
filename.erase( filename.find( '|' ) ); // po | może być nazwa kolejnej tekstury filename.erase( filename.find( '|' ) ); // po | może być nazwa kolejnej tekstury
erase_extension( filename ); // discern references to textures generated by a script
replace_slashes( filename ); // TBD: support file: for file resources?
if( filename[ 0 ] == '/' ) { auto const isgenerated { filename.find( "make:" ) == 0 };
// filename can potentially begin with a slash, and we don't need it
filename.erase( 0, 1 ); // process supplied resource name
if( isgenerated ) {
// generated resource
// scheme:(user@)path?query
// remove scheme indicator
filename.erase( 0, filename.find(':') + 1 );
// TBD, TODO: allow shader specification as part of the query?
replace_slashes( filename );
erase_leading_slashes( filename );
}
else {
// regular file resource
// (filepath/)filename.extension
erase_extension( filename );
replace_slashes( filename );
erase_leading_slashes( filename );
} }
// try to locate requested material in the databank auto const databanklookup { find_in_databank( ToLower( filename ) ) };
auto const databanklookup { find_in_databank( filename ) };
if( databanklookup != null_handle ) { if( databanklookup != null_handle ) {
return databanklookup; return databanklookup;
} }
// if this fails, try to look for it on disk
opengl_material material; opengl_material material;
auto const disklookup { find_on_disk( filename ) }; material_handle materialhandle { null_handle };
if( false == disklookup.first.empty() ) {
cParser materialparser( disklookup.first + disklookup.second, cParser::buffer_FILE ); auto const locator {
if( false == material.deserialize( materialparser, Loadnow ) ) { isgenerated ?
// deserialization failed but the .mat file does exist, so we give up at this point std::make_pair( filename, "make:" ) :
return null_handle; find_on_disk( filename ) };
if( ( false == isgenerated )
&& ( false == locator.first.empty() ) ) {
// try to parse located file resource
cParser materialparser( locator.first + locator.second, cParser::buffer_FILE );
if( true == material.deserialize( materialparser, Loadnow ) ) {
material.name = locator.first;
} }
material.name = disklookup.first;
} }
else { else {
// if there's no .mat file, this could be legacy method of referring just to diffuse texture directly, make a material out of it in such case // if there's no .mat file, this can be either autogenerated texture,
// or legacy method of referring just to diffuse texture directly.
// wrap basic material around it in either case
material.texture1 = GfxRenderer.Fetch_Texture( Filename, Loadnow ); material.texture1 = GfxRenderer.Fetch_Texture( Filename, Loadnow );
if( material.texture1 == null_handle ) { if( material.texture1 != null_handle ) {
// if there's also no texture, give up // use texture path and name to tell the newly created materials apart
return null_handle; material.name = GfxRenderer.Texture( material.texture1 ).name;
material.has_alpha = GfxRenderer.Texture( material.texture1 ).has_alpha;
} }
// use texture path and name to tell the newly created materials apart
filename = GfxRenderer.Texture( material.texture1 ).name;
erase_extension( filename );
material.name = filename;
material.has_alpha = GfxRenderer.Texture( material.texture1 ).has_alpha;
} }
material_handle handle = m_materials.size(); if( false == material.name.empty() ) {
m_materials.emplace_back( material ); // if we have material name it means resource was processed succesfully
m_materialmappings.emplace( material.name, handle ); materialhandle = m_materials.size();
return handle; m_materials.emplace_back( material );
m_materialmappings.emplace( material.name, materialhandle );
}
else {
// otherwise record our failure to process the resource, to speed up subsequent attempts
m_materialmappings.emplace( filename, materialhandle );
}
return materialhandle;
}; };
// checks whether specified material is in the material bank. returns handle to the material, or a null handle // checks whether specified material is in the material bank. returns handle to the material, or a null handle
@@ -184,9 +212,10 @@ material_manager::find_in_databank( std::string const &Materialname ) const {
std::pair<std::string, std::string> std::pair<std::string, std::string>
material_manager::find_on_disk( std::string const &Materialname ) const { material_manager::find_on_disk( std::string const &Materialname ) const {
auto const materialname { ToLower( Materialname ) };
return ( return (
FileExists( FileExists(
{ Global.asCurrentTexturePath + Materialname, Materialname, szTexturePath + Materialname }, { Global.asCurrentTexturePath + materialname, materialname, szTexturePath + materialname },
{ ".mat" } ) ); { ".mat" } ) );
} }

View File

@@ -384,6 +384,14 @@ erase_extension( std::string &Filename ) {
return false; return false;
} }
void
erase_leading_slashes( std::string &Filename ) {
while( Filename[ 0 ] == '/' ) {
Filename.erase( 0, 1 );
}
}
// potentially replaces backward slashes in provided file path with unix-compatible forward slashes // potentially replaces backward slashes in provided file path with unix-compatible forward slashes
void void
replace_slashes( std::string &Filename ) { replace_slashes( std::string &Filename ) {

View File

@@ -30,6 +30,7 @@ http://mozilla.org/MPL/2.0/.
#define szModelPath "models/" #define szModelPath "models/"
#define szDynamicPath "dynamic/" #define szDynamicPath "dynamic/"
#define szSoundPath "sounds/" #define szSoundPath "sounds/"
#define szDataPath "data/"
#define MAKE_ID4(a,b,c,d) (((std::uint32_t)(d)<<24)|((std::uint32_t)(c)<<16)|((std::uint32_t)(b)<<8)|(std::uint32_t)(a)) #define MAKE_ID4(a,b,c,d) (((std::uint32_t)(d)<<24)|((std::uint32_t)(c)<<16)|((std::uint32_t)(b)<<8)|(std::uint32_t)(a))
@@ -188,6 +189,10 @@ std::time_t last_modified( std::string const &Filename );
bool bool
erase_extension( std::string &Filename ); erase_extension( std::string &Filename );
// potentially erase leading slashes from provided file path
void
erase_leading_slashes( std::string &Filename );
// potentially replaces backward slashes in provided file path with unix-compatible forward slashes // potentially replaces backward slashes in provided file path with unix-compatible forward slashes
void void
replace_slashes( std::string &Filename ); replace_slashes( std::string &Filename );