diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index a3bed0a256..d8795341bb 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -495,7 +495,8 @@ function MyController(hand) { } }; - this.searchIndicatorOn = function(handPosition, distantPickRay) { + this.searchIndicatorOn = function(distantPickRay) { + var handPosition = distantPickRay.origin; var SEARCH_SPHERE_SIZE = 0.011; var SEARCH_SPHERE_FOLLOW_RATE = 0.50; @@ -857,7 +858,9 @@ function MyController(hand) { var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; var currentHandRotation = Controller.getPoseValue(controllerHandInput).rotation; - var currentControllerPosition = Controller.getPoseValue(controllerHandInput).position; + var currentControllerPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, + Controller.getPoseValue(controllerHandInput).translation), + MyAvatar.position); var handDeltaRotation = Quat.multiply(currentHandRotation, Quat.inverse(this.startingHandRotation)); var avatarControllerPose = Controller.getPoseValue((this.hand === RIGHT_HAND) ? @@ -865,19 +868,13 @@ function MyController(hand) { var controllerRotation = Quat.multiply(MyAvatar.orientation, avatarControllerPose.rotation); var distantPickRay = { - origin: PICK_WITH_HAND_RAY ? handPosition : Camera.position, + origin: PICK_WITH_HAND_RAY ? currentControllerPosition : Camera.position, direction: PICK_WITH_HAND_RAY ? Quat.getUp(controllerRotation) : Vec3.mix(Quat.getUp(controllerRotation), Quat.getFront(Camera.orientation), HAND_HEAD_MIX_RATIO), length: PICK_MAX_DISTANCE }; - var searchVisualizationPickRay = { - origin: currentControllerPosition, - direction: Quat.getUp(this.getHandRotation()), - length: PICK_MAX_DISTANCE - }; - // Pick at some maximum rate, not always var pickRays = []; var now = Date.now(); @@ -1086,7 +1083,7 @@ function MyController(hand) { this.lineOn(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); } - this.searchIndicatorOn(handPosition, distantPickRay); + this.searchIndicatorOn(distantPickRay); Reticle.setVisible(false); }; @@ -1668,7 +1665,7 @@ function MyController(hand) { if (intersection.intersects) { this.intersectionDistance = Vec3.distance(pickRay.origin, intersection.intersection); } - this.searchIndicatorOn(handPosition, pickRay); + this.searchIndicatorOn(pickRay); } } diff --git a/examples/utilities/render/stats.qml b/examples/utilities/render/stats.qml index d236d80dde..6d2371fd53 100644 --- a/examples/utilities/render/stats.qml +++ b/examples/utilities/render/stats.qml @@ -87,6 +87,11 @@ Item { prop: "frameTextureCount", label: "Frame", color: "#E2334D" + }, + { + prop: "textureGPUTransferCount", + label: "Transfer", + color: "#9495FF" } ] } diff --git a/ice-server/src/IceServer.cpp b/ice-server/src/IceServer.cpp index c1e92f276f..b66ccaa057 100644 --- a/ice-server/src/IceServer.cpp +++ b/ice-server/src/IceServer.cpp @@ -53,6 +53,10 @@ IceServer::IceServer(int argc, char* argv[]) : QTimer* inactivePeerTimer = new QTimer(this); connect(inactivePeerTimer, &QTimer::timeout, this, &IceServer::clearInactivePeers); inactivePeerTimer->start(CLEAR_INACTIVE_PEERS_INTERVAL_MSECS); + + // handle public keys when they arrive from the QNetworkAccessManager + auto& networkAccessManager = NetworkAccessManager::getInstance(); + connect(&networkAccessManager, &QNetworkAccessManager::finished, this, &IceServer::publicKeyReplyFinished); } bool IceServer::packetVersionMatch(const udt::Packet& packet) { @@ -200,7 +204,6 @@ bool IceServer::isVerifiedHeartbeat(const QUuid& domainID, const QByteArray& pla void IceServer::requestDomainPublicKey(const QUuid& domainID) { // send a request to the metaverse API for the public key for this domain auto& networkAccessManager = NetworkAccessManager::getInstance(); - connect(&networkAccessManager, &QNetworkAccessManager::finished, this, &IceServer::publicKeyReplyFinished); QUrl publicKeyURL { NetworkingConstants::METAVERSE_SERVER_URL }; QString publicKeyPath = QString("/api/v1/domains/%1/public_key").arg(uuidStringWithoutCurlyBraces(domainID)); diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index b7aeb7696e..2e50502840 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -107,12 +107,15 @@ elseif(WIN32) # add an executable that also has the icon itself and the configured rc file as resources add_executable(${TARGET_NAME} WIN32 ${INTERFACE_SRCS} ${QM} ${CONFIGURE_ICON_RC_OUTPUT}) - add_custom_command( - TARGET ${TARGET_NAME} - POST_BUILD - COMMAND "mt.exe" -manifest "${CMAKE_CURRENT_SOURCE_DIR}/interface.exe.manifest" -inputresource:"$<TARGET_FILE:${TARGET_NAME}>"\;\#1 -outputresource:"$<TARGET_FILE:${TARGET_NAME}>"\;\#1 - COMMENT "Adding OS version support manifest to exe" - ) + if ( NOT DEV_BUILD ) + add_custom_command( + TARGET ${TARGET_NAME} + POST_BUILD + COMMAND "mt.exe" -manifest "${CMAKE_CURRENT_SOURCE_DIR}/interface.exe.manifest" -inputresource:"$<TARGET_FILE:${TARGET_NAME}>"\;\#1 -outputresource:"$<TARGET_FILE:${TARGET_NAME}>"\;\#1 + COMMENT "Adding OS version support manifest to exe" + ) + endif() + else() add_executable(${TARGET_NAME} ${INTERFACE_SRCS} ${QM}) endif() diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 0172b3ce3a..311583acb7 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -144,9 +144,7 @@ #include "Util.h" #include "InterfaceParentFinder.h" - - -// ON Windows PC, Nvidia Optimus laptop, we want to enable NVIDIA GPU +// On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU // FIXME seems to be broken. #if defined(Q_OS_WIN) extern "C" { @@ -1255,6 +1253,9 @@ void Application::initializeGL() { // Where the gpuContext is initialized and where the TRUE Backend is created and assigned gpu::Context::init<gpu::GLBackend>(); _gpuContext = std::make_shared<gpu::Context>(); + // The gpu context can make child contexts for transfers, so + // we need to restore primary rendering context + _offscreenContext->makeCurrent(); initDisplay(); qCDebug(interfaceapp, "Initialized Display."); @@ -1470,10 +1471,6 @@ void Application::paintGL() { // update the avatar with a fresh HMD pose getMyAvatar()->updateFromHMDSensorMatrix(getHMDSensorPose()); - // update sensorToWorldMatrix for camera and hand controllers - getMyAvatar()->updateSensorToWorldMatrix(); - - auto lodManager = DependencyManager::get<LODManager>(); @@ -3406,6 +3403,9 @@ void Application::update(float deltaTime) { avatarManager->updateOtherAvatars(deltaTime); } + // update sensorToWorldMatrix for camera and hand controllers + getMyAvatar()->updateSensorToWorldMatrix(); + qApp->updateMyAvatarLookAtPosition(); { diff --git a/libraries/gl/src/gl/GLHelpers.cpp b/libraries/gl/src/gl/GLHelpers.cpp index 6ad7f816b8..c9de3ccd90 100644 --- a/libraries/gl/src/gl/GLHelpers.cpp +++ b/libraries/gl/src/gl/GLHelpers.cpp @@ -12,7 +12,7 @@ const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() { // Qt Quick may need a depth and stencil buffer. Always make sure these are available. format.setDepthBufferSize(DEFAULT_GL_DEPTH_BUFFER_BITS); format.setStencilBufferSize(DEFAULT_GL_STENCIL_BUFFER_BITS); - format.setVersion(4, 1); + format.setVersion(4, 5); #ifdef DEBUG format.setOption(QSurfaceFormat::DebugContext); #endif @@ -27,7 +27,7 @@ const QGLFormat& getDefaultGLFormat() { static QGLFormat glFormat; static std::once_flag once; std::call_once(once, [] { - glFormat.setVersion(4, 1); + glFormat.setVersion(4, 5); glFormat.setProfile(QGLFormat::CoreProfile); // Requires >=Qt-4.8.0 glFormat.setSampleBuffers(false); glFormat.setDepth(false); diff --git a/libraries/gl/src/gl/OffscreenGLCanvas.cpp b/libraries/gl/src/gl/OffscreenGLCanvas.cpp index 8e5579f90b..90ff369cd6 100644 --- a/libraries/gl/src/gl/OffscreenGLCanvas.cpp +++ b/libraries/gl/src/gl/OffscreenGLCanvas.cpp @@ -83,3 +83,8 @@ void OffscreenGLCanvas::doneCurrent() { QObject* OffscreenGLCanvas::getContextObject() { return _context; } + +void OffscreenGLCanvas::moveToThreadWithContext(QThread* thread) { + moveToThread(thread); + _context->moveToThread(thread); +} \ No newline at end of file diff --git a/libraries/gl/src/gl/OffscreenGLCanvas.h b/libraries/gl/src/gl/OffscreenGLCanvas.h index 9858c7cefa..387804bf56 100644 --- a/libraries/gl/src/gl/OffscreenGLCanvas.h +++ b/libraries/gl/src/gl/OffscreenGLCanvas.h @@ -26,6 +26,7 @@ public: bool create(QOpenGLContext* sharedContext = nullptr); bool makeCurrent(); void doneCurrent(); + void moveToThreadWithContext(QThread* thread); QOpenGLContext* getContext() { return _context; } diff --git a/libraries/gl/src/gl/OglplusHelpers.cpp b/libraries/gl/src/gl/OglplusHelpers.cpp index 1dd7068448..11c4f2fe3d 100644 --- a/libraries/gl/src/gl/OglplusHelpers.cpp +++ b/libraries/gl/src/gl/OglplusHelpers.cpp @@ -6,8 +6,10 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "OglplusHelpers.h" -#include <QSharedPointer> + #include <set> +#include <oglplus/shapes/plane.hpp> +#include <oglplus/shapes/sky_box.hpp> using namespace oglplus; using namespace oglplus::shapes; @@ -20,11 +22,13 @@ uniform mat4 mvp = mat4(1); in vec3 Position; in vec2 TexCoord; +out vec3 vPosition; out vec2 vTexCoord; void main() { gl_Position = mvp * vec4(Position, 1); - vTexCoord = TexCoord ; + vTexCoord = TexCoord; + vPosition = Position; } )VS"; @@ -35,7 +39,9 @@ static const char * SIMPLE_TEXTURED_FS = R"FS(#version 410 core uniform sampler2D sampler; uniform float alpha = 1.0; +in vec3 vPosition; in vec2 vTexCoord; + out vec4 FragColor; void main() { @@ -47,12 +53,38 @@ void main() { )FS"; +static const char * SIMPLE_TEXTURED_CUBEMAP_FS = R"FS(#version 410 core +#pragma line __LINE__ + +uniform samplerCube sampler; +uniform float alpha = 1.0; + +in vec3 vPosition; +in vec3 vTexCoord; + +out vec4 FragColor; + +void main() { + + FragColor = texture(sampler, vPosition); + FragColor.a *= alpha; +} + +)FS"; + + ProgramPtr loadDefaultShader() { ProgramPtr result; compileProgram(result, SIMPLE_TEXTURED_VS, SIMPLE_TEXTURED_FS); return result; } +ProgramPtr loadCubemapShader() { + ProgramPtr result; + compileProgram(result, SIMPLE_TEXTURED_VS, SIMPLE_TEXTURED_CUBEMAP_FS); + return result; +} + void compileProgram(ProgramPtr & result, const std::string& vs, const std::string& fs) { using namespace oglplus; try { @@ -93,6 +125,10 @@ ShapeWrapperPtr loadPlane(ProgramPtr program, float aspect) { ); } +ShapeWrapperPtr loadSkybox(ProgramPtr program) { + return ShapeWrapperPtr(new shapes::ShapeWrapper({ { "Position" } }, shapes::SkyBox(), *program)); +} + // Return a point's cartesian coordinates on a sphere from pitch and yaw static glm::vec3 getPoint(float yaw, float pitch) { return glm::vec3(glm::cos(-pitch) * (-glm::sin(yaw)), diff --git a/libraries/gl/src/gl/OglplusHelpers.h b/libraries/gl/src/gl/OglplusHelpers.h index 4734b8b213..b599e8270d 100644 --- a/libraries/gl/src/gl/OglplusHelpers.h +++ b/libraries/gl/src/gl/OglplusHelpers.h @@ -37,7 +37,6 @@ #include <oglplus/bound/framebuffer.hpp> #include <oglplus/bound/renderbuffer.hpp> #include <oglplus/shapes/wrapper.hpp> -#include <oglplus/shapes/plane.hpp> #ifdef _WIN32 #pragma warning(pop) @@ -55,7 +54,9 @@ using ProgramPtr = std::shared_ptr<oglplus::Program>; using Mat4Uniform = oglplus::Uniform<mat4>; ProgramPtr loadDefaultShader(); +ProgramPtr loadCubemapShader(); void compileProgram(ProgramPtr & result, const std::string& vs, const std::string& fs); +ShapeWrapperPtr loadSkybox(ProgramPtr program); ShapeWrapperPtr loadPlane(ProgramPtr program, float aspect = 1.0f); ShapeWrapperPtr loadSphereSection(ProgramPtr program, float fov = PI / 3.0f * 2.0f, float aspect = 16.0f / 9.0f, int slices = 32, int stacks = 32); diff --git a/libraries/gl/src/gl/QOpenGLContextWrapper.cpp b/libraries/gl/src/gl/QOpenGLContextWrapper.cpp index 6397d30e13..185fdaf7f4 100644 --- a/libraries/gl/src/gl/QOpenGLContextWrapper.cpp +++ b/libraries/gl/src/gl/QOpenGLContextWrapper.cpp @@ -13,6 +13,9 @@ #include <QOpenGLContext> +QOpenGLContext* QOpenGLContextWrapper::currentContext() { + return QOpenGLContext::currentContext(); +} QOpenGLContextWrapper::QOpenGLContextWrapper() : _context(new QOpenGLContext) diff --git a/libraries/gl/src/gl/QOpenGLContextWrapper.h b/libraries/gl/src/gl/QOpenGLContextWrapper.h index b736253213..09f1d67280 100644 --- a/libraries/gl/src/gl/QOpenGLContextWrapper.h +++ b/libraries/gl/src/gl/QOpenGLContextWrapper.h @@ -19,7 +19,6 @@ class QSurfaceFormat; class QOpenGLContextWrapper { public: QOpenGLContextWrapper(); - void setFormat(const QSurfaceFormat& format); bool create(); void swapBuffers(QSurface* surface); @@ -27,6 +26,8 @@ public: void doneCurrent(); void setShareContext(QOpenGLContext* otherContext); + static QOpenGLContext* currentContext(); + QOpenGLContext* getContext() { return _context; } diff --git a/libraries/gpu/src/gpu/Context.cpp b/libraries/gpu/src/gpu/Context.cpp index 99ecf80e39..c8e379480d 100644 --- a/libraries/gpu/src/gpu/Context.cpp +++ b/libraries/gpu/src/gpu/Context.cpp @@ -115,6 +115,7 @@ std::atomic<Buffer::Size> Context::_bufferGPUMemoryUsage{ 0 }; std::atomic<uint32_t> Context::_textureGPUCount{ 0 }; std::atomic<Texture::Size> Context::_textureGPUMemoryUsage{ 0 }; std::atomic<Texture::Size> Context::_textureGPUVirtualMemoryUsage{ 0 }; +std::atomic<uint32_t> Context::_textureGPUTransferCount{ 0 }; void Context::incrementBufferGPUCount() { _bufferGPUCount++; @@ -161,6 +162,13 @@ void Context::updateTextureGPUVirtualMemoryUsage(Size prevObjectSize, Size newOb } } +void Context::incrementTextureGPUTransferCount() { + _textureGPUTransferCount++; +} +void Context::decrementTextureGPUTransferCount() { + _textureGPUTransferCount--; +} + uint32_t Context::getBufferGPUCount() { return _bufferGPUCount.load(); } @@ -181,6 +189,10 @@ Context::Size Context::getTextureGPUVirtualMemoryUsage() { return _textureGPUVirtualMemoryUsage.load(); } +uint32_t Context::getTextureGPUTransferCount() { + return _textureGPUTransferCount.load(); +} + void Backend::incrementBufferGPUCount() { Context::incrementBufferGPUCount(); } void Backend::decrementBufferGPUCount() { Context::decrementBufferGPUCount(); } void Backend::updateBufferGPUMemoryUsage(Resource::Size prevObjectSize, Resource::Size newObjectSize) { Context::updateBufferGPUMemoryUsage(prevObjectSize, newObjectSize); } @@ -188,4 +200,5 @@ void Backend::incrementTextureGPUCount() { Context::incrementTextureGPUCount(); void Backend::decrementTextureGPUCount() { Context::decrementTextureGPUCount(); } void Backend::updateTextureGPUMemoryUsage(Resource::Size prevObjectSize, Resource::Size newObjectSize) { Context::updateTextureGPUMemoryUsage(prevObjectSize, newObjectSize); } void Backend::updateTextureGPUVirtualMemoryUsage(Resource::Size prevObjectSize, Resource::Size newObjectSize) { Context::updateTextureGPUVirtualMemoryUsage(prevObjectSize, newObjectSize); } - +void Backend::incrementTextureGPUTransferCount() { Context::incrementTextureGPUTransferCount(); } +void Backend::decrementTextureGPUTransferCount() { Context::decrementTextureGPUTransferCount(); } diff --git a/libraries/gpu/src/gpu/Context.h b/libraries/gpu/src/gpu/Context.h index 1dbe92ab38..869db97be7 100644 --- a/libraries/gpu/src/gpu/Context.h +++ b/libraries/gpu/src/gpu/Context.h @@ -130,6 +130,8 @@ public: static void decrementTextureGPUCount(); static void updateTextureGPUMemoryUsage(Resource::Size prevObjectSize, Resource::Size newObjectSize); static void updateTextureGPUVirtualMemoryUsage(Resource::Size prevObjectSize, Resource::Size newObjectSize); + static void incrementTextureGPUTransferCount(); + static void decrementTextureGPUTransferCount(); protected: StereoState _stereo; @@ -180,6 +182,7 @@ public: static uint32_t getTextureGPUCount(); static Size getTextureGPUMemoryUsage(); static Size getTextureGPUVirtualMemoryUsage(); + static uint32_t getTextureGPUTransferCount(); protected: Context(const Context& context); @@ -206,6 +209,8 @@ protected: static void decrementTextureGPUCount(); static void updateTextureGPUMemoryUsage(Size prevObjectSize, Size newObjectSize); static void updateTextureGPUVirtualMemoryUsage(Size prevObjectSize, Size newObjectSize); + static void incrementTextureGPUTransferCount(); + static void decrementTextureGPUTransferCount(); // Buffer and Texture Counters static std::atomic<uint32_t> _bufferGPUCount; @@ -214,6 +219,8 @@ protected: static std::atomic<uint32_t> _textureGPUCount; static std::atomic<Size> _textureGPUMemoryUsage; static std::atomic<Size> _textureGPUVirtualMemoryUsage; + static std::atomic<uint32_t> _textureGPUTransferCount; + friend class Backend; }; diff --git a/libraries/gpu/src/gpu/GLBackend.cpp b/libraries/gpu/src/gpu/GLBackend.cpp index b5b6437ed8..f51448f8fd 100644 --- a/libraries/gpu/src/gpu/GLBackend.cpp +++ b/libraries/gpu/src/gpu/GLBackend.cpp @@ -125,6 +125,7 @@ GLBackend::GLBackend() { glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &_uboAlignment); initInput(); initTransform(); + initTextureTransferHelper(); } GLBackend::~GLBackend() { diff --git a/libraries/gpu/src/gpu/GLBackend.h b/libraries/gpu/src/gpu/GLBackend.h index 77cb96ba9a..0f64f6d199 100644 --- a/libraries/gpu/src/gpu/GLBackend.h +++ b/libraries/gpu/src/gpu/GLBackend.h @@ -24,6 +24,8 @@ namespace gpu { +class GLTextureTransferHelper; + class GLBackend : public Backend { // Context Backend static interface required @@ -35,7 +37,6 @@ class GLBackend : public Backend { explicit GLBackend(bool syncCache); GLBackend(); public: - virtual ~GLBackend(); virtual void render(Batch& batch); @@ -75,12 +76,12 @@ public: class GLTexture : public GPUObject { public: - Stamp _storageStamp; - Stamp _contentStamp; - GLuint _texture; - GLenum _target; + const Stamp _storageStamp; + Stamp _contentStamp { 0 }; + const GLuint _texture; + const GLenum _target; - GLTexture(); + GLTexture(const gpu::Texture& gpuTexture); ~GLTexture(); GLuint size() const { return _size; } @@ -88,18 +89,59 @@ public: void updateSize(GLuint virtualSize); + enum SyncState { + // The texture is currently undergoing no processing, although it's content + // may be out of date, or it's storage may be invalid relative to the + // owning GPU texture + Idle, + // The texture has been queued for transfer to the GPU + Pending, + // The texture has been transferred to the GPU, but is awaiting + // any post transfer operations that may need to occur on the + // primary rendering thread + Transferred, + }; + + void setSyncState(SyncState syncState) { _syncState = syncState; } + SyncState getSyncState() const { return _syncState; } + + // Is the storage out of date relative to the gpu texture? + bool isInvalid() const; + + // Is the content out of date relative to the gpu texture? + bool isOutdated() const; + + // Is the texture in a state where it can be rendered with no work? + bool isReady() const; + + // Move the image bits from the CPU to the GPU + void transfer() const; + + // Execute any post-move operations that must occur only on the main thread + void postTransfer(); + + static const size_t CUBE_NUM_FACES = 6; + static const GLenum CUBE_FACE_LAYOUT[6]; + private: + void setSize(GLuint size); void setVirtualSize(GLuint size); GLuint _size; // true size as reported by the gl api GLuint _virtualSize; // theorical size as expected + + void transferMip(GLenum target, const Texture::PixelsPointer& mip) const; + + // The owning texture + const Texture& _gpuTexture; + std::atomic<SyncState> _syncState { SyncState::Idle }; }; - static GLTexture* syncGPUObject(const Texture& texture); + static GLTexture* syncGPUObject(const TexturePointer& texture); static GLuint getTextureID(const TexturePointer& texture, bool sync = true); // very specific for now - static void syncSampler(const Sampler& sampler, Texture::Type type, GLTexture* object); + static void syncSampler(const Sampler& sampler, Texture::Type type, const GLTexture* object); class GLShader : public GPUObject { public: @@ -247,6 +289,11 @@ protected: void renderPassTransfer(Batch& batch); void renderPassDraw(Batch& batch); + void initTextureTransferHelper(); + static void transferGPUObject(const TexturePointer& texture); + + static std::shared_ptr<GLTextureTransferHelper> _textureTransferHelper; + // Draw Stage void do_draw(Batch& batch, size_t paramOffset); void do_drawIndexed(Batch& batch, size_t paramOffset); @@ -490,6 +537,7 @@ protected: typedef void (GLBackend::*CommandCall)(Batch&, size_t); static CommandCall _commandCalls[Batch::NUM_COMMANDS]; + }; }; diff --git a/libraries/gpu/src/gpu/GLBackendOutput.cpp b/libraries/gpu/src/gpu/GLBackendOutput.cpp index 37a10e670b..4f714fb53d 100755 --- a/libraries/gpu/src/gpu/GLBackendOutput.cpp +++ b/libraries/gpu/src/gpu/GLBackendOutput.cpp @@ -83,7 +83,7 @@ GLBackend::GLFramebuffer* GLBackend::syncGPUObject(const Framebuffer& framebuffe for (auto& b : framebuffer.getRenderBuffers()) { surface = b._texture; if (surface) { - gltexture = GLBackend::syncGPUObject(*surface); + gltexture = GLBackend::syncGPUObject(surface); } else { gltexture = nullptr; } @@ -123,7 +123,7 @@ GLBackend::GLFramebuffer* GLBackend::syncGPUObject(const Framebuffer& framebuffe if (framebuffer.getDepthStamp() != object->_depthStamp) { auto surface = framebuffer.getDepthStencilBuffer(); if (framebuffer.hasDepthStencil() && surface) { - gltexture = GLBackend::syncGPUObject(*surface); + gltexture = GLBackend::syncGPUObject(surface); } if (gltexture) { diff --git a/libraries/gpu/src/gpu/GLBackendPipeline.cpp b/libraries/gpu/src/gpu/GLBackendPipeline.cpp index 4fe083909a..60ec478a8e 100755 --- a/libraries/gpu/src/gpu/GLBackendPipeline.cpp +++ b/libraries/gpu/src/gpu/GLBackendPipeline.cpp @@ -255,7 +255,7 @@ void GLBackend::do_setResourceTexture(Batch& batch, size_t paramOffset) { _stats._RSNumTextureBounded++; // Always make sure the GLObject is in sync - GLTexture* object = GLBackend::syncGPUObject(*resourceTexture); + GLTexture* object = GLBackend::syncGPUObject(resourceTexture); if (object) { GLuint to = object->_texture; GLuint target = object->_target; diff --git a/libraries/gpu/src/gpu/GLBackendShared.h b/libraries/gpu/src/gpu/GLBackendShared.h index d6ed591b4f..d6aab6034d 100644 --- a/libraries/gpu/src/gpu/GLBackendShared.h +++ b/libraries/gpu/src/gpu/GLBackendShared.h @@ -51,7 +51,9 @@ public: GLenum format; GLenum type; - + static GLTexelFormat evalGLTexelFormat(const gpu::Element& dstFormat) { + return evalGLTexelFormat(dstFormat, dstFormat); + } static GLTexelFormat evalGLTexelFormat(const gpu::Element& dstFormat, const gpu::Element& srcFormat); }; diff --git a/libraries/gpu/src/gpu/GLBackendTexelFormat.cpp b/libraries/gpu/src/gpu/GLBackendTexelFormat.cpp index bd31fd353a..3f64f373d6 100644 --- a/libraries/gpu/src/gpu/GLBackendTexelFormat.cpp +++ b/libraries/gpu/src/gpu/GLBackendTexelFormat.cpp @@ -22,7 +22,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E switch (dstFormat.getSemantic()) { case gpu::RGB: case gpu::RGBA: - texel.internalFormat = GL_RED; + texel.internalFormat = GL_R8; break; case gpu::COMPRESSED_R: @@ -30,7 +30,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E break; case gpu::DEPTH: - texel.internalFormat = GL_DEPTH_COMPONENT; + texel.internalFormat = GL_DEPTH_COMPONENT32; break; case gpu::DEPTH_STENCIL: texel.type = GL_UNSIGNED_INT_24_8; @@ -50,7 +50,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E switch (dstFormat.getSemantic()) { case gpu::RGB: case gpu::RGBA: - texel.internalFormat = GL_RG; + texel.internalFormat = GL_RG8; break; default: qCDebug(gpulogging) << "Unknown combination of texel format"; @@ -67,7 +67,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E switch (dstFormat.getSemantic()) { case gpu::RGB: case gpu::RGBA: - texel.internalFormat = GL_RGB; + texel.internalFormat = GL_RGB8; break; case gpu::COMPRESSED_RGB: texel.internalFormat = GL_COMPRESSED_RGB; @@ -101,16 +101,16 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E switch (dstFormat.getSemantic()) { case gpu::RGB: - texel.internalFormat = GL_RGB; + texel.internalFormat = GL_RGB8; break; case gpu::RGBA: - texel.internalFormat = GL_RGBA; + texel.internalFormat = GL_RGBA8; break; case gpu::SRGB: - texel.internalFormat = GL_SRGB; + texel.internalFormat = GL_SRGB8; break; case gpu::SRGBA: - texel.internalFormat = GL_SRGB_ALPHA; + texel.internalFormat = GL_SRGB8_ALPHA8; break; case gpu::COMPRESSED_RGBA: @@ -148,7 +148,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E } return texel; } else { - GLTexelFormat texel = { GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE }; + GLTexelFormat texel = { GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE }; switch (dstFormat.getDimension()) { case gpu::SCALAR: { @@ -171,11 +171,11 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E break; } case gpu::NUINT32: { - texel.internalFormat = GL_RED; + texel.internalFormat = GL_R8; break; } case gpu::NINT32: { - texel.internalFormat = GL_RED_SNORM; + texel.internalFormat = GL_R8_SNORM; break; } case gpu::FLOAT: { @@ -212,7 +212,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E } case gpu::NUINT8: { if ((dstFormat.getSemantic() == gpu::SRGB || dstFormat.getSemantic() == gpu::SRGBA)) { - texel.internalFormat = GL_SLUMINANCE; + texel.internalFormat = GL_SLUMINANCE8; } else { texel.internalFormat = GL_R8; } @@ -237,7 +237,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E case gpu::DEPTH: texel.format = GL_DEPTH_COMPONENT; // It's depth component to load it - texel.internalFormat = GL_DEPTH_COMPONENT; + texel.internalFormat = GL_DEPTH_COMPONENT32; switch (dstFormat.getType()) { case gpu::UINT32: case gpu::INT32: @@ -289,7 +289,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E switch (dstFormat.getSemantic()) { case gpu::RGB: case gpu::RGBA: - texel.internalFormat = GL_RG; + texel.internalFormat = GL_RG8; break; default: qCDebug(gpulogging) << "Unknown combination of texel format"; @@ -306,11 +306,11 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E switch (dstFormat.getSemantic()) { case gpu::RGB: case gpu::RGBA: - texel.internalFormat = GL_RGB; + texel.internalFormat = GL_RGB8; break; case gpu::SRGB: case gpu::SRGBA: - texel.internalFormat = GL_SRGB; // standard 2.2 gamma correction color + texel.internalFormat = GL_SRGB8; // standard 2.2 gamma correction color break; default: qCDebug(gpulogging) << "Unknown combination of texel format"; @@ -324,10 +324,10 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E switch (dstFormat.getSemantic()) { case gpu::RGB: - texel.internalFormat = GL_RGB; + texel.internalFormat = GL_RGB8; break; case gpu::RGBA: - texel.internalFormat = GL_RGBA; + texel.internalFormat = GL_RGBA8; switch (dstFormat.getType()) { case gpu::UINT32: texel.format = GL_RGBA_INTEGER; @@ -383,10 +383,10 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E } break; case gpu::SRGB: - texel.internalFormat = GL_SRGB; + texel.internalFormat = GL_SRGB8; break; case gpu::SRGBA: - texel.internalFormat = GL_SRGB_ALPHA; // standard 2.2 gamma correction color + texel.internalFormat = GL_SRGB8_ALPHA8; // standard 2.2 gamma correction color break; default: qCDebug(gpulogging) << "Unknown combination of texel format"; diff --git a/libraries/gpu/src/gpu/GLBackendTexture.cpp b/libraries/gpu/src/gpu/GLBackendTexture.cpp index e30581e53d..35c8666292 100755 --- a/libraries/gpu/src/gpu/GLBackendTexture.cpp +++ b/libraries/gpu/src/gpu/GLBackendTexture.cpp @@ -9,19 +9,83 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "GPULogging.h" + +#include <QtCore/QThread> + #include "GLBackendShared.h" +#include "GLBackendTextureTransfer.h" using namespace gpu; -GLBackend::GLTexture::GLTexture() : - _storageStamp(0), - _contentStamp(0), - _texture(0), - _target(GL_TEXTURE_2D), +GLenum gpuToGLTextureType(const Texture& texture) { + switch (texture.getType()) { + case Texture::TEX_2D: + return GL_TEXTURE_2D; + break; + + case Texture::TEX_CUBE: + return GL_TEXTURE_CUBE_MAP; + break; + + default: + qFatal("Unsupported texture type"); + } + Q_UNREACHABLE(); + return GL_TEXTURE_2D; +} + +GLuint allocateSingleTexture() { + GLuint result; + glGenTextures(1, &result); + return result; +} + +const GLenum GLBackend::GLTexture::CUBE_FACE_LAYOUT[6] = { + GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, + GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, + GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z +}; + +// Create the texture and allocate storage +GLBackend::GLTexture::GLTexture(const Texture& texture) : + _storageStamp(texture.getStamp()), + _texture(allocateSingleTexture()), + _target(gpuToGLTextureType(texture)), _size(0), - _virtualSize(0) + _virtualSize(0), + _gpuTexture(texture) { Backend::incrementTextureGPUCount(); + Backend::updateTextureGPUMemoryUsage(0, _size); + Backend::setGPUObject(texture, this); + + GLsizei width = texture.getWidth(); + GLsizei height = texture.getHeight(); + GLsizei levels = 1; + if (texture.maxMip() > 0) { + if (texture.isAutogenerateMips()) { + while ((width | height) >> levels) { + ++levels; + } + } + levels = std::max(1, std::min(texture.maxMip() + 1, levels)); + } + + GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(texture.getTexelFormat()); + withPreservedTexture(_target, [&] { + glBindTexture(_target, _texture); + (void)CHECK_GL_ERROR(); + // GO through the process of allocating the correct storage + if (GLEW_VERSION_4_2) { + glTexStorage2D(_target, levels, texelFormat.internalFormat, width, height); + } else { + glTexImage2D(_target, 0, texelFormat.internalFormat, width, height, 0, texelFormat.format, texelFormat.type, 0); + } + (void)CHECK_GL_ERROR(); + syncSampler(texture.getSampler(), texture.getType(), this); + (void)CHECK_GL_ERROR(); + updateSize((GLuint)texture.evalTotalSize()); + }); } GLBackend::GLTexture::~GLTexture() { @@ -33,6 +97,10 @@ GLBackend::GLTexture::~GLTexture() { Backend::decrementTextureGPUCount(); } +bool GLBackend::GLTexture::isInvalid() const { + return _storageStamp < _gpuTexture.getStamp(); +} + void GLBackend::GLTexture::setSize(GLuint size) { Backend::updateTextureGPUMemoryUsage(_size, size); _size = size; @@ -68,195 +136,143 @@ void GLBackend::GLTexture::updateSize(GLuint virtualSize) { } } -GLBackend::GLTexture* GLBackend::syncGPUObject(const Texture& texture) { - GLTexture* object = Backend::getGPUObject<GLBackend::GLTexture>(texture); - // If GPU object already created and in sync - bool needUpdate = false; - if (object && (object->_storageStamp == texture.getStamp())) { - // If gpu object info is in sync with sysmem version - if (object->_contentStamp >= texture.getDataStamp()) { - // Then all good, GPU object is ready to be used - return object; - } else { - // Need to update the content of the GPU object from the source sysmem of the texture - needUpdate = true; - } - } else if (!texture.isDefined()) { +bool GLBackend::GLTexture::isOutdated() const { + return _contentStamp < _gpuTexture.getDataStamp(); +} + +bool GLBackend::GLTexture::isReady() const { + // If we have an invalid texture, we're never ready + if (isInvalid()) { + return false; + } + + // If we're out of date, but the transfer is in progress, report ready + // as a special case + auto syncState = _syncState.load(); + + if (isOutdated()) { + return Pending == syncState; + } + + return Idle == syncState; +} + + +// Move content bits from the CPU to the GPU for a given mip / face +void GLBackend::GLTexture::transferMip(GLenum target, const Texture::PixelsPointer& mip) const { + GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuTexture.getTexelFormat(), mip->getFormat()); + + glTexSubImage2D(target, 0, 0, 0, _gpuTexture.getWidth(), _gpuTexture.getHeight(), texelFormat.format, texelFormat.type, mip->readData()); + (void)CHECK_GL_ERROR(); +} + +// Move content bits from the CPU to the GPU +void GLBackend::GLTexture::transfer() const { + PROFILE_RANGE(__FUNCTION__); + qDebug() << "Transferring texture: " << _texture; + // Need to update the content of the GPU object from the source sysmem of the texture + if (_contentStamp >= _gpuTexture.getDataStamp()) { + return; + } + + glBindTexture(_target, _texture); + // GO through the process of allocating the correct storage and/or update the content + switch (_gpuTexture.getType()) { + case Texture::TEX_2D: + if (_gpuTexture.isStoredMipFaceAvailable(0)) { + transferMip(GL_TEXTURE_2D, _gpuTexture.accessStoredMipFace(0)); + } + break; + + case Texture::TEX_CUBE: + // transfer pixels from each faces + for (uint8_t f = 0; f < CUBE_NUM_FACES; f++) { + if (_gpuTexture.isStoredMipFaceAvailable(0, f)) { + transferMip(CUBE_FACE_LAYOUT[f], _gpuTexture.accessStoredMipFace(0, f)); + } + } + break; + + default: + qCWarning(gpulogging) << __FUNCTION__ << " case for Texture Type " << _gpuTexture.getType() << " not supported"; + break; + } + + if (_gpuTexture.isAutogenerateMips()) { + glGenerateMipmap(_target); + (void)CHECK_GL_ERROR(); + } +} + +// Do any post-transfer operations that might be required on the main context / rendering thread +void GLBackend::GLTexture::postTransfer() { + setSyncState(GLTexture::Idle); + // At this point the mip pixels have been loaded, we can notify the gpu texture to abandon it's memory + switch (_gpuTexture.getType()) { + case Texture::TEX_2D: + _gpuTexture.notifyMipFaceGPULoaded(0, 0); + break; + + case Texture::TEX_CUBE: + for (uint8_t f = 0; f < CUBE_NUM_FACES; ++f) { + _gpuTexture.notifyMipFaceGPULoaded(0, f); + } + break; + + default: + qCWarning(gpulogging) << __FUNCTION__ << " case for Texture Type " << _gpuTexture.getType() << " not supported"; + break; + } +} + +GLBackend::GLTexture* GLBackend::syncGPUObject(const TexturePointer& texturePointer) { + const Texture& texture = *texturePointer; + if (!texture.isDefined()) { // NO texture definition yet so let's avoid thinking return nullptr; } + // If the object hasn't been created, or the object definition is out of date, drop and re-create + GLTexture* object = Backend::getGPUObject<GLBackend::GLTexture>(texture); + if (object && object->isReady()) { + return object; + } + + // Object isn't ready, check what we need to do... + + // Create the texture if need be (force re-creation if the storage stamp changes + // for easier use of immutable storage) + if (!object || object->isInvalid()) { + // This automatically destroys the old texture + object = new GLTexture(texture); + } + // need to have a gpu object? - if (!object) { - object = new GLTexture(); - glGenTextures(1, &object->_texture); - (void) CHECK_GL_ERROR(); - Backend::setGPUObject(texture, object); + if (texture.getNumSlices() != 1) { + return object; } - // GO through the process of allocating the correct storage and/or update the content - switch (texture.getType()) { - case Texture::TEX_2D: { - if (texture.getNumSlices() == 1) { - GLint boundTex = -1; - glGetIntegerv(GL_TEXTURE_BINDING_2D, &boundTex); - glBindTexture(GL_TEXTURE_2D, object->_texture); - - if (needUpdate) { - if (texture.isStoredMipFaceAvailable(0)) { - Texture::PixelsPointer mip = texture.accessStoredMipFace(0); - const GLvoid* bytes = mip->readData(); - Element srcFormat = mip->getFormat(); - - GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(texture.getTexelFormat(), srcFormat); - - glBindTexture(GL_TEXTURE_2D, object->_texture); - glTexSubImage2D(GL_TEXTURE_2D, 0, - texelFormat.internalFormat, texture.getWidth(), texture.getHeight(), 0, - texelFormat.format, texelFormat.type, bytes); - - if (texture.isAutogenerateMips()) { - glGenerateMipmap(GL_TEXTURE_2D); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - } - - object->_target = GL_TEXTURE_2D; - - syncSampler(texture.getSampler(), texture.getType(), object); - - // At this point the mip piels have been loaded, we can notify - texture.notifyMipFaceGPULoaded(0, 0); - - object->_contentStamp = texture.getDataStamp(); - object->updateSize((GLuint)texture.evalTotalSize()); - } - } else { - const GLvoid* bytes = 0; - Element srcFormat = texture.getTexelFormat(); - if (texture.isStoredMipFaceAvailable(0)) { - Texture::PixelsPointer mip = texture.accessStoredMipFace(0); - - bytes = mip->readData(); - srcFormat = mip->getFormat(); - - object->_contentStamp = texture.getDataStamp(); - } - - GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(texture.getTexelFormat(), srcFormat); - - glTexImage2D(GL_TEXTURE_2D, 0, - texelFormat.internalFormat, texture.getWidth(), texture.getHeight(), 0, - texelFormat.format, texelFormat.type, bytes); - - if (bytes && texture.isAutogenerateMips()) { - glGenerateMipmap(GL_TEXTURE_2D); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - } - object->_target = GL_TEXTURE_2D; - - syncSampler(texture.getSampler(), texture.getType(), object); - - // At this point the mip pixels have been loaded, we can notify - texture.notifyMipFaceGPULoaded(0, 0); - - object->_storageStamp = texture.getStamp(); - object->_contentStamp = texture.getDataStamp(); - object->updateSize((GLuint)texture.evalTotalSize()); - } - - glBindTexture(GL_TEXTURE_2D, boundTex); - } - break; + // Object might be outdated, if so, start the transfer + // (outdated objects that are already in transfer will have reported 'true' for ready() + if (object->isOutdated()) { + Backend::incrementTextureGPUTransferCount(); + _textureTransferHelper->transferTexture(texturePointer); } - case Texture::TEX_CUBE: { - if (texture.getNumSlices() == 1) { - GLint boundTex = -1; - glGetIntegerv(GL_TEXTURE_BINDING_CUBE_MAP, &boundTex); - glBindTexture(GL_TEXTURE_CUBE_MAP, object->_texture); - const int NUM_FACES = 6; - const GLenum FACE_LAYOUT[] = { - GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, - GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, - GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z }; - if (needUpdate) { - glBindTexture(GL_TEXTURE_CUBE_MAP, object->_texture); - // transfer pixels from each faces - for (int f = 0; f < NUM_FACES; f++) { - if (texture.isStoredMipFaceAvailable(0, f)) { - Texture::PixelsPointer mipFace = texture.accessStoredMipFace(0, f); - Element srcFormat = mipFace->getFormat(); - GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(texture.getTexelFormat(), srcFormat); - - glTexSubImage2D(FACE_LAYOUT[f], 0, texelFormat.internalFormat, texture.getWidth(), texture.getWidth(), 0, - texelFormat.format, texelFormat.type, (GLvoid*) (mipFace->readData())); - - // At this point the mip pixels have been loaded, we can notify - texture.notifyMipFaceGPULoaded(0, f); - } - } - - if (texture.isAutogenerateMips()) { - glGenerateMipmap(GL_TEXTURE_CUBE_MAP); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - } - - object->_target = GL_TEXTURE_CUBE_MAP; - - syncSampler(texture.getSampler(), texture.getType(), object); - - object->_contentStamp = texture.getDataStamp(); - - object->updateSize((GLuint)texture.evalTotalSize()); - } else { - glBindTexture(GL_TEXTURE_CUBE_MAP, object->_texture); - - // transfer pixels from each faces - for (int f = 0; f < NUM_FACES; f++) { - if (texture.isStoredMipFaceAvailable(0, f)) { - Texture::PixelsPointer mipFace = texture.accessStoredMipFace(0, f); - Element srcFormat = mipFace->getFormat(); - GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(texture.getTexelFormat(), srcFormat); - - glTexImage2D(FACE_LAYOUT[f], 0, texelFormat.internalFormat, texture.getWidth(), texture.getWidth(), 0, - texelFormat.format, texelFormat.type, (GLvoid*) (mipFace->readData())); - - // At this point the mip pixels have been loaded, we can notify - texture.notifyMipFaceGPULoaded(0, f); - } - } - - if (texture.isAutogenerateMips()) { - glGenerateMipmap(GL_TEXTURE_CUBE_MAP); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - } else { - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - } - - object->_target = GL_TEXTURE_CUBE_MAP; - - syncSampler(texture.getSampler(), texture.getType(), object); - - object->_storageStamp = texture.getStamp(); - object->_contentStamp = texture.getDataStamp(); - object->updateSize((GLuint)texture.evalTotalSize()); - } - - glBindTexture(GL_TEXTURE_CUBE_MAP, boundTex); - } - break; + if (GLTexture::Transferred == object->getSyncState()) { + Backend::decrementTextureGPUTransferCount(); + object->postTransfer(); } - default: - qCDebug(gpulogging) << "GLBackend::syncGPUObject(const Texture&) case for Texture Type " << texture.getType() << " not supported"; - } - (void) CHECK_GL_ERROR(); return object; } +std::shared_ptr<GLTextureTransferHelper> GLBackend::_textureTransferHelper; +void GLBackend::initTextureTransferHelper() { + _textureTransferHelper = std::make_shared<GLTextureTransferHelper>(); +} GLuint GLBackend::getTextureID(const TexturePointer& texture, bool sync) { if (!texture) { @@ -264,7 +280,7 @@ GLuint GLBackend::getTextureID(const TexturePointer& texture, bool sync) { } GLTexture* object { nullptr }; if (sync) { - object = GLBackend::syncGPUObject(*texture); + object = GLBackend::syncGPUObject(texture); } else { object = Backend::getGPUObject<GLBackend::GLTexture>(*texture); } @@ -275,38 +291,37 @@ GLuint GLBackend::getTextureID(const TexturePointer& texture, bool sync) { } } -void GLBackend::syncSampler(const Sampler& sampler, Texture::Type type, GLTexture* object) { +void GLBackend::syncSampler(const Sampler& sampler, Texture::Type type, const GLTexture* object) { if (!object) return; - if (!object->_texture) return; class GLFilterMode { public: GLint minFilter; GLint magFilter; }; - static const GLFilterMode filterModes[] = { - {GL_NEAREST, GL_NEAREST}, //FILTER_MIN_MAG_POINT, - {GL_NEAREST, GL_LINEAR}, //FILTER_MIN_POINT_MAG_LINEAR, - {GL_LINEAR, GL_NEAREST}, //FILTER_MIN_LINEAR_MAG_POINT, - {GL_LINEAR, GL_LINEAR}, //FILTER_MIN_MAG_LINEAR, - - {GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST}, //FILTER_MIN_MAG_MIP_POINT, - {GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST}, //FILTER_MIN_MAG_MIP_POINT, - {GL_NEAREST_MIPMAP_LINEAR, GL_NEAREST}, //FILTER_MIN_MAG_POINT_MIP_LINEAR, - {GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR}, //FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT, - {GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR}, //FILTER_MIN_POINT_MAG_MIP_LINEAR, - {GL_LINEAR_MIPMAP_NEAREST, GL_NEAREST}, //FILTER_MIN_LINEAR_MAG_MIP_POINT, - {GL_LINEAR_MIPMAP_LINEAR, GL_NEAREST}, //FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR, - {GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR}, //FILTER_MIN_MAG_LINEAR_MIP_POINT, - {GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR}, //FILTER_MIN_MAG_MIP_LINEAR, - {GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR} //FILTER_ANISOTROPIC, + static const GLFilterMode filterModes[] = { + { GL_NEAREST, GL_NEAREST }, //FILTER_MIN_MAG_POINT, + { GL_NEAREST, GL_LINEAR }, //FILTER_MIN_POINT_MAG_LINEAR, + { GL_LINEAR, GL_NEAREST }, //FILTER_MIN_LINEAR_MAG_POINT, + { GL_LINEAR, GL_LINEAR }, //FILTER_MIN_MAG_LINEAR, + + { GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST }, //FILTER_MIN_MAG_MIP_POINT, + { GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST }, //FILTER_MIN_MAG_MIP_POINT, + { GL_NEAREST_MIPMAP_LINEAR, GL_NEAREST }, //FILTER_MIN_MAG_POINT_MIP_LINEAR, + { GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR }, //FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT, + { GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR }, //FILTER_MIN_POINT_MAG_MIP_LINEAR, + { GL_LINEAR_MIPMAP_NEAREST, GL_NEAREST }, //FILTER_MIN_LINEAR_MAG_MIP_POINT, + { GL_LINEAR_MIPMAP_LINEAR, GL_NEAREST }, //FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR, + { GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR }, //FILTER_MIN_MAG_LINEAR_MIP_POINT, + { GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR }, //FILTER_MIN_MAG_MIP_LINEAR, + { GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR } //FILTER_ANISOTROPIC, }; auto fm = filterModes[sampler.getFilter()]; glTexParameteri(object->_target, GL_TEXTURE_MIN_FILTER, fm.minFilter); glTexParameteri(object->_target, GL_TEXTURE_MAG_FILTER, fm.magFilter); - static const GLenum comparisonFuncs[] = { + static const GLenum comparisonFuncs[] = { GL_NEVER, GL_LESS, GL_EQUAL, @@ -323,7 +338,7 @@ void GLBackend::syncSampler(const Sampler& sampler, Texture::Type type, GLTextur glTexParameteri(object->_target, GL_TEXTURE_COMPARE_MODE, GL_NONE); } - static const GLenum wrapModes[] = { + static const GLenum wrapModes[] = { GL_REPEAT, // WRAP_REPEAT, GL_MIRRORED_REPEAT, // WRAP_MIRROR, GL_CLAMP_TO_EDGE, // WRAP_CLAMP, @@ -334,23 +349,20 @@ void GLBackend::syncSampler(const Sampler& sampler, Texture::Type type, GLTextur glTexParameteri(object->_target, GL_TEXTURE_WRAP_T, wrapModes[sampler.getWrapModeV()]); glTexParameteri(object->_target, GL_TEXTURE_WRAP_R, wrapModes[sampler.getWrapModeW()]); - glTexParameterfv(object->_target, GL_TEXTURE_BORDER_COLOR, (const float*) &sampler.getBorderColor()); + glTexParameterfv(object->_target, GL_TEXTURE_BORDER_COLOR, (const float*)&sampler.getBorderColor()); glTexParameteri(object->_target, GL_TEXTURE_BASE_LEVEL, sampler.getMipOffset()); - glTexParameterf(object->_target, GL_TEXTURE_MIN_LOD, (float) sampler.getMinMip()); + glTexParameterf(object->_target, GL_TEXTURE_MIN_LOD, (float)sampler.getMinMip()); glTexParameterf(object->_target, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.f : sampler.getMaxMip())); glTexParameterf(object->_target, GL_TEXTURE_MAX_ANISOTROPY_EXT, sampler.getMaxAnisotropy()); - } - - void GLBackend::do_generateTextureMips(Batch& batch, size_t paramOffset) { TexturePointer resourceTexture = batch._textures.get(batch._params[paramOffset + 0]._uint); if (!resourceTexture) { return; } - GLTexture* object = GLBackend::syncGPUObject(*resourceTexture); + GLTexture* object = GLBackend::syncGPUObject(resourceTexture); if (!object) { return; } @@ -365,7 +377,7 @@ void GLBackend::do_generateTextureMips(Batch& batch, size_t paramOffset) { if (freeSlot < 0) { // If had to use slot 0 then restore state - GLTexture* boundObject = GLBackend::syncGPUObject(*_resource._textures[0]); + GLTexture* boundObject = GLBackend::syncGPUObject(_resource._textures[0]); if (boundObject) { glBindTexture(boundObject->_target, boundObject->_texture); } diff --git a/libraries/gpu/src/gpu/GLBackendTextureTransfer.cpp b/libraries/gpu/src/gpu/GLBackendTextureTransfer.cpp new file mode 100644 index 0000000000..1ad18c8c2f --- /dev/null +++ b/libraries/gpu/src/gpu/GLBackendTextureTransfer.cpp @@ -0,0 +1,76 @@ +// +// Created by Bradley Austin Davis on 2016/04/03 +// Copyright 2013-2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "GLBackendTextureTransfer.h" + +#include "GLBackendShared.h" + +#ifdef THREADED_TEXTURE_TRANSFER + +#include <gl/OffscreenGLCanvas.h> +#include <gl/QOpenGLContextWrapper.h> + +#endif + +using namespace gpu; + +GLTextureTransferHelper::GLTextureTransferHelper() { +#ifdef THREADED_TEXTURE_TRANSFER + _canvas = std::make_shared<OffscreenGLCanvas>(); + _canvas->create(QOpenGLContextWrapper::currentContext()); + if (!_canvas->makeCurrent()) { + qFatal("Unable to create texture transfer context"); + } + _canvas->doneCurrent(); + initialize(true, QThread::LowPriority); + _canvas->moveToThreadWithContext(_thread); +#endif +} + +void GLTextureTransferHelper::transferTexture(const gpu::TexturePointer& texturePointer) { + GLBackend::GLTexture* object = Backend::getGPUObject<GLBackend::GLTexture>(*texturePointer); +#ifdef THREADED_TEXTURE_TRANSFER + TextureTransferPackage package { texturePointer, glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0) }; + glFlush(); + object->setSyncState(GLBackend::GLTexture::Pending); + queueItem(package); +#else + object->transfer(); + object->setSyncState(GLBackend::GLTexture::Transferred); +#endif +} + +void GLTextureTransferHelper::setup() { +#ifdef THREADED_TEXTURE_TRANSFER + _canvas->makeCurrent(); +#endif +} + +bool GLTextureTransferHelper::processQueueItems(const Queue& messages) { + for (auto package : messages) { + glWaitSync(package.fence, 0, GL_TIMEOUT_IGNORED); + glDeleteSync(package.fence); + TexturePointer texturePointer = package.texture.lock(); + // Texture no longer exists, move on to the next + if (!texturePointer) { + continue; + } + + GLBackend::GLTexture* object = Backend::getGPUObject<GLBackend::GLTexture>(*texturePointer); + object->transfer(); + + + glBindTexture(object->_target, 0); + auto writeSync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + glClientWaitSync(writeSync, GL_SYNC_FLUSH_COMMANDS_BIT, GL_TIMEOUT_IGNORED); + glDeleteSync(writeSync); + object->_contentStamp = texturePointer->getDataStamp(); + object->setSyncState(GLBackend::GLTexture::Transferred); + } + return true; +} diff --git a/libraries/gpu/src/gpu/GLBackendTextureTransfer.h b/libraries/gpu/src/gpu/GLBackendTextureTransfer.h new file mode 100644 index 0000000000..3a147defdf --- /dev/null +++ b/libraries/gpu/src/gpu/GLBackendTextureTransfer.h @@ -0,0 +1,61 @@ +// +// Created by Bradley Austin Davis on 2016/04/03 +// Copyright 2013-2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include <GenericQueueThread.h> +#include "GLBackendShared.h" + +#define THREADED_TEXTURE_TRANSFER + +class OffscreenGLCanvas; + +namespace gpu { + +struct TextureTransferPackage { + std::weak_ptr<Texture> texture; + GLsync fence; +}; + +class GLTextureTransferHelper : public GenericQueueThread<TextureTransferPackage> { +public: + GLTextureTransferHelper(); + void transferTexture(const gpu::TexturePointer& texturePointer); + void postTransfer(const gpu::TexturePointer& texturePointer); + +protected: + void setup() override; + bool processQueueItems(const Queue& messages) override; + void transferTextureSynchronous(const gpu::Texture& texture); + +private: + std::shared_ptr<OffscreenGLCanvas> _canvas; +}; + +template <typename F> +void withPreservedTexture(GLenum target, F f) { + GLint boundTex = -1; + switch (target) { + case GL_TEXTURE_2D: + glGetIntegerv(GL_TEXTURE_BINDING_2D, &boundTex); + break; + + case GL_TEXTURE_CUBE_MAP: + glGetIntegerv(GL_TEXTURE_BINDING_CUBE_MAP, &boundTex); + break; + + default: + qFatal("Unsupported texture type"); + } + (void)CHECK_GL_ERROR(); + + f(); + + glBindTexture(target, boundTex); + (void)CHECK_GL_ERROR(); +} + +} diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index 73aac84d35..15ae609fb9 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -52,6 +52,10 @@ Texture::Size Texture::getTextureGPUVirtualMemoryUsage() { return Context::getTextureGPUVirtualMemoryUsage(); } +uint32_t Texture::getTextureGPUTransferCount() { + return Context::getTextureGPUTransferCount(); +} + uint8 Texture::NUM_FACES_PER_TYPE[NUM_TYPES] = { 1, 1, 1, 6 }; Texture::Pixels::Pixels(const Element& format, Size size, const Byte* bytes) : diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 8df2791f3b..9dde359596 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -147,6 +147,7 @@ public: static uint32_t getTextureGPUCount(); static Size getTextureGPUMemoryUsage(); static Size getTextureGPUVirtualMemoryUsage(); + static uint32_t getTextureGPUTransferCount(); class Usage { public: diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index dc78cd812b..51db1965bf 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -13,20 +13,22 @@ #include <mutex> -#include <glm/glm.hpp> -#include <glm/gtc/random.hpp> - #include <QNetworkReply> #include <QPainter> #include <QRunnable> #include <QThreadPool> -#include <qimagereader.h> +#include <QImageReader> -#include <shared/NsightHelpers.h> -#include <PathUtils.h> +#include <glm/glm.hpp> +#include <glm/gtc/random.hpp> #include <gpu/Batch.h> +#include <shared/NsightHelpers.h> + +#include <Finally.h> +#include <PathUtils.h> + #include "ModelNetworkingLogging.h" TextureCache::TextureCache() { @@ -198,7 +200,7 @@ NetworkTexture::NetworkTexture(const QUrl& url, const TextureLoaderFunc& texture { _textureLoader = textureLoader; } - + NetworkTexture::TextureLoaderFunc NetworkTexture::getTextureLoader() const { switch (_type) { case ALBEDO_TEXTURE: { @@ -253,14 +255,14 @@ NetworkTexture::TextureLoaderFunc NetworkTexture::getTextureLoader() const { class ImageReader : public QRunnable { public: - ImageReader(const QWeakPointer<Resource>& texture, const QByteArray& data, const QUrl& url = QUrl()); + ImageReader(const QWeakPointer<Resource>& resource, const QByteArray& data, const QUrl& url = QUrl()); virtual void run(); private: static void listSupportedImageFormats(); - QWeakPointer<Resource> _texture; + QWeakPointer<Resource> _resource; QUrl _url; QByteArray _content; }; @@ -274,9 +276,9 @@ void NetworkTexture::loadContent(const QByteArray& content) { QThreadPool::globalInstance()->start(new ImageReader(_self, content, _url)); } -ImageReader::ImageReader(const QWeakPointer<Resource>& texture, const QByteArray& data, +ImageReader::ImageReader(const QWeakPointer<Resource>& resource, const QByteArray& data, const QUrl& url) : - _texture(texture), + _resource(resource), _url(url), _content(data) { @@ -297,26 +299,28 @@ void ImageReader::run() { originalPriority = QThread::NormalPriority; } QThread::currentThread()->setPriority(QThread::LowPriority); + Finally restorePriority([originalPriority]{ + QThread::currentThread()->setPriority(originalPriority); + }); - auto texture = _texture.toStrongRef(); - if (!texture) { - qCWarning(modelnetworking) << "Could not get strong ref"; + if (!_resource.data()) { + qCWarning(modelnetworking) << "Abandoning load of" << _url << "; could not get strong ref"; return; } listSupportedImageFormats(); - // try to help the QImage loader by extracting the image file format from the url filename ext - // Some tga are not created properly for example without it + // Help the QImage loader by extracting the image file format from the url filename ext. + // Some tga are not created properly without it. auto filename = _url.fileName().toStdString(); auto filenameExtension = filename.substr(filename.find_last_of('.') + 1); QImage image = QImage::fromData(_content, filenameExtension.c_str()); // Note that QImage.format is the pixel format which is different from the "format" of the image file... - auto imageFormat = image.format(); + auto imageFormat = image.format(); int originalWidth = image.width(); int originalHeight = image.height(); - + if (originalWidth == 0 || originalHeight == 0 || imageFormat == QImage::Format_Invalid) { if (filenameExtension.empty()) { qCDebug(modelnetworking) << "QImage failed to create from content, no file extension:" << _url; @@ -326,26 +330,40 @@ void ImageReader::run() { return; } - gpu::Texture* theTexture = nullptr; - auto ntex = texture.dynamicCast<NetworkTexture>(); - if (ntex) { + gpu::Texture* texture = nullptr; + { + // Double-check the resource still exists between long operations. + auto resource = _resource.toStrongRef(); + if (!resource) { + qCWarning(modelnetworking) << "Abandoning load of" << _url << "; could not get strong ref"; + return; + } + + auto url = _url.toString().toStdString(); + PROFILE_RANGE_EX(__FUNCTION__"::textureLoader", 0xffffff00, nullptr); - theTexture = ntex->getTextureLoader()(image, _url.toString().toStdString()); + texture = resource.dynamicCast<NetworkTexture>()->getTextureLoader()(image, url); } - QMetaObject::invokeMethod(texture.data(), "setImage", - Q_ARG(void*, theTexture), - Q_ARG(int, originalWidth), Q_ARG(int, originalHeight)); - QThread::currentThread()->setPriority(originalPriority); + // Ensure the resource has not been deleted, and won't be while invokeMethod is in flight. + auto resource = _resource.toStrongRef(); + if (!resource) { + qCWarning(modelnetworking) << "Abandoning load of" << _url << "; could not get strong ref"; + delete texture; + } else { + QMetaObject::invokeMethod(resource.data(), "setImage", Qt::BlockingQueuedConnection, + Q_ARG(void*, texture), + Q_ARG(int, originalWidth), Q_ARG(int, originalHeight)); + } } void NetworkTexture::setImage(void* voidTexture, int originalWidth, int originalHeight) { _originalWidth = originalWidth; _originalHeight = originalHeight; - + gpu::Texture* texture = static_cast<gpu::Texture*>(voidTexture); - + // Passing ownership _textureSource->resetTexture(texture); auto gpuTexture = _textureSource->getGPUTexture(); diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 02bb17e870..63189abb3e 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -336,6 +336,7 @@ void NodeList::sendDomainServerCheckIn() { if (_numNoReplyDomainCheckIns >= MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { // we haven't heard back from DS in MAX_SILENT_DOMAIN_SERVER_CHECK_INS // so emit our signal that says that + qDebug() << "Limit of silent domain checkins reached"; emit limitOfSilentDomainCheckInsReached(); } diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index ffbc2e6709..051e4f8791 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -384,9 +384,10 @@ int Octree::readElementData(OctreeElementPointer destinationElement, const unsig // check the exists mask to see if we have a child to traverse into if (oneAtBit(childInBufferMask, childIndex)) { - if (!destinationElement->getChildAtIndex(childIndex)) { + auto childAt = destinationElement->getChildAtIndex(childIndex); + if (!childAt) { // add a child at that index, if it doesn't exist - destinationElement->addChildAtIndex(childIndex); + childAt = destinationElement->addChildAtIndex(childIndex); bool nodeIsDirty = destinationElement->isDirty(); if (nodeIsDirty) { _isDirty = true; @@ -394,8 +395,7 @@ int Octree::readElementData(OctreeElementPointer destinationElement, const unsig } // tell the child to read the subsequent data - int lowerLevelBytes = readElementData(destinationElement->getChildAtIndex(childIndex), - nodeData + bytesRead, bytesLeftToRead, args); + int lowerLevelBytes = readElementData(childAt, nodeData + bytesRead, bytesLeftToRead, args); bytesRead += lowerLevelBytes; bytesLeftToRead -= lowerLevelBytes; diff --git a/libraries/render/src/render/EngineStats.cpp b/libraries/render/src/render/EngineStats.cpp index 4fc21acc5b..4b0936d326 100644 --- a/libraries/render/src/render/EngineStats.cpp +++ b/libraries/render/src/render/EngineStats.cpp @@ -33,6 +33,7 @@ void EngineStats::run(const SceneContextPointer& sceneContext, const RenderConte config->textureCPUMemoryUsage = gpu::Texture::getTextureCPUMemoryUsage(); config->textureGPUMemoryUsage = gpu::Texture::getTextureGPUMemoryUsage(); config->textureGPUVirtualMemoryUsage = gpu::Texture::getTextureGPUVirtualMemoryUsage(); + config->textureGPUTransferCount = gpu::Texture::getTextureGPUTransferCount(); gpu::ContextStats gpuStats(_gpuStats); renderContext->args->_context->getStats(_gpuStats); diff --git a/libraries/render/src/render/EngineStats.h b/libraries/render/src/render/EngineStats.h index a5f1643d3f..ab58d2af38 100644 --- a/libraries/render/src/render/EngineStats.h +++ b/libraries/render/src/render/EngineStats.h @@ -34,6 +34,7 @@ namespace render { Q_PROPERTY(qint64 textureCPUMemoryUsage MEMBER textureCPUMemoryUsage NOTIFY dirty) Q_PROPERTY(qint64 textureGPUMemoryUsage MEMBER textureGPUMemoryUsage NOTIFY dirty) Q_PROPERTY(qint64 textureGPUVirtualMemoryUsage MEMBER textureGPUVirtualMemoryUsage NOTIFY dirty) + Q_PROPERTY(quint32 textureGPUTransferCount MEMBER textureGPUTransferCount NOTIFY dirty) Q_PROPERTY(quint32 frameAPIDrawcallCount MEMBER frameAPIDrawcallCount NOTIFY dirty) Q_PROPERTY(quint32 frameDrawcallCount MEMBER frameDrawcallCount NOTIFY dirty) @@ -59,6 +60,7 @@ namespace render { qint64 textureCPUMemoryUsage{ 0 }; qint64 textureGPUMemoryUsage{ 0 }; qint64 textureGPUVirtualMemoryUsage{ 0 }; + quint32 textureGPUTransferCount{ 0 }; quint32 frameAPIDrawcallCount{ 0 }; quint32 frameDrawcallCount{ 0 }; diff --git a/libraries/shared/src/GenericThread.cpp b/libraries/shared/src/GenericThread.cpp index c1c31ab50a..00a80a2864 100644 --- a/libraries/shared/src/GenericThread.cpp +++ b/libraries/shared/src/GenericThread.cpp @@ -46,6 +46,8 @@ void GenericThread::initialize(bool isThreaded, QThread::Priority priority) { _thread->start(); _thread->setPriority(priority); + } else { + setup(); } } @@ -60,10 +62,16 @@ void GenericThread::terminate() { _thread->deleteLater(); _thread = NULL; } + } else { + shutdown(); } } void GenericThread::threadRoutine() { + if (_isThreaded) { + setup(); + } + while (!_stopThread) { // override this function to do whatever your class actually does, return false to exit thread early @@ -78,8 +86,13 @@ void GenericThread::threadRoutine() { } } - // If we were on a thread, then quit our thread - if (_isThreaded && _thread) { - _thread->quit(); + if (_isThreaded) { + shutdown(); + + // If we were on a thread, then quit our thread + if (_thread) { + _thread->quit(); + } } + } diff --git a/libraries/shared/src/GenericThread.h b/libraries/shared/src/GenericThread.h index 8362d3ba57..47f6d9dacd 100644 --- a/libraries/shared/src/GenericThread.h +++ b/libraries/shared/src/GenericThread.h @@ -33,9 +33,6 @@ public: /// Call to stop the thread void terminate(); - /// Override this function to do whatever your class actually does, return false to exit thread early. - virtual bool process() = 0; - virtual void terminating() { }; // lets your subclass know we're terminating, and it should respond appropriately bool isThreaded() const { return _isThreaded; } @@ -48,6 +45,10 @@ signals: void finished(); protected: + /// Override this function to do whatever your class actually does, return false to exit thread early. + virtual bool process() = 0; + virtual void setup() {}; + virtual void shutdown() {}; /// Locks all the resources of the thread. void lock() { _mutex.lock(); }