diff --git a/.gitignore b/.gitignore
index ec8d795..abf13ac 100644
--- a/.gitignore
+++ b/.gitignore
@@ -35,3 +35,6 @@ pip-log.txt
# Installer Stuff
installer/Files
installer/korman.exe
+
+# C++ korlib build directory
+korlib/build
diff --git a/installer/Installer.nsi b/installer/Installer.nsi
index b043bbb..6d37201 100644
--- a/installer/Installer.nsi
+++ b/installer/Installer.nsi
@@ -153,6 +153,7 @@ Section "Files"
File "Files\HSPlasma.dll"
File "Files\PyHSPlasma.pyd"
File "Files\NxCooking.dll"
+ File "Files\_korlib.pyd"
WriteRegStr HKLM "Software\Korman" "" $INSTDIR
WriteUninstaller "$INSTDIR\korman_uninstall.exe"
@@ -164,5 +165,6 @@ Section "Uninstall"
Delete "$INSTDIR\python\lib\site-packages\HSPlasma.dll"
Delete "$INSTDIR\python\lib\site-packages\PyHSPlasma.pyd"
Delete "$INSTDIR\python\lib\site-packages\NxCooking.dll"
+ Delete "$INSTDIR\python\lib\site-packages\_korlib.pyd"
DeleteRegKey /ifempty HKLM "Software\Korman"
SectionEnd
diff --git a/korlib/CMakeLists.txt b/korlib/CMakeLists.txt
new file mode 100644
index 0000000..57e16fe
--- /dev/null
+++ b/korlib/CMakeLists.txt
@@ -0,0 +1,38 @@
+project(korlib)
+cmake_minimum_required(VERSION 3.0)
+
+# Stolen shamelessly from PyHSPlasma
+find_package(PythonLibs REQUIRED)
+find_package(PythonInterp "${PYTHONLIBS_VERSION_STRING}" REQUIRED)
+# make sure the versions match
+if (NOT "${PYTHONLIBS_VERSION_STRING}" STREQUAL "${PYTHON_VERSION_STRING}")
+ message(FATAL_ERROR "Versions of Python libraries (${PYTHONLIBS_VERSION_STRING}) and Python interpreter (${PYTHON_VERSION_STRING}) do not match. Please configure the paths manually.")
+endif()
+
+find_package(HSPlasma REQUIRED)
+find_package(OpenGL REQUIRED)
+
+# Da files
+set(korlib_HEADERS
+ buffer.h
+ korlib.h
+ texture.h
+)
+
+set(korlib_SOURCES
+ buffer.cpp
+ module.cpp
+ texture.cpp
+)
+
+include_directories(${HSPlasma_INCLUDE_DIRS})
+include_directories(${OPENGL_INCLUDE_DIR})
+include_directories(${PYTHON_INCLUDE_DIRS})
+
+add_library(_korlib SHARED ${korlib_HEADERS} ${korlib_SOURCES})
+if(NOT WIN32)
+ set_target_properties(_korlib PROPERTIES SUFFIX ".so")
+else()
+ set_target_properties(_korlib PROPERTIES SUFFIX ".pyd")
+endif()
+target_link_libraries(_korlib HSPlasma ${OPENGL_LIBRARIES} ${PYTHON_LIBRARIES})
diff --git a/korlib/buffer.cpp b/korlib/buffer.cpp
new file mode 100644
index 0000000..120f526
--- /dev/null
+++ b/korlib/buffer.cpp
@@ -0,0 +1,111 @@
+/* This file is part of Korman.
+ *
+ * Korman is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Korman is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Korman. If not, see .
+ */
+
+#include "buffer.h"
+
+extern "C" {
+
+static void pyBuffer_dealloc(pyBuffer* self) {
+ delete[] self->m_buffer;
+ Py_TYPE(self)->tp_free((PyObject*)self);
+}
+
+static PyObject* pyBuffer_new(PyTypeObject* type, PyObject* args, PyObject* kwds) {
+ PyErr_SetString(PyExc_RuntimeError, "Buffers cannot be created by mere mortals");
+ return NULL;
+}
+
+PyTypeObject pyBuffer_Type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "_korlib.Buffer", /* tp_name */
+ sizeof(pyBuffer), /* tp_basicsize */
+ 0, /* tp_itemsize */
+
+ (destructor)pyBuffer_dealloc, /* tp_dealloc */
+ NULL, /* tp_print */
+ NULL, /* tp_getattr */
+ NULL, /* tp_setattr */
+ NULL, /* tp_compare */
+ NULL, /* tp_repr */
+ NULL, /* tp_as_number */
+ NULL, /* tp_as_sequence */
+ NULL, /* tp_as_mapping */
+ NULL, /* tp_hash */
+ NULL, /* tp_call */
+ NULL, /* tp_str */
+ NULL, /* tp_getattro */
+ NULL, /* tp_setattro */
+ NULL, /* tp_as_buffer */
+
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ "Buffer", /* tp_doc */
+
+ NULL, /* tp_traverse */
+ NULL, /* tp_clear */
+ NULL, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ NULL, /* tp_iter */
+ NULL, /* tp_iternext */
+
+ NULL, /* tp_methods */
+ NULL, /* tp_members */
+ NULL, /* tp_getset */
+ NULL, /* tp_base */
+ NULL, /* tp_dict */
+ NULL, /* tp_descr_get */
+ NULL, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+
+ NULL, /* tp_init */
+ NULL, /* tp_alloc */
+ pyBuffer_new, /* tp_new */
+ NULL, /* tp_free */
+ NULL, /* tp_is_gc */
+
+ NULL, /* tp_bases */
+ NULL, /* tp_mro */
+ NULL, /* tp_cache */
+ NULL, /* tp_subclasses */
+ NULL, /* tp_weaklist */
+
+ NULL, /* tp_del */
+ 0, /* tp_version_tag */
+ NULL, /* tp_finalize */
+};
+
+PyObject* Init_pyBuffer_Type() {
+ if (PyType_Ready(&pyBuffer_Type) < 0)
+ return NULL;
+
+ Py_INCREF(&pyBuffer_Type);
+ return (PyObject*)&pyBuffer_Type;
+}
+
+int pyBuffer_Check(PyObject* obj) {
+ if (obj->ob_type == &pyBuffer_Type
+ || PyType_IsSubtype(obj->ob_type, &pyBuffer_Type))
+ return 1;
+ return 0;
+}
+
+PyObject* pyBuffer_Steal(uint8_t* buffer, size_t size) {
+ pyBuffer* obj = PyObject_New(pyBuffer, &pyBuffer_Type);
+ obj->m_buffer = buffer;
+ obj->m_size = size;
+ return (PyObject*)obj;
+}
+
+};
diff --git a/korlib/buffer.h b/korlib/buffer.h
new file mode 100644
index 0000000..33b739b
--- /dev/null
+++ b/korlib/buffer.h
@@ -0,0 +1,37 @@
+/* This file is part of Korman.
+ *
+ * Korman is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Korman is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Korman. If not, see .
+ */
+
+#ifndef _KORLIB_BUFFER_H
+#define _KORLIB_BUFFER_H
+
+#include "korlib.h"
+
+extern "C" {
+
+typedef struct {
+ PyObject_HEAD
+ uint8_t* m_buffer;
+ size_t m_size;
+} pyBuffer;
+
+extern PyTypeObject pyBuffer_Type;
+PyObject* Init_pyBuffer_Type();
+int pyBuffer_Check(PyObject*);
+PyObject* pyBuffer_Steal(uint8_t*, size_t);
+
+}
+
+#endif // _KORLIB_BUFFER_H
diff --git a/korlib/korlib.h b/korlib/korlib.h
new file mode 100644
index 0000000..226cb7d
--- /dev/null
+++ b/korlib/korlib.h
@@ -0,0 +1,35 @@
+/* This file is part of Korman.
+ *
+ * Korman is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Korman is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Korman. If not, see .
+ */
+
+#ifndef _KORLIB_H
+#define _KORLIB_H
+
+#include
+#include
+
+#define _pycs(x) const_cast(x)
+
+class PyObjectRef {
+ PyObject* m_object;
+
+public:
+ PyObjectRef(PyObject* o) : m_object(o) { }
+ ~PyObjectRef() { if (m_object) Py_DECREF(m_object); }
+
+ operator PyObject*() const { return m_object; }
+};
+
+#endif // _KORLIB_H
diff --git a/korlib/module.cpp b/korlib/module.cpp
new file mode 100644
index 0000000..3c5697d
--- /dev/null
+++ b/korlib/module.cpp
@@ -0,0 +1,45 @@
+/* This file is part of Korman.
+ *
+ * Korman is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Korman is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Korman. If not, see .
+ */
+
+#include "buffer.h"
+#include "texture.h"
+
+extern "C" {
+
+static PyModuleDef korlib_Module = {
+ PyModuleDef_HEAD_INIT, /* m_base */
+ "_korlib", /* m_name */
+ "C++ korlib implementation",/* m_doc */
+ 0, /* m_size */
+ NULL, /* m_methods */
+ NULL, /* m_reload */
+ NULL, /* m_traverse */
+ NULL, /* m_clear */
+ NULL, /* m_free */
+};
+
+PyMODINIT_FUNC PyInit__korlib() {
+ PyObject* module = PyModule_Create(&korlib_Module);
+
+ // Module classes...
+ PyModule_AddObject(module, "Buffer", Init_pyBuffer_Type());
+ PyModule_AddObject(module, "GLTexture", Init_pyGLTexture_Type());
+
+ return module;
+}
+
+};
+
diff --git a/korlib/texture.cpp b/korlib/texture.cpp
new file mode 100644
index 0000000..771d183
--- /dev/null
+++ b/korlib/texture.cpp
@@ -0,0 +1,289 @@
+/* This file is part of Korman.
+ *
+ * Korman is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Korman is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Korman. If not, see .
+ */
+
+#include "texture.h"
+#include "buffer.h"
+
+#ifdef _WIN32
+# define WIN32_LEAN_AND_MEAN
+# define NOMINMAX
+# include
+#endif // _WIN32
+
+#include
+#include
+
+#ifndef GL_GENERATE_MIPMAP
+# define GL_GENERATE_MIPMAP 0x8191
+#endif // GL_GENERATE_MIPMAP
+
+extern "C" {
+
+typedef struct {
+ PyObject_HEAD
+ PyObject* m_blenderImage;
+ bool m_ownIt;
+ GLint m_prevImage;
+ bool m_changedState;
+ GLint m_mipmapState;
+} pyGLTexture;
+
+typedef struct {
+ PyObject_HEAD
+ plMipmap* fThis;
+ bool fPyOwned;
+} pyMipmap;
+
+static void pyGLTexture_dealloc(pyGLTexture* self) {
+ if (self->m_blenderImage) Py_DECREF(self->m_blenderImage);
+ Py_TYPE(self)->tp_free((PyObject*)self);
+}
+
+static PyObject* pyGLTexture_new(PyTypeObject* type, PyObject* args, PyObject* kwds) {
+ pyGLTexture* self = (pyGLTexture*)type->tp_alloc(type, 0);
+ self->m_blenderImage = NULL;
+ self->m_ownIt = false;
+ self->m_prevImage = 0;
+ self->m_changedState = false;
+ self->m_mipmapState = 0;
+ return (PyObject*)self;
+}
+
+static int pyGLTexture___init__(pyGLTexture* self, PyObject* args, PyObject* kwds) {
+ PyObject* blender_image;
+ if (!PyArg_ParseTuple(args, "O", &blender_image)) {
+ PyErr_SetString(PyExc_TypeError, "expected a bpy.types.Image");
+ return -1;
+ }
+
+ // Save a reference to the Blender image
+ Py_INCREF(blender_image);
+ self->m_blenderImage = blender_image;
+
+ // Done!
+ return 0;
+}
+
+static PyObject* pyGLTexture__enter__(pyGLTexture* self) {
+ // Is the image already loaded?
+ PyObjectRef bindcode = PyObject_GetAttrString(self->m_blenderImage, "bindcode");
+ if (!PyLong_Check(bindcode)) {
+ PyErr_SetString(PyExc_RuntimeError, "Image bindcode isn't a long?");
+ return NULL;
+ }
+
+ glGetIntegerv(GL_TEXTURE_BINDING_2D, &self->m_prevImage);
+ GLuint image_bindcode = PyLong_AsLong(bindcode);
+ self->m_ownIt = image_bindcode == 0;
+
+ // Load image into GL
+ if (self->m_ownIt) {
+ PyObjectRef new_bind = PyObject_CallMethod(self->m_blenderImage, "gl_load", NULL);
+ if (!(PyObject*)new_bind) {
+ PyErr_SetString(PyExc_RuntimeError, "failed to load image into GL");
+ return NULL;
+ }
+ image_bindcode = PyLong_AsLong(new_bind);
+ }
+
+ // Set image as current in GL
+ if (self->m_prevImage != image_bindcode) {
+ self->m_changedState = true;
+ glBindTexture(GL_TEXTURE_2D, image_bindcode);
+ }
+
+ // Misc GL state
+ glGetTexParameteriv(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, &self->m_mipmapState);
+
+ Py_INCREF(self);
+ return (PyObject*)self;
+}
+
+static PyObject* pyGLTexture__exit__(pyGLTexture* self, PyObject*) {
+ // We don't care about the args here
+ glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, self->m_mipmapState);
+ if (self->m_changedState)
+ glBindTexture(GL_TEXTURE_2D, self->m_prevImage);
+ Py_RETURN_NONE;
+}
+
+static PyObject* pyGLTexture_generate_mipmap(pyGLTexture* self) {
+ glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, 1);
+ Py_RETURN_NONE;
+}
+
+static uint8_t* _get_level_data(pyGLTexture* self, GLint level, bool bgra, size_t* size) {
+ GLint width, height;
+ glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_WIDTH, &width);
+ glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_HEIGHT, &height);
+ GLenum fmt = bgra ? GL_BGRA_EXT : GL_RGBA;
+
+ *size = (width * height * 4);
+ uint8_t* buf = new uint8_t[*size];
+ glGetTexImage(GL_TEXTURE_2D, level, fmt, GL_UNSIGNED_BYTE, reinterpret_cast(buf));
+ return buf;
+}
+
+static PyObject* pyGLTexture_get_level_data(pyGLTexture* self, PyObject* args, PyObject* kwargs) {
+ static char* kwlist[] = { _pycs("level"), _pycs("calc_alpha"), _pycs("bgra"),
+ _pycs("quiet"), NULL };
+ GLint level = 0;
+ bool calc_alpha = false;
+ bool bgra = false;
+ bool quiet = false;
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|ibbb", kwlist, &level, &calc_alpha, &bgra, &quiet)) {
+ PyErr_SetString(PyExc_TypeError, "get_level_data expects an optional int, bool, bool, bool");
+ return NULL;
+ }
+
+ size_t bufsz;
+ uint8_t* buf = _get_level_data(self, level, bgra, &bufsz);
+ if (calc_alpha) {
+ for (size_t i = 0; i < bufsz; i += 4)
+ buf[i + 3] = (buf[i + 0] + buf[i + 1] + buf[i + 2]) / 3;
+ }
+
+ return pyBuffer_Steal(buf, bufsz);
+}
+
+static PyObject* pyGLTexture_store_in_mipmap(pyGLTexture* self, PyObject* args) {
+ pyMipmap* pymipmap;
+ PyObject* levels;
+ size_t compression;
+ if (!PyArg_ParseTuple(args, "OOn", &pymipmap, &levels, &compression) || !PySequence_Check(levels)) {
+ PyErr_SetString(PyExc_TypeError, "store_in_mipmap expects a plMipmap, sequence of Buffer and int");
+ return NULL;
+ }
+
+ // Since we actually have no way of knowing if that really is a pyMipmap...
+ plMipmap* mipmap = plMipmap::Convert(pymipmap->fThis, false);
+ if (!mipmap) {
+ PyErr_SetString(PyExc_TypeError, "store_in_mipmap expects a plMipmap, sequence of Buffer and int");
+ return NULL;
+ }
+
+ for (Py_ssize_t i = 0; i < PySequence_Size(levels); ++i) {
+ pyBuffer* item = (pyBuffer*)PySequence_GetItem(levels, i);
+ if (!pyBuffer_Check((PyObject*)item)) {
+ PyErr_SetString(PyExc_TypeError, "store_in_mipmap expects a plMipmap, sequence of Buffer and int");
+ return NULL;
+ }
+
+ if (compression == plBitmap::kDirectXCompression)
+ mipmap->CompressImage(i, item->m_buffer, item->m_size);
+ else
+ mipmap->setLevelData(i, item->m_buffer, item->m_size);
+ }
+
+ Py_RETURN_NONE;
+}
+
+static PyMethodDef pyGLTexture_Methods[] = {
+ { _pycs("__enter__"), (PyCFunction)pyGLTexture__enter__, METH_NOARGS, NULL },
+ { _pycs("__exit__"), (PyCFunction)pyGLTexture__enter__, METH_VARARGS, NULL },
+
+ { _pycs("generate_mipmap"), (PyCFunction)pyGLTexture_generate_mipmap, METH_NOARGS, NULL },
+ { _pycs("get_level_data"), (PyCFunction)pyGLTexture_get_level_data, METH_KEYWORDS | METH_VARARGS, NULL },
+ { _pycs("store_in_mipmap"), (PyCFunction)pyGLTexture_store_in_mipmap, METH_VARARGS, NULL },
+ { NULL, NULL, 0, NULL }
+};
+
+static PyObject* pyGLTexture_get_has_alpha(pyGLTexture* self, void*) {
+ size_t bufsz;
+ uint8_t* buf = _get_level_data(self, 0, false, &bufsz);
+ for (size_t i = 3; i < bufsz; i += 4) {
+ if (buf[i] != 255) {
+ delete[] buf;
+ return PyBool_FromLong(1);
+ }
+ }
+ delete[] buf;
+ return PyBool_FromLong(0);
+}
+
+static PyGetSetDef pyGLTexture_GetSet[] = {
+ { _pycs("has_alpha"), (getter)pyGLTexture_get_has_alpha, NULL, NULL, NULL },
+ { NULL, NULL, NULL, NULL, NULL }
+};
+
+PyTypeObject pyGLTexture_Type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "_korlib.GLTexture", /* tp_name */
+ sizeof(pyGLTexture), /* tp_basicsize */
+ 0, /* tp_itemsize */
+
+ (destructor)pyGLTexture_dealloc, /* tp_dealloc */
+ NULL, /* tp_print */
+ NULL, /* tp_getattr */
+ NULL, /* tp_setattr */
+ NULL, /* tp_compare */
+ NULL, /* tp_repr */
+ NULL, /* tp_as_number */
+ NULL, /* tp_as_sequence */
+ NULL, /* tp_as_mapping */
+ NULL, /* tp_hash */
+ NULL, /* tp_call */
+ NULL, /* tp_str */
+ NULL, /* tp_getattro */
+ NULL, /* tp_setattro */
+ NULL, /* tp_as_buffer */
+
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ "GLTexture", /* tp_doc */
+
+ NULL, /* tp_traverse */
+ NULL, /* tp_clear */
+ NULL, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ NULL, /* tp_iter */
+ NULL, /* tp_iternext */
+
+ pyGLTexture_Methods, /* tp_methods */
+ NULL, /* tp_members */
+ pyGLTexture_GetSet, /* tp_getset */
+ NULL, /* tp_base */
+ NULL, /* tp_dict */
+ NULL, /* tp_descr_get */
+ NULL, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+
+ (initproc)pyGLTexture___init__, /* tp_init */
+ NULL, /* tp_alloc */
+ pyGLTexture_new, /* tp_new */
+ NULL, /* tp_free */
+ NULL, /* tp_is_gc */
+
+ NULL, /* tp_bases */
+ NULL, /* tp_mro */
+ NULL, /* tp_cache */
+ NULL, /* tp_subclasses */
+ NULL, /* tp_weaklist */
+
+ NULL, /* tp_del */
+ 0, /* tp_version_tag */
+ NULL, /* tp_finalize */
+};
+
+PyObject* Init_pyGLTexture_Type() {
+ if (PyType_Ready(&pyGLTexture_Type) < 0)
+ return NULL;
+
+ Py_INCREF(&pyGLTexture_Type);
+ return (PyObject*)&pyGLTexture_Type;
+}
+
+};
+
diff --git a/korlib/texture.h b/korlib/texture.h
new file mode 100644
index 0000000..0ff42ad
--- /dev/null
+++ b/korlib/texture.h
@@ -0,0 +1,29 @@
+/* This file is part of Korman.
+ *
+ * Korman is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Korman is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Korman. If not, see .
+ */
+
+#ifndef _KORLIB_TEXTURE_H
+#define _KORLIB_TEXTURE_H
+
+#include "korlib.h"
+
+extern "C" {
+
+extern PyTypeObject pyGLTexture_Type;
+PyObject* Init_pyGLTexture_Type();
+
+};
+
+#endif // _KORLIB_TEXTURE_H
diff --git a/korman/exporter/explosions.py b/korman/exporter/explosions.py
index 16f1ee6..0b06071 100644
--- a/korman/exporter/explosions.py
+++ b/korman/exporter/explosions.py
@@ -23,11 +23,6 @@ class BlenderOptionNotSupportedError(ExportError):
super(ExportError, self).__init__("Unsupported Blender Option: '{}'".format(opt))
-class GLLoadError(ExportError):
- def __init__(self, image):
- super(ExportError, self).__init__("Failed to load '{}' into OpenGL".format(image.name))
-
-
class TooManyUVChannelsError(ExportError):
def __init__(self, obj, mat):
msg = "There are too many UV Textures on the material '{}' associated with object '{}'.".format(
diff --git a/korman/exporter/material.py b/korman/exporter/material.py
index 67ddb10..e4ac737 100644
--- a/korman/exporter/material.py
+++ b/korman/exporter/material.py
@@ -14,7 +14,6 @@
# along with Korman. If not, see .
import bpy
-import bgl
import math
import os.path
from PyHSPlasma import *
@@ -22,90 +21,9 @@ import weakref
from . import explosions
from .. import helpers
+from .. import korlib
from . import utils
-# BGL doesn't know about this as of Blender 2.74
-bgl.GL_GENERATE_MIPMAP = 0x8191
-bgl.GL_BGRA = 0x80E1
-
-class _GLTexture:
- def __init__(self, blimg):
- self._ownit = (blimg.bindcode == 0)
- if self._ownit:
- if blimg.gl_load() != 0:
- raise explosions.GLLoadError(blimg)
- self._blimg = blimg
-
- def __del__(self):
- if self._ownit:
- self._blimg.gl_free()
-
- def __enter__(self):
- """Sets the Blender Image as the active OpenGL texture"""
- self._previous_texture = self._get_integer(bgl.GL_TEXTURE_BINDING_2D)
- self._changed_state = (self._previous_texture != self._blimg.bindcode)
- if self._changed_state:
- bgl.glBindTexture(bgl.GL_TEXTURE_2D, self._blimg.bindcode)
- return self
-
- def __exit__(self, type, value, traceback):
- mipmap_state = getattr(self, "_mipmap_state", None)
- if mipmap_state is not None:
- bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_GENERATE_MIPMAP, mipmap_state)
-
- if self._changed_state:
- bgl.glBindTexture(bgl.GL_TEXTURE_2D, self._previous_texture)
-
- def generate_mipmap(self):
- """Generates all mip levels for this texture"""
- self._mipmap_state = self._get_tex_param(bgl.GL_GENERATE_MIPMAP)
-
- # Note that this is a very old feature from OpenGL 1.x -- it's new enough that Windows (and
- # Blender apparently) don't support it natively and yet old enough that it was thrown away
- # in OpenGL 3.0. The new way is glGenerateMipmap, but Blender likes oldgl, so we don't have that
- # function available to us in BGL. I don't want to deal with loading the GL dll in ctypes on
- # many platforms right now (or context headaches). If someone wants to fix this, be my guest!
- # It will simplify our state tracking a bit.
- bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_GENERATE_MIPMAP, 1)
-
- def get_level_data(self, level=0, calc_alpha=False, bgra=False, quiet=False):
- """Gets the uncompressed pixel data for a requested mip level, optionally calculating the alpha
- channel from the image color data
- """
- width = self._get_tex_param(bgl.GL_TEXTURE_WIDTH, level)
- height = self._get_tex_param(bgl.GL_TEXTURE_HEIGHT, level)
- if not quiet:
- print(" Level #{}: {}x{}".format(level, width, height))
-
- # Grab the image data
- size = width * height * 4
- buf = bgl.Buffer(bgl.GL_BYTE, size)
- fmt = bgl.GL_BGRA if bgra else bgl.GL_RGBA
- bgl.glGetTexImage(bgl.GL_TEXTURE_2D, level, fmt, bgl.GL_UNSIGNED_BYTE, buf);
-
- # Calculate le alphas
- # NOTE: the variable names are correct for GL_RGBA. We'll still get the right values for
- # BGRA, obviously, but red will suddenly be... blue. Yeah.
- if calc_alpha:
- for i in range(0, size, 4):
- r, g, b = buf[i:i+3]
- buf[i+3] = int((r + g + b) / 3)
- return bytes(buf)
-
- def _get_integer(self, arg):
- buf = bgl.Buffer(bgl.GL_INT, 1)
- bgl.glGetIntegerv(arg, buf)
- return int(buf[0])
-
- def _get_tex_param(self, param, level=None):
- buf = bgl.Buffer(bgl.GL_INT, 1)
- if level is None:
- bgl.glGetTexParameteriv(bgl.GL_TEXTURE_2D, param, buf)
- else:
- bgl.glGetTexLevelParameteriv(bgl.GL_TEXTURE_2D, level, param, buf)
- return int(buf[0])
-
-
class _Texture:
def __init__(self, texture=None, image=None, use_alpha=None, force_calc_alpha=False):
assert (texture or image)
@@ -570,7 +488,8 @@ class MaterialConverter:
numLevels = max(numLevels - 2, 2)
# Grab the image data from OpenGL and stuff it into the plBitmap
- with _GLTexture(image) as glimage:
+ helper = korlib.GLTexture(image)
+ with helper as glimage:
if key.mipmap:
print(" Generating mip levels")
glimage.generate_mipmap()
@@ -605,9 +524,7 @@ class MaterialConverter:
if page not in pages:
mipmap = plMipmap(name=name, width=eWidth, height=eHeight, numLevels=numLevels,
compType=compression, format=plBitmap.kRGB8888, dxtLevel=dxt)
- func = mipmap.CompressImage if compression == plBitmap.kDirectXCompression else mipmap.setLevel
- for i, level in enumerate(data):
- func(i, level)
+ helper.store_in_mipmap(mipmap, data, compression)
mgr.AddObject(page, mipmap)
pages[page] = mipmap
else:
@@ -658,14 +575,8 @@ class MaterialConverter:
result = False
else:
# Using bpy.types.Image.pixels is VERY VERY VERY slow...
- with _GLTexture(image) as glimage:
- data = glimage.get_level_data(quiet=True)
- for i in range(3, len(data), 4):
- if data[i] != 255:
- result = True
- break
- else:
- result = False
+ with korlib.GLTexture(image) as glimage:
+ result = glimage.has_alpha
self._alphatest[image] = result
return result
diff --git a/korman/korlib/__init__.py b/korman/korlib/__init__.py
new file mode 100644
index 0000000..a2c2644
--- /dev/null
+++ b/korman/korlib/__init__.py
@@ -0,0 +1,19 @@
+# This file is part of Korman.
+#
+# Korman is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Korman is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Korman. If not, see .
+
+try:
+ from _korlib import *
+except ImportError:
+ from .texture import *
diff --git a/korman/korlib/texture.py b/korman/korlib/texture.py
new file mode 100644
index 0000000..7480174
--- /dev/null
+++ b/korman/korlib/texture.py
@@ -0,0 +1,109 @@
+# This file is part of Korman.
+#
+# Korman is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Korman is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Korman. If not, see .
+
+import bgl
+from PyHSPlasma import plBitmap
+
+# BGL doesn't know about this as of Blender 2.74
+bgl.GL_GENERATE_MIPMAP = 0x8191
+bgl.GL_BGRA = 0x80E1
+
+class GLTexture:
+ def __init__(self, blimg):
+ self._ownit = (blimg.bindcode == 0)
+ self._blimg = blimg
+
+ def __enter__(self):
+ """Sets the Blender Image as the active OpenGL texture"""
+ if self._ownit:
+ if self._blimg.gl_load() != 0:
+ raise RuntimeError("failed to load image")
+
+ self._previous_texture = self._get_integer(bgl.GL_TEXTURE_BINDING_2D)
+ self._changed_state = (self._previous_texture != self._blimg.bindcode)
+ if self._changed_state:
+ bgl.glBindTexture(bgl.GL_TEXTURE_2D, self._blimg.bindcode)
+ return self
+
+ def __exit__(self, type, value, traceback):
+ mipmap_state = getattr(self, "_mipmap_state", None)
+ if mipmap_state is not None:
+ bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_GENERATE_MIPMAP, mipmap_state)
+ if self._changed_state:
+ bgl.glBindTexture(bgl.GL_TEXTURE_2D, self._previous_texture)
+ if self._ownit:
+ self._blimg.gl_free()
+
+ def generate_mipmap(self):
+ """Generates all mip levels for this texture"""
+ self._mipmap_state = self._get_tex_param(bgl.GL_GENERATE_MIPMAP)
+
+ # Note that this is a very old feature from OpenGL 1.x -- it's new enough that Windows (and
+ # Blender apparently) don't support it natively and yet old enough that it was thrown away
+ # in OpenGL 3.0. The new way is glGenerateMipmap, but Blender likes oldgl, so we don't have that
+ # function available to us in BGL. I don't want to deal with loading the GL dll in ctypes on
+ # many platforms right now (or context headaches). If someone wants to fix this, be my guest!
+ # It will simplify our state tracking a bit.
+ bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_GENERATE_MIPMAP, 1)
+
+ def get_level_data(self, level=0, calc_alpha=False, bgra=False, quiet=False):
+ """Gets the uncompressed pixel data for a requested mip level, optionally calculating the alpha
+ channel from the image color data
+ """
+ width = self._get_tex_param(bgl.GL_TEXTURE_WIDTH, level)
+ height = self._get_tex_param(bgl.GL_TEXTURE_HEIGHT, level)
+ if not quiet:
+ print(" Level #{}: {}x{}".format(level, width, height))
+
+ # Grab the image data
+ size = width * height * 4
+ buf = bgl.Buffer(bgl.GL_BYTE, size)
+ fmt = bgl.GL_BGRA if bgra else bgl.GL_RGBA
+ bgl.glGetTexImage(bgl.GL_TEXTURE_2D, level, fmt, bgl.GL_UNSIGNED_BYTE, buf);
+
+ # Calculate le alphas
+ # NOTE: the variable names are correct for GL_RGBA. We'll still get the right values for
+ # BGRA, obviously, but red will suddenly be... blue. Yeah.
+ if calc_alpha:
+ for i in range(0, size, 4):
+ r, g, b = buf[i:i+3]
+ buf[i+3] = int((r + g + b) / 3)
+ return bytes(buf)
+
+ def _get_integer(self, arg):
+ buf = bgl.Buffer(bgl.GL_INT, 1)
+ bgl.glGetIntegerv(arg, buf)
+ return int(buf[0])
+
+ def _get_tex_param(self, param, level=None):
+ buf = bgl.Buffer(bgl.GL_INT, 1)
+ if level is None:
+ bgl.glGetTexParameteriv(bgl.GL_TEXTURE_2D, param, buf)
+ else:
+ bgl.glGetTexLevelParameteriv(bgl.GL_TEXTURE_2D, level, param, buf)
+ return int(buf[0])
+
+ @property
+ def has_alpha(self):
+ data = self.get_level_data(quiet=True)
+ for i in range(3, len(data), 4):
+ if data[i] != 255:
+ return True
+ return False
+
+ def store_in_mipmap(self, mipmap, data, compression):
+ func = mipmap.CompressImage if compression == plBitmap.kDirectXCompression else mipmap.setLevel
+ for i, level in enumerate(data):
+ func(i, level)