From 46b04925c39c33207b5ab9264bf8641a79a04b39 Mon Sep 17 00:00:00 2001 From: Jack Andersen Date: Mon, 12 Dec 2016 10:09:53 -1000 Subject: [PATCH] More graceful handling of non-installed blender --- hecl/blender/hecl_blendershell.py | 119 ++++++----- .../hecl/Blender/BlenderConnection.hpp | 32 +-- hecl/include/hecl/UniformBufferPool.hpp | 186 +++++++++++++++++ hecl/include/hecl/VertexBufferPool.hpp | 189 ++++++++++++++++++ hecl/lib/Blender/BlenderConnection.cpp | 180 ++++++++--------- hecl/lib/CMakeLists.txt | 2 + 6 files changed, 538 insertions(+), 170 deletions(-) create mode 100644 hecl/include/hecl/UniformBufferPool.hpp create mode 100644 hecl/include/hecl/VertexBufferPool.hpp diff --git a/hecl/blender/hecl_blendershell.py b/hecl/blender/hecl_blendershell.py index 8ac6ed57f..af5c30014 100644 --- a/hecl/blender/hecl_blendershell.py +++ b/hecl/blender/hecl_blendershell.py @@ -24,24 +24,21 @@ if 'TMPDIR' in os.environ: err_path += "/hecl_%016X.derp" % os.getpid() -def readpipeline(): - retval = bytearray() - while True: - ch = os.read(readfd, 1) - if ch == b'\n' or ch == b'': - return retval - retval += ch +def readpipestr(): + read_len = struct.unpack('I', os.read(readfd, 4))[0] + return os.read(readfd, read_len) -def writepipeline(linebytes): +def writepipestr(linebytes): #print('LINE', linebytes) - os.write(writefd, linebytes + b'\n') + os.write(writefd, struct.pack('I', len(linebytes))) + os.write(writefd, linebytes) def writepipebuf(linebytes): #print('BUF', linebytes) os.write(writefd, linebytes) def quitblender(): - writepipeline(b'QUITTING') + writepipestr(b'QUITTING') bpy.ops.wm.quit_blender() # If there's a third argument, use it as the .zip path containing the addon @@ -61,17 +58,17 @@ if bpy.context.user_preferences.addons.find('hecl') == -1: try: import hecl except: - writepipeline(b'NOADDON') + writepipestr(b'NOADDON') bpy.ops.wm.quit_blender() # Quit if just installed if did_install: - writepipeline(b'ADDONINSTALLED') + writepipestr(b'ADDONINSTALLED') bpy.ops.wm.quit_blender() # Intro handshake -writepipeline(b'READY') -ackbytes = readpipeline() +writepipestr(b'READY') +ackbytes = readpipestr() if ackbytes != b'ACK': quitblender() @@ -79,9 +76,9 @@ if ackbytes != b'ACK': orig_rot = bpy.context.object.rotation_mode try: bpy.context.object.rotation_mode = 'QUATERNION_SLERP' - writepipeline(b'SLERP1') + writepipestr(b'SLERP1') except: - writepipeline(b'SLERP0') + writepipestr(b'SLERP0') bpy.context.object.rotation_mode = orig_rot # Count brackets @@ -96,7 +93,7 @@ def count_brackets(linestr): # Read line of space-separated/quoted arguments def read_cmdargs(): - cmdline = readpipeline() + cmdline = readpipestr() if cmdline == b'': print('HECL connection lost') bpy.ops.wm.quit_blender() @@ -114,11 +111,11 @@ def exec_compbuf(compbuf, globals): # Command loop for writing animation key data to blender def animin_loop(globals): - writepipeline(b'ANIMREADY') + writepipestr(b'ANIMREADY') while True: crv_type = struct.unpack('b', os.read(readfd, 1)) if crv_type[0] < 0: - writepipeline(b'ANIMDONE') + writepipestr(b'ANIMDONE') return elif crv_type[0] == 0: crvs = globals['rotCurves'] @@ -146,13 +143,13 @@ def animin_loop(globals): # Command loop for reading data from blender def dataout_loop(): - writepipeline(b'READY') + writepipestr(b'READY') while True: cmdargs = read_cmdargs() print(cmdargs) if cmdargs[0] == 'DATAEND': - writepipeline(b'DONE') + writepipestr(b'DONE') return elif cmdargs[0] == 'MESHLIST': @@ -163,17 +160,17 @@ def dataout_loop(): writepipebuf(struct.pack('I', meshCount)) for meshobj in bpy.data.objects: if meshobj.type == 'MESH' and not meshobj.library: - writepipeline(meshobj.name.encode()) + writepipestr(meshobj.name.encode()) elif cmdargs[0] == 'MESHCOMPILE': maxSkinBanks = int(cmdargs[2]) meshName = bpy.context.scene.hecl_mesh_obj if meshName not in bpy.data.objects: - writepipeline(('mesh %s not found' % meshName).encode()) + writepipestr(('mesh %s not found' % meshName).encode()) continue - writepipeline(b'OK') + writepipestr(b'OK') hecl.hmdl.cook(writepipebuf, bpy.data.objects[meshName], cmdargs[1], maxSkinBanks) elif cmdargs[0] == 'MESHCOMPILENAME': @@ -181,20 +178,20 @@ def dataout_loop(): maxSkinBanks = int(cmdargs[3]) if meshName not in bpy.data.objects: - writepipeline(('mesh %s not found' % meshName).encode()) + writepipestr(('mesh %s not found' % meshName).encode()) continue - writepipeline(b'OK') + writepipestr(b'OK') hecl.hmdl.cook(writepipebuf, bpy.data.objects[meshName], cmdargs[2], maxSkinBanks) elif cmdargs[0] == 'MESHCOMPILENAMECOLLISION': meshName = cmdargs[1] if meshName not in bpy.data.objects: - writepipeline(('mesh %s not found' % meshName).encode()) + writepipestr(('mesh %s not found' % meshName).encode()) continue - writepipeline(b'OK') + writepipestr(b'OK') hecl.hmdl.cookcol(writepipebuf, bpy.data.objects[meshName]) elif cmdargs[0] == 'MESHCOMPILEALL': @@ -209,7 +206,7 @@ def dataout_loop(): bpy.context.scene.objects.active = join_obj bpy.ops.object.join() - writepipeline(b'OK') + writepipestr(b'OK') hecl.hmdl.cook(writepipebuf, join_obj, cmdargs[1], maxSkinBanks, maxOctantLength) bpy.context.scene.objects.unlink(join_obj) @@ -217,11 +214,11 @@ def dataout_loop(): bpy.data.meshes.remove(join_mesh) elif cmdargs[0] == 'WORLDCOMPILE': - writepipeline(b'OK') + writepipestr(b'OK') hecl.swld.cook(writepipebuf) elif cmdargs[0] == 'LIGHTCOMPILEALL': - writepipeline(b'OK') + writepipestr(b'OK') lampCount = 0; for obj in bpy.context.scene.objects: if obj.type == 'LAMP': @@ -292,7 +289,7 @@ def dataout_loop(): castShadow)) elif cmdargs[0] == 'GETTEXTURES': - writepipeline(b'OK') + writepipestr(b'OK') img_count = 0 for img in bpy.data.images: @@ -307,38 +304,38 @@ def dataout_loop(): writepipebuf(path.encode()) elif cmdargs[0] == 'ACTORCOMPILE': - writepipeline(b'OK') + writepipestr(b'OK') hecl.sact.cook(writepipebuf) elif cmdargs[0] == 'ACTORCOMPILECHARACTERONLY': - writepipeline(b'OK') + writepipestr(b'OK') hecl.sact.cook_character_only(writepipebuf) elif cmdargs[0] == 'GETARMATURENAMES': - writepipeline(b'OK') + writepipestr(b'OK') hecl.sact.get_armature_names(writepipebuf) elif cmdargs[0] == 'GETSUBTYPENAMES': - writepipeline(b'OK') + writepipestr(b'OK') hecl.sact.get_subtype_names(writepipebuf) elif cmdargs[0] == 'GETACTIONNAMES': - writepipeline(b'OK') + writepipestr(b'OK') hecl.sact.get_action_names(writepipebuf) elif cmdargs[0] == 'GETBONEMATRICES': armName = cmdargs[1] if armName not in bpy.data.objects: - writepipeline(('armature %s not found' % armName).encode()) + writepipestr(('armature %s not found' % armName).encode()) continue armObj = bpy.data.objects[armName] if armObj.type != 'ARMATURE': - writepipeline(('object %s not an ARMATURE' % armName).encode()) + writepipestr(('object %s not an ARMATURE' % armName).encode()) continue - writepipeline(b'OK') + writepipestr(b'OK') writepipebuf(struct.pack('I', len(armObj.data.bones))) for bone in armObj.data.bones: writepipebuf(struct.pack('I', len(bone.name))) @@ -364,9 +361,9 @@ try: if bpy.ops.object.mode_set.poll(): bpy.ops.object.mode_set(mode = 'OBJECT') loaded_blend = cmdargs[1] - writepipeline(b'FINISHED') + writepipestr(b'FINISHED') else: - writepipeline(b'CANCELLED') + writepipestr(b'CANCELLED') elif cmdargs[0] == 'CREATE': if len(cmdargs) >= 4: @@ -379,40 +376,40 @@ try: if 'FINISHED' in bpy.ops.wm.save_as_mainfile(filepath=cmdargs[1]): bpy.ops.file.hecl_patching_load() bpy.context.scene.hecl_type = cmdargs[2] - writepipeline(b'FINISHED') + writepipestr(b'FINISHED') else: - writepipeline(b'CANCELLED') + writepipestr(b'CANCELLED') elif cmdargs[0] == 'GETTYPE': - writepipeline(bpy.context.scene.hecl_type.encode()) + writepipestr(bpy.context.scene.hecl_type.encode()) elif cmdargs[0] == 'GETMESHRIGGED': meshName = bpy.context.scene.hecl_mesh_obj if meshName not in bpy.data.objects: - writepipeline(b'FALSE') + writepipestr(b'FALSE') else: if len(bpy.data.objects[meshName].vertex_groups): - writepipeline(b'TRUE') + writepipestr(b'TRUE') else: - writepipeline(b'FALSE') + writepipestr(b'FALSE') elif cmdargs[0] == 'SAVE': bpy.context.user_preferences.filepaths.save_version = 0 print('SAVING %s' % loaded_blend) if loaded_blend: if 'FINISHED' in bpy.ops.wm.save_as_mainfile(filepath=loaded_blend, check_existing=False, compress=True): - writepipeline(b'FINISHED') + writepipestr(b'FINISHED') else: - writepipeline(b'CANCELLED') + writepipestr(b'CANCELLED') elif cmdargs[0] == 'PYBEGIN': - writepipeline(b'READY') + writepipestr(b'READY') globals = {'hecl':hecl} compbuf = str() bracket_count = 0 while True: try: - line = readpipeline() + line = readpipestr() # ANIM check if line == b'PYANIM': @@ -429,13 +426,13 @@ try: if len(compbuf): exec_compbuf(compbuf, globals) compbuf = str() - writepipeline(b'DONE') + writepipestr(b'DONE') break # Syntax filter linestr = line.decode().rstrip() if not len(linestr) or linestr.lstrip()[0] == '#': - writepipeline(b'OK') + writepipestr(b'OK') continue leading_spaces = len(linestr) - len(linestr.lstrip()) @@ -445,7 +442,7 @@ try: compbuf += '\n' compbuf += linestr bracket_count += count_brackets(linestr) - writepipeline(b'OK') + writepipestr(b'OK') continue # Complete non-block statement in compbuf @@ -457,26 +454,26 @@ try: bracket_count += count_brackets(linestr) except Exception as e: - writepipeline(b'EXCEPTION') + writepipestr(b'EXCEPTION') raise break - writepipeline(b'OK') + writepipestr(b'OK') elif cmdargs[0] == 'PYEND': - writepipeline(b'ERROR') + writepipestr(b'ERROR') elif cmdargs[0] == 'DATABEGIN': try: dataout_loop() except Exception as e: - writepipeline(b'EXCEPTION') + writepipestr(b'EXCEPTION') raise elif cmdargs[0] == 'DATAEND': - writepipeline(b'ERROR') + writepipestr(b'ERROR') else: - hecl.command(cmdargs, writepipeline, writepipebuf) + hecl.command(cmdargs, writepipestr, writepipebuf) except Exception: import traceback diff --git a/hecl/include/hecl/Blender/BlenderConnection.hpp b/hecl/include/hecl/Blender/BlenderConnection.hpp index 2d6fe0b58..0e90ead54 100644 --- a/hecl/include/hecl/Blender/BlenderConnection.hpp +++ b/hecl/include/hecl/Blender/BlenderConnection.hpp @@ -66,8 +66,10 @@ private: ProjectPath m_loadedBlend; std::string m_startupBlend; hecl::SystemString m_errPath; - size_t _readLine(char* buf, size_t bufSz); - size_t _writeLine(const char* buf); + uint32_t _readStr(char* buf, uint32_t bufSz); + uint32_t _writeStr(const char* str, uint32_t len, int wpipe); + uint32_t _writeStr(const char* str, uint32_t len) { return _writeStr(str, len, m_writepipe[1]); } + uint32_t _writeStr(const char* str) { return _writeStr(str, strlen(str)); } size_t _readBuf(void* buf, size_t len); size_t _writeBuf(const void* buf, size_t len); void _closePipe(); @@ -121,9 +123,9 @@ public: m_sbuf(*this, deleteOnError) { m_parent->m_lock = true; - m_parent->_writeLine("PYBEGIN"); + m_parent->_writeStr("PYBEGIN"); char readBuf[16]; - m_parent->_readLine(readBuf, 16); + m_parent->_readStr(readBuf, 16); if (strcmp(readBuf, "READY")) BlenderLog.report(logvisor::Fatal, "unable to open PyOutStream with blender"); } @@ -137,9 +139,9 @@ public: { if (m_parent && m_parent->m_lock) { - m_parent->_writeLine("PYEND"); + m_parent->_writeStr("PYEND"); char readBuf[16]; - m_parent->_readLine(readBuf, 16); + m_parent->_readStr(readBuf, 16); if (strcmp(readBuf, "DONE")) BlenderLog.report(logvisor::Fatal, "unable to close PyOutStream with blender"); m_parent->m_lock = false; @@ -237,9 +239,9 @@ public: ANIMOutStream(BlenderConnection* parent) : m_parent(parent) { - m_parent->_writeLine("PYANIM"); + m_parent->_writeStr("PYANIM"); char readBuf[16]; - m_parent->_readLine(readBuf, 16); + m_parent->_readStr(readBuf, 16); if (strcmp(readBuf, "ANIMREADY")) BlenderLog.report(logvisor::Fatal, "unable to open ANIMOutStream"); } @@ -248,7 +250,7 @@ public: char tp = -1; m_parent->_writeBuf(&tp, 1); char readBuf[16]; - m_parent->_readLine(readBuf, 16); + m_parent->_readStr(readBuf, 16); if (strcmp(readBuf, "ANIMDONE")) BlenderLog.report(logvisor::Fatal, "unable to close ANIMOutStream"); } @@ -307,9 +309,9 @@ public: : m_parent(parent) { m_parent->m_lock = true; - m_parent->_writeLine("DATABEGIN"); + m_parent->_writeStr("DATABEGIN"); char readBuf[16]; - m_parent->_readLine(readBuf, 16); + m_parent->_readStr(readBuf, 16); if (strcmp(readBuf, "READY")) BlenderLog.report(logvisor::Fatal, "unable to open DataStream with blender"); } @@ -322,9 +324,9 @@ public: { if (m_parent && m_parent->m_lock) { - m_parent->_writeLine("DATAEND"); + m_parent->_writeStr("DATAEND"); char readBuf[16]; - m_parent->_readLine(readBuf, 16); + m_parent->_readStr(readBuf, 16); if (strcmp(readBuf, "DONE")) BlenderLog.report(logvisor::Fatal, "unable to close DataStream with blender"); m_parent->m_lock = false; @@ -333,7 +335,7 @@ public: std::vector getMeshList() { - m_parent->_writeLine("MESHLIST"); + m_parent->_writeStr("MESHLIST"); uint32_t count; m_parent->_readBuf(&count, 4); std::vector retval; @@ -341,7 +343,7 @@ public: for (uint32_t i=0 ; i_readLine(name, 128); + m_parent->_readStr(name, 128); retval.push_back(name); } return retval; diff --git a/hecl/include/hecl/UniformBufferPool.hpp b/hecl/include/hecl/UniformBufferPool.hpp new file mode 100644 index 000000000..0a45d82e8 --- /dev/null +++ b/hecl/include/hecl/UniformBufferPool.hpp @@ -0,0 +1,186 @@ +#ifndef HECL_UNIFORMBUFFERPOOL_HPP +#define HECL_UNIFORMBUFFERPOOL_HPP + +#include +#include +#include +#include "BitVector.hpp" + +namespace hecl +{ + +#define HECL_UBUFPOOL_ALLOCATION_BLOCK 262144 + +/** This class provides a uniform structure for packing instanced uniform-buffer + * data with consistent stride into a vector of 256K 'Buckets'. + * + * This results in a space-efficient way of managing GPU data of things like UI + * widgets. These can potentially have numerous binding instances, so this avoids + * allocating a full GPU buffer object for each. */ +template +class UniformBufferPool +{ + /* Resolve div_t type using ssize_t as basis */ +#if _WIN32 + using IndexTp = SSIZE_T; +#else + using IndexTp = ssize_t; +#endif + struct InvalidTp {}; + using DivTp = std::conditional_t::value, std::lldiv_t, + std::conditional_t::value, std::ldiv_t, + std::conditional_t::value, std::div_t, InvalidTp>>>; + static_assert(!std::is_same::value, "unsupported IndexTp for DivTp resolution"); + + /** Size of single element, rounded up to 256-multiple */ + static constexpr IndexTp m_stride = ROUND_UP_256(sizeof(UniformStruct)); + static_assert(m_stride <= HECL_UBUFPOOL_ALLOCATION_BLOCK, "Stride too large for uniform pool"); + + /** Number of rounded elements per 256K bucket */ + static constexpr IndexTp m_countPerBucket = HECL_UBUFPOOL_ALLOCATION_BLOCK / m_stride; + + /** Buffer size per bucket (ideally 256K) */ + static constexpr IndexTp m_sizePerBucket = m_stride * m_countPerBucket; + + /** BitVector indicating free allocation blocks */ + hecl::llvm::BitVector m_freeBlocks; + + /** Efficient way to get bucket and block simultaneously */ + DivTp getBucketDiv(IndexTp idx) const { return std::div(idx, m_countPerBucket); } + + /** Buffer pool token */ + boo::GraphicsBufferPoolToken m_token; + + /** Private bucket info */ + struct Bucket + { + boo::IGraphicsBufferD* buffer; + uint8_t* cpuBuffer = nullptr; + size_t useCount = 0; + bool dirty = false; + Bucket() = default; + Bucket(const Bucket& other) = delete; + Bucket& operator=(const Bucket& other) = delete; + Bucket(Bucket&& other) = default; + Bucket& operator=(Bucket&& other) = default; + + void updateBuffer() + { + if (buffer) + buffer->unmap(); + cpuBuffer = nullptr; + dirty = false; + } + + void increment(UniformBufferPool& pool) + { + if (!useCount) + buffer = pool.m_token.newPoolBuffer(boo::BufferUse::Uniform, + pool.m_stride, pool.m_countPerBucket); + ++useCount; + } + + void decrement(UniformBufferPool& pool) + { + --useCount; + if (!useCount) + { + pool.m_token.deletePoolBuffer(buffer); + buffer = nullptr; + } + } + }; + std::vector m_buckets; + +public: + /** User block-owning token */ + class Token + { + friend class UniformBufferPool; + UniformBufferPool& m_pool; + IndexTp m_index; + DivTp m_div; + Token(UniformBufferPool& pool) + : m_pool(pool) + { + auto& freeSpaces = pool.m_freeBlocks; + auto& buckets = pool.m_buckets; + int idx = freeSpaces.find_first(); + if (idx == -1) + { + buckets.emplace_back(); + m_index = freeSpaces.size(); + freeSpaces.resize(freeSpaces.size() + pool.m_countPerBucket, true); + } + else + { + m_index = idx; + } + freeSpaces.reset(m_index); + m_div = pool.getBucketDiv(m_index); + + Bucket& bucket = m_pool.m_buckets[m_div.quot]; + bucket.increment(m_pool); + } + + public: + Token(const Token& other) = delete; + Token& operator=(const Token& other) = delete; + Token& operator=(Token&& other) = delete; + Token(Token&& other) + : m_pool(other.m_pool), m_index(other.m_index), + m_div(other.m_div) + { + other.m_index = -1; + } + + ~Token() + { + if (m_index != -1) + { + m_pool.m_freeBlocks.set(m_index); + Bucket& bucket = m_pool.m_buckets[m_div.quot]; + bucket.decrement(m_pool); + } + } + + UniformStruct& access() + { + Bucket& bucket = m_pool.m_buckets[m_div.quot]; + if (!bucket.cpuBuffer) + bucket.cpuBuffer = reinterpret_cast(bucket.buffer->map(m_sizePerBucket)); + bucket.dirty = true; + return reinterpret_cast(bucket.cpuBuffer[m_div.rem * m_pool.m_stride]); + } + + std::pair getBufferInfo() const + { + Bucket& bucket = m_pool.m_buckets[m_div.quot]; + return {bucket.buffer, m_div.rem * m_pool.m_stride}; + } + }; + + UniformBufferPool() = default; + UniformBufferPool(const UniformBufferPool& other) = delete; + UniformBufferPool& operator=(const UniformBufferPool& other) = delete; + + /** Load dirty buffer data into GPU */ + void updateBuffers() + { + for (Bucket& bucket : m_buckets) + if (bucket.dirty) + bucket.updateBuffer(); + } + + /** Allocate free block into client-owned Token */ + Token allocateBlock(boo::IGraphicsDataFactory* factory) + { + if (!m_token) + m_token = factory->newBufferPool(); + return Token(*this); + } +}; + +} + +#endif // HECL_UNIFORMBUFFERPOOL_HPP diff --git a/hecl/include/hecl/VertexBufferPool.hpp b/hecl/include/hecl/VertexBufferPool.hpp new file mode 100644 index 000000000..3a639db60 --- /dev/null +++ b/hecl/include/hecl/VertexBufferPool.hpp @@ -0,0 +1,189 @@ +#ifndef HECL_VERTEXBUFFERPOOL_HPP +#define HECL_VERTEXBUFFERPOOL_HPP + +#include +#include +#include +#include "BitVector.hpp" + +namespace hecl +{ + +#define HECL_VBUFPOOL_ALLOCATION_BLOCK 262144 + +/** This class provides a uniform structure for packing instanced vertex-buffer + * data with consistent stride into a vector of 256K 'Buckets'. + * + * This results in a space-efficient way of managing GPU data of things like UI + * widgets. These can potentially have numerous binding instances, so this avoids + * allocating a full GPU buffer object for each. */ +template +class VertexBufferPool +{ + /* Resolve div_t type using ssize_t as basis */ +#if _WIN32 + using IndexTp = SSIZE_T; +#else + using IndexTp = ssize_t; +#endif + struct InvalidTp {}; + using DivTp = std::conditional_t::value, std::lldiv_t, + std::conditional_t::value, std::ldiv_t, + std::conditional_t::value, std::div_t, InvalidTp>>>; + static_assert(!std::is_same::value, "unsupported IndexTp for DivTp resolution"); + + /** Size of single element */ + static constexpr IndexTp m_stride = sizeof(VertStruct); + static_assert(m_stride <= HECL_VBUFPOOL_ALLOCATION_BLOCK, "Stride too large for vertex pool"); + + /** Number of elements per 256K bucket */ + static constexpr IndexTp m_countPerBucket = HECL_VBUFPOOL_ALLOCATION_BLOCK / m_stride; + + /** Buffer size per bucket (ideally 256K) */ + static constexpr IndexTp m_sizePerBucket = m_stride * m_countPerBucket; + + /** BitVector indicating free allocation elements */ + hecl::llvm::BitVector m_freeElements; + + /** Efficient way to get bucket and element simultaneously */ + DivTp getBucketDiv(IndexTp idx) const { return std::div(idx, m_countPerBucket); } + + /** Buffer pool token */ + boo::GraphicsBufferPoolToken m_token; + + /** Private bucket info */ + struct Bucket + { + boo::IGraphicsBufferD* buffer; + uint8_t* cpuBuffer = nullptr; + size_t useCount = 0; + bool dirty = false; + Bucket() = default; + Bucket(const Bucket& other) = delete; + Bucket& operator=(const Bucket& other) = delete; + Bucket(Bucket&& other) = default; + Bucket& operator=(Bucket&& other) = default; + + void updateBuffer() + { + if (buffer) + buffer->unmap(); + cpuBuffer = nullptr; + dirty = false; + } + + void increment(VertexBufferPool& pool) + { + if (!useCount) + buffer = pool.m_token.newPoolBuffer(boo::BufferUse::Vertex, + pool.m_stride, pool.m_countPerBucket); + ++useCount; + } + + void decrement(VertexBufferPool& pool) + { + --useCount; + if (!useCount) + { + pool.m_token.deletePoolBuffer(buffer); + buffer = nullptr; + } + } + }; + std::vector m_buckets; + +public: + /** User element-owning token */ + class Token + { + friend class VertexBufferPool; + VertexBufferPool& m_pool; + IndexTp m_index; + IndexTp m_count; + DivTp m_div; + Token(VertexBufferPool& pool, IndexTp count) + : m_pool(pool), m_count(count) + { + assert(count <= pool.m_countPerBucket && "unable to fit in bucket"); + auto& freeSpaces = pool.m_freeElements; + auto& buckets = pool.m_buckets; + int idx = freeSpaces.find_first_contiguous(count, pool.m_countPerBucket); + if (idx == -1) + { + buckets.emplace_back(); + m_index = freeSpaces.size(); + freeSpaces.resize(freeSpaces.size() + pool.m_countPerBucket, true); + } + else + { + m_index = idx; + } + freeSpaces.reset(m_index, m_index + count); + m_div = pool.getBucketDiv(m_index); + + Bucket& bucket = m_pool.m_buckets[m_div.quot]; + bucket.increment(pool); + } + public: + Token(const Token& other) = delete; + Token& operator=(const Token& other) = delete; + Token& operator=(Token&& other) = delete; + Token(Token&& other) + : m_pool(other.m_pool), m_index(other.m_index), + m_count(other.m_count), m_div(other.m_div) + { + other.m_index = -1; + } + + ~Token() + { + if (m_index != -1) + { + m_pool.m_freeElements.set(m_index, m_index + m_count); + Bucket& bucket = m_pool.m_buckets[m_div.quot]; + bucket.decrement(m_pool); + } + } + + VertStruct* access() + { + Bucket& bucket = m_pool.m_buckets[m_div.quot]; + if (!bucket.cpuBuffer) + bucket.cpuBuffer = reinterpret_cast(bucket.buffer->map(m_sizePerBucket)); + bucket.dirty = true; + return reinterpret_cast(&bucket.cpuBuffer[m_div.rem * m_pool.m_stride]); + } + + std::pair getBufferInfo() const + { + Bucket& bucket = m_pool.m_buckets[m_div.quot]; + return {bucket.buffer, m_div.rem}; + } + }; + + VertexBufferPool() = default; + VertexBufferPool(const VertexBufferPool& other) = delete; + VertexBufferPool& operator=(const VertexBufferPool& other) = delete; + + /** Load dirty buffer data into GPU */ + void updateBuffers() + { + for (Bucket& bucket : m_buckets) + if (bucket.dirty) + bucket.updateBuffer(); + } + + /** Allocate free block into client-owned Token */ + Token allocateBlock(boo::IGraphicsDataFactory* factory, IndexTp count) + { + if (!m_token) + m_token = factory->newBufferPool(); + return Token(*this, count); + } + + static constexpr IndexTp bucketCapacity() { return m_countPerBucket; } +}; + +} + +#endif // HECL_VERTEXBUFFERPOOL_HPP diff --git a/hecl/lib/Blender/BlenderConnection.cpp b/hecl/lib/Blender/BlenderConnection.cpp index 44b6c17e4..3727a4304 100644 --- a/hecl/lib/Blender/BlenderConnection.cpp +++ b/hecl/lib/Blender/BlenderConnection.cpp @@ -82,58 +82,50 @@ static void InstallStartup(const char* path) fclose(fp); } -size_t BlenderConnection::_readLine(char* buf, size_t bufSz) +uint32_t BlenderConnection::_readStr(char* buf, uint32_t bufSz) { - size_t readBytes = 0; - while (true) + uint32_t readLen; + int ret = read(m_readpipe[0], &readLen, 4); + if (ret < 4) { - if (readBytes >= bufSz) + _blenderDied(); + return 0; + } + + if (readLen >= bufSz) + { + BlenderLog.report(logvisor::Fatal, "Pipe buffer overrun [%d/%d]", readLen, bufSz); + *buf = '\0'; + return 0; + } + + ret = read(m_readpipe[0], buf, readLen); + if (ret < 0) + { + BlenderLog.report(logvisor::Fatal, strerror(errno)); + return 0; + } + else if (readLen >= 4) + if (!memcmp(buf, "EXCEPTION", std::min(readLen, uint32_t(9)))) { - BlenderLog.report(logvisor::Fatal, "Pipe buffer overrun"); - *(buf-1) = '\0'; - return bufSz - 1; - } - int ret; - while ((ret = read(m_readpipe[0], buf, 1)) < 0 && errno == EINTR) {} - if (ret < 0) - { - BlenderLog.report(logvisor::Fatal, strerror(errno)); + _blenderDied(); return 0; } - else if (ret == 1) - { - if (*buf == '\n') - { - *buf = '\0'; - if (readBytes >= 4) - if (!memcmp(buf, "EXCEPTION", std::min(readBytes, size_t(9)))) - _blenderDied(); - return readBytes; - } - ++readBytes; - ++buf; - } - else - { - *buf = '\0'; - if (readBytes >= 4) - if (!memcmp(buf, "EXCEPTION", std::min(readBytes, size_t(9)))) - _blenderDied(); - return readBytes; - } - } + + *(buf+readLen) = '\0'; + return readLen; } -size_t BlenderConnection::_writeLine(const char* buf) +uint32_t BlenderConnection::_writeStr(const char* buf, uint32_t len, int wpipe) { int ret, nlerr; - ret = write(m_writepipe[1], buf, strlen(buf)); + nlerr = write(wpipe, &len, 4); + if (nlerr < 4) + goto err; + ret = write(wpipe, buf, len); if (ret < 0) goto err; - nlerr = write(m_writepipe[1], "\n", 1); - if (nlerr < 0) - goto err; - return (size_t)ret; + return (uint32_t)ret; err: _blenderDied(); return 0; @@ -382,8 +374,8 @@ BlenderConnection::BlenderConnection(int verbosityLevel) "--", readfds, writefds, vLevel, blenderAddonPath.c_str(), NULL); if (errno != ENOENT) { - snprintf(errbuf, 256, "NOLAUNCH %s\n", strerror(errno)); - write(m_writepipe[1], errbuf, strlen(errbuf)); + snprintf(errbuf, 256, "NOLAUNCH %s", strerror(errno)); + _writeStr(errbuf, strlen(errbuf), m_readpipe[1]); exit(1); } } @@ -394,15 +386,14 @@ BlenderConnection::BlenderConnection(int verbosityLevel) "--", readfds, writefds, vLevel, blenderAddonPath.c_str(), NULL); if (errno != ENOENT) { - snprintf(errbuf, 256, "NOLAUNCH %s\n", strerror(errno)); - write(m_writepipe[1], errbuf, strlen(errbuf)); + snprintf(errbuf, 256, "NOLAUNCH %s", strerror(errno)); + _writeStr(errbuf, strlen(errbuf), m_readpipe[1]); exit(1); } /* Unable to find blender */ - write(m_writepipe[1], "NOBLENDER\n", 10); + _writeStr("NOBLENDER", 9, m_readpipe[1]); exit(1); - } close(m_writepipe[0]); close(m_readpipe[1]); @@ -415,13 +406,14 @@ BlenderConnection::BlenderConnection(int verbosityLevel) /* Handle first response */ char lineBuf[256]; - _readLine(lineBuf, sizeof(lineBuf)); - if (!strcmp(lineBuf, "NOLAUNCH")) + _readStr(lineBuf, sizeof(lineBuf)); + + if (!strncmp(lineBuf, "NOLAUNCH", 8)) { _closePipe(); - BlenderLog.report(logvisor::Fatal, "Unable to launch blender"); + BlenderLog.report(logvisor::Fatal, "Unable to launch blender: %s", lineBuf + 9); } - else if (!strcmp(lineBuf, "NOBLENDER")) + else if (!strncmp(lineBuf, "NOBLENDER", 9)) { _closePipe(); if (blenderBin) @@ -451,9 +443,9 @@ BlenderConnection::BlenderConnection(int verbosityLevel) _closePipe(); BlenderLog.report(logvisor::Fatal, "read '%s' from blender; expected 'READY'", lineBuf); } - _writeLine("ACK"); + _writeStr("ACK"); - _readLine(lineBuf, 7); + _readStr(lineBuf, 7); if (!strcmp(lineBuf, "SLERP0")) m_hasSlerp = false; else if (!strcmp(lineBuf, "SLERP1")) @@ -484,9 +476,9 @@ BlenderConnection::PyOutStream::StreamBuf::overflow(int_type ch) return ch; } //printf("FLUSHING %s\n", m_lineBuf.c_str()); - m_parent.m_parent->_writeLine(m_lineBuf.c_str()); + m_parent.m_parent->_writeStr(m_lineBuf.c_str()); char readBuf[16]; - m_parent.m_parent->_readLine(readBuf, 16); + m_parent.m_parent->_readStr(readBuf, 16); if (strcmp(readBuf, "OK")) { if (m_deleteOnError) @@ -518,9 +510,9 @@ bool BlenderConnection::createBlend(const ProjectPath& path, BlendType type) "BlenderConnection::createBlend() musn't be called with stream active"); return false; } - _writeLine(("CREATE \"" + path.getAbsolutePathUTF8() + "\" " + BlendTypeStrs[int(type)] + " \"" + m_startupBlend + "\"").c_str()); + _writeStr(("CREATE \"" + path.getAbsolutePathUTF8() + "\" " + BlendTypeStrs[int(type)] + " \"" + m_startupBlend + "\"").c_str()); char lineBuf[256]; - _readLine(lineBuf, sizeof(lineBuf)); + _readStr(lineBuf, sizeof(lineBuf)); if (!strcmp(lineBuf, "FINISHED")) { /* Delete immediately in case save doesn't occur */ @@ -542,14 +534,14 @@ bool BlenderConnection::openBlend(const ProjectPath& path, bool force) } if (!force && path == m_loadedBlend) return true; - _writeLine(("OPEN \"" + path.getAbsolutePathUTF8() + "\"").c_str()); + _writeStr(("OPEN \"" + path.getAbsolutePathUTF8() + "\"").c_str()); char lineBuf[256]; - _readLine(lineBuf, sizeof(lineBuf)); + _readStr(lineBuf, sizeof(lineBuf)); if (!strcmp(lineBuf, "FINISHED")) { m_loadedBlend = path; - _writeLine("GETTYPE"); - _readLine(lineBuf, sizeof(lineBuf)); + _writeStr("GETTYPE"); + _readStr(lineBuf, sizeof(lineBuf)); m_loadedType = BlendType::None; unsigned idx = 0; while (BlendTypeStrs[idx]) @@ -564,8 +556,8 @@ bool BlenderConnection::openBlend(const ProjectPath& path, bool force) m_loadedRigged = false; if (m_loadedType == BlendType::Mesh) { - _writeLine("GETMESHRIGGED"); - _readLine(lineBuf, sizeof(lineBuf)); + _writeStr("GETMESHRIGGED"); + _readStr(lineBuf, sizeof(lineBuf)); if (!strcmp("TRUE", lineBuf)) m_loadedRigged = true; } @@ -582,9 +574,9 @@ bool BlenderConnection::saveBlend() "BlenderConnection::saveBlend() musn't be called with stream active"); return false; } - _writeLine("SAVE"); + _writeStr("SAVE"); char lineBuf[256]; - _readLine(lineBuf, sizeof(lineBuf)); + _readStr(lineBuf, sizeof(lineBuf)); if (!strcmp(lineBuf, "FINISHED")) return true; return false; @@ -709,7 +701,7 @@ BlenderConnection::DataStream::Mesh::Mesh for (uint32_t i=0 ; i_writeLine(req); + m_parent->_writeStr(req); char readBuf[256]; - m_parent->_readLine(readBuf, 256); + m_parent->_readStr(readBuf, 256); if (strcmp(readBuf, "OK")) BlenderLog.report(logvisor::Fatal, "unable to cook mesh: %s", readBuf); @@ -1254,10 +1246,10 @@ BlenderConnection::DataStream::compileMesh(const std::string& name, char req[128]; snprintf(req, 128, "MESHCOMPILENAME %s %s %d", name.c_str(), MeshOutputModeString(topology), skinSlotCount); - m_parent->_writeLine(req); + m_parent->_writeStr(req); char readBuf[256]; - m_parent->_readLine(readBuf, 256); + m_parent->_readStr(readBuf, 256); if (strcmp(readBuf, "OK")) BlenderLog.report(logvisor::Fatal, "unable to cook mesh '%s': %s", name.c_str(), readBuf); @@ -1273,10 +1265,10 @@ BlenderConnection::DataStream::compileColMesh(const std::string& name) char req[128]; snprintf(req, 128, "MESHCOMPILENAMECOLLISION %s", name.c_str()); - m_parent->_writeLine(req); + m_parent->_writeStr(req); char readBuf[256]; - m_parent->_readLine(readBuf, 256); + m_parent->_readStr(readBuf, 256); if (strcmp(readBuf, "OK")) BlenderLog.report(logvisor::Fatal, "unable to cook collision mesh '%s': %s", name.c_str(), readBuf); @@ -1297,10 +1289,10 @@ BlenderConnection::DataStream::compileAllMeshes(HMDLTopology topology, snprintf(req, 128, "MESHCOMPILEALL %s %d %f", MeshOutputModeString(topology), skinSlotCount, maxOctantLength); - m_parent->_writeLine(req); + m_parent->_writeStr(req); char readBuf[256]; - m_parent->_readLine(readBuf, 256); + m_parent->_readStr(readBuf, 256); if (strcmp(readBuf, "OK")) BlenderLog.report(logvisor::Fatal, "unable to cook all meshes: %s", readBuf); @@ -1313,10 +1305,10 @@ std::vector BlenderConnection::DataStream: BlenderLog.report(logvisor::Fatal, _S("%s is not an AREA blend"), m_parent->m_loadedBlend.getAbsolutePath().c_str()); - m_parent->_writeLine("LIGHTCOMPILEALL"); + m_parent->_writeStr("LIGHTCOMPILEALL"); char readBuf[256]; - m_parent->_readLine(readBuf, 256); + m_parent->_readStr(readBuf, 256); if (strcmp(readBuf, "OK")) BlenderLog.report(logvisor::Fatal, "unable to gather all lights: %s", readBuf); @@ -1334,10 +1326,10 @@ std::vector BlenderConnection::DataStream: std::vector BlenderConnection::DataStream::getTextures() { - m_parent->_writeLine("GETTEXTURES"); + m_parent->_writeStr("GETTEXTURES"); char readBuf[256]; - m_parent->_readLine(readBuf, 256); + m_parent->_readStr(readBuf, 256); if (strcmp(readBuf, "OK")) BlenderLog.report(logvisor::Fatal, "unable to get textures: %s", readBuf); @@ -1367,10 +1359,10 @@ BlenderConnection::DataStream::Actor BlenderConnection::DataStream::compileActor BlenderLog.report(logvisor::Fatal, _S("%s is not an ACTOR blend"), m_parent->m_loadedBlend.getAbsolutePath().c_str()); - m_parent->_writeLine("ACTORCOMPILE"); + m_parent->_writeStr("ACTORCOMPILE"); char readBuf[256]; - m_parent->_readLine(readBuf, 256); + m_parent->_readStr(readBuf, 256); if (strcmp(readBuf, "OK")) BlenderLog.report(logvisor::Fatal, "unable to compile actor: %s", readBuf); @@ -1384,10 +1376,10 @@ BlenderConnection::DataStream::compileActorCharacterOnly() BlenderLog.report(logvisor::Fatal, _S("%s is not an ACTOR blend"), m_parent->m_loadedBlend.getAbsolutePath().c_str()); - m_parent->_writeLine("ACTORCOMPILECHARACTERONLY"); + m_parent->_writeStr("ACTORCOMPILECHARACTERONLY"); char readBuf[256]; - m_parent->_readLine(readBuf, 256); + m_parent->_readStr(readBuf, 256); if (strcmp(readBuf, "OK")) BlenderLog.report(logvisor::Fatal, "unable to compile actor: %s", readBuf); @@ -1401,10 +1393,10 @@ BlenderConnection::DataStream::compileWorld() BlenderLog.report(logvisor::Fatal, _S("%s is not an WORLD blend"), m_parent->m_loadedBlend.getAbsolutePath().c_str()); - m_parent->_writeLine("WORLDCOMPILE"); + m_parent->_writeStr("WORLDCOMPILE"); char readBuf[256]; - m_parent->_readLine(readBuf, 256); + m_parent->_readStr(readBuf, 256); if (strcmp(readBuf, "OK")) BlenderLog.report(logvisor::Fatal, "unable to compile world: %s", readBuf); @@ -1417,10 +1409,10 @@ std::vector BlenderConnection::DataStream::getArmatureNames() BlenderLog.report(logvisor::Fatal, _S("%s is not an ACTOR blend"), m_parent->m_loadedBlend.getAbsolutePath().c_str()); - m_parent->_writeLine("GETARMATURENAMES"); + m_parent->_writeStr("GETARMATURENAMES"); char readBuf[256]; - m_parent->_readLine(readBuf, 256); + m_parent->_readStr(readBuf, 256); if (strcmp(readBuf, "OK")) BlenderLog.report(logvisor::Fatal, "unable to get armatures of actor: %s", readBuf); @@ -1448,10 +1440,10 @@ std::vector BlenderConnection::DataStream::getSubtypeNames() BlenderLog.report(logvisor::Fatal, _S("%s is not an ACTOR blend"), m_parent->m_loadedBlend.getAbsolutePath().c_str()); - m_parent->_writeLine("GETSUBTYPENAMES"); + m_parent->_writeStr("GETSUBTYPENAMES"); char readBuf[256]; - m_parent->_readLine(readBuf, 256); + m_parent->_readStr(readBuf, 256); if (strcmp(readBuf, "OK")) BlenderLog.report(logvisor::Fatal, "unable to get subtypes of actor: %s", readBuf); @@ -1479,10 +1471,10 @@ std::vector BlenderConnection::DataStream::getActionNames() BlenderLog.report(logvisor::Fatal, _S("%s is not an ACTOR blend"), m_parent->m_loadedBlend.getAbsolutePath().c_str()); - m_parent->_writeLine("GETACTIONNAMES"); + m_parent->_writeStr("GETACTIONNAMES"); char readBuf[256]; - m_parent->_readLine(readBuf, 256); + m_parent->_readStr(readBuf, 256); if (strcmp(readBuf, "OK")) BlenderLog.report(logvisor::Fatal, "unable to get actions of actor: %s", readBuf); @@ -1516,10 +1508,10 @@ BlenderConnection::DataStream::getBoneMatrices(const std::string& name) char req[128]; snprintf(req, 128, "GETBONEMATRICES %s", name.c_str()); - m_parent->_writeLine(req); + m_parent->_writeStr(req); char readBuf[256]; - m_parent->_readLine(readBuf, 256); + m_parent->_readStr(readBuf, 256); if (strcmp(readBuf, "OK")) BlenderLog.report(logvisor::Fatal, "unable to get matrices of armature: %s", readBuf); @@ -1557,9 +1549,9 @@ BlenderConnection::DataStream::getBoneMatrices(const std::string& name) void BlenderConnection::quitBlender() { - _writeLine("QUIT"); + _writeStr("QUIT"); char lineBuf[256]; - _readLine(lineBuf, sizeof(lineBuf)); + _readStr(lineBuf, sizeof(lineBuf)); } BlenderConnection& BlenderConnection::SharedConnection() diff --git a/hecl/lib/CMakeLists.txt b/hecl/lib/CMakeLists.txt index ac90f4726..ba83c0c6d 100644 --- a/hecl/lib/CMakeLists.txt +++ b/hecl/lib/CMakeLists.txt @@ -49,6 +49,8 @@ add_library(hecl-common ../include/hecl/ClientProcess.hpp ../include/hecl/BitVector.hpp ../include/hecl/MathExtras.hpp + ../include/hecl/UniformBufferPool.hpp + ../include/hecl/VertexBufferPool.hpp ClientProcess.cpp atdna_HMDLMeta.cpp atdna_Frontend.cpp