gobj: Support registering texture pool filters from Python code
Closes #954
This commit is contained in:
parent
e787929c85
commit
c4af56620b
|
|
@ -167,6 +167,10 @@ set(P3GOBJ_IGATEEXT
|
|||
texture_ext.h
|
||||
textureCollection_ext.cxx
|
||||
textureCollection_ext.h
|
||||
texturePool_ext.cxx
|
||||
texturePool_ext.h
|
||||
pythonTexturePoolFilter.cxx
|
||||
pythonTexturePoolFilter.h
|
||||
)
|
||||
|
||||
composite_sources(p3gobj P3GOBJ_SOURCES)
|
||||
|
|
|
|||
|
|
@ -2,3 +2,5 @@
|
|||
#include "geomVertexArrayData_ext.cxx"
|
||||
#include "texture_ext.cxx"
|
||||
#include "textureCollection_ext.cxx"
|
||||
#include "texturePool_ext.cxx"
|
||||
#include "pythonTexturePoolFilter.cxx"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,206 @@
|
|||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file pythonTexturePoolFilter.cxx
|
||||
* @author Derzsi Daniel
|
||||
* @date 2020-06-14
|
||||
*/
|
||||
|
||||
#include "pythonTexturePoolFilter.h"
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
|
||||
#include "pythonThread.h"
|
||||
#include "py_panda.h"
|
||||
|
||||
extern struct Dtool_PyTypedObject Dtool_Filename;
|
||||
extern struct Dtool_PyTypedObject Dtool_LoaderOptions;
|
||||
extern struct Dtool_PyTypedObject Dtool_Texture;
|
||||
|
||||
TypeHandle PythonTexturePoolFilter::_type_handle;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
PythonTexturePoolFilter::
|
||||
PythonTexturePoolFilter() {
|
||||
init_type();
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor.
|
||||
*/
|
||||
PythonTexturePoolFilter::
|
||||
~PythonTexturePoolFilter() {
|
||||
if (_pre_load_func != nullptr || _post_load_func != nullptr) {
|
||||
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
|
||||
PyGILState_STATE gstate;
|
||||
gstate = PyGILState_Ensure();
|
||||
#endif
|
||||
|
||||
Py_CLEAR(_entry_point);
|
||||
Py_CLEAR(_pre_load_func);
|
||||
Py_CLEAR(_post_load_func);
|
||||
|
||||
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
|
||||
PyGILState_Release(gstate);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the filter to use the given Python filter object.
|
||||
*
|
||||
* This method expects a Python object that implements either
|
||||
* the pre_load or the post_load method.
|
||||
*/
|
||||
bool PythonTexturePoolFilter::
|
||||
init(PyObject *tex_filter) {
|
||||
nassertr(tex_filter != nullptr, false);
|
||||
nassertr(_entry_point == nullptr, false);
|
||||
nassertr(_pre_load_func == nullptr, false);
|
||||
nassertr(_post_load_func == nullptr, false);
|
||||
|
||||
_pre_load_func = PyObject_GetAttrString(tex_filter, "pre_load");
|
||||
_post_load_func = PyObject_GetAttrString(tex_filter, "post_load");
|
||||
PyErr_Clear();
|
||||
|
||||
if (_pre_load_func == nullptr && _post_load_func == nullptr) {
|
||||
PyErr_Format(PyExc_TypeError,
|
||||
"texture pool filter plug-in %R does not define pre_load or post_load function",
|
||||
tex_filter);
|
||||
return false;
|
||||
}
|
||||
|
||||
_entry_point = tex_filter;
|
||||
|
||||
Py_INCREF(_entry_point);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called before each texture is loaded from disk, via the
|
||||
* TexturePool, for the first time. We delegate this functionality to
|
||||
* our Python module, loaded through the init function.
|
||||
*/
|
||||
PT(Texture) PythonTexturePoolFilter::
|
||||
pre_load(const Filename &orig_filename, const Filename &orig_alpha_filename,
|
||||
int primary_file_num_channels, int alpha_file_channel,
|
||||
bool read_mipmaps, const LoaderOptions &options) {
|
||||
if (_pre_load_func == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
|
||||
PyGILState_STATE gstate;
|
||||
gstate = PyGILState_Ensure();
|
||||
#endif
|
||||
|
||||
// Wrap the arguments.
|
||||
PyObject *args = Py_BuildValue("(OOiiNO)",
|
||||
DTool_CreatePyInstance((void *)&orig_filename, Dtool_Filename, false, true),
|
||||
DTool_CreatePyInstance((void *)&orig_alpha_filename, Dtool_Filename, false, true),
|
||||
primary_file_num_channels,
|
||||
alpha_file_channel,
|
||||
PyBool_FromLong(read_mipmaps),
|
||||
DTool_CreatePyInstance((void *)&options, Dtool_LoaderOptions, false, true)
|
||||
);
|
||||
|
||||
PyObject *result = PythonThread::call_python_func(_pre_load_func, args);
|
||||
Py_DECREF(args);
|
||||
|
||||
PT(Texture) tex;
|
||||
if (result != nullptr) {
|
||||
if (result != Py_None) {
|
||||
if (DtoolInstance_Check(result)) {
|
||||
tex = (Texture *)DtoolInstance_UPCAST(result, Dtool_Texture);
|
||||
}
|
||||
|
||||
if (tex == nullptr) {
|
||||
gobj_cat.error()
|
||||
<< "Preloading " << orig_filename.get_basename() << " failed as "
|
||||
<< "preloaded texture is not of Texture type.\n";
|
||||
}
|
||||
}
|
||||
|
||||
Py_DECREF(result);
|
||||
} else {
|
||||
PyObject *exc_type = _PyErr_OCCURRED();
|
||||
nassertr(exc_type != nullptr, nullptr);
|
||||
|
||||
gobj_cat.error()
|
||||
<< "Preloading " << orig_filename.get_basename() << " failed with "
|
||||
<< ((PyTypeObject *)exc_type)->tp_name << " exception.\n";
|
||||
PyErr_Clear();
|
||||
}
|
||||
|
||||
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
|
||||
PyGILState_Release(gstate);
|
||||
#endif
|
||||
|
||||
return tex;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called after each texture has been loaded from disk, via the
|
||||
* TexturePool, for the first time. We delegate this functionality to
|
||||
* our Python module, loaded through the init function.
|
||||
*/
|
||||
PT(Texture) PythonTexturePoolFilter::
|
||||
post_load(Texture *tex) {
|
||||
if (_post_load_func == nullptr) {
|
||||
return tex;
|
||||
}
|
||||
|
||||
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
|
||||
PyGILState_STATE gstate;
|
||||
gstate = PyGILState_Ensure();
|
||||
#endif
|
||||
|
||||
// Wrap the arguments.
|
||||
PyObject *args = PyTuple_Pack(1,
|
||||
DTool_CreatePyInstance(tex, Dtool_Texture, true, false)
|
||||
);
|
||||
PyObject *result = PythonThread::call_python_func(_post_load_func, args);
|
||||
Py_DECREF(args);
|
||||
|
||||
PT(Texture) result_tex;
|
||||
if (result != nullptr) {
|
||||
if (result != Py_None) {
|
||||
// Check if the call returned a texture pointer.
|
||||
if (DtoolInstance_Check(result)) {
|
||||
result_tex = (Texture *)DtoolInstance_UPCAST(result, Dtool_Texture);
|
||||
}
|
||||
|
||||
if (result_tex == nullptr) {
|
||||
// No valid texture was returned, use the original pointer.
|
||||
gobj_cat.error()
|
||||
<< "Postloading failed as the returned texture is not of the Texture type.\n";
|
||||
result_tex = tex;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
PyObject *exc_type = _PyErr_OCCURRED();
|
||||
nassertr(exc_type != nullptr, result_tex);
|
||||
|
||||
gobj_cat.error()
|
||||
<< "Postloading texture failed with "
|
||||
<< ((PyTypeObject *)exc_type)->tp_name << " exception.\n";
|
||||
PyErr_Clear();
|
||||
|
||||
result_tex = tex;
|
||||
}
|
||||
|
||||
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
|
||||
PyGILState_Release(gstate);
|
||||
#endif
|
||||
|
||||
return result_tex;
|
||||
}
|
||||
|
||||
#endif // HAVE_PYTHON
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file pythonTexturePoolFilter.h
|
||||
* @author Derzsi Daniel
|
||||
* @date 2020-06-14
|
||||
*/
|
||||
|
||||
#ifndef PYTHONTEXTUREPOOLFILTER_H
|
||||
#define PYTHONTEXTUREPOOLFILTER_H
|
||||
|
||||
#include "filename.h"
|
||||
#include "loaderOptions.h"
|
||||
#include "texture.h"
|
||||
#include "texturePool.h"
|
||||
#include "texturePoolFilter.h"
|
||||
#include "pandabase.h"
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
|
||||
/**
|
||||
* This defines a Python-based texture pool filter plug-in. Instances of
|
||||
* this must be constructed by inheritance and explicitly registered.
|
||||
*
|
||||
* The Python texture pool filter must implement either the pre_load
|
||||
* or the post_load function of a typical texture pool filter.
|
||||
*
|
||||
* @since 1.11.0
|
||||
*/
|
||||
class PythonTexturePoolFilter : public TexturePoolFilter {
|
||||
public:
|
||||
PythonTexturePoolFilter();
|
||||
~PythonTexturePoolFilter();
|
||||
|
||||
bool init(PyObject *tex_filter);
|
||||
|
||||
virtual PT(Texture) pre_load(const Filename &orig_filename,
|
||||
const Filename &orig_alpha_filename,
|
||||
int primary_file_num_channels,
|
||||
int alpha_file_channel,
|
||||
bool read_mipmaps,
|
||||
const LoaderOptions &options) override;
|
||||
virtual PT(Texture) post_load(Texture *tex) override;
|
||||
|
||||
private:
|
||||
PyObject *_pre_load_func = nullptr;
|
||||
PyObject *_post_load_func = nullptr;
|
||||
PyObject *_entry_point = nullptr;
|
||||
|
||||
friend class Extension<TexturePool>;
|
||||
|
||||
public:
|
||||
static TypeHandle get_class_type() {
|
||||
return _type_handle;
|
||||
}
|
||||
static void init_type() {
|
||||
TexturePoolFilter::init_type();
|
||||
register_type(_type_handle, "PythonTexturePoolFilter",
|
||||
TexturePoolFilter::get_class_type());
|
||||
}
|
||||
virtual TypeHandle get_type() const override {
|
||||
return get_class_type();
|
||||
}
|
||||
virtual TypeHandle force_init_type() override {
|
||||
init_type();
|
||||
return get_class_type();
|
||||
}
|
||||
|
||||
private:
|
||||
static TypeHandle _type_handle;
|
||||
};
|
||||
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#endif
|
||||
|
|
@ -276,6 +276,40 @@ make_texture(const std::string &extension) {
|
|||
return get_global_ptr()->ns_make_texture(extension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Records a TexturePoolFilter object that may operate on texture images as
|
||||
* they are loaded from disk.
|
||||
*/
|
||||
INLINE bool TexturePool::
|
||||
register_filter(TexturePoolFilter *tex_filter) {
|
||||
return get_global_ptr()->ns_register_filter(tex_filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops all TexturePoolFilter objects from operating on this pool.
|
||||
*/
|
||||
INLINE void TexturePool::
|
||||
clear_filters() {
|
||||
get_global_ptr()->ns_clear_filters();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given TexturePoolFilter object is
|
||||
* currently registered in the texture pool or not.
|
||||
*/
|
||||
INLINE bool TexturePool::
|
||||
is_filter_registered(TexturePoolFilter *tex_filter) {
|
||||
return get_global_ptr()->ns_is_filter_registered(tex_filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops a TexturePoolFilter object from operating on this pool.
|
||||
*/
|
||||
INLINE bool TexturePool::
|
||||
unregister_filter(TexturePoolFilter *tex_filter) {
|
||||
return get_global_ptr()->ns_unregister_filter(tex_filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines relative ordering between LookupKey instances.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@
|
|||
#include "mutexHolder.h"
|
||||
#include "dcast.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using std::istream;
|
||||
using std::ostream;
|
||||
using std::string;
|
||||
|
|
@ -66,13 +68,87 @@ register_texture_type(MakeTextureFunc *func, const string &extensions) {
|
|||
* Records a TexturePoolFilter object that may operate on texture images as
|
||||
* they are loaded from disk.
|
||||
*/
|
||||
void TexturePool::
|
||||
register_filter(TexturePoolFilter *filter) {
|
||||
MutexHolder holder(_lock);
|
||||
bool TexturePool::
|
||||
ns_register_filter(TexturePoolFilter *tex_filter) {
|
||||
MutexHolder holder(_filter_lock);
|
||||
|
||||
// Make sure we haven't already registered this filter.
|
||||
if (std::find(_filter_registry.begin(), _filter_registry.end(), tex_filter) != _filter_registry.end()) {
|
||||
gobj_cat->warning()
|
||||
<< "Attempted to register texture filter " << *tex_filter
|
||||
<< " more than once.\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
gobj_cat.info()
|
||||
<< "Registering Texture filter " << *filter << "\n";
|
||||
_filter_registry.push_back(filter);
|
||||
<< "Registering Texture filter " << *tex_filter << "\n";
|
||||
_filter_registry.push_back(tex_filter);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops all TexturePoolFilter objects from operating on this pool.
|
||||
*/
|
||||
void TexturePool::
|
||||
ns_clear_filters() {
|
||||
MutexHolder holder(_filter_lock);
|
||||
|
||||
_filter_registry.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given TexturePoolFilter object is
|
||||
* currently registered in the texture pool or not.
|
||||
*/
|
||||
bool TexturePool::
|
||||
ns_is_filter_registered(TexturePoolFilter *tex_filter) {
|
||||
MutexHolder holder(_filter_lock);
|
||||
|
||||
return std::find(_filter_registry.begin(), _filter_registry.end(), tex_filter) != _filter_registry.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops a TexturePoolFilter object from operating on this pool.
|
||||
*/
|
||||
bool TexturePool::
|
||||
ns_unregister_filter(TexturePoolFilter *tex_filter) {
|
||||
MutexHolder holder(_filter_lock);
|
||||
|
||||
FilterRegistry::iterator fi = std::find(_filter_registry.begin(), _filter_registry.end(), tex_filter);
|
||||
|
||||
if (fi == _filter_registry.end()) {
|
||||
gobj_cat.warning()
|
||||
<< "Attempted to unregister texture filter " << *tex_filter
|
||||
<< " which was not registered.\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
gobj_cat.info()
|
||||
<< "Unregistering Texture filter " << *tex_filter << "\n";
|
||||
_filter_registry.erase(fi);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of registered texture pool filters.
|
||||
*/
|
||||
size_t TexturePool::
|
||||
get_num_filters() const {
|
||||
return _filter_registry.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the nth texture pool filter registered.
|
||||
*/
|
||||
TexturePoolFilter *TexturePool::
|
||||
get_filter(size_t i) const {
|
||||
MutexHolder holder(_filter_lock);
|
||||
|
||||
if (i >= 0 && i < _filter_registry.size()) {
|
||||
return _filter_registry[i];
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -223,10 +299,13 @@ ns_load_texture(const Filename &orig_filename, int primary_file_num_channels,
|
|||
PT(Texture) tex;
|
||||
PT(BamCacheRecord) record;
|
||||
bool store_record = false;
|
||||
bool use_filters = (options.get_texture_flags() & LoaderOptions::TF_no_filters) == 0;
|
||||
|
||||
// Can one of our texture filters supply the texture?
|
||||
tex = pre_load(orig_filename, Filename(), primary_file_num_channels, 0,
|
||||
read_mipmaps, options);
|
||||
if (use_filters) {
|
||||
tex = pre_load(orig_filename, Filename(), primary_file_num_channels, 0,
|
||||
read_mipmaps, options);
|
||||
}
|
||||
|
||||
BamCache *cache = BamCache::get_global_ptr();
|
||||
bool compressed_cache_record = false;
|
||||
|
|
@ -346,7 +425,9 @@ ns_load_texture(const Filename &orig_filename, int primary_file_num_channels,
|
|||
nassertr(!tex->get_fullpath().empty(), tex);
|
||||
|
||||
// Finally, apply any post-loading texture filters.
|
||||
tex = post_load(tex);
|
||||
if (use_filters) {
|
||||
tex = post_load(tex);
|
||||
}
|
||||
|
||||
return tex;
|
||||
}
|
||||
|
|
@ -386,10 +467,13 @@ ns_load_texture(const Filename &orig_filename,
|
|||
PT(Texture) tex;
|
||||
PT(BamCacheRecord) record;
|
||||
bool store_record = false;
|
||||
bool use_filters = (options.get_texture_flags() & LoaderOptions::TF_no_filters) == 0;
|
||||
|
||||
// Can one of our texture filters supply the texture?
|
||||
tex = pre_load(orig_filename, orig_alpha_filename, primary_file_num_channels,
|
||||
alpha_file_channel, read_mipmaps, options);
|
||||
if (use_filters) {
|
||||
tex = pre_load(orig_filename, orig_alpha_filename, primary_file_num_channels,
|
||||
alpha_file_channel, read_mipmaps, options);
|
||||
}
|
||||
|
||||
BamCache *cache = BamCache::get_global_ptr();
|
||||
bool compressed_cache_record = false;
|
||||
|
|
@ -476,7 +560,9 @@ ns_load_texture(const Filename &orig_filename,
|
|||
nassertr(!tex->get_fullpath().empty(), tex);
|
||||
|
||||
// Finally, apply any post-loading texture filters.
|
||||
tex = post_load(tex);
|
||||
if (use_filters) {
|
||||
tex = post_load(tex);
|
||||
}
|
||||
|
||||
return tex;
|
||||
}
|
||||
|
|
@ -1212,8 +1298,7 @@ pre_load(const Filename &orig_filename, const Filename &orig_alpha_filename,
|
|||
int primary_file_num_channels, int alpha_file_channel,
|
||||
bool read_mipmaps, const LoaderOptions &options) {
|
||||
PT(Texture) tex;
|
||||
|
||||
MutexHolder holder(_lock);
|
||||
MutexHolder holder(_filter_lock);
|
||||
|
||||
FilterRegistry::iterator fi;
|
||||
for (fi = _filter_registry.begin();
|
||||
|
|
@ -1237,7 +1322,7 @@ PT(Texture) TexturePool::
|
|||
post_load(Texture *tex) {
|
||||
PT(Texture) result = tex;
|
||||
|
||||
MutexHolder holder(_lock);
|
||||
MutexHolder holder(_filter_lock);
|
||||
|
||||
FilterRegistry::iterator fi;
|
||||
for (fi = _filter_registry.begin();
|
||||
|
|
|
|||
|
|
@ -80,18 +80,31 @@ PUBLISHED:
|
|||
INLINE static const Filename &get_fake_texture_image();
|
||||
INLINE static PT(Texture) make_texture(const std::string &extension);
|
||||
|
||||
INLINE static bool register_filter(TexturePoolFilter *tex_filter);
|
||||
INLINE static bool unregister_filter(TexturePoolFilter *tex_filter);
|
||||
INLINE static void clear_filters();
|
||||
|
||||
INLINE static bool is_filter_registered(TexturePoolFilter *tex_filter);
|
||||
|
||||
size_t get_num_filters() const;
|
||||
TexturePoolFilter *get_filter(size_t i) const;
|
||||
MAKE_SEQ_PROPERTY(filters, get_num_filters, get_filter);
|
||||
|
||||
EXTENSION(bool register_filter(PyObject *tex_filter));
|
||||
EXTENSION(bool unregister_filter(PyObject *tex_filter));
|
||||
EXTENSION(bool is_filter_registered(PyObject *tex_filter));
|
||||
|
||||
static TexturePool *get_global_ptr();
|
||||
|
||||
static void write(std::ostream &out);
|
||||
|
||||
public:
|
||||
typedef Texture::MakeTextureFunc MakeTextureFunc;
|
||||
void register_texture_type(MakeTextureFunc *func, const std::string &extensions);
|
||||
void register_filter(TexturePoolFilter *filter);
|
||||
|
||||
MakeTextureFunc *get_texture_type(const std::string &extension) const;
|
||||
void write_texture_types(std::ostream &out, int indent_level) const;
|
||||
|
||||
static TexturePool *get_global_ptr();
|
||||
|
||||
private:
|
||||
TexturePool();
|
||||
|
||||
|
|
@ -144,11 +157,19 @@ private:
|
|||
bool read_mipmaps, const LoaderOptions &options);
|
||||
PT(Texture) post_load(Texture *tex);
|
||||
|
||||
bool ns_register_filter(TexturePoolFilter *tex_filter);
|
||||
bool ns_unregister_filter(TexturePoolFilter *tex_filter);
|
||||
void ns_clear_filters();
|
||||
|
||||
bool ns_is_filter_registered(TexturePoolFilter *tex_filter);
|
||||
|
||||
void load_filters();
|
||||
|
||||
static TexturePool *_global_ptr;
|
||||
|
||||
Mutex _lock;
|
||||
Mutex _filter_lock;
|
||||
|
||||
struct LookupKey {
|
||||
Filename _fullpath;
|
||||
Filename _alpha_fullpath;
|
||||
|
|
@ -173,6 +194,8 @@ private:
|
|||
|
||||
typedef pvector<TexturePoolFilter *> FilterRegistry;
|
||||
FilterRegistry _filter_registry;
|
||||
|
||||
friend class Extension<TexturePool>;
|
||||
};
|
||||
|
||||
#include "texturePool.I"
|
||||
|
|
|
|||
|
|
@ -15,13 +15,6 @@
|
|||
|
||||
TypeHandle TexturePoolFilter::_type_handle;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
TexturePoolFilter::
|
||||
~TexturePoolFilter() {
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called before each texture is loaded from disk, via the
|
||||
* TexturePool, for the first time. If this method returns NULL, then a new
|
||||
|
|
|
|||
|
|
@ -40,9 +40,11 @@ class LoaderOptions;
|
|||
* problem should call tex->set_keep_ram_image(true).
|
||||
*/
|
||||
class EXPCL_PANDA_GOBJ TexturePoolFilter : public TypedObject {
|
||||
public:
|
||||
virtual ~TexturePoolFilter();
|
||||
PUBLISHED:
|
||||
TexturePoolFilter() = default;
|
||||
virtual ~TexturePoolFilter() = default;
|
||||
|
||||
public:
|
||||
virtual PT(Texture) pre_load(const Filename &orig_filename,
|
||||
const Filename &orig_alpha_filename,
|
||||
int primary_file_num_channels,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,109 @@
|
|||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file texturePool_ext.cxx
|
||||
* @author Derzsi Daniel
|
||||
* @date 2020-06-24
|
||||
*/
|
||||
|
||||
#include "texturePool_ext.h"
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
|
||||
#include "pythonTexturePoolFilter.h"
|
||||
#include "texturePoolFilter.h"
|
||||
|
||||
/**
|
||||
* Registers a texture pool filter that is implemented in Python.
|
||||
*/
|
||||
bool Extension<TexturePool>::
|
||||
register_filter(PyObject *tex_filter) {
|
||||
if (find_existing_filter(tex_filter) != nullptr) {
|
||||
PyObject *repr = PyObject_Repr(tex_filter);
|
||||
|
||||
gobj_cat->warning()
|
||||
<< "Attempted to register Python texture filter " << PyUnicode_AsUTF8(repr)
|
||||
<< " more than once.\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
PythonTexturePoolFilter *filter = new PythonTexturePoolFilter();
|
||||
|
||||
if (filter->init(tex_filter)) {
|
||||
return _this->ns_register_filter(filter);
|
||||
}
|
||||
|
||||
delete filter;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the given Python texture pool filter is registered, unregisters it.
|
||||
*/
|
||||
bool Extension<TexturePool>::
|
||||
unregister_filter(PyObject *tex_filter) {
|
||||
// Keep looping until we've removed all instances of it.
|
||||
bool unregistered = false;
|
||||
|
||||
while (true) {
|
||||
TexturePoolFilter *filter = find_existing_filter(tex_filter);
|
||||
|
||||
if (filter == nullptr) {
|
||||
// Last filter has been removed.
|
||||
break;
|
||||
}
|
||||
|
||||
unregistered = _this->ns_unregister_filter(filter);
|
||||
}
|
||||
|
||||
if (!unregistered) {
|
||||
PyObject *repr = PyObject_Repr(tex_filter);
|
||||
|
||||
gobj_cat->warning()
|
||||
<< "Attempted to unregister Python texture filter " << PyUnicode_AsUTF8(repr)
|
||||
<< " which was not registered.\n";
|
||||
}
|
||||
|
||||
return unregistered;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the given Python texture pool filter is registered, returns true.
|
||||
*/
|
||||
bool Extension<TexturePool>::
|
||||
is_filter_registered(PyObject *tex_filter) {
|
||||
return find_existing_filter(tex_filter) != nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks for a texture pool filter that is using the
|
||||
* given Python implementation of the texture filter.
|
||||
*
|
||||
* Returns nullptr if none has been found.
|
||||
*/
|
||||
TexturePoolFilter *Extension<TexturePool>::
|
||||
find_existing_filter(PyObject *tex_filter) {
|
||||
size_t num_filters = _this->get_num_filters();
|
||||
|
||||
for (size_t i = 0; i < num_filters; ++i) {
|
||||
TexturePoolFilter *filter = _this->get_filter(i);
|
||||
|
||||
if (filter != nullptr && filter->is_of_type(PythonTexturePoolFilter::get_class_type())) {
|
||||
PythonTexturePoolFilter *py_filter = (PythonTexturePoolFilter *)filter;
|
||||
|
||||
if (py_filter->_entry_point == tex_filter) {
|
||||
return filter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No filter found.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file texturePool_ext.h
|
||||
* @author Derzsi Daniel
|
||||
* @date 2020-06-24
|
||||
*/
|
||||
|
||||
#ifndef TEXTUREPOOL_EXT_H
|
||||
#define TEXTUREPOOL_EXT_H
|
||||
|
||||
#include "pandabase.h"
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
|
||||
#include "extension.h"
|
||||
#include "texturePoolFilter.h"
|
||||
#include "texturePool.h"
|
||||
#include "py_panda.h"
|
||||
|
||||
/**
|
||||
* This class defines the extension methods for TexturePool, which are called
|
||||
* instead of any C++ methods with the same prototype.
|
||||
*
|
||||
* @since 1.11.0
|
||||
*/
|
||||
template<>
|
||||
class Extension<TexturePool> : public ExtensionBase<TexturePool> {
|
||||
public:
|
||||
bool register_filter(PyObject *tex_filter);
|
||||
bool unregister_filter(PyObject *tex_filter);
|
||||
bool is_filter_registered(PyObject *tex_filter);
|
||||
|
||||
private:
|
||||
TexturePoolFilter *find_existing_filter(PyObject *tex_filter);
|
||||
};
|
||||
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#endif
|
||||
|
|
@ -87,6 +87,7 @@ output(std::ostream &out) const {
|
|||
write_texture_flag(out, sep, "TF_allow_1d", TF_allow_1d);
|
||||
write_texture_flag(out, sep, "TF_generate_mipmaps", TF_generate_mipmaps);
|
||||
write_texture_flag(out, sep, "TF_allow_compression", TF_allow_compression);
|
||||
write_texture_flag(out, sep, "TF_no_filters", TF_no_filters);
|
||||
if (sep.empty()) {
|
||||
out << "0";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ PUBLISHED:
|
|||
TF_integer = 0x0080, // Load as an integer (RGB) texture
|
||||
TF_float = 0x0100, // Load as a floating-point (depth) texture
|
||||
TF_allow_compression = 0x0200, // Consider compressing RAM image
|
||||
TF_no_filters = 0x0400, // disallow using texture pool filters
|
||||
};
|
||||
|
||||
LoaderOptions(int flags = LF_search | LF_report_errors);
|
||||
|
|
|
|||
|
|
@ -30,8 +30,7 @@ NotifyCategoryDef(txafile, "");
|
|||
Configure(config_txaFileFilter);
|
||||
ConfigureFn(config_txaFileFilter) {
|
||||
TxaFileFilter::init_type();
|
||||
TexturePool *pool = TexturePool::get_global_ptr();
|
||||
pool->register_filter(new TxaFileFilter);
|
||||
TexturePool::register_filter(new TxaFileFilter);
|
||||
}
|
||||
|
||||
TypeHandle TxaFileFilter::_type_handle;
|
||||
|
|
|
|||
|
|
@ -2,14 +2,6 @@ from panda3d import core
|
|||
import pytest
|
||||
import tempfile
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def pool():
|
||||
"This fixture ensures the pool is properly emptied"
|
||||
pool = core.TexturePool
|
||||
pool.release_all_textures()
|
||||
yield pool
|
||||
pool.release_all_textures()
|
||||
|
||||
|
||||
def write_image(filename, channels):
|
||||
img = core.PNMImage(1, 1, channels)
|
||||
|
|
@ -17,40 +9,108 @@ def write_image(filename, channels):
|
|||
assert img.write(filename)
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def image_rgb_path():
|
||||
"Generates an RGB image."
|
||||
|
||||
file = tempfile.NamedTemporaryFile(suffix='-rgb.png')
|
||||
def yield_image(suffix, channels):
|
||||
file = tempfile.NamedTemporaryFile(suffix=suffix)
|
||||
path = core.Filename.from_os_specific(file.name)
|
||||
path.make_true_case()
|
||||
write_image(path, 3)
|
||||
write_image(path, channels)
|
||||
yield path
|
||||
file.close()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def image_rgba_path():
|
||||
"Generates an RGBA image."
|
||||
def register_filter(pool, tex_filter):
|
||||
assert pool.get_num_filters() == 0
|
||||
assert pool.register_filter(tex_filter)
|
||||
assert pool.get_num_filters() == 1
|
||||
|
||||
file = tempfile.NamedTemporaryFile(suffix='-rgba.png')
|
||||
path = core.Filename.from_os_specific(file.name)
|
||||
path.make_true_case()
|
||||
write_image(path, 4)
|
||||
yield path
|
||||
file.close()
|
||||
|
||||
def yield_registered_filter(filter_type):
|
||||
tex_filter = filter_type()
|
||||
yield tex_filter
|
||||
|
||||
p = core.TexturePool.get_global_ptr()
|
||||
|
||||
if p.is_filter_registered(tex_filter):
|
||||
p.unregister_filter(tex_filter)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def pool():
|
||||
"This fixture ensures the pool is properly emptied"
|
||||
pool = core.TexturePool.get_global_ptr()
|
||||
pool.release_all_textures()
|
||||
yield pool
|
||||
pool.release_all_textures()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def image_gray_path():
|
||||
"Generates a grayscale image."
|
||||
yield from yield_image('-gray.png', channels=1)
|
||||
|
||||
file = tempfile.NamedTemporaryFile(suffix='-gray.png')
|
||||
path = core.Filename.from_os_specific(file.name)
|
||||
path.make_true_case()
|
||||
write_image(path, 1)
|
||||
yield path
|
||||
file.close()
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def image_rgb_path():
|
||||
"Generates an RGB image."
|
||||
yield from yield_image('-rgb.png', channels=3)
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def image_rgba_path():
|
||||
"Generates an RGBA image."
|
||||
yield from yield_image('-rgba.png', channels=4)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def pre_filter():
|
||||
"Creates a texture pool preload filter."
|
||||
class PreLoadTextureFilter(object):
|
||||
|
||||
def pre_load(self, orig_filename, orig_alpha_filename,
|
||||
primary_file_num_channels, alpha_file_channel,
|
||||
read_mipmaps, options):
|
||||
return core.Texture('preloaded')
|
||||
|
||||
yield from yield_registered_filter(PreLoadTextureFilter)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def post_filter():
|
||||
"Creates a texture pool postload filter."
|
||||
class PostLoadTextureFilter(object):
|
||||
|
||||
def post_load(self, tex):
|
||||
tex.set_name('postloaded')
|
||||
return tex
|
||||
|
||||
yield from yield_registered_filter(PostLoadTextureFilter)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def mix_filter():
|
||||
"Creates a texture pool mix filter."
|
||||
class MixTextureFilter(object):
|
||||
|
||||
def pre_load(self, orig_filename, orig_alpha_filename,
|
||||
primary_file_num_channels, alpha_file_channel,
|
||||
read_mipmaps, options):
|
||||
return core.Texture('preloaded')
|
||||
|
||||
def post_load(self, tex):
|
||||
tex.set_name(tex.get_name() + '-postloaded')
|
||||
return tex
|
||||
|
||||
yield from yield_registered_filter(MixTextureFilter)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def invalid_filter():
|
||||
"Creates an invalid texture filter."
|
||||
class InvalidTextureFilter(object):
|
||||
pass
|
||||
|
||||
tex_filter = InvalidTextureFilter()
|
||||
yield tex_filter
|
||||
|
||||
|
||||
def test_load_texture_rgba(pool, image_rgba_path):
|
||||
|
|
@ -200,3 +260,123 @@ def test_reload_texture_without_alpha(pool, image_rgb_path, image_gray_path):
|
|||
|
||||
tex = pool.load_texture(image_rgb_path)
|
||||
assert tex.num_components == 3
|
||||
|
||||
|
||||
def test_empty_texture_filters(pool):
|
||||
assert pool.get_num_filters() == 0
|
||||
|
||||
|
||||
def test_register_pre_texture_filter(pool, pre_filter):
|
||||
register_filter(pool, pre_filter)
|
||||
|
||||
|
||||
def test_register_post_texture_filter(pool, post_filter):
|
||||
register_filter(pool, post_filter)
|
||||
|
||||
|
||||
def test_register_mix_texture_filter(pool, mix_filter):
|
||||
register_filter(pool, mix_filter)
|
||||
|
||||
|
||||
def test_register_invalid_texture_filter(pool, invalid_filter):
|
||||
assert pool.get_num_filters() == 0
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
pool.register_filter(invalid_filter)
|
||||
|
||||
assert pool.get_num_filters() == 0
|
||||
|
||||
|
||||
def test_register_null_texture_filter(pool):
|
||||
assert pool.get_num_filters() == 0
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
pool.register_filter(None)
|
||||
|
||||
assert pool.get_num_filters() == 0
|
||||
|
||||
|
||||
def test_register_all_texture_filters(pool, pre_filter, post_filter, mix_filter):
|
||||
assert pool.get_num_filters() == 0
|
||||
assert pool.register_filter(pre_filter)
|
||||
assert pool.register_filter(post_filter)
|
||||
assert pool.register_filter(mix_filter)
|
||||
assert pool.get_num_filters() == 3
|
||||
|
||||
|
||||
def test_unregister_texture_filter(pool, mix_filter):
|
||||
register_filter(pool, mix_filter)
|
||||
assert pool.unregister_filter(mix_filter)
|
||||
assert pool.get_num_filters() == 0
|
||||
|
||||
|
||||
def test_clear_texture_filters(pool, pre_filter, post_filter):
|
||||
assert pool.get_num_filters() == 0
|
||||
assert pool.register_filter(pre_filter)
|
||||
assert pool.register_filter(post_filter)
|
||||
assert pool.get_num_filters() == 2
|
||||
|
||||
pool.clear_filters()
|
||||
assert pool.get_num_filters() == 0
|
||||
|
||||
|
||||
def test_double_register_texture_filter(pool, mix_filter):
|
||||
register_filter(pool, mix_filter)
|
||||
assert not pool.register_filter(mix_filter)
|
||||
assert pool.get_num_filters() == 1
|
||||
|
||||
|
||||
def test_double_unregister_texture_filter(pool, mix_filter):
|
||||
register_filter(pool, mix_filter)
|
||||
assert pool.unregister_filter(mix_filter)
|
||||
assert not pool.unregister_filter(mix_filter)
|
||||
assert pool.get_num_filters() == 0
|
||||
|
||||
|
||||
def test_is_texture_filter_registered(pool, pre_filter, mix_filter):
|
||||
assert not pool.is_filter_registered(mix_filter)
|
||||
assert pool.register_filter(mix_filter)
|
||||
assert pool.is_filter_registered(mix_filter)
|
||||
assert not pool.is_filter_registered(pre_filter)
|
||||
|
||||
|
||||
def test_get_texture_filter(pool, pre_filter):
|
||||
assert not pool.get_filter(0)
|
||||
|
||||
assert pool.register_filter(pre_filter)
|
||||
tex_filter = pool.get_filter(0)
|
||||
assert isinstance(tex_filter, core.TexturePoolFilter)
|
||||
|
||||
assert not pool.get_filter(1)
|
||||
|
||||
|
||||
def test_texture_pre_filter(pool, pre_filter):
|
||||
register_filter(pool, pre_filter)
|
||||
|
||||
texture = pool.load_texture('nonexistent')
|
||||
assert isinstance(texture, core.Texture)
|
||||
assert texture.get_name() == 'preloaded'
|
||||
|
||||
|
||||
def test_texture_post_filter(pool, post_filter, image_rgb_path):
|
||||
register_filter(pool, post_filter)
|
||||
|
||||
texture = pool.load_texture(image_rgb_path, 3)
|
||||
assert isinstance(texture, core.Texture)
|
||||
assert texture.get_name() == 'postloaded'
|
||||
|
||||
|
||||
def test_texture_mix_filter(pool, mix_filter):
|
||||
register_filter(pool, mix_filter)
|
||||
|
||||
texture = pool.load_texture('nonexistent')
|
||||
assert isinstance(texture, core.Texture)
|
||||
assert texture.get_name() == 'preloaded-postloaded'
|
||||
|
||||
|
||||
def test_no_texture_filter_option(pool, pre_filter, image_rgb_path):
|
||||
register_filter(pool, pre_filter)
|
||||
|
||||
texture = pool.load_texture(image_rgb_path, 3, False, core.LoaderOptions(0, core.LoaderOptions.TF_no_filters))
|
||||
assert isinstance(texture, core.Texture)
|
||||
assert texture.get_name() != 'preloaded'
|
||||
|
|
|
|||
Loading…
Reference in New Issue