gobj: Support registering texture pool filters from Python code

Closes #954
This commit is contained in:
Tohka 2020-07-25 14:26:39 +02:00 committed by rdb
parent e787929c85
commit c4af56620b
15 changed files with 821 additions and 57 deletions

View File

@ -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)

View File

@ -2,3 +2,5 @@
#include "geomVertexArrayData_ext.cxx"
#include "texture_ext.cxx"
#include "textureCollection_ext.cxx"
#include "texturePool_ext.cxx"
#include "pythonTexturePoolFilter.cxx"

View File

@ -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

View File

@ -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

View File

@ -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.
*/

View File

@ -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();

View File

@ -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"

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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";
}

View File

@ -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);

View File

@ -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;

View File

@ -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'