diff --git a/qrenderdoc/Code/pyrenderdoc/container_handling.h b/qrenderdoc/Code/pyrenderdoc/container_handling.h new file mode 100644 index 000000000..ca41f5254 --- /dev/null +++ b/qrenderdoc/Code/pyrenderdoc/container_handling.h @@ -0,0 +1,288 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2017 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#pragma once + +#include + +/////////////////////////////////////////////////////////////////////////// +// utility function to get the real C++ this pointer out of PyObject* given +// a type, so we can modify it by reference if we want +template +arrayType *array_thisptr(PyObject *self) +{ + void *ptr = NULL; + int res = 0; + swig_type_info *typeInfo = TypeConversion::GetTypeInfo(); + + if(!typeInfo) + SWIG_exception_fail(SWIG_RuntimeError, "Internal error fetching type info"); + + res = SWIG_ConvertPtr(self, &ptr, typeInfo, 0); + if(!SWIG_IsOK(res)) + SWIG_exception_fail(SWIG_ArgError(res), "Couldn't convert array type"); + + return (arrayType *)ptr; + +fail: + return NULL; +} + +/////////////////////////////////////////////////////////////////////////// +// Bit of a hack - use a dispatch struct templated on a constant bool, so +// we can do a compile-time check if we're converting 'self' and only invoke +// array_thisptr for arrays that we want to be modifying by reference. +template +struct self_dispatch +{ + template + static arrayType *getthis(PyObject *self) + { + return NULL; + } +}; + +template <> +struct self_dispatch +{ + template + static arrayType *getthis(PyObject *self) + { + return ::array_thisptr(self); + } +}; +/////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////// +// templated implementations of array functions for both slots and named +// functions in python sequences + +// returns the index into an array, including reverseing negative indices to refer to elements from +// the end of the array (-1 = last, -2 = second last, etc). +template +Py_ssize_t array_revindex(arrayType *thisptr, PyObject *index) +{ + Py_ssize_t idx = 0; + + if(!PyIndex_Check(index)) + SWIG_exception_fail(SWIG_TypeError, "invalid index type"); + + idx = PyNumber_AsSsize_t(index, PyExc_IndexError); + + // don't need to fail, it's already been thrown + if(idx == -1 && PyErr_Occurred()) + return PY_SSIZE_T_MIN; + + // if the index is negative, treat it as an offset from the end, with -1 being not last but + // next-to-last + if(idx < 0) + idx = (Py_ssize_t)thisptr->size() + idx; + + return idx; +fail: + return PY_SSIZE_T_MIN; +} + +template +PyObject *array_repr(arrayType *thisptr) +{ + PyObject *result = NULL; + PyObject *list = NULL; + + // make a copy as a python list + list = ConvertToPy(*thisptr); + + if(list == NULL) + SWIG_exception_fail(SWIG_ValueError, "invalid array"); + + // use the python list repr function + result = PyObject_Repr(list); + Py_DECREF(list); + return result; +fail: + return NULL; +} + +template +PyObject *array_getitem(arrayType *thisptr, Py_ssize_t idx) +{ + if(idx < 0 || (size_t)idx >= thisptr->size()) + SWIG_exception_fail(SWIG_IndexError, "list index out of range"); + + return ConvertToPy(thisptr->at(idx)); +fail: + return NULL; +} + +template +int array_setitem(arrayType *thisptr, Py_ssize_t idx, PyObject *val) +{ + int res = SWIG_OK; + + if(idx < 0 || (size_t)idx >= thisptr->size()) + SWIG_exception_fail(SWIG_IndexError, "list assignment index out of range"); + + if(val == NULL) + { + thisptr->erase(idx); + res = SWIG_OK; + } + else + { + res = ConvertFromPy(val, thisptr->at(idx)); + } + + if(SWIG_IsOK(res)) + return 0; +fail: + return -1; +} + +template +Py_ssize_t array_len(arrayType *thisptr) +{ + return thisptr->size(); +} + +template +PyObject *array_clear(arrayType *thisptr) +{ + thisptr->clear(); + return SWIG_Py_Void(); +} + +template +PyObject *array_reverse(arrayType *thisptr) +{ + std::reverse(thisptr->begin(), thisptr->end()); + return SWIG_Py_Void(); +} + +template +PyObject *array_copy(arrayType *thisptr) +{ + PyObject *list = PyList_New(0); + if(!list) + return NULL; + + PyObject *ret = NULL; + + for(size_t i = 0; i < thisptr->size(); i++) + { + ret = ConvertToPy(thisptr->at(i)); + + PyList_Append(list, ret); + + if(!ret) + SWIG_exception_fail(SWIG_TypeError, "failed to convert element while copying"); + } + + return list; +fail: + if(list) + Py_XDECREF(list); + + return NULL; +} + +template +PyObject *array_sort(arrayType *thisptr, PyObject *key, bool reverse) +{ + // TODO - implement sort + return SWIG_Py_Void(); +} + +template +PyObject *array_append(arrayType *thisptr, PyObject *value) +{ + typename arrayType::value_type converted; + int res = ConvertFromPy(value, converted); + + if(SWIG_IsOK(res)) + { + thisptr->push_back(converted); + return SWIG_Py_Void(); + } + + SWIG_exception_fail(SWIG_ArgError(res), "failed to convert element while appending"); +fail: + return NULL; +} + +template +PyObject *array_insert(arrayType *thisptr, PyObject *index, PyObject *value) +{ + typename arrayType::value_type converted; + int res = 0; + Py_ssize_t idx = array_revindex(thisptr, index); + + // if an error occurred an exception has been thrown, just return NULL + if(idx == PY_SSIZE_T_MIN) + return NULL; + + // insert clamps the index + if(idx < 0) + idx = 0; + if(idx > thisptr->count()) + idx = thisptr->count(); + + res = ConvertFromPy(value, converted); + + if(SWIG_IsOK(res)) + { + thisptr->insert(idx, converted); + return SWIG_Py_Void(); + } + + SWIG_exception_fail(SWIG_ArgError(res), "failed to convert element while inserting"); +fail: + return NULL; +} + +template +PyObject *array_pop(arrayType *thisptr, PyObject *index) +{ + PyObject *ret = NULL; + Py_ssize_t idx = index ? array_revindex(thisptr, index) : thisptr->size() - 1; + + // if an error occurred an exception has been thrown, just return NULL + if(idx == PY_SSIZE_T_MIN) + return NULL; + + if(idx < 0 || idx > thisptr->count()) + SWIG_exception_fail(SWIG_IndexError, "pop index out of range"); + + if(thisptr->empty()) + SWIG_exception_fail(SWIG_IndexError, "pop from empty list"); + + ret = ConvertToPy(thisptr->at(idx)); + + if(!ret) + SWIG_exception_fail(SWIG_TypeError, "failed to convert element while popping"); + + thisptr->erase(idx); + return ret; +fail: + return NULL; +} diff --git a/qrenderdoc/Code/pyrenderdoc/container_handling.i b/qrenderdoc/Code/pyrenderdoc/container_handling.i new file mode 100644 index 000000000..a7a38c1a7 --- /dev/null +++ b/qrenderdoc/Code/pyrenderdoc/container_handling.i @@ -0,0 +1,259 @@ +/////////////////////////////////////////////////////////////////////////////////////////////// +// handling for making C++ types act as python lists/tuples, to allow a limited amount of +// by-reference access from python +// +// We do this mostly by adding named methods and slots that modify or access the list in place. + +%header %{ +// hacky macro to check if two variables are the same by looking at their length & second letter. +// Used to identify if we're dealing with 'self' in a typemap or not +#define STR_EQ(a, b) (sizeof(#a) == sizeof(#b) && (#a[1] == #b[1])) +%} + +// a typemap that allows us to modify 'self' in place, but convert any inputs by value. +%define LIST_MODIFY_IN_PLACE_TYPEMAP(ContainerType) + +%typemap(in) ContainerType * (unsigned char tempmem[32], bool wasSelf = false) { + using array_type = std::remove_pointer::type; + + // don't convert 'self', leave as-is so we can modify by reference. Everything else needs to be + // converted/copied to allow passing lists (or any sequence) python objects to a function + // expecting a particular C++ array type + constexpr bool isSelf = STR_EQ($input, self); + wasSelf = isSelf; + if(isSelf) + { + // we use an indirect dispatch class here (see self_dispatch) to avoid the need to instantiate + // the conversion template for types we don't care about + $1 = self_dispatch::getthis(self); + } + else + { + // convert the sequence by value using ConvertFromPy + static_assert(sizeof(tempmem) >= sizeof(array_type), "not enough temp space for $1_basetype"); + + tempalloc($1, tempmem); + + int failIdx = 0; + int res = TypeConversion::ConvertFromPy($input, indirect($1), &failIdx); + + if(!SWIG_IsOK(res)) + { + if(res == SWIG_TypeError) + { + SWIG_exception_fail(SWIG_ArgError(res), "in method '$symname' argument $argnum of type '$1_basetype'"); + } + else + { + snprintf(convert_error, sizeof(convert_error)-1, "in method '$symname' argument $argnum of type '$1_basetype', decoding element %d", failIdx); + SWIG_exception_fail(SWIG_ArgError(res), convert_error); + } + } + } +} + +%typemap(freearg) ContainerType * { + // if we converted the sequence by-value, then destroy it again + if(!wasSelf$argnum) + tempdealloc($1); +} + +%enddef // %define LIST_MODIFY_IN_PLACE_TYPEMAP + +// this macro defines the list array class named methods, forwarding to templated implementations +%define EXTEND_ARRAY_CLASS_METHODS(Container) + %extend Container { + // functions with optional parameters need to use kwargs otherwise SWIG wraps them in multiple + // overloads + %feature("kwargs") pop; + %feature("kwargs") sort; + + PyObject *append(PyObject *value) + { + return array_append($self, value); + } + + PyObject *clear() + { + return array_clear($self); + } + + PyObject *insert(PyObject *index, PyObject *value) + { + return array_insert($self, index, value); + } + + PyObject *pop(PyObject *index = NULL) + { + return array_pop($self, index); + } + + PyObject *sort(PyObject *key = NULL, bool reverse = false) + { + return array_sort($self, key, reverse); + } + + PyObject *copy() + { + return array_copy($self); + } + + PyObject *reverse() + { + return array_reverse($self); + } + + /* +// named functions to implement for list compatibility +index(x[, start[, end]]); // Return zero-based index in the list of the first item whose value is x +count(value); // Return the number of times x appears in the list. +extend(iterable); // Extend the list by appending all the items from the iterable +remove(x); // Remove the first item from the list whose value is x. +*/ + } // %extend Container +%enddef // define EXTEND_ARRAY_CLASS_METHODS(Container) + +// Declare the slots that we're going to add - see ARRAY_DEFINE_SLOTS for the definitions +%define ARRAY_ADD_SLOTS(array_type, unique_name) + +// define slots +%feature("python:tp_repr") array_type STRINGIZE(repr_##unique_name); +%feature("python:tp_str") array_type STRINGIZE(repr_##unique_name); +%feature("python:sq_item") array_type STRINGIZE(getitem_##unique_name); +%feature("python:sq_ass_item") array_type STRINGIZE(setitem_##unique_name); +%feature("python:sq_length") array_type STRINGIZE(length_##unique_name); + +// https://docs.python.org/3/library/collections.abc.html +// https://docs.python.org/3/library/stdtypes.html#typesseq-common +// https://docs.python.org/3/library/stdtypes.html#typesseq-mutable +// https://docs.python.org/3/library/stdtypes.html#list + +/* +// slots to implement for list compatibility +slice retrieve +binaryfunc mp_subscript; + +slice set/delete +objobjargproc mp_ass_subscript; + +concat/repeat +binaryfunc sq_concat; +ssizeargfunc sq_repeat; +binaryfunc sq_inplace_concat; +ssizeargfunc sq_inplace_repeat; +*/ + +%enddef + +// define C exported wrappers to bind to the slots that forward to templated implementations +%define ARRAY_DEFINE_SLOTS(array_type, unique_name) + +// define C-exported wrappers that forward to templated implementations +%wrapper %{ +PyObject *repr_##unique_name(PyObject *self) +{ + array_type *thisptr = array_thisptr(self); + + if(!thisptr) + return NULL; + + return array_repr(thisptr); +} + +PyObject *getitem_##unique_name(PyObject *self, Py_ssize_t idx) +{ + array_type *thisptr = array_thisptr(self); + + if(!thisptr) + return NULL; + + return array_getitem(thisptr, idx); +} + +int setitem_##unique_name(PyObject *self, Py_ssize_t idx, PyObject *val) +{ + array_type *thisptr = array_thisptr(self); + + if(!thisptr) + return -1; + + return array_setitem(thisptr, idx, val); +} + +int length_##unique_name(PyObject *self, Py_ssize_t idx, PyObject *val) +{ + array_type *thisptr = array_thisptr(self); + + if(!thisptr) + return -1; + + return array_len(thisptr); +} +%} + +%enddef + +%define ARRAY_INSTANTIATION_CHECK_NAME(typeName) add_your_use_of_##typeName##_to_swig_interface %enddef + +// these pair of macros (TEMPLATE_ARRAY_DECLARE / NON_TEMPLATE_ARRAY_INSTANTIATE) are defined before +// including the header that defines a class, to set up typemaps and other things that must be +// available before the definition is parsed. +// +// Afterwards, you must call EXTEND_ARRAY_CLASS_METHODS to add the necessary named methods. + +%define TEMPLATE_ARRAY_DECLARE(typeName) + +LIST_MODIFY_IN_PLACE_TYPEMAP(typeName) + +// add a check to make sure that we explicitly instantiate all uses of this template (as a reference +// type, not as a purely in parameter to a function - those are converted by value to C++ to allow +// passing pure lists that aren't C++ side at all). +%header %{ +template +void ARRAY_INSTANTIATION_CHECK_NAME(typeName)(typeName *) +{ + // TODO remove this to make it an error to miss the instantiation of this array type +} +%} + +// override these typemaps to instantiate a checking template. +%typemap(check) typeName * { ARRAY_INSTANTIATION_CHECK_NAME(typeName)($1); } +%typemap(check) typeName & { ARRAY_INSTANTIATION_CHECK_NAME(typeName)($1); } +%typemap(check) typeName { ARRAY_INSTANTIATION_CHECK_NAME(typeName)($1); } +%typemap(ret) typeName * { ARRAY_INSTANTIATION_CHECK_NAME(typeName)($1); } +%typemap(ret) typeName & { ARRAY_INSTANTIATION_CHECK_NAME(typeName)($1); } +%typemap(ret) typeName { ARRAY_INSTANTIATION_CHECK_NAME(typeName)(&$1); } + +%enddef + +%define NON_TEMPLATE_ARRAY_INSTANTIATE(typeName) + +ARRAY_ADD_SLOTS(typeName, typeName) +ARRAY_DEFINE_SLOTS(typeName, typeName) + +LIST_MODIFY_IN_PLACE_TYPEMAP(typeName) + +%enddef + +// this instantiates a templated array for a particular type and sets it up to act like a python +// sequence. +%define TEMPLATE_ARRAY_INSTANTIATE(arrayType, innerType) + +ARRAY_ADD_SLOTS(arrayType, arrayType##_of_##innerType) + +// instantiate template +%rename(arrayType##_of_##innerType) arrayType; +%template(arrayType##_of_##innerType) arrayType; + +ARRAY_DEFINE_SLOTS(arrayType, arrayType##_of_##innerType) + +%header %{ + +template<> +void ARRAY_INSTANTIATION_CHECK_NAME(arrayType)(arrayType *) +{ +} + +%} + +%enddef diff --git a/qrenderdoc/Code/pyrenderdoc/cosmetics.i b/qrenderdoc/Code/pyrenderdoc/cosmetics.i new file mode 100644 index 000000000..63efa3e42 --- /dev/null +++ b/qrenderdoc/Code/pyrenderdoc/cosmetics.i @@ -0,0 +1,43 @@ + +// add some useful builtin functions for ResourceId +%feature("python:tp_str") ResourceId "resid_str"; +%feature("python:tp_repr") ResourceId "resid_str"; +%feature("python:nb_int") ResourceId "resid_int"; + +%wrapper %{ +static PyObject *resid_str(PyObject *resid) +{ + void *resptr = NULL; + unsigned long long *id = NULL; + int res = SWIG_ConvertPtr(resid, &resptr, SWIGTYPE_p_ResourceId, 0); + if (!SWIG_IsOK(res)) { + SWIG_exception_fail(SWIG_ArgError(res), "in method 'ResourceId.str', ResourceId is not correct type"); + } + + // cast as unsigned long long + id = (unsigned long long *)resptr; + static_assert(sizeof(unsigned long long) == sizeof(ResourceId), "Wrong size"); + + return PyUnicode_FromFormat("", *id); +fail: + return NULL; +} + +static PyObject *resid_int(PyObject *resid) +{ + void *resptr = NULL; + unsigned long long *id = NULL; + int res = SWIG_ConvertPtr(resid, &resptr, SWIGTYPE_p_ResourceId, 0); + if (!SWIG_IsOK(res)) { + SWIG_exception_fail(SWIG_ArgError(res), "in method 'ResourceId.str', ResourceId is not correct type"); + } + + // cast as unsigned long long + id = (unsigned long long *)resptr; + static_assert(sizeof(unsigned long long) == sizeof(ResourceId), "Wrong size"); + + return PyLong_FromUnsignedLongLong(*id); +fail: + return NULL; +} +%} \ No newline at end of file diff --git a/qrenderdoc/Code/pyrenderdoc/function_conversion.h b/qrenderdoc/Code/pyrenderdoc/function_conversion.h new file mode 100644 index 000000000..276f45029 --- /dev/null +++ b/qrenderdoc/Code/pyrenderdoc/function_conversion.h @@ -0,0 +1,215 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2017 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#pragma once + +// this is defined elsewhere for managing the opaque global_handle object +extern "C" PyThreadState *GetExecutingThreadState(PyObject *global_handle); +extern "C" void HandleException(PyObject *global_handle); +extern "C" bool IsThreadBlocking(PyObject *global_handle); +extern "C" void SetThreadBlocking(PyObject *global_handle, bool block); + +struct ExceptionHandling +{ + bool failFlag = false; + PyObject *exObj = NULL; + PyObject *valueObj = NULL; + PyObject *tracebackObj = NULL; +}; + +// this function handles failures in callback functions. If we're synchronously calling the callback +// from within an execute scope, then we can assign to failflag and let the error propagate upwards. +// If we're not, then the callback is being executed on another thread with no knowledge of python, +// so we need to use the global handle to try and emit the exception through the context. None of +// this is multi-threaded because we're inside the GIL at all times +inline void HandleCallbackFailure(PyObject *global_handle, ExceptionHandling &exHandle) +{ + // if there's no global handle assume we are not running in the usual environment, so there are no + // external-to-python threads + if(!global_handle) + { + exHandle.failFlag = true; + return; + } + + PyThreadState *current = PyGILState_GetThisThreadState(); + PyThreadState *executing = GetExecutingThreadState(global_handle); + + // we are executing synchronously, set the flag and return + if(current == executing) + { + exHandle.failFlag = true; + return; + } + + // if we have the blocking flag set, then we may be on another thread but we can still propagate + // up the error + if(IsThreadBlocking(global_handle)) + { + exHandle.failFlag = true; + + // we need to rethrow the exception to that thread, so fetch (and clear it) on this thread. + // + // Note that the exception can only propagate up to one place. However since we know that python + // is inherently single threaded, so if we're doing this blocking funciton call on another + // thread then we *know* there isn't python further up the stack. Therefore we're safe to + // swallow the exception here (since there's nowhere for it to bubble up to anyway) and rethrow + // on the python thread. + PyErr_Fetch(&exHandle.exObj, &exHandle.valueObj, &exHandle.tracebackObj); + + return; + } + + // in this case we are executing asynchronously, and must handle the exception manually as there's + // nothing above us that knows about python exceptions + HandleException(global_handle); +} + +template +inline T get_return(const char *funcname, PyObject *result, PyObject *global_handle, + ExceptionHandling &exHandle) +{ + T val = T(); + + int res = ConvertToPy(result, val); + + if(!SWIG_IsOK(res)) + { + HandleCallbackFailure(global_handle, exHandle); + + PyErr_Format(PyExc_TypeError, "Expected a '%s' for return value of callback in %s", + TypeName(), funcname); + } + + Py_XDECREF(result); + + return val; +} + +template <> +inline void get_return(const char *funcname, PyObject *result, PyObject *global_handle, + ExceptionHandling &exHandle) +{ + Py_XDECREF(result); +} + +template +struct varfunc +{ + varfunc(const char *funcname, paramTypes... params) + { + args = PyTuple_New(sizeof...(paramTypes)); + + currentarg = 0; + + // avoid unused parameter errors when calling a parameter-less function + (void)funcname; + + using expand_type = int[]; + (void)expand_type{0, (push_arg(funcname, params), 0)...}; + } + + template + void push_arg(const char *funcname, const T &arg) + { + if(!args) + return; + + PyObject *obj = ConvertToPy(arg); + + if(!obj) + { + Py_DecRef(args); + args = NULL; + + PyErr_Format(PyExc_TypeError, "Unexpected type for arg %d of callback in %s", currentarg + 1, + funcname); + + return; + } + + PyTuple_SetItem(args, currentarg++, obj); + } + + ~varfunc() { Py_XDECREF(args); } + rettype call(const char *funcname, PyObject *func, PyObject *global_handle, + ExceptionHandling &exHandle) + { + if(!func || func == Py_None || !PyCallable_Check(func) || !args) + { + HandleCallbackFailure(global_handle, exHandle); + return rettype(); + } + + PyObject *result = PyObject_Call(func, args, 0); + + if(result == NULL) + HandleCallbackFailure(global_handle, exHandle); + + Py_DECREF(args); + + return get_return(funcname, result, global_handle, exHandle); + } + + int currentarg = 0; + PyObject *args; +}; + +struct ScopedFuncCall +{ + ScopedFuncCall(PyObject *h) + { + handle = h; + Py_XINCREF(handle); + gil = PyGILState_Ensure(); + } + + ~ScopedFuncCall() + { + Py_XDECREF(handle); + PyGILState_Release(gil); + } + + PyObject *handle; + PyGILState_STATE gil; +}; + +template +funcType ConvertFunc(const char *funcname, PyObject *func, ExceptionHandling &exHandle) +{ + // add a reference to the global object so it stays alive while we execute, in case this is an + // async call + PyObject *global_internal_handle = NULL; + + PyObject *globals = PyEval_GetGlobals(); + if(globals) + global_internal_handle = PyDict_GetItemString(globals, "_renderdoc_internal"); + + return [global_internal_handle, funcname, func, &exHandle](auto... param) { + ScopedFuncCall gil(global_internal_handle); + + varfunc f(funcname, param...); + return f.call(funcname, func, global_internal_handle, exHandle); + }; +} diff --git a/qrenderdoc/Code/pyrenderdoc/pyconversion.h b/qrenderdoc/Code/pyrenderdoc/pyconversion.h index 1ded1dffb..bd65b72b8 100644 --- a/qrenderdoc/Code/pyrenderdoc/pyconversion.h +++ b/qrenderdoc/Code/pyrenderdoc/pyconversion.h @@ -24,8 +24,8 @@ #pragma once +#include #include - // struct to allow partial specialisation for enums template ::value> struct TypeConversion @@ -458,6 +458,8 @@ struct TypeConversion, false> if(elem) { PyList_Append(list, elem); + // release our reference + Py_DecRef(elem); } else { @@ -551,294 +553,7 @@ struct TypeConversion } }; -#ifdef ENABLE_QT_CONVERT - -template <> -struct TypeConversion -{ - static int ConvertFromPy(PyObject *in, QString &out) - { - if(PyUnicode_Check(in)) - { - PyObject *bytes = PyUnicode_AsUTF8String(in); - - if(!bytes) - return SWIG_ERROR; - - char *buf = NULL; - Py_ssize_t size = 0; - - int ret = PyBytes_AsStringAndSize(bytes, &buf, &size); - - if(ret == 0) - { - out = QString::fromUtf8(buf, (int)size); - - Py_DecRef(bytes); - - return SWIG_OK; - } - - Py_DecRef(bytes); - - return SWIG_ERROR; - } - - return SWIG_ERROR; - } - - static PyObject *ConvertToPy(const QString &in) - { - QByteArray bytes = in.toUtf8(); - return PyUnicode_FromStringAndSize(bytes.data(), bytes.size()); - } -}; - -template <> -struct TypeConversion -{ - static int ConvertFromPy(PyObject *in, QDateTime &out) - { - if(!PyDateTime_Check(in)) - return SWIG_TypeError; - - QDate date(PyDateTime_GET_YEAR(in), PyDateTime_GET_MONTH(in), PyDateTime_GET_DAY(in)); - QTime time(PyDateTime_DATE_GET_HOUR(in), PyDateTime_DATE_GET_MINUTE(in), - PyDateTime_DATE_GET_SECOND(in), PyDateTime_DATE_GET_MICROSECOND(in) / 1000); - - out = QDateTime(date, time, QTimeZone::utc()); - - return SWIG_OK; - } - - static PyObject *ConvertToPy(const QDateTime &in) - { - QDate date = in.date(); - QTime time = in.time(); - return PyDateTime_FromDateAndTime(date.year(), date.month(), date.day(), time.hour(), - time.minute(), time.second(), time.msec() * 1000); - } -}; - -template -struct ContainerConversion -{ - // we add some extra parameters so the typemaps for array can use these to get - // nicer failure error messages out with the index that failed - static int ConvertFromPy(PyObject *in, Container &out, int *failIdx) - { - if(!PyList_Check(in)) - return SWIG_TypeError; - - Py_ssize_t len = PyList_Size(in); - - for(Py_ssize_t i = 0; i < len; i++) - { - U u; - int ret = TypeConversion::ConvertFromPy(PyList_GetItem(in, i), u); - if(!SWIG_IsOK(ret)) - { - if(failIdx) - *failIdx = i; - return ret; - } - out.append(u); - } - - return SWIG_OK; - } - - static int ConvertFromPy(PyObject *in, Container &out) { return ConvertFromPy(in, out, NULL); } - static PyObject *ConvertToPyInPlace(PyObject *list, const Container &in, int *failIdx) - { - for(int i = 0; i < in.size(); i++) - { - PyObject *elem = TypeConversion::ConvertToPy(in[i]); - - if(elem) - { - PyList_Append(list, elem); - } - else - { - if(failIdx) - *failIdx = i; - - return NULL; - } - } - - return list; - } - - static PyObject *ConvertToPy(const Container &in, int *failIdx) - { - PyObject *list = PyList_New(0); - if(!list) - return NULL; - - PyObject *ret = ConvertToPyInPlace(list, in, failIdx); - - // if a failure happened, don't leak the list we created - if(!ret) - Py_XDECREF(list); - - return ret; - } - - static PyObject *ConvertToPy(const Container &in) { return ConvertToPy(in, NULL); } -}; - -template -struct TypeConversion, false> : ContainerConversion, U> -{ -}; - -template <> -struct TypeConversion : ContainerConversion, QString> -{ -}; - -template -struct TypeConversion, false> : ContainerConversion, U> -{ -}; - -// specialisation for pair -template -struct TypeConversion, false> -{ - static int ConvertFromPy(PyObject *in, QPair &out) - { - if(!PyTuple_Check(in)) - return SWIG_TypeError; - - Py_ssize_t size = PyTuple_Size(in); - - if(size != 2) - return SWIG_TypeError; - - int ret = TypeConversion::ConvertFromPy(PyTuple_GetItem(in, 0), out.first); - if(SWIG_IsOK(ret)) - ret = TypeConversion::ConvertFromPy(PyTuple_GetItem(in, 1), out.second); - - return ret; - } - - static PyObject *ConvertToPy(const QPair &in) - { - PyObject *first = TypeConversion::ConvertToPy(in.first); - if(!first) - return NULL; - - PyObject *second = TypeConversion::ConvertToPy(in.second); - if(!second) - return NULL; - - PyObject *ret = PyTuple_New(2); - if(!ret) - return NULL; - - PyTuple_SetItem(ret, 0, first); - PyTuple_SetItem(ret, 1, second); - - return ret; - } -}; - -template -struct TypeConversion, false> -{ - // we add some extra parameters so the typemaps for array can use these to get - // nicer failure error messages out with the index that failed - static int ConvertFromPy(PyObject *in, QMap &out, int *failIdx) - { - if(!PyDict_Check(in)) - return SWIG_TypeError; - - PyObject *keys = PyDict_Keys(in); - - if(!keys) - return SWIG_TypeError; - - Py_ssize_t len = PyList_Size(keys); - - for(Py_ssize_t i = 0; i < len; i++) - { - K k; - V v; - - PyObject *key = PyList_GetItem(keys, i); - PyObject *value = PyDict_GetItem(in, key); - int ret = TypeConversion::ConvertFromPy(key, k); - int ret2 = TypeConversion::ConvertFromPy(value, v); - if(!SWIG_IsOK(ret) || !SWIG_IsOK(ret2)) - { - if(failIdx) - *failIdx = i; - Py_DecRef(keys); - if(!SWIG_IsOK(ret)) - return ret; - else - return ret2; - } - out.insert(k, v); - } - - Py_DecRef(keys); - - return SWIG_OK; - } - - static int ConvertFromPy(PyObject *in, QMap &out) { return ConvertFromPy(in, out, NULL); } - static PyObject *ConvertToPyInPlace(PyObject *pymap, const QMap &in, int *failIdx) - { - QList keys = in.keys(); - - for(int i = 0; i < keys.size(); i++) - { - const K &k = keys[i]; - PyObject *key = TypeConversion::ConvertToPy(k); - - if(key) - { - PyObject *value = TypeConversion::ConvertToPy(in[k]); - - if(value) - { - PyDict_SetItem(pymap, key, value); - continue; - } - } - - if(failIdx) - *failIdx = i; - - return NULL; - } - - return pymap; - } - - static PyObject *ConvertToPy(const QMap &in, int *failIdx) - { - PyObject *list = PyDict_New(); - if(!list) - return NULL; - - PyObject *ret = ConvertToPyInPlace(list, in, failIdx); - - // if a failure happened, don't leak the map we created - if(!ret) - Py_XDECREF(list); - - return ret; - } - - static PyObject *ConvertToPy(const QMap &in) { return ConvertToPy(in, NULL); } -}; - -#endif +#include "qt_conversion.h" // free functions forward to struct template @@ -853,196 +568,6 @@ PyObject *ConvertToPy(const T &in) return TypeConversion::ConvertToPy(in); } -// this is defined elsewhere for managing the opaque global_handle object -extern "C" PyThreadState *GetExecutingThreadState(PyObject *global_handle); -extern "C" void HandleException(PyObject *global_handle); -extern "C" bool IsThreadBlocking(PyObject *global_handle); -extern "C" void SetThreadBlocking(PyObject *global_handle, bool block); - -struct ExceptionHandling -{ - bool failFlag = false; - PyObject *exObj = NULL; - PyObject *valueObj = NULL; - PyObject *tracebackObj = NULL; -}; - -// this function handles failures in callback functions. If we're synchronously calling the callback -// from within an execute scope, then we can assign to failflag and let the error propagate upwards. -// If we're not, then the callback is being executed on another thread with no knowledge of python, -// so we need to use the global handle to try and emit the exception through the context. None of -// this is multi-threaded because we're inside the GIL at all times -inline void HandleCallbackFailure(PyObject *global_handle, ExceptionHandling &exHandle) -{ - // if there's no global handle assume we are not running in the usual environment, so there are no - // external-to-python threads - if(!global_handle) - { - exHandle.failFlag = true; - return; - } - - PyThreadState *current = PyGILState_GetThisThreadState(); - PyThreadState *executing = GetExecutingThreadState(global_handle); - - // we are executing synchronously, set the flag and return - if(current == executing) - { - exHandle.failFlag = true; - return; - } - - // if we have the blocking flag set, then we may be on another thread but we can still propagate - // up the error - if(IsThreadBlocking(global_handle)) - { - exHandle.failFlag = true; - - // we need to rethrow the exception to that thread, so fetch (and clear it) on this thread. - // - // Note that the exception can only propagate up to one place. However since we know that python - // is inherently single threaded, so if we're doing this blocking funciton call on another - // thread then we *know* there isn't python further up the stack. Therefore we're safe to - // swallow the exception here (since there's nowhere for it to bubble up to anyway) and rethrow - // on the python thread. - PyErr_Fetch(&exHandle.exObj, &exHandle.valueObj, &exHandle.tracebackObj); - - return; - } - - // in this case we are executing asynchronously, and must handle the exception manually as there's - // nothing above us that knows about python exceptions - HandleException(global_handle); -} - -template -inline T get_return(const char *funcname, PyObject *result, PyObject *global_handle, - ExceptionHandling &exHandle) -{ - T val = T(); - - int res = ConvertToPy(result, val); - - if(!SWIG_IsOK(res)) - { - HandleCallbackFailure(global_handle, exHandle); - - PyErr_Format(PyExc_TypeError, "Expected a '%s' for return value of callback in %s", - TypeName(), funcname); - } - - Py_XDECREF(result); - - return val; -} - -template <> -inline void get_return(const char *funcname, PyObject *result, PyObject *global_handle, - ExceptionHandling &exHandle) -{ - Py_XDECREF(result); -} - -template -struct varfunc -{ - varfunc(const char *funcname, paramTypes... params) - { - args = PyTuple_New(sizeof...(paramTypes)); - - currentarg = 0; - - // avoid unused parameter errors when calling a parameter-less function - (void)funcname; - - using expand_type = int[]; - (void)expand_type{0, (push_arg(funcname, params), 0)...}; - } - - template - void push_arg(const char *funcname, const T &arg) - { - if(!args) - return; - - PyObject *obj = ConvertToPy(arg); - - if(!obj) - { - Py_DecRef(args); - args = NULL; - - PyErr_Format(PyExc_TypeError, "Unexpected type for arg %d of callback in %s", currentarg + 1, - funcname); - - return; - } - - PyTuple_SetItem(args, currentarg++, obj); - } - - ~varfunc() { Py_XDECREF(args); } - rettype call(const char *funcname, PyObject *func, PyObject *global_handle, - ExceptionHandling &exHandle) - { - if(!func || func == Py_None || !PyCallable_Check(func) || !args) - { - HandleCallbackFailure(global_handle, exHandle); - return rettype(); - } - - PyObject *result = PyObject_Call(func, args, 0); - - if(result == NULL) - HandleCallbackFailure(global_handle, exHandle); - - Py_DECREF(args); - - return get_return(funcname, result, global_handle, exHandle); - } - - int currentarg = 0; - PyObject *args; -}; - -struct ScopedFuncCall -{ - ScopedFuncCall(PyObject *h) - { - handle = h; - Py_XINCREF(handle); - gil = PyGILState_Ensure(); - } - - ~ScopedFuncCall() - { - Py_XDECREF(handle); - PyGILState_Release(gil); - } - - PyObject *handle; - PyGILState_STATE gil; -}; - -template -funcType ConvertFunc(const char *funcname, PyObject *func, ExceptionHandling &exHandle) -{ - // add a reference to the global object so it stays alive while we execute, in case this is an - // async call - PyObject *global_internal_handle = NULL; - - PyObject *globals = PyEval_GetGlobals(); - if(globals) - global_internal_handle = PyDict_GetItemString(globals, "_renderdoc_internal"); - - return [global_internal_handle, funcname, func, &exHandle](auto... param) { - ScopedFuncCall gil(global_internal_handle); - - varfunc f(funcname, param...); - return f.call(funcname, func, global_internal_handle, exHandle); - }; -} - namespace { template ::value> @@ -1096,3 +621,7 @@ inline typename std::remove_pointer::type &indirect(T &ptr) { return pointer_unwrap::indirect(ptr); } + +#include "function_conversion.h" + +#include "container_handling.h" diff --git a/qrenderdoc/Code/pyrenderdoc/pyconversion.i b/qrenderdoc/Code/pyrenderdoc/pyconversion.i index d913b907b..1354ee398 100644 --- a/qrenderdoc/Code/pyrenderdoc/pyconversion.i +++ b/qrenderdoc/Code/pyrenderdoc/pyconversion.i @@ -1,5 +1,8 @@ // this file is included from renderdoc.i, it's not a module in itself +%define STRINGIZE(val) #val %enddef + +/////////////////////////////////////////////////////////////////////////////////////////////// // typemaps for more sensible fixed-array handling, based on typemaps from SWIG documentation %define FIXED_ARRAY_TYPEMAPS(BaseType) @@ -50,8 +53,12 @@ %enddef +/////////////////////////////////////////////////////////////////////////////////////////////// +// simple typemaps for an object that's converted directly by-value. This is perfect for python +// immutable objects like strings or datetimes + %define SIMPLE_TYPEMAPS_VARIANT(BaseType, SimpleType) -%typemap(in, fragment="pyconvert") SimpleType (BaseType temp) { +%typemap(in) SimpleType (BaseType temp) { tempset($1, &temp); int res = ConvertFromPy($input, indirect($1)); @@ -61,8 +68,8 @@ } } -%typemap(out, fragment="pyconvert") SimpleType { - $result = ConvertToPy( indirect($1)); +%typemap(out) SimpleType { + $result = ConvertToPy(indirect($1)); } %enddef @@ -74,50 +81,44 @@ SIMPLE_TYPEMAPS_VARIANT(SimpleType, SimpleType &) %enddef -%define CONTAINER_TYPEMAPS_VARIANT(ContainerType) +/////////////////////////////////////////////////////////////////////////////////////////////// +// typemaps for std::function -%typemap(in, fragment="pyconvert") ContainerType (unsigned char tempmem[32]) { - static_assert(sizeof(tempmem) >= sizeof(std::remove_pointer::type), "not enough temp space for $1_basetype"); +%typemap(in) std::function { + PyObject *func = $input; + $1 = ConvertFunc<$1_ltype>("$symname", func, exHandle$argnum); +} + +%typemap(argout) std::function (ExceptionHandling exHandle) { + if(exHandle.failFlag) { + PyErr_Restore(exHandle.exObj, exHandle.valueObj, exHandle.tracebackObj); + SWIG_fail; + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +// inserted code to include C++ conversion header + +%{ + #include "renderdoc_replay.h" + + static char convert_error[1024] = {}; - tempalloc($1, tempmem); + #include "Code/pyrenderdoc/pyconversion.h" - int failIdx = 0; - int res = TypeConversion::type>::ConvertFromPy($input, indirect($1), &failIdx); + // declare the basic building blocks as stringize types + DECLARE_REFLECTION_STRUCT(int8_t); + DECLARE_REFLECTION_STRUCT(uint8_t); + DECLARE_REFLECTION_STRUCT(int16_t); + DECLARE_REFLECTION_STRUCT(uint16_t); + DECLARE_REFLECTION_STRUCT(int32_t); + DECLARE_REFLECTION_STRUCT(uint32_t); + DECLARE_REFLECTION_STRUCT(int64_t); + DECLARE_REFLECTION_STRUCT(uint64_t); + DECLARE_REFLECTION_STRUCT(float); + DECLARE_REFLECTION_STRUCT(double); + DECLARE_REFLECTION_STRUCT(rdcstr); - if(!SWIG_IsOK(res)) - { - if(res == SWIG_TypeError) - { - SWIG_exception_fail(SWIG_ArgError(res), "in method '$symname' argument $argnum of type '$1_basetype'"); - } - else - { - snprintf(convert_error, sizeof(convert_error)-1, "in method '$symname' argument $argnum of type '$1_basetype', decoding element %d", failIdx); - SWIG_exception_fail(SWIG_ArgError(res), convert_error); - } - } -} +%} -%typemap(freearg, fragment="pyconvert") ContainerType { - tempdealloc($1); -} - -%typemap(out, fragment="pyconvert") ContainerType { - int failIdx = 0; - $result = TypeConversion::type>::ConvertToPy( indirect($1), &failIdx); - if(!$result) - { - snprintf(convert_error, sizeof(convert_error)-1, "in method '$symname' returning type '$1_basetype', encoding element %d", failIdx); - SWIG_exception_fail(SWIG_ValueError, convert_error); - } -} - -%enddef - -%define CONTAINER_TYPEMAPS(ContainerType) - -CONTAINER_TYPEMAPS_VARIANT(ContainerType) -CONTAINER_TYPEMAPS_VARIANT(ContainerType *) -CONTAINER_TYPEMAPS_VARIANT(ContainerType &) - -%enddef +%include "container_handling.i" diff --git a/qrenderdoc/Code/pyrenderdoc/pyrenderdoc_module.vcxproj b/qrenderdoc/Code/pyrenderdoc/pyrenderdoc_module.vcxproj index f8eab9675..fad367e90 100644 --- a/qrenderdoc/Code/pyrenderdoc/pyrenderdoc_module.vcxproj +++ b/qrenderdoc/Code/pyrenderdoc/pyrenderdoc_module.vcxproj @@ -225,7 +225,7 @@ Document - %(Fullpath);$(SolutionDir)renderdoc\api\replay\basic_types.h;$(SolutionDir)renderdoc\api\replay\capture_options.h;$(SolutionDir)renderdoc\api\replay\control_types.h;$(SolutionDir)renderdoc\api\replay\d3d11_pipestate.h;$(SolutionDir)renderdoc\api\replay\d3d12_pipestate.h;$(SolutionDir)renderdoc\api\replay\data_types.h;$(SolutionDir)renderdoc\api\replay\gl_pipestate.h;$(SolutionDir)renderdoc\api\replay\renderdoc_replay.h;$(SolutionDir)renderdoc\api\replay\replay_enums.h;$(SolutionDir)renderdoc\api\replay\shader_types.h;$(SolutionDir)renderdoc\api\replay\version.h;$(SolutionDir)renderdoc\api\replay\vk_pipestate.h;%(AdditionalInputs) + %(Fullpath);container_handling.i;pyconversion.i;cosmetics.i;$(SolutionDir)renderdoc\api\replay\basic_types.h;$(SolutionDir)renderdoc\api\replay\capture_options.h;$(SolutionDir)renderdoc\api\replay\control_types.h;$(SolutionDir)renderdoc\api\replay\d3d11_pipestate.h;$(SolutionDir)renderdoc\api\replay\d3d12_pipestate.h;$(SolutionDir)renderdoc\api\replay\data_types.h;$(SolutionDir)renderdoc\api\replay\gl_pipestate.h;$(SolutionDir)renderdoc\api\replay\renderdoc_replay.h;$(SolutionDir)renderdoc\api\replay\replay_enums.h;$(SolutionDir)renderdoc\api\replay\shader_types.h;$(SolutionDir)renderdoc\api\replay\version.h;$(SolutionDir)renderdoc\api\replay\vk_pipestate.h;%(AdditionalInputs) $(ProjectDir)..\..\3rdparty\swig\swig.exe -v -Wextra -Werror -O -c++ -python -modern -modernargs -enumclass -fastunpack -py3 -builtin -I$(SolutionDir)renderdoc\api\replay -outdir $(IntDir)generated -o $(IntDir)generated\%(Filename)_python.cxx %(FullPath) Compiling SWIG interface $(IntDir)generated\%(Filename).py;$(IntDir)generated\r%(Filename)_python.cxx;%(Outputs) diff --git a/qrenderdoc/Code/pyrenderdoc/qrenderdoc.i b/qrenderdoc/Code/pyrenderdoc/qrenderdoc.i index 85d58ce8c..db114791f 100644 --- a/qrenderdoc/Code/pyrenderdoc/qrenderdoc.i +++ b/qrenderdoc/Code/pyrenderdoc/qrenderdoc.i @@ -10,21 +10,32 @@ %begin %{ -#undef slots + #undef slots +%} + +%{ + #define ENABLE_QT_CONVERT + + #include + #include + #include + #include + #include + #include + + #include "datetime.h" %} +%include "pyconversion.i" + // import the renderdoc interface that we depend on %import "renderdoc.i" SIMPLE_TYPEMAPS(QString) SIMPLE_TYPEMAPS(QDateTime) -SIMPLE_TYPEMAPS(QPair) -CONTAINER_TYPEMAPS(QList) -CONTAINER_TYPEMAPS(QStringList) -CONTAINER_TYPEMAPS(QVector) -CONTAINER_TYPEMAPS(QMap) +TEMPLATE_ARRAY_DECLARE(rdcarray); // pass QWidget objects to PySide %typemap(in) QWidget * { @@ -50,17 +61,6 @@ CONTAINER_TYPEMAPS(QMap) %rename("%(regex:/^I([A-Z].*)/\\1/)s", %$isclass) ""; %{ - #define ENABLE_QT_CONVERT - - #include - #include - #include - #include - #include - #include - - #include "datetime.h" - #ifndef slots #define slots #endif diff --git a/qrenderdoc/Code/pyrenderdoc/qt_conversion.h b/qrenderdoc/Code/pyrenderdoc/qt_conversion.h new file mode 100644 index 000000000..f295059ed --- /dev/null +++ b/qrenderdoc/Code/pyrenderdoc/qt_conversion.h @@ -0,0 +1,323 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2017 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#pragma once + +#ifdef ENABLE_QT_CONVERT + +template <> +struct TypeConversion +{ + static int ConvertFromPy(PyObject *in, QString &out) + { + if(PyUnicode_Check(in)) + { + PyObject *bytes = PyUnicode_AsUTF8String(in); + + if(!bytes) + return SWIG_ERROR; + + char *buf = NULL; + Py_ssize_t size = 0; + + int ret = PyBytes_AsStringAndSize(bytes, &buf, &size); + + if(ret == 0) + { + out = QString::fromUtf8(buf, (int)size); + + Py_DecRef(bytes); + + return SWIG_OK; + } + + Py_DecRef(bytes); + + return SWIG_ERROR; + } + + return SWIG_ERROR; + } + + static PyObject *ConvertToPy(const QString &in) + { + QByteArray bytes = in.toUtf8(); + return PyUnicode_FromStringAndSize(bytes.data(), bytes.size()); + } +}; + +template <> +struct TypeConversion +{ + static int ConvertFromPy(PyObject *in, QDateTime &out) + { + if(!PyDateTime_Check(in)) + return SWIG_TypeError; + + QDate date(PyDateTime_GET_YEAR(in), PyDateTime_GET_MONTH(in), PyDateTime_GET_DAY(in)); + QTime time(PyDateTime_DATE_GET_HOUR(in), PyDateTime_DATE_GET_MINUTE(in), + PyDateTime_DATE_GET_SECOND(in), PyDateTime_DATE_GET_MICROSECOND(in) / 1000); + + out = QDateTime(date, time, QTimeZone::utc()); + + return SWIG_OK; + } + + static PyObject *ConvertToPy(const QDateTime &in) + { + QDate date = in.date(); + QTime time = in.time(); + return PyDateTime_FromDateAndTime(date.year(), date.month(), date.day(), time.hour(), + time.minute(), time.second(), time.msec() * 1000); + } +}; + +template +struct ContainerConversion +{ + // we add some extra parameters so the typemaps for array can use these to get + // nicer failure error messages out with the index that failed + static int ConvertFromPy(PyObject *in, Container &out, int *failIdx) + { + if(!PyList_Check(in)) + return SWIG_TypeError; + + Py_ssize_t len = PyList_Size(in); + + for(Py_ssize_t i = 0; i < len; i++) + { + U u; + int ret = TypeConversion::ConvertFromPy(PyList_GetItem(in, i), u); + if(!SWIG_IsOK(ret)) + { + if(failIdx) + *failIdx = i; + return ret; + } + out.append(u); + } + + return SWIG_OK; + } + + static int ConvertFromPy(PyObject *in, Container &out) { return ConvertFromPy(in, out, NULL); } + static PyObject *ConvertToPyInPlace(PyObject *list, const Container &in, int *failIdx) + { + for(int i = 0; i < in.size(); i++) + { + PyObject *elem = TypeConversion::ConvertToPy(in[i]); + + if(elem) + { + PyList_Append(list, elem); + // release our reference + Py_DecRef(elem); + } + else + { + if(failIdx) + *failIdx = i; + + return NULL; + } + } + + return list; + } + + static PyObject *ConvertToPy(const Container &in, int *failIdx) + { + PyObject *list = PyList_New(0); + if(!list) + return NULL; + + PyObject *ret = ConvertToPyInPlace(list, in, failIdx); + + // if a failure happened, don't leak the list we created + if(!ret) + Py_XDECREF(list); + + return ret; + } + + static PyObject *ConvertToPy(const Container &in) { return ConvertToPy(in, NULL); } +}; + +template +struct TypeConversion, false> : ContainerConversion, U> +{ +}; + +template <> +struct TypeConversion : ContainerConversion, QString> +{ +}; + +template +struct TypeConversion, false> : ContainerConversion, U> +{ +}; + +// specialisation for pair +template +struct TypeConversion, false> +{ + static int ConvertFromPy(PyObject *in, QPair &out) + { + if(!PyTuple_Check(in)) + return SWIG_TypeError; + + Py_ssize_t size = PyTuple_Size(in); + + if(size != 2) + return SWIG_TypeError; + + int ret = TypeConversion::ConvertFromPy(PyTuple_GetItem(in, 0), out.first); + if(SWIG_IsOK(ret)) + ret = TypeConversion::ConvertFromPy(PyTuple_GetItem(in, 1), out.second); + + return ret; + } + + static PyObject *ConvertToPy(const QPair &in) + { + PyObject *first = TypeConversion::ConvertToPy(in.first); + if(!first) + return NULL; + + PyObject *second = TypeConversion::ConvertToPy(in.second); + if(!second) + return NULL; + + PyObject *ret = PyTuple_New(2); + if(!ret) + return NULL; + + PyTuple_SetItem(ret, 0, first); + PyTuple_SetItem(ret, 1, second); + + return ret; + } +}; + +template +struct TypeConversion, false> +{ + // we add some extra parameters so the typemaps for array can use these to get + // nicer failure error messages out with the index that failed + static int ConvertFromPy(PyObject *in, QMap &out, int *failIdx) + { + if(!PyDict_Check(in)) + return SWIG_TypeError; + + PyObject *keys = PyDict_Keys(in); + + if(!keys) + return SWIG_TypeError; + + Py_ssize_t len = PyList_Size(keys); + + for(Py_ssize_t i = 0; i < len; i++) + { + K k; + V v; + + PyObject *key = PyList_GetItem(keys, i); + PyObject *value = PyDict_GetItem(in, key); + int ret = TypeConversion::ConvertFromPy(key, k); + int ret2 = TypeConversion::ConvertFromPy(value, v); + if(!SWIG_IsOK(ret) || !SWIG_IsOK(ret2)) + { + if(failIdx) + *failIdx = i; + Py_DecRef(keys); + if(!SWIG_IsOK(ret)) + return ret; + else + return ret2; + } + out.insert(k, v); + } + + Py_DecRef(keys); + + return SWIG_OK; + } + + static int ConvertFromPy(PyObject *in, QMap &out) { return ConvertFromPy(in, out, NULL); } + static PyObject *ConvertToPyInPlace(PyObject *pymap, const QMap &in, int *failIdx) + { + QList keys = in.keys(); + + for(int i = 0; i < keys.size(); i++) + { + const K &k = keys[i]; + PyObject *key = TypeConversion::ConvertToPy(k); + + if(key) + { + PyObject *value = TypeConversion::ConvertToPy(in[k]); + + if(value) + { + PyDict_SetItem(pymap, key, value); + // release our reference + Py_DecRef(key); + // release our reference + Py_DecRef(value); + continue; + } + + // destroy unused key + Py_DecRef(key); + } + + if(failIdx) + *failIdx = i; + + return NULL; + } + + return pymap; + } + + static PyObject *ConvertToPy(const QMap &in, int *failIdx) + { + PyObject *list = PyDict_New(); + if(!list) + return NULL; + + PyObject *ret = ConvertToPyInPlace(list, in, failIdx); + + // if a failure happened, don't leak the map we created + if(!ret) + Py_XDECREF(list); + + return ret; + } + + static PyObject *ConvertToPy(const QMap &in) { return ConvertToPy(in, NULL); } +}; + +#endif diff --git a/qrenderdoc/Code/pyrenderdoc/renderdoc.i b/qrenderdoc/Code/pyrenderdoc/renderdoc.i index 5c9c77cb3..cb346a484 100644 --- a/qrenderdoc/Code/pyrenderdoc/renderdoc.i +++ b/qrenderdoc/Code/pyrenderdoc/renderdoc.i @@ -42,42 +42,13 @@ %} -%fragment("pyconvert", "header") { - static char convert_error[1024] = {}; - - %#include "Code/pyrenderdoc/pyconversion.h" -} - %include "pyconversion.i" -SIMPLE_TYPEMAPS(rdcstr) -CONTAINER_TYPEMAPS(rdcarray) -CONTAINER_TYPEMAPS(rdcpair) +// Add custom conversion/__str__/__repr__ functions for beautification +%include "cosmetics.i" -FIXED_ARRAY_TYPEMAPS(ResourceId) -FIXED_ARRAY_TYPEMAPS(double) -FIXED_ARRAY_TYPEMAPS(float) -FIXED_ARRAY_TYPEMAPS(bool) -FIXED_ARRAY_TYPEMAPS(uint64_t) -FIXED_ARRAY_TYPEMAPS(uint32_t) -FIXED_ARRAY_TYPEMAPS(int32_t) -FIXED_ARRAY_TYPEMAPS(uint16_t) - -%typemap(in, fragment="pyconvert") std::function { - PyObject *func = $input; - $1 = ConvertFunc<$1_ltype>("$symname", func, exHandle$argnum); -} - -%typemap(argout) std::function (ExceptionHandling exHandle) { - if(exHandle.failFlag) { - PyErr_Restore(exHandle.exObj, exHandle.valueObj, exHandle.tracebackObj); - SWIG_fail; - } -} - -// ignore some operators SWIG doesn't have to worry about -// ignore array members +// ignore all the array member functions, only wrap the ones that are in python's list %ignore rdcarray::rdcarray; %ignore rdcarray::begin; %ignore rdcarray::end; @@ -94,6 +65,7 @@ FIXED_ARRAY_TYPEMAPS(uint16_t) %ignore rdcarray::empty; %ignore rdcarray::isEmpty; %ignore rdcarray::resize; +%ignore rdcarray::clear; %ignore rdcarray::reserve; %ignore rdcarray::swap; %ignore rdcarray::push_back; @@ -102,52 +74,25 @@ FIXED_ARRAY_TYPEMAPS(uint16_t) %ignore rdcstr::operator=; %ignore rdcstr::operator std::string; -// add __str__ functions -%feature("python:tp_str") ResourceId "resid_str"; -%feature("python:tp_repr") ResourceId "resid_str"; -%feature("python:nb_int") ResourceId "resid_int"; +SIMPLE_TYPEMAPS(rdcstr) +SIMPLE_TYPEMAPS(bytebuf) -%wrapper %{ -static PyObject *resid_str(PyObject *resid) -{ - void *resptr = NULL; - unsigned long long *id = NULL; - int res = SWIG_ConvertPtr(resid, &resptr, SWIGTYPE_p_ResourceId, 0); - if (!SWIG_IsOK(res)) { - SWIG_exception_fail(SWIG_ArgError(res), "in method 'ResourceId.str', ResourceId is not correct type"); - } +FIXED_ARRAY_TYPEMAPS(ResourceId) +FIXED_ARRAY_TYPEMAPS(double) +FIXED_ARRAY_TYPEMAPS(float) +FIXED_ARRAY_TYPEMAPS(bool) +FIXED_ARRAY_TYPEMAPS(uint64_t) +FIXED_ARRAY_TYPEMAPS(uint32_t) +FIXED_ARRAY_TYPEMAPS(int32_t) +FIXED_ARRAY_TYPEMAPS(uint16_t) - // cast as unsigned long long - id = (unsigned long long *)resptr; - static_assert(sizeof(unsigned long long) == sizeof(ResourceId), "Wrong size"); +// these types are to be treated like python lists/arrays, and will be instantiated after declaration +// below +TEMPLATE_ARRAY_DECLARE(rdcarray); - return PyUnicode_FromFormat("", *id); -fail: - return NULL; -} - -static PyObject *resid_int(PyObject *resid) -{ - void *resptr = NULL; - unsigned long long *id = NULL; - int res = SWIG_ConvertPtr(resid, &resptr, SWIGTYPE_p_ResourceId, 0); - if (!SWIG_IsOK(res)) { - SWIG_exception_fail(SWIG_ArgError(res), "in method 'ResourceId.str', ResourceId is not correct type"); - } - - // cast as unsigned long long - id = (unsigned long long *)resptr; - static_assert(sizeof(unsigned long long) == sizeof(ResourceId), "Wrong size"); - - return PyLong_FromUnsignedLongLong(*id); -fail: - return NULL; -} -%} - -%{ - #include "renderdoc_replay.h" -%} +/////////////////////////////////////////////////////////////////////////////////////////// +// Actually include header files here. Note that swig is configured not to recurse, so we +// need to list all headers in include order that we want to process %include @@ -163,6 +108,25 @@ fail: %include "shader_types.h" %include "vk_pipestate.h" +%feature("docstring") ""; + +%extend rdcarray { + // we ignored insert and clear before, need to restore them so we can declare our own impls + %rename("%s") insert; + %rename("%s") clear; +} + +// add python array members that aren't in slots +EXTEND_ARRAY_CLASS_METHODS(rdcarray) + +// list of array types. These are the concrete types used in rdcarray that will be bound +// If you get an error with add_your_use_of_rdcarray_to_swig_interface missing, add your type here +// or in qrenderdoc.i, depending on which one is appropriate +TEMPLATE_ARRAY_INSTANTIATE(rdcarray, int) +TEMPLATE_ARRAY_INSTANTIATE(rdcarray, rdcstr) +TEMPLATE_ARRAY_INSTANTIATE(rdcarray, float) + +/////////////////////////////////////////////////////////////////////////////////////////// // declare a function for passing external objects into python %wrapper %{ @@ -177,6 +141,9 @@ PyObject *PassObjectToPython(const char *type, void *obj) %} +/////////////////////////////////////////////////////////////////////////////////////////// +// Check documentation for types is set up correctly + %header %{ #include #include "Code/pyrenderdoc/document_check.h" diff --git a/qrenderdoc/qrenderdoc_local.vcxproj b/qrenderdoc/qrenderdoc_local.vcxproj index b95b9f5ff..37f13b287 100644 --- a/qrenderdoc/qrenderdoc_local.vcxproj +++ b/qrenderdoc/qrenderdoc_local.vcxproj @@ -867,7 +867,10 @@ + + + @@ -1236,7 +1239,7 @@ Document - %(Fullpath);pyconversion.i;document_check.h;$(SolutionDir)renderdoc\api\replay\basic_types.h;$(SolutionDir)renderdoc\api\replay\capture_options.h;$(SolutionDir)renderdoc\api\replay\control_types.h;$(SolutionDir)renderdoc\api\replay\d3d11_pipestate.h;$(SolutionDir)renderdoc\api\replay\d3d12_pipestate.h;$(SolutionDir)renderdoc\api\replay\data_types.h;$(SolutionDir)renderdoc\api\replay\gl_pipestate.h;$(SolutionDir)renderdoc\api\replay\renderdoc_replay.h;$(SolutionDir)renderdoc\api\replay\replay_enums.h;$(SolutionDir)renderdoc\api\replay\shader_types.h;$(SolutionDir)renderdoc\api\replay\vk_pipestate.h;%(AdditionalInputs) + %(Fullpath);container_handling.i;pyconversion.i;cosmetics.i;document_check.h;$(SolutionDir)renderdoc\api\replay\basic_types.h;$(SolutionDir)renderdoc\api\replay\capture_options.h;$(SolutionDir)renderdoc\api\replay\control_types.h;$(SolutionDir)renderdoc\api\replay\d3d11_pipestate.h;$(SolutionDir)renderdoc\api\replay\d3d12_pipestate.h;$(SolutionDir)renderdoc\api\replay\data_types.h;$(SolutionDir)renderdoc\api\replay\gl_pipestate.h;$(SolutionDir)renderdoc\api\replay\renderdoc_replay.h;$(SolutionDir)renderdoc\api\replay\replay_enums.h;$(SolutionDir)renderdoc\api\replay\shader_types.h;$(SolutionDir)renderdoc\api\replay\vk_pipestate.h;%(AdditionalInputs) "$(ProjectDir)3rdparty\swig\swig.exe" -v -Wextra -Werror -O -c++ -python -modern -modernargs -enumclass -fastunpack -py3 -builtin -I"$(SolutionDir)renderdoc\api\replay" -outdir "$(IntDir)generated" -o "$(IntDir)generated\%(Filename)_python.cxx" "%(FullPath)" Compiling SWIG interface $(IntDir)generated\%(Filename).py;$(IntDir)generated\%(Filename)_python.cxx;%(Outputs) @@ -1447,8 +1450,10 @@ + + diff --git a/qrenderdoc/qrenderdoc_local.vcxproj.filters b/qrenderdoc/qrenderdoc_local.vcxproj.filters index 070b32956..fbe6272e3 100644 --- a/qrenderdoc/qrenderdoc_local.vcxproj.filters +++ b/qrenderdoc/qrenderdoc_local.vcxproj.filters @@ -986,6 +986,15 @@ Generated Files + + Code\pyrenderdoc + + + Code\pyrenderdoc + + + Code\pyrenderdoc + @@ -1168,6 +1177,12 @@ Code\pyrenderdoc + + Code\pyrenderdoc + + + Code\pyrenderdoc +