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)