From 3c9643ce7462ab1fbab139ac463974558a2dad44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sun, 15 Sep 2019 11:24:08 +0200 Subject: [PATCH] python: expose textures and related APIs. --- doc/python/magnum.gl.rst | 30 +++ src/python/magnum/__init__.py | 4 +- src/python/magnum/bootstrap.h | 15 ++ src/python/magnum/gl.cpp | 319 +++++++++++++++++++++++++++ src/python/magnum/magnum.cpp | 29 +-- src/python/magnum/test/test_gl_gl.py | 105 +++++++++ 6 files changed, 487 insertions(+), 15 deletions(-) diff --git a/doc/python/magnum.gl.rst b/doc/python/magnum.gl.rst index b4cc81d..08db132 100644 --- a/doc/python/magnum.gl.rst +++ b/doc/python/magnum.gl.rst @@ -57,3 +57,33 @@ this property can be set using either `magnum.MeshPrimitive` or `gl.MeshPrimitive`, similarly to how the overloaded :dox:`GL::Mesh::setPrimitive()` works. + +.. py:property:: magnum.gl.Texture1D.minification_filter + + See `Texture2D.minification_filter` for more information. + +.. py:property:: magnum.gl.Texture2D.minification_filter + + This property accepts either a tuple of `magnum.SamplerFilter` / + `gl.SamplerFilter` and `magnum.SamplerMipmap` / `gl.SamplerMipmap` values + or just `magnum.SamplerFilter` / `gl.SamplerFilter` alone in which case + `gl.SamplerMipmap.BASE` will be used implicitly; similarly to how the + overloaded :dox:`GL::Texture::setMinificationFilter()` works. + +.. py:property:: magnum.gl.Texture3D.minification_filter + + See `Texture2D.minification_filter` for more information. + +.. py:property:: magnum.gl.Texture1D.magnification_filter + + See `Texture2D.magnification_filter` for more information. + +.. py:property:: magnum.gl.Texture2D.magnification_filter + + This property accepts either `magnum.SamplerFilter` or `gl.SamplerFilter`, + similarly to how the overloaded :dox:`GL::Texture::setMagnificationFilter()` + works. + +.. py:property:: magnum.gl.Texture3D.magnification_filter + + See `Texture2D.magnification_filter` for more information. diff --git a/src/python/magnum/__init__.py b/src/python/magnum/__init__.py index 92377a3..1cba4f9 100644 --- a/src/python/magnum/__init__.py +++ b/src/python/magnum/__init__.py @@ -90,7 +90,9 @@ __all__ = [ 'PixelFormat', 'PixelStorage', 'ImageView1D', 'ImageView2D', 'ImageView3D', - 'MutableImageView1D', 'MutableImageView2D', 'MutableImageView3D' + 'MutableImageView1D', 'MutableImageView2D', 'MutableImageView3D', + + 'SamplerFilter', 'SamplerMipmap', 'SamplerWrapping' # TARGET_*, BUILD_* are omitted as `from magnum import *` would pull them # to globals and this would likely cause conflicts (corrade also defines diff --git a/src/python/magnum/bootstrap.h b/src/python/magnum/bootstrap.h index cd642c9..5dffb88 100644 --- a/src/python/magnum/bootstrap.h +++ b/src/python/magnum/bootstrap.h @@ -26,6 +26,7 @@ */ #include +#include namespace pybind11 { class module; } namespace Magnum {} @@ -35,6 +36,20 @@ namespace magnum { using namespace Magnum; namespace py = pybind11; +template struct PyDimensionTraits; +template struct PyDimensionTraits<1, T> { + typedef T VectorType; + static VectorType from(const Math::Vector<1, T>& vec) { return vec[0]; } +}; +template struct PyDimensionTraits<2, T> { + typedef Math::Vector2 VectorType; + static VectorType from(const Math::Vector<2, T>& vec) { return vec; } +}; +template struct PyDimensionTraits<3, T> { + typedef Math::Vector3 VectorType; + static VectorType from(const Math::Vector<3, T>& vec) { return vec; } +}; + void math(py::module& root, py::module& m); void mathVectorFloat(py::module& root, py::module& m); void mathVectorIntegral(py::module& root, py::module& m); diff --git a/src/python/magnum/gl.cpp b/src/python/magnum/gl.cpp index 8ec8f90..506f938 100644 --- a/src/python/magnum/gl.cpp +++ b/src/python/magnum/gl.cpp @@ -38,6 +38,8 @@ #include #include #include +#include +#include #include #include @@ -96,6 +98,144 @@ template void setUniform(GL::AbstractShaderProgram& self, Int location, static_cast(self).setUniform(location, value); } +template void texture(py::class_, GL::AbstractTexture>& c) { + c + /** @todo limits */ + .def(py::init(), "Constructor") + /** @todo bindImage(), bindImageLayered */ + #ifndef MAGNUM_TARGET_GLES2 + .def_property("base_level", nullptr, &GL::Texture::setBaseLevel, "Base mip level") + #endif + #if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2)) + .def_property("max_level", nullptr, &GL::Texture::setMaxLevel, "Max mip level") + #endif + .def_property("minification_filter", nullptr, + [](GL::Texture& self, py::object value) { + if(py::isinstance(value)) + self.setMinificationFilter(py::cast(value)); + else if(py::isinstance(value)) + self.setMinificationFilter(py::cast(value)); + else if(py::isinstance(value) && py::cast(value).size() == 2) { + auto tuple = py::cast(value); + + GL::SamplerFilter filter; + if(py::isinstance(tuple[0])) + filter = GL::samplerFilter(py::cast(tuple[0])); + else if(py::isinstance(tuple[0])) + filter = py::cast(tuple[0]); + else { + PyErr_Format(PyExc_TypeError, "expected a tuple with SamplerFilter or gl.SamplerFilter as the first element, got %A", value.get_type().ptr()); + throw py::error_already_set{}; + } + + GL::SamplerMipmap mipmap; + if(py::isinstance(tuple[1])) + mipmap = GL::samplerMipmap(py::cast(tuple[1])); + else if(py::isinstance(tuple[1])) + mipmap = py::cast(tuple[1]); + else { + PyErr_Format(PyExc_TypeError, "expected a tuple with SamplerMipmap or gl.SamplerMipmap as the second element, got %A", value.get_type().ptr()); + throw py::error_already_set{}; + } + + self.setMinificationFilter(filter, mipmap); + } else { + PyErr_Format(PyExc_TypeError, "expected SamplerFilter, gl.SamplerFilter or a two-element tuple, got %A", value.get_type().ptr()); + throw py::error_already_set{}; + } + }, "Minification filter") + .def_property("magnification_filter", nullptr, + [](GL::Texture& self, py::object filter) { + if(py::isinstance(filter)) + self.setMagnificationFilter(py::cast(filter)); + else if(py::isinstance(filter)) + self.setMagnificationFilter(py::cast(filter)); + else { + PyErr_Format(PyExc_TypeError, "expected SamplerFilter or gl.SamplerFilter, got %A", filter.get_type().ptr()); + throw py::error_already_set{}; + } + }, "Magnification filter") + #ifndef MAGNUM_TARGET_GLES2 + .def_property("min_lod", nullptr, &GL::Texture::setMinLod, "Minimum level-of-detail") + .def_property("max_lod", nullptr, &GL::Texture::setMaxLod, "Maximum level-of-detail") + #endif + #ifndef MAGNUM_TARGET_GLES + .def_property("lod_bias", nullptr, &GL::Texture::setLodBias, "Level-of-detail bias") + #endif + .def_property("wrapping", nullptr, + [](GL::Texture& self, py::object wrapping) { + /** @todo accept two/three different values as well */ + if(py::isinstance(wrapping)) + self.setWrapping(py::cast(wrapping)); + else if(py::isinstance(wrapping)) + self.setWrapping(py::cast(wrapping)); + else { + PyErr_Format(PyExc_TypeError, "expected SamplerWrapping or gl.SamplerWrapping, got %A", wrapping.get_type().ptr()); + throw py::error_already_set{}; + } + }, "Wrapping") + #ifndef MAGNUM_TARGET_WEBGL + .def_property("border_color", nullptr, + #ifdef MAGNUM_TARGET_GLES2 + &GL::Texture::setBorderColor, + #else + [](GL::Texture& self, py::object color) { + if(py::isinstance(color)) + self.setBorderColor(py::cast(color)); + else if(py::isinstance(color)) + self.setBorderColor(py::cast(color)); + else if(py::isinstance(color)) + self.setBorderColor(py::cast(color)); + else if(py::isinstance(color)) + self.setBorderColor(py::cast(color)); + else { + PyErr_Format(PyExc_TypeError, "expected Color3, Color4, Vector4ui or Vector4i, got %A", color.get_type().ptr()); + throw py::error_already_set{}; + } + }, + #endif + "Border color") + #endif + .def_property("max_anisotropy", nullptr, &GL::Texture::setMaxAnisotropy, "Max anisotropy") + #ifndef MAGNUM_TARGET_WEBGL + .def_property("srgb_decode", nullptr, &GL::Texture::setSrgbDecode, "sRGB decoding") + #endif + /** @todo component swizzle (it's compile-time on C++ side, ugh) */ + #if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2)) + .def_property("compare_mode", nullptr, &GL::Texture::setCompareMode, "Depth texture comparison mode") + .def_property("compare_function", nullptr, &GL::Texture::setCompareFunction, "Depth texture comparison function") + #endif + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + .def_property("depth_stencil_mode", nullptr, &GL::Texture::setDepthStencilMode, "Depth/stencil texture mode") + #endif + /* Using a lambda to avoid method chaining leaking to Python */ + .def("set_storage", [](GL::Texture& self, Int levels, GL::TextureFormat internalFormat, const typename PyDimensionTraits::VectorType& size) { + self.setStorage(levels, internalFormat, size); + }, "Set storage", py::arg("levels"), py::arg("internal_format"), py::arg("size")) + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + .def("image_size", [](GL::Texture& self, Int level) { + return PyDimensionTraits::from(self.imageSize(level)); + }, "Image size in given mip level", py::arg("level")) + #endif + /** @todo (compressed/buffer) (sub)image queries */ + /* Using a lambda to avoid method chaining leaking to Python */ + .def("set_image", [](GL::Texture& self, Int level, GL::TextureFormat internalFormat, const BasicImageView& image) { + self.setImage(level, internalFormat, image); + }, "Set image data", py::arg("level"), py::arg("internal_format"), py::arg("image")) + /** @todo compressed/buffer setImage() */ + .def("set_sub_image", [](GL::Texture& self, Int level, const typename PyDimensionTraits::VectorType& offset, const BasicImageView& image) { + self.setSubImage(level, offset, image); + }, "Set image subdata", py::arg("level"), py::arg("offset"), py::arg("image")) + /** @todo compressed/buffer setSubImage() */ + .def("generate_mipmap", [](GL::Texture& self) { + self.generateMipmap(); + }, "Generate mipmap") + .def("invalidate_image", &GL::Texture::invalidateImage, "Invalidate texture image", py::arg("level")) + .def("invalidate_sub_image", [](GL::Texture& self, Int level, const typename PyDimensionTraits::VectorType& offset, const typename PyDimensionTraits::VectorType& size) { + self.invalidateSubImage(level, offset, size); + }, "Invalidate texture subimage", py::arg("level"), py::arg("offset"), py::arg("size")); +} + } void gl(py::module& m) { @@ -714,6 +854,185 @@ void gl(py::module& m) { return GL::Renderer::error(); }, "Error status"); } + + /* Textures */ + + /** @todo enum constructors converting generic value to GL-specific? */ + py::enum_{m, "SamplerFilter", "Texture sampler filtering"} + .value("NEAREST", GL::SamplerFilter::Nearest) + .value("LINEAR", GL::SamplerFilter::Linear); + py::enum_{m, "SamplerMipmap", "Texture sampler mip level selection"} + .value("BASE", GL::SamplerMipmap::Base) + .value("NEAREST", GL::SamplerMipmap::Nearest) + .value("LINEAR", GL::SamplerMipmap::Linear); + py::enum_{m, "SamplerWrapping", "Texture sampler wrapping"} + .value("REPEAT", GL::SamplerWrapping::Repeat) + .value("MIRRORED_REPEAT", GL::SamplerWrapping::MirroredRepeat) + .value("CLAMP_TO_EDGE", GL::SamplerWrapping::ClampToEdge) + #ifndef MAGNUM_TARGET_WEBGL + .value("CLAMP_TO_BORDER", GL::SamplerWrapping::ClampToBorder) + #endif + #ifndef MAGNUM_TARGET_GLES + .value("MIRROR_CLAMP_TO_EDGE", GL::SamplerWrapping::MirrorClampToEdge) + #endif + ; + #if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2)) + py::enum_{m, "SamplerCompareMode", "Depth texture comparison mode"} + .value("NONE", GL::SamplerCompareMode::None) + .value("COMPARE_REF_TO_TEXTURE", GL::SamplerCompareMode::CompareRefToTexture); + py::enum_{m, "SamplerCompareFunction", "Texture sampler depth comparison function"} + .value("NEVER", GL::SamplerCompareFunction::Never) + .value("ALWAYS", GL::SamplerCompareFunction::Always) + .value("LESS", GL::SamplerCompareFunction::Less) + .value("LESS_OR_EQUAL", GL::SamplerCompareFunction::LessOrEqual) + .value("EQUAL", GL::SamplerCompareFunction::Equal) + .value("NOT_EQUAL", GL::SamplerCompareFunction::NotEqual) + .value("GREATER_OR_EQUAL", GL::SamplerCompareFunction::GreaterOrEqual) + .value("GREATER", GL::SamplerCompareFunction::Greater); + #endif + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + py::enum_{m, "SamplerDepthStencilMode", "Texture sampler depth/stencil mode"} + .value("DEPTH_COMPONENT", GL::SamplerDepthStencilMode::DepthComponent) + .value("STENCIL_INDEX", GL::SamplerDepthStencilMode::StencilIndex); + #endif + + py::enum_{m, "TextureFormat", "Internal texture format"} + #if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2)) + .value("RED", GL::TextureFormat::Red) + .value("R8", GL::TextureFormat::R8) + .value("RG", GL::TextureFormat::RG) + #endif + .value("RGB", GL::TextureFormat::RGB) + #if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2)) + .value("RGB8", GL::TextureFormat::RGB8) + #endif + .value("RGBA", GL::TextureFormat::RGBA) + #if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2)) + .value("RGBA8", GL::TextureFormat::RGBA8) + #endif + #ifndef MAGNUM_TARGET_WEBGL + .value("SR8", GL::TextureFormat::SR8) + #ifdef MAGNUM_TARGET_GLES + /** @todo how to expose this one in the docs? */ + .value("SRG8", GL::TextureFormat::SRG8) + #endif + #endif + #if !defined(MAGNUM_TARGET_GLES) || defined(MAGNUM_TARGET_GLES2) + .value("SRGB", GL::TextureFormat::SRGB) + #endif + #ifndef MAGNUM_TARGET_GLES2 + .value("SRGB8", GL::TextureFormat::SRGB8) + #endif + #if !defined(MAGNUM_TARGET_GLES) || defined(MAGNUM_TARGET_GLES2) + .value("SRGB_ALPHA", GL::TextureFormat::SRGBAlpha) + #endif + #if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2)) + .value("SRGB8_ALPHA8", GL::TextureFormat::SRGB8Alpha8) + #endif + #ifndef MAGNUM_TARGET_GLES2 + .value("R8_SNORM", GL::TextureFormat::R8Snorm) + .value("RG8_SNORM", GL::TextureFormat::RG8Snorm) + .value("RGB8_SNORM", GL::TextureFormat::RGB8Snorm) + .value("RGBA8_SNORM", GL::TextureFormat::RGBA8Snorm) + #endif + #ifndef MAGNUM_TARGET_GLES + .value("R16", GL::TextureFormat::R16) + .value("RG16", GL::TextureFormat::RG16) + .value("RGB16", GL::TextureFormat::RGB16) + .value("RGBA16", GL::TextureFormat::RGBA16) + .value("R16_SNORM", GL::TextureFormat::R16Snorm) + .value("RG16_SNORM", GL::TextureFormat::RG16Snorm) + .value("RGB16_SNORM", GL::TextureFormat::RGB16Snorm) + .value("RGBA16_SNORM", GL::TextureFormat::RGBA16Snorm) + #endif + #ifndef MAGNUM_TARGET_GLES2 + .value("R8UI", GL::TextureFormat::R8UI) + .value("RG8UI", GL::TextureFormat::RG8UI) + .value("RGB8UI", GL::TextureFormat::RGB8UI) + .value("RGBA8UI", GL::TextureFormat::RGBA8UI) + .value("R8I", GL::TextureFormat::R8I) + .value("RG8I", GL::TextureFormat::RG8I) + .value("RGB8I", GL::TextureFormat::RGB8I) + .value("RGBA8I", GL::TextureFormat::RGBA8I) + .value("R16UI", GL::TextureFormat::R16UI) + .value("RG16UI", GL::TextureFormat::RG16UI) + .value("RGB16UI", GL::TextureFormat::RGB16UI) + .value("RGBA16UI", GL::TextureFormat::RGBA16UI) + .value("R16I", GL::TextureFormat::R16I) + .value("RG16I", GL::TextureFormat::RG16I) + .value("RGB16I", GL::TextureFormat::RGB16I) + .value("RGBA16I", GL::TextureFormat::RGBA16I) + .value("R32UI", GL::TextureFormat::R32UI) + .value("RG32UI", GL::TextureFormat::RG32UI) + .value("RGB32UI", GL::TextureFormat::RGB32UI) + .value("RGBA32UI", GL::TextureFormat::RGBA32UI) + .value("R32I", GL::TextureFormat::R32I) + .value("RG32I", GL::TextureFormat::RG32I) + .value("RGB32I", GL::TextureFormat::RGB32I) + .value("RGBA32I", GL::TextureFormat::RGBA32I) + .value("R16F", GL::TextureFormat::R16F) + .value("RG16F", GL::TextureFormat::RG16F) + .value("RGB16F", GL::TextureFormat::RGB16F) + .value("RGBA16F", GL::TextureFormat::RGBA16F) + .value("R32F", GL::TextureFormat::R32F) + .value("RG32F", GL::TextureFormat::RG32F) + .value("RGB32F", GL::TextureFormat::RGB32F) + .value("RGBA32F", GL::TextureFormat::RGBA32F) + #endif + #ifdef MAGNUM_TARGET_GLES2 + /** @todo how to expose those in the docs? */ + .value("LUMINANCE", GL::TextureFormat::Luminance) + .value("LUMINANCE_ALPHA", GL::TextureFormat::LuminanceAlpha) + #endif + #ifndef MAGNUM_TARGET_GLES + .value("R3B3G2", GL::TextureFormat::R3G3B2) + .value("RGB4", GL::TextureFormat::RGB4) + .value("RGB5", GL::TextureFormat::RGB5) + #endif + .value("RGB565", GL::TextureFormat::RGB565) + #if !defined(MAGNUM_TARGET_GLES) || (defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL)) + .value("RGB10", GL::TextureFormat::RGB10) + #endif + #ifndef MAGNUM_TARGET_GLES + .value("RGB12", GL::TextureFormat::RGB12) + #endif + #ifndef MAGNUM_TARGET_GLES2 + .value("R11FG11FB10F", GL::TextureFormat::R11FG11FB10F) + .value("RGB9E5", GL::TextureFormat::RGB9E5) + #endif + #ifndef MAGNUM_TARGET_GLES + .value("RGBA2", GL::TextureFormat::RGBA2) + #endif + .value("RGBA4", GL::TextureFormat::RGBA4) + .value("RGB5A1", GL::TextureFormat::RGB5A1) + #if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2)) + .value("RGB10A2", GL::TextureFormat::RGB10A2) + #endif + #ifndef MAGNUM_TARGET_GLES2 + .value("RGB10A2UI", GL::TextureFormat::RGB10A2UI) + #endif + #ifndef MAGNUM_TARGET_GLES + .value("RGBA12", GL::TextureFormat::RGBA12) + #endif + ; + /** @todo compressed formats */ + + PyNonDestructibleClass{m, "AbstractTexture", "Base for textures"} + /** @todo limits */ + .def_property_readonly("id", &GL::AbstractTexture::id, "OpenGL texture ID") + /** @todo list-taking bind */ + .def("bind", static_cast(&GL::AbstractTexture::bind), "Bind texture to given texture unit"); + + #ifndef MAGNUM_TARGET_GLES + py::class_ texture1D{m, "Texture1D", "One-dimensional texture"}; + texture(texture1D); + #endif + py::class_ texture2D{m, "Texture2D", "Two-dimensional texture"}; + texture(texture2D); + #if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2)) + py::class_ texture3D{m, "Texture3D", "Three-dimensional texture"}; + texture(texture3D); + #endif } } diff --git a/src/python/magnum/magnum.cpp b/src/python/magnum/magnum.cpp index d4e90db..29d3a93 100644 --- a/src/python/magnum/magnum.cpp +++ b/src/python/magnum/magnum.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include "Corrade/Python.h" #include "Corrade/Containers/Python.h" @@ -45,20 +46,6 @@ namespace py = pybind11; namespace magnum { namespace { -template struct PyDimensionTraits; -template struct PyDimensionTraits<1, T> { - typedef T VectorType; - static VectorType from(const Math::Vector<1, T>& vec) { return vec[0]; } -}; -template struct PyDimensionTraits<2, T> { - typedef Math::Vector2 VectorType; - static VectorType from(const Math::Vector<2, T>& vec) { return vec; } -}; -template struct PyDimensionTraits<3, T> { - typedef Math::Vector3 VectorType; - static VectorType from(const Math::Vector<3, T>& vec) { return vec; } -}; - template void imageView(py::class_>& c) { /* Missing APIs: @@ -282,6 +269,20 @@ void magnum(py::module& m) { imageViewFromMutable(imageView1D); imageViewFromMutable(imageView2D); imageViewFromMutable(imageView3D); + + py::enum_{m, "SamplerFilter", "Texture sampler filtering"} + .value("NEAREST", SamplerFilter::Nearest) + .value("LINEAR", SamplerFilter::Linear); + py::enum_{m, "SamplerMipmap", "Texture sampler mip level selection"} + .value("BASE", SamplerMipmap::Base) + .value("NEAREST", SamplerMipmap::Nearest) + .value("LINEAR", SamplerMipmap::Linear); + py::enum_{m, "SamplerWrapping", "Texture sampler wrapping"} + .value("REPEAT", SamplerWrapping::Repeat) + .value("MIRRORED_REPEAT", SamplerWrapping::MirroredRepeat) + .value("CLAMP_TO_EDGE", SamplerWrapping::ClampToEdge) + .value("CLAMP_TO_BORDER", SamplerWrapping::ClampToBorder) + .value("MIRROR_CLAMP_TO_EDGE", SamplerWrapping::MirrorClampToEdge); } }} diff --git a/src/python/magnum/test/test_gl_gl.py b/src/python/magnum/test/test_gl_gl.py index 660bf3c..2080506 100644 --- a/src/python/magnum/test/test_gl_gl.py +++ b/src/python/magnum/test/test_gl_gl.py @@ -227,3 +227,108 @@ class Shader(GLTestCase): } """) self.assertTrue(a.compile()) + +class Texture(GLTestCase): + def test_minification_filter(self): + a = gl.Texture2D() + + # Both generic and GL value should work + a.minification_filter = gl.SamplerFilter.LINEAR + a.minification_filter = SamplerFilter.LINEAR + + # A tuple as well -- any combination + a.minification_filter = (gl.SamplerFilter.LINEAR, gl.SamplerMipmap.LINEAR) + a.minification_filter = (gl.SamplerFilter.LINEAR, SamplerMipmap.LINEAR) + a.minification_filter = (SamplerFilter.LINEAR, gl.SamplerMipmap.LINEAR) + a.minification_filter = (SamplerFilter.LINEAR, SamplerMipmap.LINEAR) + + def test_minification_filter_invalid(self): + a = gl.Texture2D() + + with self.assertRaisesRegex(TypeError, "expected SamplerFilter, gl.SamplerFilter or a two-element tuple"): + a.minification_filter = 3 + with self.assertRaisesRegex(TypeError, "expected a tuple with SamplerFilter or gl.SamplerFilter as the first element"): + a.minification_filter = (3, SamplerMipmap.BASE) + with self.assertRaisesRegex(TypeError, "expected a tuple with SamplerMipmap or gl.SamplerMipmap as the second element"): + a.minification_filter = (SamplerFilter.NEAREST, 3) + with self.assertRaisesRegex(TypeError, "expected a tuple with SamplerFilter or gl.SamplerFilter as the first element"): + a.minification_filter = (3, SamplerMipmap.BASE) + + # List doesn't work ATM, sorry + with self.assertRaisesRegex(TypeError, "expected SamplerFilter, gl.SamplerFilter or a two-element tuple"): + a.minification_filter = [gl.SamplerFilter.LINEAR, gl.SamplerMipmap.LINEAR] + + def test_magnification_filter(self): + a = gl.Texture2D() + + # Both generic and GL value should work + a.magnification_filter = gl.SamplerFilter.LINEAR + a.magnification_filter = SamplerFilter.LINEAR + + def test_magnification_filter_invalid(self): + a = gl.Texture2D() + + with self.assertRaisesRegex(TypeError, "expected SamplerFilter or gl.SamplerFilter"): + a.magnification_filter = 3 + + def test_wrapping(self): + a = gl.Texture2D() + + # Both generic and GL value should work + a.wrapping = gl.SamplerWrapping.REPEAT + a.wrapping = SamplerWrapping.REPEAT + + def test_wrapping_invalid(self): + a = gl.Texture2D() + + with self.assertRaisesRegex(TypeError, "expected SamplerWrapping or gl.SamplerWrapping"): + a.wrapping = 3 + + # TODO: re-enable on ES when extensions can be checked + @unittest.skipUnless(not magnum.TARGET_WEBGL and not magnum.TARGET_GLES, "border color is not available on WebGL and requires an extension on ES which we can't check") + def test_border_color(self): + a = gl.Texture2D() + + # Both three- and four-component should work + a.border_color = Color3() + a.border_color = Color4() + + if not magnum.TARGET_GLES2: + a.border_color = Vector4ui() + a.border_color = Vector4i() + + # TODO: re-enable on ES when extensions can be checked + @unittest.skipUnless(not magnum.TARGET_WEBGL and not magnum.TARGET_GLES, "border color is not available on WebGL and requires an extension on ES which we can't check") + def test_border_color_invalid(self): + a = gl.Texture2D() + + if not magnum.TARGET_GLES2: + with self.assertRaisesRegex(TypeError, "expected Color3, Color4, Vector4ui or Vector4i"): + a.border_color = 3 + else: + # On ES2 this is handled by pybind itself, so the message is + # different + with self.assertRaisesRegex(TypeError, "incompatible function arguments"): + a.border_color = 3 + + # This should raise a type error on ES2, as only floats are + # supported + with self.assertRaisesRegex(TypeError, "incompatible function arguments"): + a.border_color = Vector4ui() + + def test_set_image(self): + a = gl.Texture2D() + a.set_image(level=0, internal_format=gl.TextureFormat.RGBA8, + image=ImageView2D(PixelFormat.RGBA8_UNORM, Vector2i(16))) + + def test_set_storage_subimage(self): + a = gl.Texture2D() + a.set_storage(levels=5, internal_format=gl.TextureFormat.RGBA8, + size=Vector2i(16)) + a.set_sub_image(0, Vector2i(), ImageView2D(PixelFormat.RGBA8_UNORM, Vector2i(16))) + a.generate_mipmap() + + if not magnum.TARGET_GLES: + # This is in ES3.2 too, but we don't have a way to check for + # extensions / version yet + self.assertEqual(a.image_size(0), Vector2i(16, 16))