From f9851bdb1eda2e07ee8caf7b0d61faf4b0bdd283 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sun, 8 Sep 2019 16:25:47 +0200 Subject: [PATCH] python: expose shader interface APIs. Custom shaders now possible. --- src/python/magnum/gl.cpp | 185 ++++++++++++++++++++++++++- src/python/magnum/test/test_gl_gl.py | 75 +++++++++++ 2 files changed, 257 insertions(+), 3 deletions(-) diff --git a/src/python/magnum/gl.cpp b/src/python/magnum/gl.cpp index 39d7c31..efab784 100644 --- a/src/python/magnum/gl.cpp +++ b/src/python/magnum/gl.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include #include @@ -62,6 +63,41 @@ PYBIND11_DECLARE_HOLDER_TYPE(T, magnum::NonDefaultFramebufferHolder) namespace magnum { +namespace { + +struct PublicizedAbstractShaderProgram: GL::AbstractShaderProgram { + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + using GL::AbstractShaderProgram::setRetrievableBinary; + #endif + #ifndef MAGNUM_TARGET_WEBGL + using GL::AbstractShaderProgram::setSeparable; + #endif + using GL::AbstractShaderProgram::attachShader; + using GL::AbstractShaderProgram::bindAttributeLocation; + #ifndef MAGNUM_TARGET_GLES + using GL::AbstractShaderProgram::bindFragmentDataLocation; + using GL::AbstractShaderProgram::bindFragmentDataLocationIndexed; + #endif + #ifndef MAGNUM_TARGET_GLES2 + using GL::AbstractShaderProgram::setTransformFeedbackOutputs; + #endif + using GL::AbstractShaderProgram::link; + using GL::AbstractShaderProgram::uniformLocation; + #ifndef MAGNUM_TARGET_GLES2 + using GL::AbstractShaderProgram::uniformBlockIndex; + #endif + using GL::AbstractShaderProgram::setUniform; + #ifndef MAGNUM_TARGET_GLES2 + using GL::AbstractShaderProgram::setUniformBlockBinding; + #endif +}; + +template void setUniform(GL::AbstractShaderProgram& self, Int location, T value) { + static_cast(self).setUniform(location, value); +} + +} + void gl(py::module& m) { /* Missing APIs: @@ -101,10 +137,153 @@ void gl(py::module& m) { .def("version", static_cast(*)(GL::Version)>(GL::version), "Major and minor version number from enum value", py::arg("version")) .def("is_version_es", GL::isVersionES, "Whether given version is OpenGL ES or WebGL"); + /* Shader (used by AbstractShaderProgram, so needs to be before) */ + { + py::class_ shader{m, "Shader", "Shader"}; + + py::enum_{shader, "Type", "Shader type"} + .value("VERTEX", GL::Shader::Type::Vertex) + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + .value("TESSELLATION_CONTROL", GL::Shader::Type::TessellationControl) + .value("TESSELLATION_EVALUATION", GL::Shader::Type::TessellationEvaluation) + .value("GEOMETRY", GL::Shader::Type::Geometry) + .value("COMPUTE", GL::Shader::Type::Compute) + #endif + .value("FRAGMENT", GL::Shader::Type::Fragment); + + shader + /** @todo limit queries */ + /* Constructors */ + .def(py::init(), "Constructor", py::arg("version"), py::arg("type")) + + /* Interface */ + .def_property_readonly("id", &GL::Shader::id, "OpenGL shader ID") + .def_property_readonly("type", &GL::Shader::type, "Shader type") + .def_property_readonly("sources", &GL::Shader::sources, "Shader sources") + /* Using lambdas to avoid method chaining leaking to Python */ + .def("add_source", [](GL::Shader& self, std::string source) { + self.addSource(std::move(source)); + }, "Add shader source") + .def("add_file", [](GL::Shader& self, const std::string& filename) { + self.addFile(filename); + }, "Add shader source file") + .def("compile", static_cast(&GL::Shader::compile), "Compile shader"); + } + /* Abstract shader program */ - PyNonDestructibleClass{m, - "AbstractShaderProgram", "Base for shader program implementations"}; - /** @todo more */ + { + /* The original class has protected functions and a pure virtual + destructor to force people to subclass it. */ + struct PyAbstractShaderProgram: GL::AbstractShaderProgram { + using GL::AbstractShaderProgram::AbstractShaderProgram; + }; + py::class_ abstractShaderProgram{m, "AbstractShaderProgram", "Base for shader program implementations"}; + + #ifndef MAGNUM_TARGET_GLES2 + py::enum_{abstractShaderProgram, "TransformFeedbackBufferMode", "Buffer mode for transform feedback"} + .value("INTERLEAVED_ATTRIBUTES", GL::AbstractShaderProgram::TransformFeedbackBufferMode::InterleavedAttributes) + .value("SEPARATE_ATTRIBUTES", GL::AbstractShaderProgram::TransformFeedbackBufferMode::SeparateAttributes); + #endif + + abstractShaderProgram + /** @todo limit queries */ + + /* Constructors */ + .def_static("no_create", []() { + return PyAbstractShaderProgram{NoCreate}; + }, "Construct without creating the underlying OpenGL object") + .def(py::init(), "Constructor") + + /* Public interface */ + .def_property_readonly("id", &GL::AbstractShaderProgram::id, "OpenGL program ID") + .def("validate", &GL::AbstractShaderProgram::validate, "Validate program") + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + .def("dispatch_compute", &GL::AbstractShaderProgram::dispatchCompute, "Dispatch compute") + #endif + + /* Protected interface */ + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + .def_property("retrievable_binary", nullptr, &PublicizedAbstractShaderProgram::setRetrievableBinary, "Allow retrieving program binary") + #endif + #ifndef MAGNUM_TARGET_WEBGL + .def_property("separable", nullptr, &PublicizedAbstractShaderProgram::setSeparable, "Allow the program to be bound to individual pipeline stages") + #endif + .def("attach_shader", &PublicizedAbstractShaderProgram::attachShader, "Attach a shader") + /** @todo list-taking shader attach function */ + /* Somehow the overload static_casts don't work and it complains it + can't bind a protected function, have to use lambdas */ + .def("bind_attribute_location", [](GL::AbstractShaderProgram& self, UnsignedInt location, const std::string& name) { + static_cast(self).bindAttributeLocation(location, name); + }, "Bind an attribute to given location", py::arg("location"), py::arg("name")) + #ifndef MAGNUM_TARGET_GLES + .def("bind_fragment_data_location_indexed", [](GL::AbstractShaderProgram& self, UnsignedInt location, UnsignedInt index, const std::string& name) { + static_cast(self).bindFragmentDataLocationIndexed(location, index, name); + }, "Bind fragment data to given location and first color input index", py::arg("location"), py::arg("index"), py::arg("name")) + .def("bind_fragment_data_location", [](GL::AbstractShaderProgram& self, UnsignedInt location, const std::string& name) { + static_cast(self).bindFragmentDataLocation(location, name); + }, "Bind fragment data to given location and first color input index", py::arg("location"), py::arg("name")) + #endif + /** @todo setTransformFeedbackOutputs, list-taking link functions */ + /* Somehow the overload static_casts don't work and it complains it + can't bind a protected function, have to use lambdas */ + .def("link", [](GL::AbstractShaderProgram& self) { + return static_cast(self).link(); + }, "Link the shader") + .def("uniform_location", [](GL::AbstractShaderProgram& self, const std::string& name) { + return static_cast(self).uniformLocation(name); + }, "Get uniform location") + #ifndef MAGNUM_TARGET_GLES2 + .def("uniform_block_index", [](GL::AbstractShaderProgram& self, const std::string& name) { + return static_cast(self).uniformBlockIndex(name); + }, "Get uniform block index") + #endif + .def("set_uniform", setUniform, "Set uniform value") + .def("set_uniform", setUniform, "Set uniform value") + #ifndef MAGNUM_TARGET_GLES2 + /** @todo How to distinguish *this*? Python has just an int. */ + .def("set_uniform", setUniform, "Set uniform value") + #endif + /** @todo double scalar uniform, how to distinguish? if I add it, it will get a priority over floats */ + .def("set_uniform", setUniform, "Set uniform value") + .def("set_uniform", setUniform, "Set uniform value") + .def("set_uniform", setUniform, "Set uniform value") + .def("set_uniform", setUniform, "Set uniform value") + .def("set_uniform", setUniform, "Set uniform value") + .def("set_uniform", setUniform, "Set uniform value") + #ifndef MAGNUM_TARGET_GLES2 + .def("set_uniform", setUniform, "Set uniform value") + .def("set_uniform", setUniform, "Set uniform value") + .def("set_uniform", setUniform, "Set uniform value") + #endif + #ifndef MAGNUM_TARGET_GLES + .def("set_uniform", setUniform, "Set uniform value") + .def("set_uniform", setUniform, "Set uniform value") + .def("set_uniform", setUniform, "Set uniform value") + #endif + .def("set_uniform", setUniform, "Set uniform value") + .def("set_uniform", setUniform, "Set uniform value") + .def("set_uniform", setUniform, "Set uniform value") + #ifndef MAGNUM_TARGET_GLES2 + .def("set_uniform", setUniform, "Set uniform value") + .def("set_uniform", setUniform, "Set uniform value") + .def("set_uniform", setUniform, "Set uniform value") + .def("set_uniform", setUniform, "Set uniform value") + .def("set_uniform", setUniform, "Set uniform value") + .def("set_uniform", setUniform, "Set uniform value") + #endif + #ifndef MAGNUM_TARGET_GLES + .def("set_uniform", setUniform, "Set uniform value") + .def("set_uniform", setUniform, "Set uniform value") + .def("set_uniform", setUniform, "Set uniform value") + .def("set_uniform", setUniform, "Set uniform value") + .def("set_uniform", setUniform, "Set uniform value") + .def("set_uniform", setUniform, "Set uniform value") + #endif + #ifndef MAGNUM_TARGET_GLES2 + .def("set_uniform_block_binding", &PublicizedAbstractShaderProgram::setUniformBlockBinding, "Set uniform block binding") + #endif + ; + } /* (Dynamic) attribute */ py::class_ attribute{m, "Attribute", "Vertex attribute location and type"}; diff --git a/src/python/magnum/test/test_gl_gl.py b/src/python/magnum/test/test_gl_gl.py index dd89ab2..b5c4e77 100644 --- a/src/python/magnum/test/test_gl_gl.py +++ b/src/python/magnum/test/test_gl_gl.py @@ -31,9 +31,69 @@ import unittest # be run from . import GLTestCase, setUpModule +import magnum from magnum import * from magnum import gl +class AbstractShaderProgram(GLTestCase): + def test(self): + a = gl.AbstractShaderProgram() + + if magnum.TARGET_GLES2: + vert = gl.Shader(gl.Version.GLES200, gl.Shader.Type.VERTEX) + elif magnum.TARGET_GLES: + vert = gl.Shader(gl.Version.GLES300, gl.Shader.Type.VERTEX) + else: + vert = gl.Shader(gl.Version.GL300, gl.Shader.Type.VERTEX) + if magnum.TARGET_GLES2: + vert.add_source(""" +attribute lowp vec4 position; +uniform lowp mat4 transformationProjectionMatrix; + +void main() { + gl_Position = transformationProjectionMatrix*position; +} + """.strip()) + else: + vert.add_source(""" +in lowp vec4 position; +uniform lowp mat4 transformationProjectionMatrix; + +void main() { + gl_Position = transformationProjectionMatrix*position; +} + """.strip()) + + self.assertTrue(vert.compile()) + a.attach_shader(vert) + + if magnum.TARGET_GLES2: + frag = gl.Shader(gl.Version.GLES200, gl.Shader.Type.FRAGMENT) + elif magnum.TARGET_GLES: + frag = gl.Shader(gl.Version.GLES300, gl.Shader.Type.FRAGMENT) + else: + frag = gl.Shader(gl.Version.GL300, gl.Shader.Type.FRAGMENT) + if magnum.TARGET_GLES2: + frag.add_source(""" +void main() { + gl_FragColor = vec4(0.0); +} + """.strip()) + else: + frag.add_source(""" +out lowp vec4 color; + +void main() { + color = vec4(0.0); +} + """.strip()) + self.assertTrue(frag.compile()) + a.attach_shader(frag) + + a.bind_attribute_location(0, "position") + self.assertTrue(a.link()) + self.assertGreaterEqual(a.uniform_location("transformationProjectionMatrix"), 0) + class Buffer(GLTestCase): def test_init(self): a = gl.Buffer() @@ -149,3 +209,18 @@ class Renderer(GLTestCase): gl.Renderer.enable(gl.Renderer.Feature.DEPTH_TEST) gl.Renderer.disable(gl.Renderer.Feature.FACE_CULLING) gl.Renderer.set_feature(gl.Renderer.Feature.STENCIL_TEST, True) + +class Shader(GLTestCase): + def test(self): + if magnum.TARGET_GLES2: + a = gl.Shader(gl.Version.GLES200, gl.Shader.Type.VERTEX) + elif magnum.TARGET_GLES: + a = gl.Shader(gl.Version.GLES300, gl.Shader.Type.VERTEX) + else: + a = gl.Shader(gl.Version.GL300, gl.Shader.Type.VERTEX) + a.add_source(""" + void main() { + gl_Position = vec4(0.0); + } + """) + self.assertTrue(a.compile())