From a8831e89ff7a1af077bac1335e458b2f5aa4c25a Mon Sep 17 00:00:00 2001 From: David Kelly Date: Thu, 16 Feb 2017 09:50:56 -0700 Subject: [PATCH 01/28] Ban only by machine fingerprint, when possible --- .../src/DomainServerSettingsManager.cpp | 104 +++++++++--------- 1 file changed, 51 insertions(+), 53 deletions(-) diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 23f663817d..661a6213b8 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -30,6 +30,7 @@ #include #include #include //for KillAvatarReason +#include #include "DomainServerNodeData.h" const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/describe-settings.json"; @@ -439,7 +440,7 @@ bool DomainServerSettingsManager::unpackPermissionsForKeypath(const QString& key foreach (QVariant permsHash, permissionsList) { NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) }; QString id = perms->getID(); - + NodePermissionsKey idKey = perms->getKey(); if (mapPointer->contains(idKey)) { @@ -484,7 +485,7 @@ void DomainServerSettingsManager::unpackPermissions() { // make sure that this permission row is for a non-empty hardware if (perms->getKey().first.isEmpty()) { _macPermissions.remove(perms->getKey()); - + // we removed a row from the MAC permissions, we'll need a re-pack needPack = true; } @@ -555,7 +556,7 @@ void DomainServerSettingsManager::unpackPermissions() { QList> permissionsSets; permissionsSets << _standardAgentPermissions.get() << _agentPermissions.get() << _groupPermissions.get() << _groupForbiddens.get() - << _ipPermissions.get() << _macPermissions.get() + << _ipPermissions.get() << _macPermissions.get() << _machineFingerprintPermissions.get(); foreach (auto permissionSet, permissionsSets) { @@ -668,71 +669,68 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointerclear(NodePermissions::Permission::canConnectToDomain); } else { - // otherwise we apply the kick to the IP from active socket for this node and the MAC address - - // remove connect permissions for the IP (falling back to the public socket if not yet active) - auto& kickAddress = matchingNode->getActiveSocket() - ? matchingNode->getActiveSocket()->getAddress() - : matchingNode->getPublicSocket().getAddress(); - - // probably isLoopback covers it, as whenever I try to ban an agent on same machine as the domain-server - // it is always 127.0.0.1, but looking at the public and local addresses just to be sure - // TODO: soon we will have feedback (in the form of a message to the client) after we kick. When we - // do, we will have a success flag, and perhaps a reason for failure. For now, just don't do it. - if (kickAddress == limitedNodeList->getPublicSockAddr().getAddress() || - kickAddress == limitedNodeList->getLocalSockAddr().getAddress() || - kickAddress.isLoopback() ) { - qWarning() << "attempt to kick node running on same machine as domain server, ignoring KickRequest"; - return; - } - NodePermissionsKey ipAddressKey(kickAddress.toString(), QUuid()); - - // check if there were already permissions for the IP - bool hadIPPermissions = hasPermissionsForIP(kickAddress); - - // grab or create permissions for the given IP address - auto ipPermissions = _ipPermissions[ipAddressKey]; - - if (!hadIPPermissions || ipPermissions->can(NodePermissions::Permission::canConnectToDomain)) { - newPermissions = true; - - ipPermissions->clear(NodePermissions::Permission::canConnectToDomain); - } - - // potentially remove connect permissions for the MAC address and machine fingerprint + // remove connect permissions for the machine fingerprint DomainServerNodeData* nodeData = static_cast(matchingNode->getLinkedData()); if (nodeData) { - // mac address first - NodePermissionsKey macAddressKey(nodeData->getHardwareAddress(), 0); + // get this machine's fingerprint + auto domainServerFingerprint = FingerprintUtils::getMachineFingerprint(); - bool hadMACPermissions = hasPermissionsForMAC(nodeData->getHardwareAddress()); - - auto macPermissions = _macPermissions[macAddressKey]; - - if (!hadMACPermissions || macPermissions->can(NodePermissions::Permission::canConnectToDomain)) { - newPermissions = true; - - macPermissions->clear(NodePermissions::Permission::canConnectToDomain); + if (nodeData->getMachineFingerprint() == domainServerFingerprint) { + qWarning() << "attempt to kick node running on same machine as domain server (by fingerprint), ignoring KickRequest"; + return; } - - // now for machine fingerprint NodePermissionsKey machineFingerprintKey(nodeData->getMachineFingerprint().toString(), 0); - + + // check if there were already permissions for the fingerprint bool hadFingerprintPermissions = hasPermissionsForMachineFingerprint(nodeData->getMachineFingerprint()); - + + // grab or create permissions for the given fingerprint auto fingerprintPermissions = _machineFingerprintPermissions[machineFingerprintKey]; - + + // write them if (!hadFingerprintPermissions || fingerprintPermissions->can(NodePermissions::Permission::canConnectToDomain)) { newPermissions = true; fingerprintPermissions->clear(NodePermissions::Permission::canConnectToDomain); } + } else { + // if no node data, all we can do is IP address + auto& kickAddress = matchingNode->getActiveSocket() + ? matchingNode->getActiveSocket()->getAddress() + : matchingNode->getPublicSocket().getAddress(); + + // probably isLoopback covers it, as whenever I try to ban an agent on same machine as the domain-server + // it is always 127.0.0.1, but looking at the public and local addresses just to be sure + // TODO: soon we will have feedback (in the form of a message to the client) after we kick. When we + // do, we will have a success flag, and perhaps a reason for failure. For now, just don't do it. + if (kickAddress == limitedNodeList->getPublicSockAddr().getAddress() || + kickAddress == limitedNodeList->getLocalSockAddr().getAddress() || + kickAddress.isLoopback() ) { + qWarning() << "attempt to kick node running on same machine as domain server, ignoring KickRequest"; + return; + } + + + NodePermissionsKey ipAddressKey(kickAddress.toString(), QUuid()); + + // check if there were already permissions for the IP + bool hadIPPermissions = hasPermissionsForIP(kickAddress); + + // grab or create permissions for the given IP address + auto ipPermissions = _ipPermissions[ipAddressKey]; + + if (!hadIPPermissions || ipPermissions->can(NodePermissions::Permission::canConnectToDomain)) { + newPermissions = true; + + ipPermissions->clear(NodePermissions::Permission::canConnectToDomain); + } } } - // if we are here, then we kicked them, so send the KillAvatar message to everyone + + // if we are here, then we kicked them, so send the KillAvatar message auto packet = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason), true); packet->write(nodeUUID.toRfc4122()); packet->writePrimitive(KillAvatarReason::NoReason); - + // send to avatar mixer, it sends the kill to everyone else limitedNodeList->broadcastToNodes(std::move(packet), NodeSet() << NodeType::AvatarMixer); @@ -743,7 +741,7 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointer Date: Thu, 16 Feb 2017 10:53:02 -0800 Subject: [PATCH 02/28] cleanup some accumulated cruft in pal code --- interface/resources/qml/hifi/Pal.qml | 4 +-- scripts/system/pal.js | 40 +++++++++++----------------- 2 files changed, 18 insertions(+), 26 deletions(-) diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index 0c7104fba5..c1fea7c09b 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -360,7 +360,7 @@ Rectangle { TextMetrics { id: displayNameHeaderMetrics text: displayNameHeader.title - font: displayNameHeader.font + // font: displayNameHeader.font // was this always undefined? giving error now... } // This Rectangle refers to the [?] popup button next to "NAMES" Rectangle { @@ -426,7 +426,6 @@ Rectangle { onExited: adminHelpText.color = hifi.colors.redHighlight } } - } HifiControls.Keyboard { id: keyboard @@ -438,6 +437,7 @@ Rectangle { right: parent.right } } + } // Timer used when selecting table rows that aren't yet present in the model // (i.e. when selecting avatars using edit.js or sphere overlays) diff --git a/scripts/system/pal.js b/scripts/system/pal.js index 2e07a2d431..57648da79a 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -290,17 +290,14 @@ function populateUserList(selectData) { userName: '', sessionId: id || '', audioLevel: 0.0, - admin: false + admin: false, + personalMute: !!id && Users.getPersonalMuteStatus(id), // expects proper boolean, not null + ignore: !!id && Users.getIgnoreStatus(id) // ditto }; - // Request the username, fingerprint, and admin status from the given UUID - // Username and fingerprint returns default constructor output if the requesting user isn't an admin - Users.requestUsernameFromID(id); - // Request personal mute status and ignore status - // from NodeList (as long as we're not requesting it for our own ID) if (id) { - avatarPalDatum['personalMute'] = Users.getPersonalMuteStatus(id); - avatarPalDatum['ignore'] = Users.getIgnoreStatus(id); addAvatarNode(id); // No overlay for ourselves + // Everyone needs to see admin status. Username and fingerprint returns default constructor output if the requesting user isn't an admin. + Users.requestUsernameFromID(id); } data.push(avatarPalDatum); print('PAL data:', JSON.stringify(avatarPalDatum)); @@ -314,20 +311,13 @@ function populateUserList(selectData) { // The function that handles the reply from the server function usernameFromIDReply(id, username, machineFingerprint, isAdmin) { - var data; - // If the ID we've received is our ID... - if (MyAvatar.sessionUUID === id) { - // Set the data to contain specific strings. - data = ['', username, isAdmin]; - } else if (Users.canKick) { - // Set the data to contain the ID and the username (if we have one) - // or fingerprint (if we don't have a username) string. - data = [id, username || machineFingerprint, isAdmin]; - } else { - // Set the data to contain specific strings. - data = [id, '', isAdmin]; - } - print('Username Data:', JSON.stringify(data)); + var data = [ + (MyAvatar.sessionUUID === id) ? '' : id, // Pal.qml recognizes empty id specially. + // If we get username (e.g., if in future we receive it when we're friends), use it. + // Otherwise, use valid machineFingerprint (which is not valid when not an admin). + username || (Users.canKick && machineFingerprint) || '', + isAdmin + ]; // Ship the data off to QML sendToQml({ method: 'updateUsername', params: data }); } @@ -339,13 +329,15 @@ function updateOverlays() { if (!id) { return; // don't update ourself } - + var avatar = AvatarList.getAvatar(id); + if (!avatar) { + return; // will be deleted below if there had been an overlay. + } var overlay = ExtendedOverlay.get(id); if (!overlay) { // For now, we're treating this as a temporary loss, as from the personal space bubble. Add it back. print('Adding non-PAL avatar node', id); overlay = addAvatarNode(id); } - var avatar = AvatarList.getAvatar(id); var target = avatar.position; var distance = Vec3.distance(target, eye); var offset = 0.2; From 07e97a0cabefde7f739d116849e3c58fd91a3f03 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Thu, 16 Feb 2017 21:40:53 +0000 Subject: [PATCH 03/28] unqueue from front for audio packets --- assignment-client/src/audio/AudioMixerClientData.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index fe43e6f730..d5e06504a6 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -61,7 +61,7 @@ void AudioMixerClientData::processPackets() { _packetQueue.node.clear(); while (!_packetQueue.empty()) { - auto& packet = _packetQueue.back(); + auto& packet = _packetQueue.front(); switch (packet->getType()) { case PacketType::MicrophoneAudioNoEcho: From 04712e374448736f4821ff3879bd81ec28346ff2 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 16 Feb 2017 16:43:16 -0800 Subject: [PATCH 04/28] fix rquestsDomainListData --- assignment-client/src/avatars/AvatarMixer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index bf85918145..45b04c4189 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -410,7 +410,7 @@ void AvatarMixer::broadcastAvatarData() { bool isInView = nodeData->otherAvatarInView(otherNodeBox); // this throttles the extra data to only be sent every Nth message - if (!isInView && getsOutOfView && (lastSeqToReceiver % EXTRA_AVATAR_DATA_FRAME_RATIO > 0)) { + if (!isInView && !getsOutOfView && (lastSeqToReceiver % EXTRA_AVATAR_DATA_FRAME_RATIO > 0)) { return; } @@ -572,6 +572,7 @@ void AvatarMixer::handleRequestsDomainListDataPacket(QSharedPointerreadPrimitive(&isRequesting); nodeData->setRequestsDomainListData(isRequesting); + qDebug() << "node" << nodeData->getNodeID() << "requestsDomainListData" << isRequesting; } } } From 7fb7aa87eb8c64b11a99eb864541743f7c797630 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Tue, 24 Jan 2017 15:37:18 -0800 Subject: [PATCH 05/28] Working on new texture management strategy --- interface/src/ui/ApplicationOverlay.cpp | 6 +- interface/src/ui/overlays/Web3DOverlay.cpp | 2 +- .../display-plugins/OpenGLDisplayPlugin.cpp | 3 +- .../display-plugins/hmd/HmdDisplayPlugin.cpp | 32 +- .../src/RenderableWebEntityItem.cpp | 2 +- libraries/gpu-gl/src/gpu/gl/GLBackend.cpp | 7 +- libraries/gpu-gl/src/gpu/gl/GLBackend.h | 13 +- .../gpu-gl/src/gpu/gl/GLBackendTexture.cpp | 54 +- libraries/gpu-gl/src/gpu/gl/GLFramebuffer.cpp | 9 +- libraries/gpu-gl/src/gpu/gl/GLFramebuffer.h | 2 +- libraries/gpu-gl/src/gpu/gl/GLTexture.cpp | 233 +---- libraries/gpu-gl/src/gpu/gl/GLTexture.h | 207 +--- .../gpu-gl/src/gpu/gl/GLTextureTransfer.cpp | 208 ---- .../gpu-gl/src/gpu/gl/GLTextureTransfer.h | 78 -- libraries/gpu-gl/src/gpu/gl41/GL41Backend.h | 33 +- .../gpu-gl/src/gpu/gl41/GL41BackendOutput.cpp | 10 +- .../src/gpu/gl41/GL41BackendTexture.cpp | 187 ++-- libraries/gpu-gl/src/gpu/gl45/GL45Backend.cpp | 5 - libraries/gpu-gl/src/gpu/gl45/GL45Backend.h | 177 +++- .../gpu-gl/src/gpu/gl45/GL45BackendOutput.cpp | 10 +- .../src/gpu/gl45/GL45BackendTexture.cpp | 545 +++-------- .../gpu/gl45/GL45BackendVariableTexture.cpp | 888 ++++++++++++++++++ libraries/gpu/src/gpu/Batch.cpp | 7 - libraries/gpu/src/gpu/Framebuffer.cpp | 12 +- libraries/gpu/src/gpu/Texture.cpp | 35 +- libraries/gpu/src/gpu/Texture.h | 23 +- .../src/model-networking/TextureCache.cpp | 15 +- .../src/model-networking/TextureCache.h | 1 + libraries/model/src/model/TextureMap.cpp | 13 +- libraries/model/src/model/TextureMap.h | 3 +- .../render-utils/src/AntialiasingEffect.cpp | 2 +- .../render-utils/src/DeferredFramebuffer.cpp | 10 +- .../src/DeferredLightingEffect.cpp | 4 +- .../render-utils/src/RenderDeferredTask.cpp | 2 +- .../render-utils/src/SubsurfaceScattering.cpp | 6 +- .../render-utils/src/SurfaceGeometryPass.cpp | 12 +- .../src/OculusLegacyDisplayPlugin.cpp | 2 +- plugins/openvr/src/OpenVrDisplayPlugin.cpp | 8 +- tests/render-perf/src/main.cpp | 1 - tests/render-texture-load/src/main.cpp | 1 + 40 files changed, 1508 insertions(+), 1360 deletions(-) delete mode 100644 libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.cpp delete mode 100644 libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.h create mode 100644 libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 364dff52a3..730e21139f 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -99,7 +99,7 @@ void ApplicationOverlay::renderQmlUi(RenderArgs* renderArgs) { PROFILE_RANGE(app, __FUNCTION__); if (!_uiTexture) { - _uiTexture = gpu::TexturePointer(gpu::Texture::createExternal2D(OffscreenQmlSurface::getDiscardLambda())); + _uiTexture = gpu::TexturePointer(gpu::Texture::createExternal(OffscreenQmlSurface::getDiscardLambda())); _uiTexture->setSource(__FUNCTION__); } // Once we move UI rendering and screen rendering to different @@ -272,13 +272,13 @@ void ApplicationOverlay::buildFramebufferObject() { auto width = uiSize.x; auto height = uiSize.y; if (!_overlayFramebuffer->getDepthStencilBuffer()) { - auto overlayDepthTexture = gpu::TexturePointer(gpu::Texture::create2D(DEPTH_FORMAT, width, height, DEFAULT_SAMPLER)); + auto overlayDepthTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(DEPTH_FORMAT, width, height, DEFAULT_SAMPLER)); _overlayFramebuffer->setDepthStencilBuffer(overlayDepthTexture, DEPTH_FORMAT); } if (!_overlayFramebuffer->getRenderBuffer(0)) { const gpu::Sampler OVERLAY_SAMPLER(gpu::Sampler::FILTER_MIN_MAG_LINEAR, gpu::Sampler::WRAP_CLAMP); - auto colorBuffer = gpu::TexturePointer(gpu::Texture::create2D(COLOR_FORMAT, width, height, OVERLAY_SAMPLER)); + auto colorBuffer = gpu::TexturePointer(gpu::Texture::createRenderBuffer(COLOR_FORMAT, width, height, OVERLAY_SAMPLER)); _overlayFramebuffer->setRenderBuffer(0, colorBuffer); } } diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index cb649e8766..f67acb0304 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -251,7 +251,7 @@ void Web3DOverlay::render(RenderArgs* args) { if (!_texture) { auto webSurface = _webSurface; - _texture = gpu::TexturePointer(gpu::Texture::createExternal2D(OffscreenQmlSurface::getDiscardLambda())); + _texture = gpu::TexturePointer(gpu::Texture::createExternal(OffscreenQmlSurface::getDiscardLambda())); _texture->setSource(__FUNCTION__); } OffscreenQmlSurface::TextureAndFence newTextureAndFence; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index cf6b39812a..1bfe4f3dcc 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -355,7 +355,7 @@ void OpenGLDisplayPlugin::customizeContext() { if ((image.width() > 0) && (image.height() > 0)) { cursorData.texture.reset( - gpu::Texture::create2D( + gpu::Texture::createStrict( gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA), image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); @@ -363,6 +363,7 @@ void OpenGLDisplayPlugin::customizeContext() { auto usage = gpu::Texture::Usage::Builder().withColor().withAlpha(); cursorData.texture->setUsage(usage.build()); cursorData.texture->assignStoredMip(0, gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA), image.byteCount(), image.constBits()); + cursorData.texture->autoGenerateMips(-1); } } } diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index a8b8ba3618..24f4e429ef 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -296,7 +296,7 @@ void HmdDisplayPlugin::internalPresent() { image = image.convertToFormat(QImage::Format_RGBA8888); if (!_previewTexture) { _previewTexture.reset( - gpu::Texture::create2D( + gpu::Texture::createStrict( gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA), image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); @@ -306,23 +306,21 @@ void HmdDisplayPlugin::internalPresent() { _previewTexture->autoGenerateMips(-1); } - if (getGLBackend()->isTextureReady(_previewTexture)) { - auto viewport = getViewportForSourceSize(uvec2(_previewTexture->getDimensions())); + auto viewport = getViewportForSourceSize(uvec2(_previewTexture->getDimensions())); - render([&](gpu::Batch& batch) { - batch.enableStereo(false); - batch.resetViewTransform(); - batch.setFramebuffer(gpu::FramebufferPointer()); - batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, vec4(0)); - batch.setStateScissorRect(viewport); - batch.setViewportTransform(viewport); - batch.setResourceTexture(0, _previewTexture); - batch.setPipeline(_presentPipeline); - batch.draw(gpu::TRIANGLE_STRIP, 4); - }); - _clearPreviewFlag = false; - swapBuffers(); - } + render([&](gpu::Batch& batch) { + batch.enableStereo(false); + batch.resetViewTransform(); + batch.setFramebuffer(gpu::FramebufferPointer()); + batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, vec4(0)); + batch.setStateScissorRect(viewport); + batch.setViewportTransform(viewport); + batch.setResourceTexture(0, _previewTexture); + batch.setPipeline(_presentPipeline); + batch.draw(gpu::TRIANGLE_STRIP, 4); + }); + _clearPreviewFlag = false; + swapBuffers(); } postPreview(); diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 972c23d534..b3978b9356 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -214,7 +214,7 @@ void RenderableWebEntityItem::render(RenderArgs* args) { if (!_texture) { auto webSurface = _webSurface; - _texture = gpu::TexturePointer(gpu::Texture::createExternal2D(OffscreenQmlSurface::getDiscardLambda())); + _texture = gpu::TexturePointer(gpu::Texture::createExternal(OffscreenQmlSurface::getDiscardLambda())); _texture->setSource(__FUNCTION__); } OffscreenQmlSurface::TextureAndFence newTextureAndFence; diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp index c51f468908..76cc64f3e3 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp @@ -62,8 +62,6 @@ BackendPointer GLBackend::createBackend() { INSTANCE = result.get(); void* voidInstance = &(*result); qApp->setProperty(hifi::properties::gl::BACKEND, QVariant::fromValue(voidInstance)); - - gl::GLTexture::initTextureTransferHelper(); return result; } @@ -623,6 +621,7 @@ void GLBackend::queueLambda(const std::function lambda) const { } void GLBackend::recycle() const { + PROFILE_RANGE(render_gpu_gl, __FUNCTION__) { std::list> lamdbasTrash; { @@ -745,10 +744,6 @@ void GLBackend::recycle() const { glDeleteQueries((GLsizei)ids.size(), ids.data()); } } - -#ifndef THREADED_TEXTURE_TRANSFER - gl::GLTexture::_textureTransferHelper->process(); -#endif } void GLBackend::setCameraCorrection(const Mat4& correction) { diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackend.h b/libraries/gpu-gl/src/gpu/gl/GLBackend.h index 950ac65a3f..76c950ec2b 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackend.h +++ b/libraries/gpu-gl/src/gpu/gl/GLBackend.h @@ -187,10 +187,15 @@ public: virtual void do_setStateScissorRect(const Batch& batch, size_t paramOffset) final; virtual GLuint getFramebufferID(const FramebufferPointer& framebuffer) = 0; - virtual GLuint getTextureID(const TexturePointer& texture, bool needTransfer = true) = 0; + virtual GLuint getTextureID(const TexturePointer& texture) final; virtual GLuint getBufferID(const Buffer& buffer) = 0; virtual GLuint getQueryID(const QueryPointer& query) = 0; - virtual bool isTextureReady(const TexturePointer& texture); + + virtual GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer) = 0; + virtual GLBuffer* syncGPUObject(const Buffer& buffer) = 0; + virtual GLTexture* syncGPUObject(const TexturePointer& texture); + virtual GLQuery* syncGPUObject(const Query& query) = 0; + //virtual bool isTextureReady(const TexturePointer& texture); virtual void releaseBuffer(GLuint id, Size size) const; virtual void releaseExternalTexture(GLuint id, const Texture::ExternalRecycler& recycler) const; @@ -206,10 +211,6 @@ public: protected: void recycle() const override; - virtual GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer) = 0; - virtual GLBuffer* syncGPUObject(const Buffer& buffer) = 0; - virtual GLTexture* syncGPUObject(const TexturePointer& texture, bool sync = true) = 0; - virtual GLQuery* syncGPUObject(const Query& query) = 0; static const size_t INVALID_OFFSET = (size_t)-1; bool _inRenderTransferPass { false }; diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendTexture.cpp index f51eac0e33..ca4e328612 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendTexture.cpp @@ -14,12 +14,56 @@ using namespace gpu; using namespace gpu::gl; -bool GLBackend::isTextureReady(const TexturePointer& texture) { - // DO not transfer the texture, this call is expected for rendering texture - GLTexture* object = syncGPUObject(texture, true); - return object && object->isReady(); + +GLuint GLBackend::getTextureID(const TexturePointer& texture) { + GLTexture* object = syncGPUObject(texture); + + if (!object) { + return 0; + } + + return object->_id; } +GLTexture* GLBackend::syncGPUObject(const TexturePointer& texturePointer) { + const Texture& texture = *texturePointer; + // Special case external textures + if (TextureUsageType::EXTERNAL == texture.getUsageType()) { + Texture::ExternalUpdates updates = texture.getUpdates(); + if (!updates.empty()) { + Texture::ExternalRecycler recycler = texture.getExternalRecycler(); + Q_ASSERT(recycler); + // Discard any superfluous updates + while (updates.size() > 1) { + const auto& update = updates.front(); + // Superfluous updates will never have been read, but we want to ensure the previous + // writes to them are complete before they're written again, so return them with the + // same fences they arrived with. This can happen on any thread because no GL context + // work is involved + recycler(update.first, update.second); + updates.pop_front(); + } + + // The last texture remaining is the one we'll use to create the GLTexture + const auto& update = updates.front(); + // Check for a fence, and if it exists, inject a wait into the command stream, then destroy the fence + if (update.second) { + GLsync fence = static_cast(update.second); + glWaitSync(fence, 0, GL_TIMEOUT_IGNORED); + glDeleteSync(fence); + } + + // Create the new texture object (replaces any previous texture object) + new GLExternalTexture(shared_from_this(), texture, update.first); + } + + // Return the texture object (if any) associated with the texture, without extensive logic + // (external textures are + return Backend::getGPUObject(texture); + } + + return nullptr; +} void GLBackend::do_generateTextureMips(const Batch& batch, size_t paramOffset) { TexturePointer resourceTexture = batch._textures.get(batch._params[paramOffset + 0]._uint); @@ -28,7 +72,7 @@ void GLBackend::do_generateTextureMips(const Batch& batch, size_t paramOffset) { } // DO not transfer the texture, this call is expected for rendering texture - GLTexture* object = syncGPUObject(resourceTexture, false); + GLTexture* object = syncGPUObject(resourceTexture); if (!object) { return; } diff --git a/libraries/gpu-gl/src/gpu/gl/GLFramebuffer.cpp b/libraries/gpu-gl/src/gpu/gl/GLFramebuffer.cpp index 85cf069062..2ac7e9d060 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLFramebuffer.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLFramebuffer.cpp @@ -21,13 +21,12 @@ GLFramebuffer::~GLFramebuffer() { } } -bool GLFramebuffer::checkStatus(GLenum target) const { - bool result = false; +bool GLFramebuffer::checkStatus() const { switch (_status) { case GL_FRAMEBUFFER_COMPLETE: // Success ! - result = true; - break; + return true; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: qCWarning(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT."; break; @@ -44,5 +43,5 @@ bool GLFramebuffer::checkStatus(GLenum target) const { qCWarning(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_UNSUPPORTED."; break; } - return result; + return false; } diff --git a/libraries/gpu-gl/src/gpu/gl/GLFramebuffer.h b/libraries/gpu-gl/src/gpu/gl/GLFramebuffer.h index 9b4f9703fc..c0633cfdef 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLFramebuffer.h +++ b/libraries/gpu-gl/src/gpu/gl/GLFramebuffer.h @@ -64,7 +64,7 @@ public: protected: GLenum _status { GL_FRAMEBUFFER_COMPLETE }; virtual void update() = 0; - bool checkStatus(GLenum target) const; + bool checkStatus() const; GLFramebuffer(const std::weak_ptr& backend, const Framebuffer& framebuffer, GLuint id) : GLObject(backend, framebuffer, id) {} ~GLFramebuffer(); diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp index 1e0dd08ae1..1de820e1df 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp @@ -10,15 +10,13 @@ #include -#include "GLTextureTransfer.h" #include "GLBackend.h" using namespace gpu; using namespace gpu::gl; -std::shared_ptr GLTexture::_textureTransferHelper; -const GLenum GLTexture::CUBE_FACE_LAYOUT[6] = { +const GLenum GLTexture::CUBE_FACE_LAYOUT[GLTexture::TEXTURE_CUBE_NUM_FACES] = { 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 @@ -67,6 +65,17 @@ GLenum GLTexture::getGLTextureType(const Texture& texture) { } +uint8_t GLTexture::getFaceCount(GLenum target) { + switch (target) { + case GL_TEXTURE_2D: + return TEXTURE_2D_NUM_FACES; + case GL_TEXTURE_CUBE_MAP: + return TEXTURE_CUBE_NUM_FACES; + default: + Q_UNREACHABLE(); + break; + } +} const std::vector& GLTexture::getFaceTargets(GLenum target) { static std::vector cubeFaceTargets { GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, @@ -89,216 +98,34 @@ const std::vector& GLTexture::getFaceTargets(GLenum target) { return faceTargets; } -// Default texture memory = GPU total memory - 2GB -#define GPU_MEMORY_RESERVE_BYTES MB_TO_BYTES(2048) -// Minimum texture memory = 1GB -#define TEXTURE_MEMORY_MIN_BYTES MB_TO_BYTES(1024) - - -float GLTexture::getMemoryPressure() { - // Check for an explicit memory limit - auto availableTextureMemory = Texture::getAllowedGPUMemoryUsage(); - - - // If no memory limit has been set, use a percentage of the total dedicated memory - if (!availableTextureMemory) { -#if 0 - auto totalMemory = getDedicatedMemory(); - if ((GPU_MEMORY_RESERVE_BYTES + TEXTURE_MEMORY_MIN_BYTES) > totalMemory) { - availableTextureMemory = TEXTURE_MEMORY_MIN_BYTES; - } else { - availableTextureMemory = totalMemory - GPU_MEMORY_RESERVE_BYTES; - } -#else - // Hardcode texture limit for sparse textures at 1 GB for now - availableTextureMemory = TEXTURE_MEMORY_MIN_BYTES; -#endif - } - - // Return the consumed texture memory divided by the available texture memory. - auto consumedGpuMemory = Context::getTextureGPUMemoryUsage() - Context::getTextureGPUFramebufferMemoryUsage(); - float memoryPressure = (float)consumedGpuMemory / (float)availableTextureMemory; - static Context::Size lastConsumedGpuMemory = 0; - if (memoryPressure > 1.0f && lastConsumedGpuMemory != consumedGpuMemory) { - lastConsumedGpuMemory = consumedGpuMemory; - qCDebug(gpugllogging) << "Exceeded max allowed texture memory: " << consumedGpuMemory << " / " << availableTextureMemory; - } - return memoryPressure; -} - - -// Create the texture and allocate storage -GLTexture::GLTexture(const std::weak_ptr& backend, const Texture& texture, GLuint id, bool transferrable) : - GLObject(backend, texture, id), - _external(false), - _source(texture.source()), - _storageStamp(texture.getStamp()), - _target(getGLTextureType(texture)), - _internalFormat(gl::GLTexelFormat::evalGLTexelFormatInternal(texture.getTexelFormat())), - _maxMip(texture.maxMip()), - _minMip(texture.minMip()), - _virtualSize(texture.evalTotalSize()), - _transferrable(transferrable) -{ - auto strongBackend = _backend.lock(); - strongBackend->recycle(); - Backend::incrementTextureGPUCount(); - Backend::updateTextureGPUVirtualMemoryUsage(0, _virtualSize); - Backend::setGPUObject(texture, this); -} - GLTexture::GLTexture(const std::weak_ptr& backend, const Texture& texture, GLuint id) : GLObject(backend, texture, id), - _external(true), _source(texture.source()), - _storageStamp(0), - _target(getGLTextureType(texture)), - _internalFormat(GL_RGBA8), - // FIXME force mips to 0? - _maxMip(texture.maxMip()), - _minMip(texture.minMip()), - _virtualSize(0), - _transferrable(false) + _target(getGLTextureType(texture)) { Backend::setGPUObject(texture, this); - - // FIXME Is this necessary? - //withPreservedTexture([this] { - // syncSampler(); - // if (_gpuObject.isAutogenerateMips()) { - // generateMips(); - // } - //}); } GLTexture::~GLTexture() { + auto backend = _backend.lock(); + if (backend && _id) { + backend->releaseTexture(_id, 0); + } +} + + +GLExternalTexture::GLExternalTexture(const std::weak_ptr& backend, const Texture& texture, GLuint id) + : Parent(backend, texture, id) { } + +GLExternalTexture::~GLExternalTexture() { auto backend = _backend.lock(); if (backend) { - if (_external) { - auto recycler = _gpuObject.getExternalRecycler(); - if (recycler) { - backend->releaseExternalTexture(_id, recycler); - } else { - qWarning() << "No recycler available for texture " << _id << " possible leak"; - } - } else if (_id) { - // WARNING! Sparse textures do not use this code path. See GL45BackendTexture for - // the GL45Texture destructor for doing any required work tracking GPU stats - backend->releaseTexture(_id, _size); + auto recycler = _gpuObject.getExternalRecycler(); + if (recycler) { + backend->releaseExternalTexture(_id, recycler); + } else { + qWarning() << "No recycler available for texture " << _id << " possible leak"; } - - if (!_external && !_transferrable) { - Backend::updateTextureGPUFramebufferMemoryUsage(_size, 0); - } - } - Backend::updateTextureGPUVirtualMemoryUsage(_virtualSize, 0); -} - -void GLTexture::createTexture() { - withPreservedTexture([&] { - allocateStorage(); - (void)CHECK_GL_ERROR(); - syncSampler(); - (void)CHECK_GL_ERROR(); - }); -} - -void GLTexture::withPreservedTexture(std::function f) const { - 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(); - - glBindTexture(_target, _texture); - f(); - glBindTexture(_target, boundTex); - (void)CHECK_GL_ERROR(); -} - -void GLTexture::setSize(GLuint size) const { - if (!_external && !_transferrable) { - Backend::updateTextureGPUFramebufferMemoryUsage(_size, size); - } - Backend::updateTextureGPUMemoryUsage(_size, size); - const_cast(_size) = size; -} - -bool GLTexture::isInvalid() const { - return _storageStamp < _gpuObject.getStamp(); -} - -bool GLTexture::isOutdated() const { - return GLSyncState::Idle == _syncState && _contentStamp < _gpuObject.getDataStamp(); -} - -bool GLTexture::isReady() const { - // If we have an invalid texture, we're never ready - if (isInvalid()) { - return false; - } - - auto syncState = _syncState.load(); - if (isOutdated() || Idle != syncState) { - return false; - } - - return true; -} - - -// Do any post-transfer operations that might be required on the main context / rendering thread -void GLTexture::postTransfer() { - setSyncState(GLSyncState::Idle); - ++_transferCount; - - // At this point the mip pixels have been loaded, we can notify the gpu texture to abandon it's memory - switch (_gpuObject.getType()) { - case Texture::TEX_2D: - for (uint16_t i = 0; i < Sampler::MAX_MIP_LEVEL; ++i) { - if (_gpuObject.isStoredMipFaceAvailable(i)) { - _gpuObject.notifyMipFaceGPULoaded(i); - } - } - break; - - case Texture::TEX_CUBE: - // transfer pixels from each faces - for (uint8_t f = 0; f < CUBE_NUM_FACES; f++) { - for (uint16_t i = 0; i < Sampler::MAX_MIP_LEVEL; ++i) { - if (_gpuObject.isStoredMipFaceAvailable(i, f)) { - _gpuObject.notifyMipFaceGPULoaded(i, f); - } - } - } - break; - - default: - qCWarning(gpugllogging) << __FUNCTION__ << " case for Texture Type " << _gpuObject.getType() << " not supported"; - break; + const_cast(_id) = 0; } } - -void GLTexture::initTextureTransferHelper() { - _textureTransferHelper = std::make_shared(); -} - -void GLTexture::startTransfer() { - createTexture(); -} - -void GLTexture::finishTransfer() { - if (_gpuObject.isAutogenerateMips()) { - generateMips(); - } -} - diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.h b/libraries/gpu-gl/src/gpu/gl/GLTexture.h index 0f75a6fe51..1f91e17157 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTexture.h +++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.h @@ -9,7 +9,6 @@ #define hifi_gpu_gl_GLTexture_h #include "GLShared.h" -#include "GLTextureTransfer.h" #include "GLBackend.h" #include "GLTexelFormat.h" @@ -20,210 +19,48 @@ struct GLFilterMode { GLint magFilter; }; - class GLTexture : public GLObject { + using Parent = GLObject; + friend class GLBackend; public: static const uint16_t INVALID_MIP { (uint16_t)-1 }; static const uint8_t INVALID_FACE { (uint8_t)-1 }; - static void initTextureTransferHelper(); - static std::shared_ptr _textureTransferHelper; - - template - static GLTexture* sync(GLBackend& backend, const TexturePointer& texturePointer, bool needTransfer) { - const Texture& texture = *texturePointer; - - // Special case external textures - if (texture.getUsage().isExternal()) { - Texture::ExternalUpdates updates = texture.getUpdates(); - if (!updates.empty()) { - Texture::ExternalRecycler recycler = texture.getExternalRecycler(); - Q_ASSERT(recycler); - // Discard any superfluous updates - while (updates.size() > 1) { - const auto& update = updates.front(); - // Superfluous updates will never have been read, but we want to ensure the previous - // writes to them are complete before they're written again, so return them with the - // same fences they arrived with. This can happen on any thread because no GL context - // work is involved - recycler(update.first, update.second); - updates.pop_front(); - } - - // The last texture remaining is the one we'll use to create the GLTexture - const auto& update = updates.front(); - // Check for a fence, and if it exists, inject a wait into the command stream, then destroy the fence - if (update.second) { - GLsync fence = static_cast(update.second); - glWaitSync(fence, 0, GL_TIMEOUT_IGNORED); - glDeleteSync(fence); - } - - // Create the new texture object (replaces any previous texture object) - new GLTextureType(backend.shared_from_this(), texture, update.first); - } - - // Return the texture object (if any) associated with the texture, without extensive logic - // (external textures are - return Backend::getGPUObject(texture); - } - - 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(texture); - - // 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 any previous texture - object = new GLTextureType(backend.shared_from_this(), texture, needTransfer); - if (!object->_transferrable) { - object->createTexture(); - object->_contentStamp = texture.getDataStamp(); - object->updateSize(); - object->postTransfer(); - } - } - - // Object maybe doens't neet to be tranasferred after creation - if (!object->_transferrable) { - return object; - } - - // If we just did a transfer, return the object after doing post-transfer work - if (GLSyncState::Transferred == object->getSyncState()) { - object->postTransfer(); - } - - if (object->isOutdated()) { - // Object might be outdated, if so, start the transfer - // (outdated objects that are already in transfer will have reported 'true' for ready() - _textureTransferHelper->transferTexture(texturePointer); - return nullptr; - } - - if (!object->isReady()) { - return nullptr; - } - - ((GLTexture*)object)->updateMips(); - - return object; - } - - template - static GLuint getId(GLBackend& backend, const TexturePointer& texture, bool shouldSync) { - if (!texture) { - return 0; - } - GLTexture* object { nullptr }; - if (shouldSync) { - object = sync(backend, texture, shouldSync); - } else { - object = Backend::getGPUObject(*texture); - } - - if (!object) { - return 0; - } - - if (!shouldSync) { - return object->_id; - } - - // Don't return textures that are in transfer state - if ((object->getSyncState() != GLSyncState::Idle) || - // Don't return transferrable textures that have never completed transfer - (!object->_transferrable || 0 != object->_transferCount)) { - return 0; - } - - return object->_id; - } - ~GLTexture(); - // Is this texture generated outside the GPU library? - const bool _external; const GLuint& _texture { _id }; const std::string _source; - const Stamp _storageStamp; const GLenum _target; - const GLenum _internalFormat; - const uint16 _maxMip; - uint16 _minMip; - const GLuint _virtualSize; // theoretical size as expected - Stamp _contentStamp { 0 }; - const bool _transferrable; - Size _transferCount { 0 }; - GLuint size() const { return _size; } - GLSyncState getSyncState() const { return _syncState; } - // Is the storage out of date relative to the gpu texture? - bool isInvalid() const; + static const std::vector& getFaceTargets(GLenum textureType); + static uint8_t getFaceCount(GLenum textureType); + static GLenum getGLTextureType(const Texture& texture); - // 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; - - // Execute any post-move operations that must occur only on the main thread - virtual void postTransfer(); - - uint16 usedMipLevels() const { return (_maxMip - _minMip) + 1; } - - static const size_t CUBE_NUM_FACES = 6; - static const GLenum CUBE_FACE_LAYOUT[6]; + static const uint8_t TEXTURE_2D_NUM_FACES = 1; + static const uint8_t TEXTURE_CUBE_NUM_FACES = 6; + static const GLenum CUBE_FACE_LAYOUT[TEXTURE_CUBE_NUM_FACES]; static const GLFilterMode FILTER_MODES[Sampler::NUM_FILTERS]; static const GLenum WRAP_MODES[Sampler::NUM_WRAP_MODES]; - // Return a floating point value indicating how much of the allowed - // texture memory we are currently consuming. A value of 0 indicates - // no texture memory usage, while a value of 1 indicates all available / allowed memory - // is consumed. A value above 1 indicates that there is a problem. - static float getMemoryPressure(); protected: - - static const std::vector& getFaceTargets(GLenum textureType); - - static GLenum getGLTextureType(const Texture& texture); - - - const GLuint _size { 0 }; // true size as reported by the gl api - std::atomic _syncState { GLSyncState::Idle }; - - GLTexture(const std::weak_ptr& backend, const Texture& texture, GLuint id, bool transferrable); - GLTexture(const std::weak_ptr& backend, const Texture& texture, GLuint id); - - void setSyncState(GLSyncState syncState) { _syncState = syncState; } - - void createTexture(); - - virtual void updateMips() {} - virtual void allocateStorage() const = 0; - virtual void updateSize() const = 0; - virtual void syncSampler() const = 0; + virtual uint32 size() const = 0; virtual void generateMips() const = 0; - virtual void withPreservedTexture(std::function f) const; -protected: - void setSize(GLuint size) const; - - virtual void startTransfer(); - // Returns true if this is the last block required to complete transfer - virtual bool continueTransfer() { return false; } - virtual void finishTransfer(); - -private: - friend class GLTextureTransferHelper; - friend class GLBackend; + GLTexture(const std::weak_ptr& backend, const Texture& texture, GLuint id); }; +class GLExternalTexture : public GLTexture { + using Parent = GLTexture; + friend class GLBackend; +public: + ~GLExternalTexture(); +protected: + GLExternalTexture(const std::weak_ptr& backend, const Texture& texture, GLuint id); + void generateMips() const override {} + uint32 size() const override { return 0; } +}; + + } } #endif diff --git a/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.cpp b/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.cpp deleted file mode 100644 index 9dac2986e3..0000000000 --- a/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.cpp +++ /dev/null @@ -1,208 +0,0 @@ -// -// 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 "GLTextureTransfer.h" - -#include -#include - -#include - -#include "GLShared.h" -#include "GLTexture.h" - -#ifdef HAVE_NSIGHT -#include "nvToolsExt.h" -std::unordered_map _map; -#endif - - -#ifdef TEXTURE_TRANSFER_PBOS -#define TEXTURE_TRANSFER_BLOCK_SIZE (64 * 1024) -#define TEXTURE_TRANSFER_PBO_COUNT 128 -#endif - -using namespace gpu; -using namespace gpu::gl; - -GLTextureTransferHelper::GLTextureTransferHelper() { -#ifdef THREADED_TEXTURE_TRANSFER - setObjectName("TextureTransferThread"); - _context.create(); - initialize(true, QThread::LowPriority); - // Clean shutdown on UNIX, otherwise _canvas is freed early - connect(qApp, &QCoreApplication::aboutToQuit, [&] { terminate(); }); -#else - initialize(false, QThread::LowPriority); -#endif -} - -GLTextureTransferHelper::~GLTextureTransferHelper() { -#ifdef THREADED_TEXTURE_TRANSFER - if (isStillRunning()) { - terminate(); - } -#else - terminate(); -#endif -} - -void GLTextureTransferHelper::transferTexture(const gpu::TexturePointer& texturePointer) { - GLTexture* object = Backend::getGPUObject(*texturePointer); - - Backend::incrementTextureGPUTransferCount(); - object->setSyncState(GLSyncState::Pending); - Lock lock(_mutex); - _pendingTextures.push_back(texturePointer); -} - -void GLTextureTransferHelper::setup() { -#ifdef THREADED_TEXTURE_TRANSFER - _context.makeCurrent(); - -#ifdef TEXTURE_TRANSFER_FORCE_DRAW - // FIXME don't use opengl 4.5 DSA functionality without verifying it's present - glCreateRenderbuffers(1, &_drawRenderbuffer); - glNamedRenderbufferStorage(_drawRenderbuffer, GL_RGBA8, 128, 128); - glCreateFramebuffers(1, &_drawFramebuffer); - glNamedFramebufferRenderbuffer(_drawFramebuffer, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _drawRenderbuffer); - glCreateFramebuffers(1, &_readFramebuffer); -#endif - -#ifdef TEXTURE_TRANSFER_PBOS - std::array pbos; - glCreateBuffers(TEXTURE_TRANSFER_PBO_COUNT, &pbos[0]); - for (uint32_t i = 0; i < TEXTURE_TRANSFER_PBO_COUNT; ++i) { - TextureTransferBlock newBlock; - newBlock._pbo = pbos[i]; - glNamedBufferStorage(newBlock._pbo, TEXTURE_TRANSFER_BLOCK_SIZE, 0, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT); - newBlock._mapped = glMapNamedBufferRange(newBlock._pbo, 0, TEXTURE_TRANSFER_BLOCK_SIZE, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT); - _readyQueue.push(newBlock); - } -#endif -#endif -} - -void GLTextureTransferHelper::shutdown() { -#ifdef THREADED_TEXTURE_TRANSFER - _context.makeCurrent(); -#endif - -#ifdef TEXTURE_TRANSFER_FORCE_DRAW - glNamedFramebufferRenderbuffer(_drawFramebuffer, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 0); - glDeleteFramebuffers(1, &_drawFramebuffer); - _drawFramebuffer = 0; - glDeleteFramebuffers(1, &_readFramebuffer); - _readFramebuffer = 0; - - glNamedFramebufferTexture(_readFramebuffer, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0); - glDeleteRenderbuffers(1, &_drawRenderbuffer); - _drawRenderbuffer = 0; -#endif -} - -void GLTextureTransferHelper::queueExecution(VoidLambda lambda) { - Lock lock(_mutex); - _pendingCommands.push_back(lambda); -} - -#define MAX_TRANSFERS_PER_PASS 2 - -bool GLTextureTransferHelper::process() { - // Take any new textures or commands off the queue - VoidLambdaList pendingCommands; - TextureList newTransferTextures; - { - Lock lock(_mutex); - newTransferTextures.swap(_pendingTextures); - pendingCommands.swap(_pendingCommands); - } - - if (!pendingCommands.empty()) { - for (auto command : pendingCommands) { - command(); - } - glFlush(); - } - - if (!newTransferTextures.empty()) { - for (auto& texturePointer : newTransferTextures) { -#ifdef HAVE_NSIGHT - _map[texturePointer] = nvtxRangeStart("TextureTansfer"); -#endif - GLTexture* object = Backend::getGPUObject(*texturePointer); - object->startTransfer(); - _transferringTextures.push_back(texturePointer); - _textureIterator = _transferringTextures.begin(); - } - _transferringTextures.sort([](const gpu::TexturePointer& a, const gpu::TexturePointer& b)->bool { - return a->getSize() < b->getSize(); - }); - } - - // No transfers in progress, sleep - if (_transferringTextures.empty()) { -#ifdef THREADED_TEXTURE_TRANSFER - QThread::usleep(1); -#endif - return true; - } - PROFILE_COUNTER_IF_CHANGED(render_gpu_gl, "transferringTextures", int, (int) _transferringTextures.size()) - - static auto lastReport = usecTimestampNow(); - auto now = usecTimestampNow(); - auto lastReportInterval = now - lastReport; - if (lastReportInterval > USECS_PER_SECOND * 4) { - lastReport = now; - qCDebug(gpulogging) << "Texture list " << _transferringTextures.size(); - } - - size_t transferCount = 0; - for (_textureIterator = _transferringTextures.begin(); _textureIterator != _transferringTextures.end();) { - if (++transferCount > MAX_TRANSFERS_PER_PASS) { - break; - } - auto texture = *_textureIterator; - GLTexture* gltexture = Backend::getGPUObject(*texture); - if (gltexture->continueTransfer()) { - ++_textureIterator; - continue; - } - - gltexture->finishTransfer(); - -#ifdef TEXTURE_TRANSFER_FORCE_DRAW - // FIXME force a draw on the texture transfer thread before passing the texture to the main thread for use -#endif - -#ifdef THREADED_TEXTURE_TRANSFER - clientWait(); -#endif - gltexture->_contentStamp = gltexture->_gpuObject.getDataStamp(); - gltexture->updateSize(); - gltexture->setSyncState(gpu::gl::GLSyncState::Transferred); - Backend::decrementTextureGPUTransferCount(); -#ifdef HAVE_NSIGHT - // Mark the texture as transferred - nvtxRangeEnd(_map[texture]); - _map.erase(texture); -#endif - _textureIterator = _transferringTextures.erase(_textureIterator); - } - -#ifdef THREADED_TEXTURE_TRANSFER - if (!_transferringTextures.empty()) { - // Don't saturate the GPU - clientWait(); - } else { - // Don't saturate the CPU - QThread::msleep(1); - } -#endif - - return true; -} diff --git a/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.h b/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.h deleted file mode 100644 index a23c282fd4..0000000000 --- a/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.h +++ /dev/null @@ -1,78 +0,0 @@ -// -// 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 -// -#ifndef hifi_gpu_gl_GLTextureTransfer_h -#define hifi_gpu_gl_GLTextureTransfer_h - -#include -#include - -#include - -#include - -#include "GLShared.h" - -#ifdef Q_OS_WIN -#define THREADED_TEXTURE_TRANSFER -#endif - -#ifdef THREADED_TEXTURE_TRANSFER -// FIXME when sparse textures are enabled, it's harder to force a draw on the transfer thread -// also, the current draw code is implicitly using OpenGL 4.5 functionality -//#define TEXTURE_TRANSFER_FORCE_DRAW -// FIXME PBO's increase the complexity and don't seem to work reliably -//#define TEXTURE_TRANSFER_PBOS -#endif - -namespace gpu { namespace gl { - -using TextureList = std::list; -using TextureListIterator = TextureList::iterator; - -class GLTextureTransferHelper : public GenericThread { -public: - using VoidLambda = std::function; - using VoidLambdaList = std::list; - using Pointer = std::shared_ptr; - GLTextureTransferHelper(); - ~GLTextureTransferHelper(); - void transferTexture(const gpu::TexturePointer& texturePointer); - void queueExecution(VoidLambda lambda); - - void setup() override; - void shutdown() override; - bool process() override; - -private: -#ifdef THREADED_TEXTURE_TRANSFER - ::gl::OffscreenContext _context; -#endif - -#ifdef TEXTURE_TRANSFER_FORCE_DRAW - // Framebuffers / renderbuffers for forcing access to the texture on the transfer thread - GLuint _drawRenderbuffer { 0 }; - GLuint _drawFramebuffer { 0 }; - GLuint _readFramebuffer { 0 }; -#endif - - // A mutex for protecting items access on the render and transfer threads - Mutex _mutex; - // Commands that have been submitted for execution on the texture transfer thread - VoidLambdaList _pendingCommands; - // Textures that have been submitted for transfer - TextureList _pendingTextures; - // Textures currently in the transfer process - // Only used on the transfer thread - TextureList _transferringTextures; - TextureListIterator _textureIterator; - -}; - -} } - -#endif \ No newline at end of file diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h index 72e2f5a804..6d2f91c436 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h +++ b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h @@ -40,18 +40,28 @@ public: class GL41Texture : public GLTexture { using Parent = GLTexture; - GLuint allocate(); - public: - GL41Texture(const std::weak_ptr& backend, const Texture& buffer, GLuint externalId); - GL41Texture(const std::weak_ptr& backend, const Texture& buffer, bool transferrable); + static GLuint allocate(); + + public: + ~GL41Texture(); + + private: + GL41Texture(const std::weak_ptr& backend, const Texture& buffer); - protected: - void transferMip(uint16_t mipLevel, uint8_t face) const; - void startTransfer() override; - void allocateStorage() const override; - void updateSize() const override; - void syncSampler() const override; void generateMips() const override; + uint32 size() const override; + + friend class GL41Backend; + const Stamp _storageStamp; + mutable Stamp _contentStamp { 0 }; + mutable Stamp _samplerStamp { 0 }; + const uint32 _size; + + + bool isOutdated() const; + void withPreservedTexture(std::function f) const; + void syncContent() const; + void syncSampler() const; }; @@ -62,8 +72,7 @@ protected: GLuint getBufferID(const Buffer& buffer) override; GLBuffer* syncGPUObject(const Buffer& buffer) override; - GLuint getTextureID(const TexturePointer& texture, bool needTransfer = true) override; - GLTexture* syncGPUObject(const TexturePointer& texture, bool sync = true) override; + GLTexture* syncGPUObject(const TexturePointer& texture) override; GLuint getQueryID(const QueryPointer& query) override; GLQuery* syncGPUObject(const Query& query) override; diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendOutput.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendOutput.cpp index 6d11a52035..195b155bf3 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendOutput.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendOutput.cpp @@ -53,10 +53,12 @@ public: GL_COLOR_ATTACHMENT15 }; int unit = 0; + auto backend = _backend.lock(); for (auto& b : _gpuObject.getRenderBuffers()) { surface = b._texture; if (surface) { - gltexture = gl::GLTexture::sync(*_backend.lock().get(), surface, false); // Grab the gltexture and don't transfer + Q_ASSERT(TextureUsageType::RENDERBUFFER == surface->getUsageType()); + gltexture = backend->syncGPUObject(surface); } else { gltexture = nullptr; } @@ -81,9 +83,11 @@ public: } if (_gpuObject.getDepthStamp() != _depthStamp) { + auto backend = _backend.lock(); auto surface = _gpuObject.getDepthStencilBuffer(); if (_gpuObject.hasDepthStencil() && surface) { - gltexture = gl::GLTexture::sync(*_backend.lock().get(), surface, false); // Grab the gltexture and don't transfer + Q_ASSERT(TextureUsageType::RENDERBUFFER == surface->getUsageType()); + gltexture = backend->syncGPUObject(surface); } if (gltexture) { @@ -110,7 +114,7 @@ public: glBindFramebuffer(GL_DRAW_FRAMEBUFFER, currentFBO); } - checkStatus(GL_DRAW_FRAMEBUFFER); + checkStatus(); } diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp index 65c45111db..65c4dda202 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp @@ -29,20 +29,97 @@ GLuint GL41Texture::allocate() { return result; } -GLuint GL41Backend::getTextureID(const TexturePointer& texture, bool transfer) { - return GL41Texture::getId(*this, texture, transfer); +GLTexture* GL41Backend::syncGPUObject(const TexturePointer& texturePointer) { + if (!texturePointer) { + return nullptr; + } + const Texture& texture = *texturePointer; + if (TextureUsageType::EXTERNAL == texture.getUsageType()) { + return Parent::syncGPUObject(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 + GL41Texture* object = Backend::getGPUObject(texture); + if (!object || object->_storageStamp < texture.getStamp()) { + // This automatically any previous texture + object = new GL41Texture(shared_from_this(), texture); + } + + // FIXME internalize to GL41Texture 'sync' function + if (object->isOutdated()) { + object->withPreservedTexture([&] { + if (object->_contentStamp < texture.getDataStamp()) { + // FIXME implement synchronous texture transfer here + object->syncContent(); + } + + if (object->_samplerStamp < texture.getSamplerStamp()) { + object->syncSampler(); + } + }); + } + + return object; } -GLTexture* GL41Backend::syncGPUObject(const TexturePointer& texture, bool transfer) { - return GL41Texture::sync(*this, texture, transfer); +GL41Texture::GL41Texture(const std::weak_ptr& backend, const Texture& texture) + : GLTexture(backend, texture, allocate()), _storageStamp { texture.getStamp() }, _size(texture.evalTotalSize()) { + + withPreservedTexture([&] { + GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat()); + const Sampler& sampler = _gpuObject.getSampler(); + auto minMip = sampler.getMinMip(); + auto maxMip = sampler.getMaxMip(); + for (uint16_t l = minMip; l <= maxMip; l++) { + // Get the mip level dimensions, accounting for the downgrade level + Vec3u dimensions = _gpuObject.evalMipDimensions(l); + for (GLenum target : getFaceTargets(_target)) { + glTexImage2D(target, l - minMip, texelFormat.internalFormat, dimensions.x, dimensions.y, 0, texelFormat.format, texelFormat.type, NULL); + (void)CHECK_GL_ERROR(); + } + } + }); } -GL41Texture::GL41Texture(const std::weak_ptr& backend, const Texture& texture, GLuint externalId) - : GLTexture(backend, texture, externalId) { +GL41Texture::~GL41Texture() { + } -GL41Texture::GL41Texture(const std::weak_ptr& backend, const Texture& texture, bool transferrable) - : GLTexture(backend, texture, allocate(), transferrable) { +bool GL41Texture::isOutdated() const { + if (_samplerStamp <= _gpuObject.getSamplerStamp()) { + return true; + } + if (TextureUsageType::RESOURCE == _gpuObject.getUsageType() && _contentStamp <= _gpuObject.getDataStamp()) { + return true; + } + return false; +} + +void GL41Texture::withPreservedTexture(std::function f) const { + 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(); + + glBindTexture(_target, _texture); + f(); + glBindTexture(_target, boundTex); + (void)CHECK_GL_ERROR(); } void GL41Texture::generateMips() const { @@ -52,94 +129,12 @@ void GL41Texture::generateMips() const { (void)CHECK_GL_ERROR(); } -void GL41Texture::allocateStorage() const { - GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat()); - glTexParameteri(_target, GL_TEXTURE_BASE_LEVEL, 0); - (void)CHECK_GL_ERROR(); - glTexParameteri(_target, GL_TEXTURE_MAX_LEVEL, _maxMip - _minMip); - (void)CHECK_GL_ERROR(); - if (GLEW_VERSION_4_2 && !_gpuObject.getTexelFormat().isCompressed()) { - // Get the dimensions, accounting for the downgrade level - Vec3u dimensions = _gpuObject.evalMipDimensions(_minMip); - glTexStorage2D(_target, usedMipLevels(), texelFormat.internalFormat, dimensions.x, dimensions.y); - (void)CHECK_GL_ERROR(); - } else { - for (uint16_t l = _minMip; l <= _maxMip; l++) { - // Get the mip level dimensions, accounting for the downgrade level - Vec3u dimensions = _gpuObject.evalMipDimensions(l); - for (GLenum target : getFaceTargets(_target)) { - glTexImage2D(target, l - _minMip, texelFormat.internalFormat, dimensions.x, dimensions.y, 0, texelFormat.format, texelFormat.type, NULL); - (void)CHECK_GL_ERROR(); - } - } - } +void GL41Texture::syncContent() const { + // FIXME actually copy the texture data + _contentStamp = _gpuObject.getDataStamp() + 1; } -void GL41Texture::updateSize() const { - setSize(_virtualSize); - if (!_id) { - return; - } - - if (_gpuObject.getTexelFormat().isCompressed()) { - GLenum proxyType = GL_TEXTURE_2D; - GLuint numFaces = 1; - if (_gpuObject.getType() == gpu::Texture::TEX_CUBE) { - proxyType = CUBE_FACE_LAYOUT[0]; - numFaces = (GLuint)CUBE_NUM_FACES; - } - GLint gpuSize{ 0 }; - glGetTexLevelParameteriv(proxyType, 0, GL_TEXTURE_COMPRESSED, &gpuSize); - (void)CHECK_GL_ERROR(); - - if (gpuSize) { - for (GLuint level = _minMip; level < _maxMip; level++) { - GLint levelSize{ 0 }; - glGetTexLevelParameteriv(proxyType, level, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &levelSize); - levelSize *= numFaces; - - if (levelSize <= 0) { - break; - } - gpuSize += levelSize; - } - (void)CHECK_GL_ERROR(); - setSize(gpuSize); - return; - } - } -} - -// Move content bits from the CPU to the GPU for a given mip / face -void GL41Texture::transferMip(uint16_t mipLevel, uint8_t face) const { - auto mip = _gpuObject.accessStoredMipFace(mipLevel, face); - GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat(), mip->getFormat()); - //GLenum target = getFaceTargets()[face]; - GLenum target = _target == GL_TEXTURE_2D ? GL_TEXTURE_2D : CUBE_FACE_LAYOUT[face]; - auto size = _gpuObject.evalMipDimensions(mipLevel); - glTexSubImage2D(target, mipLevel, 0, 0, size.x, size.y, texelFormat.format, texelFormat.type, mip->readData()); - (void)CHECK_GL_ERROR(); -} - -void GL41Texture::startTransfer() { - PROFILE_RANGE(render_gpu_gl, __FUNCTION__); - Parent::startTransfer(); - - glBindTexture(_target, _id); - (void)CHECK_GL_ERROR(); - - // transfer pixels from each faces - uint8_t numFaces = (Texture::TEX_CUBE == _gpuObject.getType()) ? CUBE_NUM_FACES : 1; - for (uint8_t f = 0; f < numFaces; f++) { - for (uint16_t i = 0; i < Sampler::MAX_MIP_LEVEL; ++i) { - if (_gpuObject.isStoredMipFaceAvailable(i, f)) { - transferMip(i, f); - } - } - } -} - -void GL41Backend::GL41Texture::syncSampler() const { +void GL41Texture::syncSampler() const { const Sampler& sampler = _gpuObject.getSampler(); const auto& fm = FILTER_MODES[sampler.getFilter()]; glTexParameteri(_target, GL_TEXTURE_MIN_FILTER, fm.minFilter); @@ -161,5 +156,9 @@ void GL41Backend::GL41Texture::syncSampler() const { glTexParameterf(_target, GL_TEXTURE_MIN_LOD, (float)sampler.getMinMip()); glTexParameterf(_target, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.f : sampler.getMaxMip())); glTexParameterf(_target, GL_TEXTURE_MAX_ANISOTROPY_EXT, sampler.getMaxAnisotropy()); + _samplerStamp = _gpuObject.getSamplerStamp() + 1; } +uint32 GL41Texture::size() const { + return _size; +} diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.cpp index d7dde8b7d6..f0ef2ac7a8 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.cpp @@ -163,8 +163,3 @@ void GL45Backend::do_multiDrawIndexedIndirect(const Batch& batch, size_t paramOf _stats._DSNumAPIDrawcalls++; (void)CHECK_GL_ERROR(); } - -void GL45Backend::recycle() const { - Parent::recycle(); - derezTextures(); -} diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h index 2242bba5d9..e44f71e3e5 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h +++ b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h @@ -8,6 +8,7 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#pragma once #ifndef hifi_gpu_45_GL45Backend_h #define hifi_gpu_45_GL45Backend_h @@ -19,6 +20,7 @@ namespace gpu { namespace gl45 { using namespace gpu::gl; +using TextureWeakPointer = std::weak_ptr; class GL45Backend : public GLBackend { using Parent = GLBackend; @@ -31,60 +33,152 @@ public: class GL45Texture : public GLTexture { using Parent = GLTexture; + friend class GL45Backend; static GLuint allocate(const Texture& texture); - static const uint32_t DEFAULT_PAGE_DIMENSION = 128; - static const uint32_t DEFAULT_MAX_SPARSE_LEVEL = 0xFFFF; + protected: + GL45Texture(const std::weak_ptr& backend, const Texture& texture); + void generateMips() const override; + void copyMipFromTexture(uint16_t sourceMip, uint16_t targetMip) const; + virtual void syncSampler() const; + }; + + // + // Textures that have fixed allocation sizes and cannot be managed at runtime + // + + class GL45FixedAllocationTexture : public GL45Texture { + using Parent = GL45Texture; + friend class GL45Backend; public: - GL45Texture(const std::weak_ptr& backend, const Texture& texture, GLuint externalId); - GL45Texture(const std::weak_ptr& backend, const Texture& texture, bool transferrable); - ~GL45Texture(); + GL45FixedAllocationTexture(const std::weak_ptr& backend, const Texture& texture); + ~GL45FixedAllocationTexture(); - void postTransfer() override; + protected: + uint32 size() const override { return _size; } + void allocateStorage() const; + void syncSampler() const override; + const uint32 _size { 0 }; + }; - struct SparseInfo { - SparseInfo(GL45Texture& texture); - void maybeMakeSparse(); - void update(); - uvec3 getPageCounts(const uvec3& dimensions) const; - uint32_t getPageCount(const uvec3& dimensions) const; - uint32_t getSize() const; + class GL45AttachmentTexture : public GL45FixedAllocationTexture { + using Parent = GL45FixedAllocationTexture; + friend class GL45Backend; + protected: + GL45AttachmentTexture(const std::weak_ptr& backend, const Texture& texture); + ~GL45AttachmentTexture(); + }; - GL45Texture& texture; - bool sparse { false }; - uvec3 pageDimensions { DEFAULT_PAGE_DIMENSION }; - GLuint maxSparseLevel { DEFAULT_MAX_SPARSE_LEVEL }; - uint32_t allocatedPages { 0 }; - uint32_t maxPages { 0 }; - uint32_t pageBytes { 0 }; - GLint pageDimensionsIndex { 0 }; + class GL45StrictResourceTexture : public GL45FixedAllocationTexture { + using Parent = GL45FixedAllocationTexture; + friend class GL45Backend; + protected: + GL45StrictResourceTexture(const std::weak_ptr& backend, const Texture& texture); + }; + + // + // Textures that can be managed at runtime to increase or decrease their memory load + // + + class GL45VariableAllocationTexture : public GL45Texture { + using Parent = GL45Texture; + friend class GL45Backend; + using PromoteLambda = std::function; + + public: + enum class MemoryPressureState { + Idle, + Transfer, + Oversubscribed, + Undersubscribed, }; protected: - void updateMips() override; - void stripToMip(uint16_t newMinMip); - void startTransfer() override; - bool continueTransfer() override; - void finishTransfer() override; - void incrementalTransfer(const uvec3& size, const gpu::Texture::PixelsPointer& mip, std::function f) const; - void transferMip(uint16_t mipLevel, uint8_t face = 0) const; - void allocateMip(uint16_t mipLevel, uint8_t face = 0) const; - void allocateStorage() const override; - void updateSize() const override; - void syncSampler() const override; - void generateMips() const override; - void withPreservedTexture(std::function f) const override; - void derez(); + static std::atomic _memoryPressureStateStale; + static MemoryPressureState _memoryPressureState; + static std::list _memoryManagedTextures; + static const uvec3 INITIAL_MIP_TRANSFER_DIMENSIONS; - SparseInfo _sparseInfo; - uint16_t _mipOffset { 0 }; - friend class GL45Backend; + static void updateMemoryPressure(); + static void processWorkQueues(); + static void addMemoryManagedTexture(const TexturePointer& texturePointer); + + static void manageMemory(); + + protected: + GL45VariableAllocationTexture(const std::weak_ptr& backend, const Texture& texture); + ~GL45VariableAllocationTexture(); + //bool canPromoteNoAllocate() const { return _allocatedMip < _populatedMip; } + bool canPromote() const { return _allocatedMip > 0; } + bool canDemote() const { return _allocatedMip < _maxAllocatedMip; } + bool hasPendingTransfers() const { return !_pendingTransfers.empty(); } + void executeNextTransfer(); + uint32 size() const override { return _size; } + virtual void populateTransferQueue() = 0; + virtual void promote() = 0; + virtual void demote() = 0; + + uint16 _populatedMip { 0 }; + uint16 _allocatedMip { 0 }; + uint16 _maxAllocatedMip { 0 }; + uint32 _size { 0 }; + std::queue _pendingTransfers; }; + class GL45ResourceTexture : public GL45VariableAllocationTexture { + using Parent = GL45VariableAllocationTexture; + friend class GL45Backend; + protected: + GL45ResourceTexture(const std::weak_ptr& backend, const Texture& texture); + + void syncSampler() const override; + void promote() override; + void demote() override; + void populateTransferQueue() override; + + void allocateStorage(uint16 mip); + void copyMipsFromTexture(); + private: + }; + +#if 0 + class GL45SparseResourceTexture : public GL45VariableAllocationTexture { + using Parent = GL45VariableAllocationTexture; + friend class GL45Backend; + using TextureTypeFormat = std::pair; + using PageDimensions = std::vector; + using PageDimensionsMap = std::map; + static PageDimensionsMap pageDimensionsByFormat; + static Mutex pageDimensionsMutex; + + static bool isSparseEligible(const Texture& texture); + static PageDimensions getPageDimensionsForFormat(const TextureTypeFormat& typeFormat); + static PageDimensions getPageDimensionsForFormat(GLenum type, GLenum format); + static const uint32_t DEFAULT_PAGE_DIMENSION = 128; + static const uint32_t DEFAULT_MAX_SPARSE_LEVEL = 0xFFFF; + + protected: + GL45SparseResourceTexture(const std::weak_ptr& backend, const Texture& texture); + ~GL45SparseResourceTexture(); + uint32 size() const override { return _allocatedPages * _pageBytes; } + void promote() override; + void demote() override; + + private: + uvec3 getPageCounts(const uvec3& dimensions) const; + uint32_t getPageCount(const uvec3& dimensions) const; + + uint32_t _allocatedPages { 0 }; + uint32_t _pageBytes { 0 }; + uvec3 _pageDimensions { DEFAULT_PAGE_DIMENSION }; + GLuint _maxSparseLevel { DEFAULT_MAX_SPARSE_LEVEL }; + }; +#endif + protected: + void recycle() const override; - void derezTextures() const; GLuint getFramebufferID(const FramebufferPointer& framebuffer) override; GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer) override; @@ -92,8 +186,7 @@ protected: GLuint getBufferID(const Buffer& buffer) override; GLBuffer* syncGPUObject(const Buffer& buffer) override; - GLuint getTextureID(const TexturePointer& texture, bool needTransfer = true) override; - GLTexture* syncGPUObject(const TexturePointer& texture, bool sync = true) override; + GLTexture* syncGPUObject(const TexturePointer& texture) override; GLuint getQueryID(const QueryPointer& query) override; GLQuery* syncGPUObject(const Query& query) override; @@ -126,5 +219,5 @@ protected: Q_DECLARE_LOGGING_CATEGORY(gpugl45logging) - #endif + diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendOutput.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendOutput.cpp index c5b84b7deb..9648af9b21 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendOutput.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendOutput.cpp @@ -49,10 +49,12 @@ public: GL_COLOR_ATTACHMENT15 }; int unit = 0; + auto backend = _backend.lock(); for (auto& b : _gpuObject.getRenderBuffers()) { surface = b._texture; if (surface) { - gltexture = gl::GLTexture::sync(*_backend.lock().get(), surface, false); // Grab the gltexture and don't transfer + Q_ASSERT(TextureUsageType::RENDERBUFFER == surface->getUsageType()); + gltexture = backend->syncGPUObject(surface); } else { gltexture = nullptr; } @@ -78,8 +80,10 @@ public: if (_gpuObject.getDepthStamp() != _depthStamp) { auto surface = _gpuObject.getDepthStencilBuffer(); + auto backend = _backend.lock(); if (_gpuObject.hasDepthStencil() && surface) { - gltexture = gl::GLTexture::sync(*_backend.lock().get(), surface, false); // Grab the gltexture and don't transfer + Q_ASSERT(TextureUsageType::RENDERBUFFER == surface->getUsageType()); + gltexture = backend->syncGPUObject(surface); } if (gltexture) { @@ -102,7 +106,7 @@ public: _status = glCheckNamedFramebufferStatus(_id, GL_DRAW_FRAMEBUFFER); // restore the current framebuffer - checkStatus(GL_DRAW_FRAMEBUFFER); + checkStatus(); } diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp index 6948a045a2..c46c07ee37 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp @@ -8,9 +8,10 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "GL45Backend.h" +#include "GL45Backend.h" #include +#include #include #include #include @@ -19,142 +20,73 @@ #include #include +#include #include "../gl/GLTexelFormat.h" using namespace gpu; using namespace gpu::gl; using namespace gpu::gl45; -// Allocate 1 MB of buffer space for paged transfers -#define DEFAULT_PAGE_BUFFER_SIZE (1024*1024) -#define DEFAULT_GL_PIXEL_ALIGNMENT 4 - -using GL45Texture = GL45Backend::GL45Texture; - -static std::map> texturesByMipCounts; -static Mutex texturesByMipCountsMutex; -using TextureTypeFormat = std::pair; -std::map> sparsePageDimensionsByFormat; -Mutex sparsePageDimensionsByFormatMutex; - -static std::vector getPageDimensionsForFormat(const TextureTypeFormat& typeFormat) { - { - Lock lock(sparsePageDimensionsByFormatMutex); - if (sparsePageDimensionsByFormat.count(typeFormat)) { - return sparsePageDimensionsByFormat[typeFormat]; - } - } - GLint count = 0; - glGetInternalformativ(typeFormat.first, typeFormat.second, GL_NUM_VIRTUAL_PAGE_SIZES_ARB, 1, &count); - - std::vector result; - if (count > 0) { - std::vector x, y, z; - x.resize(count); - glGetInternalformativ(typeFormat.first, typeFormat.second, GL_VIRTUAL_PAGE_SIZE_X_ARB, 1, &x[0]); - y.resize(count); - glGetInternalformativ(typeFormat.first, typeFormat.second, GL_VIRTUAL_PAGE_SIZE_Y_ARB, 1, &y[0]); - z.resize(count); - glGetInternalformativ(typeFormat.first, typeFormat.second, GL_VIRTUAL_PAGE_SIZE_Z_ARB, 1, &z[0]); - - result.resize(count); - for (GLint i = 0; i < count; ++i) { - result[i] = uvec3(x[i], y[i], z[i]); - } - } - - { - Lock lock(sparsePageDimensionsByFormatMutex); - if (0 == sparsePageDimensionsByFormat.count(typeFormat)) { - sparsePageDimensionsByFormat[typeFormat] = result; - } - } - - return result; -} - -static std::vector getPageDimensionsForFormat(GLenum target, GLenum format) { - return getPageDimensionsForFormat({ target, format }); -} - -GLTexture* GL45Backend::syncGPUObject(const TexturePointer& texture, bool transfer) { - return GL45Texture::sync(*this, texture, transfer); -} - -using SparseInfo = GL45Backend::GL45Texture::SparseInfo; - -SparseInfo::SparseInfo(GL45Texture& texture) - : texture(texture) { -} - -void SparseInfo::maybeMakeSparse() { - // Don't enable sparse for objects with explicitly managed mip levels - if (!texture._gpuObject.isAutogenerateMips()) { - return; - } - return; - - const uvec3 dimensions = texture._gpuObject.getDimensions(); - auto allowedPageDimensions = getPageDimensionsForFormat(texture._target, texture._internalFormat); - // In order to enable sparse the texture size must be an integer multiple of the page size - for (size_t i = 0; i < allowedPageDimensions.size(); ++i) { - pageDimensionsIndex = (uint32_t) i; - pageDimensions = allowedPageDimensions[i]; - // Is this texture an integer multiple of page dimensions? - if (uvec3(0) == (dimensions % pageDimensions)) { - qCDebug(gpugl45logging) << "Enabling sparse for texture " << texture._source.c_str(); - sparse = true; - break; - } - } - - if (sparse) { - glTextureParameteri(texture._id, GL_TEXTURE_SPARSE_ARB, GL_TRUE); - glTextureParameteri(texture._id, GL_VIRTUAL_PAGE_SIZE_INDEX_ARB, pageDimensionsIndex); - } else { - qCDebug(gpugl45logging) << "Size " << dimensions.x << " x " << dimensions.y << - " is not supported by any sparse page size for texture" << texture._source.c_str(); - } -} - #define SPARSE_PAGE_SIZE_OVERHEAD_ESTIMATE 1.3f -// This can only be called after we've established our storage size -void SparseInfo::update() { - if (!sparse) { - return; +GLTexture* GL45Backend::syncGPUObject(const TexturePointer& texturePointer) { + if (!texturePointer) { + return nullptr; } - glGetTextureParameterIuiv(texture._id, GL_NUM_SPARSE_LEVELS_ARB, &maxSparseLevel); - pageBytes = texture._gpuObject.getTexelFormat().getSize(); - pageBytes *= pageDimensions.x * pageDimensions.y * pageDimensions.z; - // Testing with a simple texture allocating app shows an estimated 20% GPU memory overhead for - // sparse textures as compared to non-sparse, so we acount for that here. - pageBytes = (uint32_t)(pageBytes * SPARSE_PAGE_SIZE_OVERHEAD_ESTIMATE); - for (uint16_t mipLevel = 0; mipLevel <= maxSparseLevel; ++mipLevel) { - auto mipDimensions = texture._gpuObject.evalMipDimensions(mipLevel); - auto mipPageCount = getPageCount(mipDimensions); - maxPages += mipPageCount; + const Texture& texture = *texturePointer; + if (std::string("cursor texture") == texture.source()) { + qDebug() << "Loading cursor texture"; } - if (texture._target == GL_TEXTURE_CUBE_MAP) { - maxPages *= GLTexture::CUBE_NUM_FACES; + if (TextureUsageType::EXTERNAL == texture.getUsageType()) { + return Parent::syncGPUObject(texturePointer); } + + if (!texture.isDefined()) { + // NO texture definition yet so let's avoid thinking + return nullptr; + } + + GL45Texture* object = Backend::getGPUObject(texture); + if (!object) { + switch (texture.getUsageType()) { + case TextureUsageType::RENDERBUFFER: + object = new GL45AttachmentTexture(shared_from_this(), texture); + break; + + case TextureUsageType::STRICT_RESOURCE: + qCDebug(gpugllogging) << "Strict texture " << texture.source().c_str(); + object = new GL45StrictResourceTexture(shared_from_this(), texture); + break; + + case TextureUsageType::RESOURCE: { + + GL45VariableAllocationTexture* varObject { nullptr }; +#if 0 + if (isTextureManagementSparseEnabled() && GL45Texture::isSparseEligible(texture)) { + varObject = new GL45SparseResourceTexture(shared_from_this(), texture); + } else { + varObject = new GL45ResourceTexture(shared_from_this(), texture); + } +#else + varObject = new GL45ResourceTexture(shared_from_this(), texture); +#endif + GL45VariableAllocationTexture::addMemoryManagedTexture(texturePointer); + object = varObject; + break; + } + + default: + Q_UNREACHABLE(); + } + } + + return object; } -uvec3 SparseInfo::getPageCounts(const uvec3& dimensions) const { - auto result = (dimensions / pageDimensions) + - glm::clamp(dimensions % pageDimensions, glm::uvec3(0), glm::uvec3(1)); - return result; -} - -uint32_t SparseInfo::getPageCount(const uvec3& dimensions) const { - auto pageCounts = getPageCounts(dimensions); - return pageCounts.x * pageCounts.y * pageCounts.z; -} - - -uint32_t SparseInfo::getSize() const { - return allocatedPages * pageBytes; +void GL45Backend::recycle() const { + Parent::recycle(); + GL45VariableAllocationTexture::manageMemory(); } void GL45Backend::initTextureManagementStage() { @@ -171,6 +103,11 @@ void GL45Backend::initTextureManagementStage() { } } +using GL45Texture = GL45Backend::GL45Texture; + +GL45Texture::GL45Texture(const std::weak_ptr& backend, const Texture& texture) + : GLTexture(backend, texture, allocate(texture)) { +} GLuint GL45Texture::allocate(const Texture& texture) { GLuint result; @@ -178,162 +115,37 @@ GLuint GL45Texture::allocate(const Texture& texture) { return result; } -GLuint GL45Backend::getTextureID(const TexturePointer& texture, bool transfer) { - return GL45Texture::getId(*this, texture, transfer); -} - -GL45Texture::GL45Texture(const std::weak_ptr& backend, const Texture& texture, GLuint externalId) - : GLTexture(backend, texture, externalId), _sparseInfo(*this) -{ -} - -GL45Texture::GL45Texture(const std::weak_ptr& backend, const Texture& texture, bool transferrable) - : GLTexture(backend, texture, allocate(texture), transferrable), _sparseInfo(*this) - { - - auto theBackend = _backend.lock(); - if (_transferrable && theBackend && theBackend->isTextureManagementSparseEnabled()) { - _sparseInfo.maybeMakeSparse(); - if (_sparseInfo.sparse) { - Backend::incrementTextureGPUSparseCount(); - } - } -} - -GL45Texture::~GL45Texture() { - // Remove this texture from the candidate list of derezzable textures - if (_transferrable) { - auto mipLevels = usedMipLevels(); - Lock lock(texturesByMipCountsMutex); - if (texturesByMipCounts.count(mipLevels)) { - auto& textures = texturesByMipCounts[mipLevels]; - textures.erase(this); - if (textures.empty()) { - texturesByMipCounts.erase(mipLevels); - } - } - } - - if (_sparseInfo.sparse) { - Backend::decrementTextureGPUSparseCount(); - - // Experimenation suggests that allocating sparse textures on one context/thread and deallocating - // them on another is buggy. So for sparse textures we need to queue a lambda with the deallocation - // callls to the transfer thread - auto id = _id; - // Set the class _id to 0 so we don't try to double delete - const_cast(_id) = 0; - std::list> destructionFunctions; - - uint8_t maxFace = (uint8_t)((_target == GL_TEXTURE_CUBE_MAP) ? GLTexture::CUBE_NUM_FACES : 1); - auto maxSparseMip = std::min(_maxMip, _sparseInfo.maxSparseLevel); - for (uint16_t mipLevel = _minMip; mipLevel <= maxSparseMip; ++mipLevel) { - auto mipDimensions = _gpuObject.evalMipDimensions(mipLevel); - destructionFunctions.push_back([id, maxFace, mipLevel, mipDimensions] { - glTexturePageCommitmentEXT(id, mipLevel, 0, 0, 0, mipDimensions.x, mipDimensions.y, maxFace, GL_FALSE); - }); - - auto deallocatedPages = _sparseInfo.getPageCount(mipDimensions) * maxFace; - assert(deallocatedPages <= _sparseInfo.allocatedPages); - _sparseInfo.allocatedPages -= deallocatedPages; - } - - if (0 != _sparseInfo.allocatedPages) { - qCWarning(gpugl45logging) << "Allocated pages remaining " << _id << " " << _sparseInfo.allocatedPages; - } - - auto size = _size; - const_cast(_size) = 0; - _textureTransferHelper->queueExecution([id, size, destructionFunctions] { - for (auto function : destructionFunctions) { - function(); - } - glDeleteTextures(1, &id); - Backend::decrementTextureGPUCount(); - Backend::updateTextureGPUMemoryUsage(size, 0); - Backend::updateTextureGPUSparseMemoryUsage(size, 0); - }); - } -} - -void GL45Texture::withPreservedTexture(std::function f) const { - f(); -} - void GL45Texture::generateMips() const { glGenerateTextureMipmap(_id); (void)CHECK_GL_ERROR(); } -void GL45Texture::allocateStorage() const { - if (_gpuObject.getTexelFormat().isCompressed()) { - qFatal("Compressed textures not yet supported"); +void GL45Texture::copyMipFromTexture(uint16_t sourceMip, uint16_t targetMip) const { + const auto& texture = _gpuObject; + if (!texture.isStoredMipFaceAvailable(sourceMip)) { + return; } - glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, 0); - glTextureParameteri(_id, GL_TEXTURE_MAX_LEVEL, _maxMip - _minMip); - // Get the dimensions, accounting for the downgrade level - Vec3u dimensions = _gpuObject.evalMipDimensions(_minMip + _mipOffset); - glTextureStorage2D(_id, usedMipLevels(), _internalFormat, dimensions.x, dimensions.y); - (void)CHECK_GL_ERROR(); -} - -void GL45Texture::updateSize() const { - if (_gpuObject.getTexelFormat().isCompressed()) { - qFatal("Compressed textures not yet supported"); - } - - if (_transferrable && _sparseInfo.sparse) { - auto size = _sparseInfo.getSize(); - Backend::updateTextureGPUSparseMemoryUsage(_size, size); - setSize(size); - } else { - setSize(_gpuObject.evalTotalSize(_mipOffset)); - } -} - -void GL45Texture::startTransfer() { - Parent::startTransfer(); - _sparseInfo.update(); -} - -bool GL45Texture::continueTransfer() { - PROFILE_RANGE(render_gpu_gl, "continueTransfer") - size_t maxFace = GL_TEXTURE_CUBE_MAP == _target ? CUBE_NUM_FACES : 1; + size_t maxFace = GLTexture::getFaceCount(_target); for (uint8_t face = 0; face < maxFace; ++face) { - for (uint16_t mipLevel = _minMip; mipLevel <= _maxMip; ++mipLevel) { - auto size = _gpuObject.evalMipDimensions(mipLevel); - if (_sparseInfo.sparse && mipLevel <= _sparseInfo.maxSparseLevel) { - glTexturePageCommitmentEXT(_id, mipLevel, 0, 0, face, size.x, size.y, 1, GL_TRUE); - _sparseInfo.allocatedPages += _sparseInfo.getPageCount(size); - } - if (_gpuObject.isStoredMipFaceAvailable(mipLevel, face)) { - PROFILE_RANGE_EX(render_gpu_gl, "texSubImage", 0x0000ffff, (size.x * size.y * maxFace / 1024)); - - auto mip = _gpuObject.accessStoredMipFace(mipLevel, face); - GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat(), mip->getFormat()); - if (GL_TEXTURE_2D == _target) { - glTextureSubImage2D(_id, mipLevel, 0, 0, size.x, size.y, texelFormat.format, texelFormat.type, mip->readData()); - } else if (GL_TEXTURE_CUBE_MAP == _target) { - // DSA ARB does not work on AMD, so use EXT - // unless EXT is not available on the driver - if (glTextureSubImage2DEXT) { - auto target = CUBE_FACE_LAYOUT[face]; - glTextureSubImage2DEXT(_id, target, mipLevel, 0, 0, size.x, size.y, texelFormat.format, texelFormat.type, mip->readData()); - } else { - glTextureSubImage3D(_id, mipLevel, 0, 0, face, size.x, size.y, 1, texelFormat.format, texelFormat.type, mip->readData()); - } - } else { - Q_ASSERT(false); - } - (void)CHECK_GL_ERROR(); + auto size = texture.evalMipDimensions(sourceMip); + auto mipData = texture.accessStoredMipFace(sourceMip, face); + GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(texture.getTexelFormat(), mipData->getFormat()); + if (GL_TEXTURE_2D == _target) { + glTextureSubImage2D(_id, targetMip, 0, 0, size.x, size.y, texelFormat.format, texelFormat.type, mipData->readData()); + } else if (GL_TEXTURE_CUBE_MAP == _target) { + // DSA ARB does not work on AMD, so use EXT + // unless EXT is not available on the driver + if (glTextureSubImage2DEXT) { + auto target = GLTexture::CUBE_FACE_LAYOUT[face]; + glTextureSubImage2DEXT(_id, target, targetMip, 0, 0, size.x, size.y, texelFormat.format, texelFormat.type, mipData->readData()); + } else { + glTextureSubImage3D(_id, targetMip, 0, 0, face, size.x, size.y, 1, texelFormat.format, texelFormat.type, mipData->readData()); } + } else { + Q_ASSERT(false); } + (void)CHECK_GL_ERROR(); } - return false; -} - -void GL45Texture::finishTransfer() { - Parent::finishTransfer(); } void GL45Texture::syncSampler() const { @@ -353,163 +165,66 @@ void GL45Texture::syncSampler() const { glTextureParameteri(_id, GL_TEXTURE_WRAP_S, WRAP_MODES[sampler.getWrapModeU()]); glTextureParameteri(_id, GL_TEXTURE_WRAP_T, WRAP_MODES[sampler.getWrapModeV()]); glTextureParameteri(_id, GL_TEXTURE_WRAP_R, WRAP_MODES[sampler.getWrapModeW()]); + glTextureParameterf(_id, GL_TEXTURE_MAX_ANISOTROPY_EXT, sampler.getMaxAnisotropy()); glTextureParameterfv(_id, GL_TEXTURE_BORDER_COLOR, (const float*)&sampler.getBorderColor()); + +#if 0 // FIXME account for mip offsets here auto baseMip = std::max(sampler.getMipOffset(), _minMip); glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, baseMip); glTextureParameterf(_id, GL_TEXTURE_MIN_LOD, (float)sampler.getMinMip()); glTextureParameterf(_id, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.f : sampler.getMaxMip() - _mipOffset)); - glTextureParameterf(_id, GL_TEXTURE_MAX_ANISOTROPY_EXT, sampler.getMaxAnisotropy()); +#endif } -void GL45Texture::postTransfer() { - Parent::postTransfer(); - auto mipLevels = usedMipLevels(); - if (_transferrable && mipLevels > 1 && _minMip < _sparseInfo.maxSparseLevel) { - Lock lock(texturesByMipCountsMutex); - texturesByMipCounts[mipLevels].insert(this); - } -} +using GL45FixedAllocationTexture = GL45Backend::GL45FixedAllocationTexture; -void GL45Texture::stripToMip(uint16_t newMinMip) { - if (newMinMip < _minMip) { - qCWarning(gpugl45logging) << "Cannot decrease the min mip"; - return; - } - - if (_sparseInfo.sparse && newMinMip > _sparseInfo.maxSparseLevel) { - qCWarning(gpugl45logging) << "Cannot increase the min mip into the mip tail"; - return; - } - - PROFILE_RANGE(render_gpu_gl, "GL45Texture::stripToMip"); - - auto mipLevels = usedMipLevels(); - { - Lock lock(texturesByMipCountsMutex); - assert(0 != texturesByMipCounts.count(mipLevels)); - assert(0 != texturesByMipCounts[mipLevels].count(this)); - texturesByMipCounts[mipLevels].erase(this); - if (texturesByMipCounts[mipLevels].empty()) { - texturesByMipCounts.erase(mipLevels); - } - } - - // If we weren't generating mips before, we need to now that we're stripping down mip levels. - if (!_gpuObject.isAutogenerateMips()) { - qCDebug(gpugl45logging) << "Force mip generation for texture"; - glGenerateTextureMipmap(_id); - } - - - uint8_t maxFace = (uint8_t)((_target == GL_TEXTURE_CUBE_MAP) ? GLTexture::CUBE_NUM_FACES : 1); - if (_sparseInfo.sparse) { - for (uint16_t mip = _minMip; mip < newMinMip; ++mip) { - auto id = _id; - auto mipDimensions = _gpuObject.evalMipDimensions(mip); - _textureTransferHelper->queueExecution([id, mip, mipDimensions, maxFace] { - glTexturePageCommitmentEXT(id, mip, 0, 0, 0, mipDimensions.x, mipDimensions.y, maxFace, GL_FALSE); - }); - - auto deallocatedPages = _sparseInfo.getPageCount(mipDimensions) * maxFace; - assert(deallocatedPages < _sparseInfo.allocatedPages); - _sparseInfo.allocatedPages -= deallocatedPages; - } - _minMip = newMinMip; - } else { - GLuint oldId = _id; - // Find the distance between the old min mip and the new one - uint16 mipDelta = newMinMip - _minMip; - _mipOffset += mipDelta; - const_cast(_maxMip) -= mipDelta; - auto newLevels = usedMipLevels(); - - // Create and setup the new texture (allocate) - { - Vec3u newDimensions = _gpuObject.evalMipDimensions(_mipOffset); - PROFILE_RANGE_EX(render_gpu_gl, "Re-Allocate", 0xff0000ff, (newDimensions.x * newDimensions.y)); - - glCreateTextures(_target, 1, &const_cast(_id)); - glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, 0); - glTextureParameteri(_id, GL_TEXTURE_MAX_LEVEL, _maxMip - _minMip); - glTextureStorage2D(_id, newLevels, _internalFormat, newDimensions.x, newDimensions.y); - } - - // Copy the contents of the old texture to the new - { - PROFILE_RANGE(render_gpu_gl, "Blit"); - // Preferred path only available in 4.3 - for (uint16 targetMip = _minMip; targetMip <= _maxMip; ++targetMip) { - uint16 sourceMip = targetMip + mipDelta; - Vec3u mipDimensions = _gpuObject.evalMipDimensions(targetMip + _mipOffset); - for (GLenum target : getFaceTargets(_target)) { - glCopyImageSubData( - oldId, target, sourceMip, 0, 0, 0, - _id, target, targetMip, 0, 0, 0, - mipDimensions.x, mipDimensions.y, 1 - ); - (void)CHECK_GL_ERROR(); - } - } - - glDeleteTextures(1, &oldId); - } - } - - // Re-sync the sampler to force access to the new mip level +GL45FixedAllocationTexture::GL45FixedAllocationTexture(const std::weak_ptr& backend, const Texture& texture) : GL45Texture(backend, texture), _size(texture.evalTotalSize()) { + allocateStorage(); syncSampler(); - updateSize(); +} - // Re-insert into the texture-by-mips map if appropriate - mipLevels = usedMipLevels(); - if (mipLevels > 1 && (!_sparseInfo.sparse || _minMip < _sparseInfo.maxSparseLevel)) { - Lock lock(texturesByMipCountsMutex); - texturesByMipCounts[mipLevels].insert(this); +GL45FixedAllocationTexture::~GL45FixedAllocationTexture() { +} + +void GL45FixedAllocationTexture::allocateStorage() const { + const GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat()); + const auto dimensions = _gpuObject.getDimensions(); + const auto mips = _gpuObject.evalNumMips(); + glTextureStorage2D(_id, mips, texelFormat.internalFormat, dimensions.x, dimensions.y); +} + +void GL45FixedAllocationTexture::syncSampler() const { + Parent::syncSampler(); + const Sampler& sampler = _gpuObject.getSampler(); + auto baseMip = std::max(sampler.getMipOffset(), sampler.getMinMip()); + glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, baseMip); + glTextureParameterf(_id, GL_TEXTURE_MIN_LOD, (float)sampler.getMinMip()); + glTextureParameterf(_id, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.f : sampler.getMaxMip())); +} + +// Renderbuffer attachment textures +using GL45AttachmentTexture = GL45Backend::GL45AttachmentTexture; + +GL45AttachmentTexture::GL45AttachmentTexture(const std::weak_ptr& backend, const Texture& texture) : GL45FixedAllocationTexture(backend, texture) { + Backend::updateTextureGPUFramebufferMemoryUsage(0, size()); +} + +GL45AttachmentTexture::~GL45AttachmentTexture() { + Backend::updateTextureGPUFramebufferMemoryUsage(size(), 0); +} + +// Strict resource textures +using GL45StrictResourceTexture = GL45Backend::GL45StrictResourceTexture; + +GL45StrictResourceTexture::GL45StrictResourceTexture(const std::weak_ptr& backend, const Texture& texture) : GL45FixedAllocationTexture(backend, texture) { + auto mipLevels = _gpuObject.evalNumMips(); + for (uint16_t sourceMip = 0; sourceMip < mipLevels; ++sourceMip) { + uint16_t targetMip = sourceMip; + copyMipFromTexture(sourceMip, targetMip); + } + if (texture.isAutogenerateMips()) { + generateMips(); } } -void GL45Texture::updateMips() { - if (!_sparseInfo.sparse) { - return; - } - auto newMinMip = std::min(_gpuObject.minMip(), _sparseInfo.maxSparseLevel); - if (_minMip < newMinMip) { - stripToMip(newMinMip); - } -} - -void GL45Texture::derez() { - if (_sparseInfo.sparse) { - assert(_minMip < _sparseInfo.maxSparseLevel); - } - assert(_minMip < _maxMip); - assert(_transferrable); - stripToMip(_minMip + 1); -} - -void GL45Backend::derezTextures() const { - if (GLTexture::getMemoryPressure() < 1.0f) { - return; - } - - Lock lock(texturesByMipCountsMutex); - if (texturesByMipCounts.empty()) { - // No available textures to derez - return; - } - - auto mipLevel = texturesByMipCounts.rbegin()->first; - if (mipLevel <= 1) { - // No mips available to remove - return; - } - - GL45Texture* targetTexture = nullptr; - { - auto& textures = texturesByMipCounts[mipLevel]; - assert(!textures.empty()); - targetTexture = *textures.begin(); - } - lock.unlock(); - targetTexture->derez(); -} diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp new file mode 100644 index 0000000000..c5db65058d --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp @@ -0,0 +1,888 @@ +// +// GL45BackendTexture.cpp +// libraries/gpu/src/gpu +// +// Created by Sam Gateau on 1/19/2015. +// Copyright 2014 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 "GL45Backend.h" +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include "../gl/GLTexelFormat.h" + +using namespace gpu; +using namespace gpu::gl; +using namespace gpu::gl45; + +// Variable sized textures +using GL45VariableAllocationTexture = GL45Backend::GL45VariableAllocationTexture; +using MemoryPressureState = GL45VariableAllocationTexture::MemoryPressureState; + +std::list GL45VariableAllocationTexture::_memoryManagedTextures; +MemoryPressureState GL45VariableAllocationTexture::_memoryPressureState = MemoryPressureState::Idle; +std::atomic GL45VariableAllocationTexture::_memoryPressureStateStale { false }; +const uvec3 GL45VariableAllocationTexture::INITIAL_MIP_TRANSFER_DIMENSIONS { 64, 64, 1 }; + +#define OVERSUBSCRIBED_PRESSURE_VALUE 0.95f +#define UNDERSUBSCRIBED_PRESSURE_VALUE 0.85f +#define DEFAULT_ALLOWED_TEXTURE_MEMORY_MB ((size_t)1024) + +static const size_t DEFAULT_ALLOWED_TEXTURE_MEMORY = MB_TO_BYTES(DEFAULT_ALLOWED_TEXTURE_MEMORY_MB); + +using QueuePair = std::pair; +class QueuePairLess { +public: + bool operator()(const QueuePair& a, const QueuePair& b) { + return a.second < b.second; + } +}; +class QueuePairGreater { +public: + bool operator()(const QueuePair& a, const QueuePair& b) { + return a.second > b.second; + } +}; +using DemoteQueue = std::priority_queue, QueuePairLess>; +using PromoteQueue = std::priority_queue, QueuePairGreater>; +using TransferQueue = std::queue; +static DemoteQueue demoteQueue; +static PromoteQueue promoteQueue; +static TransferQueue transferQueue; + +void GL45VariableAllocationTexture::addMemoryManagedTexture(const TexturePointer& texturePointer) { + _memoryManagedTextures.push_back(texturePointer); + GL45VariableAllocationTexture* object = Backend::getGPUObject(*texturePointer); + switch (_memoryPressureState) { + case MemoryPressureState::Oversubscribed: + if (object->canDemote()) { + demoteQueue.push({ texturePointer, object->size() }); + } + break; + + case MemoryPressureState::Undersubscribed: + if (object->canPromote()) { + promoteQueue.push({ texturePointer, object->size() }); + } + break; + + case MemoryPressureState::Transfer: + if (object->hasPendingTransfers()) { + transferQueue.push( texturePointer ); + } + break; + + case MemoryPressureState::Idle: + break; + + default: + Q_UNREACHABLE(); + } +} + +void GL45VariableAllocationTexture::updateMemoryPressure() { + static size_t lastAllowedMemoryAllocation = gpu::Texture::getAllowedGPUMemoryUsage(); + size_t allowedMemoryAllocation = gpu::Texture::getAllowedGPUMemoryUsage(); + if (allowedMemoryAllocation != lastAllowedMemoryAllocation) { + _memoryPressureStateStale = true; + lastAllowedMemoryAllocation = allowedMemoryAllocation; + } + + if (!_memoryPressureStateStale) { + return; + } + _memoryPressureStateStale = false; + // Clear any defunct textures + _memoryManagedTextures.remove_if([&](const TextureWeakPointer& weakPointer) { + return weakPointer.expired(); + }); + + // Convert weak pointers to strong + std::list strongTextures; { + std::transform( + _memoryManagedTextures.begin(), _memoryManagedTextures.end(), + std::back_inserter(strongTextures), + [](const TextureWeakPointer& p) { return p.lock(); }); + } + + size_t totalVariableMemoryAllocation = 0; + size_t idealMemoryAllocation = 0; + bool canDemote = false; + bool canPromote = false; + bool hasTransfers = false; + for (const auto& texture : strongTextures) { + // Race conditions can still leave nulls in the list, so we need to check + if (!texture) { + continue; + } + idealMemoryAllocation += texture->evalTotalSize(); + GL45VariableAllocationTexture* object = Backend::getGPUObject(*texture); + totalVariableMemoryAllocation += object->size(); + canDemote |= object->canDemote(); + canPromote |= object->canPromote(); + hasTransfers |= object->hasPendingTransfers(); + } + + size_t unallocated = idealMemoryAllocation - totalVariableMemoryAllocation; + if (0 == allowedMemoryAllocation) { + allowedMemoryAllocation = DEFAULT_ALLOWED_TEXTURE_MEMORY; + } + + + float pressure = (float)totalVariableMemoryAllocation / (float)allowedMemoryAllocation; + + auto newState = MemoryPressureState::Idle; + if (pressure > OVERSUBSCRIBED_PRESSURE_VALUE && canDemote) { + newState = MemoryPressureState::Oversubscribed; + } else if (pressure < UNDERSUBSCRIBED_PRESSURE_VALUE && unallocated != 0 && canPromote) { + newState = MemoryPressureState::Undersubscribed; + } else if (hasTransfers) { + newState = MemoryPressureState::Transfer; + } + + if (newState != _memoryPressureState) { + _memoryPressureState = newState; + + demoteQueue = DemoteQueue(); + promoteQueue = PromoteQueue(); + transferQueue = TransferQueue(); + + switch (_memoryPressureState) { + case MemoryPressureState::Idle: + break; + + case MemoryPressureState::Oversubscribed: + for (const auto& texture : strongTextures) { + if (!texture) { + continue; + } + GL45VariableAllocationTexture* object = Backend::getGPUObject(*texture); + if (object->canDemote()) { + demoteQueue.push({ texture, object->size() }); + } + } + break; + + case MemoryPressureState::Undersubscribed: + for (const auto& texture : strongTextures) { + if (!texture) { + continue; + } + GL45VariableAllocationTexture* object = Backend::getGPUObject(*texture); + if (object->canPromote()) { + promoteQueue.push({ texture, object->size() }); + } + } + break; + + case MemoryPressureState::Transfer: + for (const auto& texture : strongTextures) { + if (!texture) { + continue; + } + GL45VariableAllocationTexture* object = Backend::getGPUObject(*texture); + if (object->hasPendingTransfers()) { + transferQueue.push(texture); + } + } + break; + + default: + Q_UNREACHABLE(); + break; + } + } +} + +void GL45VariableAllocationTexture::processWorkQueues() { + switch (_memoryPressureState) { + case MemoryPressureState::Idle: + break; + + case MemoryPressureState::Oversubscribed: + // Grab the first item off the demote queue + while (!demoteQueue.empty()) { + auto demoteTarget = demoteQueue.top(); + demoteQueue.pop(); + auto texture = demoteTarget.first.lock(); + if (!texture) { + continue; + } + + GL45VariableAllocationTexture* object = Backend::getGPUObject(*texture); + if (!object->canDemote()) { + continue; + } + + //qDebug() << "QQQ executing demote for " << texture->source().c_str(); + object->demote(); + // if the object can be further demoted, reinsert into the queue + if (object->canDemote()) { + demoteQueue.push({ demoteTarget.first, object->size() }); + } + break; + } + if (demoteQueue.empty()) { + _memoryPressureState = MemoryPressureState::Idle; + } + break; + + case MemoryPressureState::Undersubscribed: + while (!promoteQueue.empty()) { + auto promoteTarget = promoteQueue.top(); + promoteQueue.pop(); + auto texture = promoteTarget.first.lock(); + if (!texture) { + continue; + } + GL45VariableAllocationTexture* object = Backend::getGPUObject(*texture); + if (!object->canPromote()) { + continue; + } + //qDebug() << "QQQ executing promote for " << texture->source().c_str(); + object->promote(); + if (object->canPromote()) { + promoteQueue.push({ promoteTarget.first, object->size() }); + } + break; + } + if (promoteQueue.empty()) { + _memoryPressureState = MemoryPressureState::Idle; + } + break; + + case MemoryPressureState::Transfer: + while (!transferQueue.empty()) { + auto weakTexture = transferQueue.front(); + transferQueue.pop(); + auto texture = weakTexture.lock(); + if (!texture) { + continue; + } + GL45VariableAllocationTexture* object = Backend::getGPUObject(*texture); + if (!object->hasPendingTransfers()) { + continue; + } + //qDebug() << "QQQ executing transfer for " << texture->source().c_str(); + object->executeNextTransfer(); + if (object->hasPendingTransfers()) { + transferQueue.push(weakTexture); + } + break; + } + if (transferQueue.empty()) { + _memoryPressureState = MemoryPressureState::Idle; + } + break; + + default: + Q_UNREACHABLE(); + break; + } +} + +void GL45VariableAllocationTexture::manageMemory() { + static auto lastProcessTime = usecTimestampNow(); + auto now = usecTimestampNow(); + auto interval = now - lastProcessTime; + if (interval > (USECS_PER_MSEC * 20)) { + lastProcessTime = now; + updateMemoryPressure(); + processWorkQueues(); + } +} + +GL45VariableAllocationTexture::GL45VariableAllocationTexture(const std::weak_ptr& backend, const Texture& texture) : GL45Texture(backend, texture) { +} + +GL45VariableAllocationTexture::~GL45VariableAllocationTexture() { + _memoryPressureStateStale = true; + Backend::updateTextureGPUMemoryUsage(_size, 0); +} + +void GL45VariableAllocationTexture::executeNextTransfer() { + if (!_pendingTransfers.empty()) { + _pendingTransfers.front()(); + _pendingTransfers.pop(); + } +} + +// Managed size resource textures +using GL45ResourceTexture = GL45Backend::GL45ResourceTexture; + +GL45ResourceTexture::GL45ResourceTexture(const std::weak_ptr& backend, const Texture& texture) : GL45VariableAllocationTexture(backend, texture) { + auto mipLevels = texture.evalNumMips(); + _allocatedMip = mipLevels; + uvec3 mipDimensions; + for (uint16_t mip = 0; mip < mipLevels; ++mip) { + if (glm::all(glm::lessThanEqual(texture.evalMipDimensions(mip), INITIAL_MIP_TRANSFER_DIMENSIONS))) { + _maxAllocatedMip = _populatedMip = mip; + break; + } + } + + uint16_t allocatedMip = _populatedMip - std::min(_populatedMip, 2); + allocateStorage(allocatedMip); + _memoryPressureStateStale = true; + copyMipsFromTexture(); + syncSampler(); + +} + +void GL45ResourceTexture::allocateStorage(uint16 allocatedMip) { + _allocatedMip = allocatedMip; + const GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat()); + const auto dimensions = _gpuObject.evalMipDimensions(_allocatedMip); + const auto totalMips = _gpuObject.evalNumMips(); + const auto mips = totalMips - _allocatedMip; + glTextureStorage2D(_id, mips, texelFormat.internalFormat, dimensions.x, dimensions.y); + auto mipLevels = _gpuObject.evalNumMips(); + _size = 0; + for (uint16_t mip = _allocatedMip; mip < mipLevels; ++mip) { + _size += _gpuObject.evalMipSize(mip); + } + Backend::updateTextureGPUMemoryUsage(0, _size); + +} + +void GL45ResourceTexture::copyMipsFromTexture() { + auto mipLevels = _gpuObject.evalNumMips(); + for (uint16_t sourceMip = _populatedMip; sourceMip < mipLevels; ++sourceMip) { + uint16_t targetMip = sourceMip - _allocatedMip; + copyMipFromTexture(sourceMip, targetMip); + } +} + +void GL45ResourceTexture::syncSampler() const { + Parent::syncSampler(); + const Sampler& sampler = _gpuObject.getSampler(); + uint16_t maxMip = _gpuObject.evalNumMips() - _allocatedMip; + auto minMip = std::max(sampler.getMipOffset(), sampler.getMinMip()); + minMip = std::min(minMip, maxMip); + glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, _populatedMip - _allocatedMip); + glTextureParameterf(_id, GL_TEXTURE_MIN_LOD, (float)minMip); + glTextureParameterf(_id, GL_TEXTURE_MAX_LOD, (float)maxMip); +} + +void GL45ResourceTexture::promote() { + Q_ASSERT(_allocatedMip > 0); + GLuint oldId = _id; + uint32_t oldSize = _size; + // create new texture + const_cast(_id) = allocate(_gpuObject); + uint16_t oldAllocatedMip = _allocatedMip; + // allocate storage for new level + allocateStorage(_allocatedMip - std::min(_allocatedMip, 2)); + uint16_t mips = _gpuObject.evalNumMips(); + // copy pre-existing mips + for (uint16_t mip = _populatedMip; mip < mips; ++mip) { + auto mipDimensions = _gpuObject.evalMipDimensions(mip); + uint16_t targetMip = mip - _allocatedMip; + uint16_t sourceMip = mip - oldAllocatedMip; + auto faces = getFaceCount(_target); + for (uint8_t face = 0; face < faces; ++face) { + glCopyImageSubData( + oldId, _target, sourceMip, 0, 0, face, + _id, _target, targetMip, 0, 0, face, + mipDimensions.x, mipDimensions.y, 1 + ); + (void)CHECK_GL_ERROR(); + } + } + // destroy the old texture + glDeleteTextures(1, &oldId); + // update the memory usage + Backend::updateTextureGPUMemoryUsage(oldSize, 0); + _memoryPressureStateStale = true; + syncSampler(); + populateTransferQueue(); +} + +void GL45ResourceTexture::demote() { + Q_ASSERT(_allocatedMip < _maxAllocatedMip); + auto oldId = _id; + auto oldSize = _size; + const_cast(_id) = allocate(_gpuObject); + allocateStorage(_allocatedMip + 1); + _populatedMip = std::max(_populatedMip, _allocatedMip); + uint16_t mips = _gpuObject.evalNumMips(); + // copy pre-existing mips + for (uint16_t mip = _populatedMip; mip < mips; ++mip) { + auto mipDimensions = _gpuObject.evalMipDimensions(mip); + uint16_t targetMip = mip - _allocatedMip; + uint16_t sourceMip = targetMip + 1; + auto faces = getFaceCount(_target); + for (uint8_t face = 0; face < faces; ++face) { + glCopyImageSubData( + oldId, _target, sourceMip, 0, 0, face, + _id, _target, targetMip, 0, 0, face, + mipDimensions.x, mipDimensions.y, 1 + ); + (void)CHECK_GL_ERROR(); + } + } + // destroy the old texture + glDeleteTextures(1, &oldId); + // update the memory usage + Backend::updateTextureGPUMemoryUsage(oldSize, 0); + _memoryPressureStateStale = true; + syncSampler(); + populateTransferQueue(); +} + +void GL45ResourceTexture::populateTransferQueue() { + _pendingTransfers = std::queue(); + if (_populatedMip <= _allocatedMip) { + return; + } + + for (int16_t mip = _populatedMip - 1; mip >= _allocatedMip; --mip) { + // FIXME break down the transfers into chunks so that no single transfer is + // consuming more than X bandwidth + _pendingTransfers.push([mip, this] { + Q_ASSERT(mip >= _allocatedMip); + // FIXME modify the copy mechanism to be incremental + copyMipFromTexture(mip, mip - _allocatedMip); + _populatedMip = mip; + syncSampler(); + }); + } +} + +// Sparsely allocated, managed size resource textures +#if 0 +#define SPARSE_PAGE_SIZE_OVERHEAD_ESTIMATE 1.3f + +using GL45SparseResourceTexture = GL45Backend::GL45SparseResourceTexture; + +GL45Texture::PageDimensionsMap GL45Texture::pageDimensionsByFormat; +Mutex GL45Texture::pageDimensionsMutex; + +GL45Texture::PageDimensions GL45Texture::getPageDimensionsForFormat(const TextureTypeFormat& typeFormat) { + { + Lock lock(pageDimensionsMutex); + if (pageDimensionsByFormat.count(typeFormat)) { + return pageDimensionsByFormat[typeFormat]; + } + } + + GLint count = 0; + glGetInternalformativ(typeFormat.first, typeFormat.second, GL_NUM_VIRTUAL_PAGE_SIZES_ARB, 1, &count); + + std::vector result; + if (count > 0) { + std::vector x, y, z; + x.resize(count); + glGetInternalformativ(typeFormat.first, typeFormat.second, GL_VIRTUAL_PAGE_SIZE_X_ARB, 1, &x[0]); + y.resize(count); + glGetInternalformativ(typeFormat.first, typeFormat.second, GL_VIRTUAL_PAGE_SIZE_Y_ARB, 1, &y[0]); + z.resize(count); + glGetInternalformativ(typeFormat.first, typeFormat.second, GL_VIRTUAL_PAGE_SIZE_Z_ARB, 1, &z[0]); + + result.resize(count); + for (GLint i = 0; i < count; ++i) { + result[i] = uvec3(x[i], y[i], z[i]); + } + } + + { + Lock lock(pageDimensionsMutex); + if (0 == pageDimensionsByFormat.count(typeFormat)) { + pageDimensionsByFormat[typeFormat] = result; + } + } + + return result; +} + +GL45Texture::PageDimensions GL45Texture::getPageDimensionsForFormat(GLenum target, GLenum format) { + return getPageDimensionsForFormat({ target, format }); +} +bool GL45Texture::isSparseEligible(const Texture& texture) { + Q_ASSERT(TextureUsageType::RESOURCE == texture.getUsageType()); + + // Disabling sparse for the momemnt + return false; + + const auto allowedPageDimensions = getPageDimensionsForFormat(getGLTextureType(texture), + gl::GLTexelFormat::evalGLTexelFormatInternal(texture.getTexelFormat())); + const auto textureDimensions = texture.getDimensions(); + for (const auto& pageDimensions : allowedPageDimensions) { + if (uvec3(0) == (textureDimensions % pageDimensions)) { + return true; + } + } + + return false; +} + + +GL45SparseResourceTexture::GL45SparseResourceTexture(const std::weak_ptr& backend, const Texture& texture) : GL45VariableAllocationTexture(backend, texture) { + const GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat()); + const uvec3 dimensions = _gpuObject.getDimensions(); + auto allowedPageDimensions = getPageDimensionsForFormat(_target, texelFormat.internalFormat); + uint32_t pageDimensionsIndex = 0; + // In order to enable sparse the texture size must be an integer multiple of the page size + for (size_t i = 0; i < allowedPageDimensions.size(); ++i) { + pageDimensionsIndex = (uint32_t)i; + _pageDimensions = allowedPageDimensions[i]; + // Is this texture an integer multiple of page dimensions? + if (uvec3(0) == (dimensions % _pageDimensions)) { + qCDebug(gpugl45logging) << "Enabling sparse for texture " << _gpuObject.source().c_str(); + break; + } + } + glTextureParameteri(_id, GL_TEXTURE_SPARSE_ARB, GL_TRUE); + glTextureParameteri(_id, GL_VIRTUAL_PAGE_SIZE_INDEX_ARB, pageDimensionsIndex); + glGetTextureParameterIuiv(_id, GL_NUM_SPARSE_LEVELS_ARB, &_maxSparseLevel); + + _pageBytes = _gpuObject.getTexelFormat().getSize(); + _pageBytes *= _pageDimensions.x * _pageDimensions.y * _pageDimensions.z; + // Testing with a simple texture allocating app shows an estimated 20% GPU memory overhead for + // sparse textures as compared to non-sparse, so we acount for that here. + _pageBytes = (uint32_t)(_pageBytes * SPARSE_PAGE_SIZE_OVERHEAD_ESTIMATE); + + //allocateStorage(); + syncSampler(); +} + +GL45SparseResourceTexture::~GL45SparseResourceTexture() { + Backend::updateTextureGPUVirtualMemoryUsage(size(), 0); +} + +uvec3 GL45SparseResourceTexture::getPageCounts(const uvec3& dimensions) const { + auto result = (dimensions / _pageDimensions) + + glm::clamp(dimensions % _pageDimensions, glm::uvec3(0), glm::uvec3(1)); + return result; +} + +uint32_t GL45SparseResourceTexture::getPageCount(const uvec3& dimensions) const { + auto pageCounts = getPageCounts(dimensions); + return pageCounts.x * pageCounts.y * pageCounts.z; +} + +void GL45SparseResourceTexture::promote() { +} + +void GL45SparseResourceTexture::demote() { +} + +SparseInfo::SparseInfo(GL45Texture& texture) + : texture(texture) { +} + +void SparseInfo::maybeMakeSparse() { + // Don't enable sparse for objects with explicitly managed mip levels + if (!texture._gpuObject.isAutogenerateMips()) { + return; + } + + const uvec3 dimensions = texture._gpuObject.getDimensions(); + auto allowedPageDimensions = getPageDimensionsForFormat(texture._target, texture._internalFormat); + // In order to enable sparse the texture size must be an integer multiple of the page size + for (size_t i = 0; i < allowedPageDimensions.size(); ++i) { + pageDimensionsIndex = (uint32_t)i; + pageDimensions = allowedPageDimensions[i]; + // Is this texture an integer multiple of page dimensions? + if (uvec3(0) == (dimensions % pageDimensions)) { + qCDebug(gpugl45logging) << "Enabling sparse for texture " << texture._source.c_str(); + sparse = true; + break; + } + } + + if (sparse) { + glTextureParameteri(texture._id, GL_TEXTURE_SPARSE_ARB, GL_TRUE); + glTextureParameteri(texture._id, GL_VIRTUAL_PAGE_SIZE_INDEX_ARB, pageDimensionsIndex); + } else { + qCDebug(gpugl45logging) << "Size " << dimensions.x << " x " << dimensions.y << + " is not supported by any sparse page size for texture" << texture._source.c_str(); + } +} + + +// This can only be called after we've established our storage size +void SparseInfo::update() { + if (!sparse) { + return; + } + glGetTextureParameterIuiv(texture._id, GL_NUM_SPARSE_LEVELS_ARB, &maxSparseLevel); + + for (uint16_t mipLevel = 0; mipLevel <= maxSparseLevel; ++mipLevel) { + auto mipDimensions = texture._gpuObject.evalMipDimensions(mipLevel); + auto mipPageCount = getPageCount(mipDimensions); + maxPages += mipPageCount; + } + if (texture._target == GL_TEXTURE_CUBE_MAP) { + maxPages *= GLTexture::CUBE_NUM_FACES; + } +} + + +void SparseInfo::allocateToMip(uint16_t targetMip) { + // Not sparse, do nothing + if (!sparse) { + return; + } + + if (allocatedMip == INVALID_MIP) { + allocatedMip = maxSparseLevel + 1; + } + + // Don't try to allocate below the maximum sparse level + if (targetMip > maxSparseLevel) { + targetMip = maxSparseLevel; + } + + // Already allocated this level + if (allocatedMip <= targetMip) { + return; + } + + uint32_t maxFace = (uint32_t)(GL_TEXTURE_CUBE_MAP == texture._target ? CUBE_NUM_FACES : 1); + for (uint16_t mip = targetMip; mip < allocatedMip; ++mip) { + auto size = texture._gpuObject.evalMipDimensions(mip); + glTexturePageCommitmentEXT(texture._id, mip, 0, 0, 0, size.x, size.y, maxFace, GL_TRUE); + allocatedPages += getPageCount(size); + } + allocatedMip = targetMip; +} + +uint32_t SparseInfo::getSize() const { + return allocatedPages * pageBytes; +} +using SparseInfo = GL45Backend::GL45Texture::SparseInfo; + +void GL45Texture::updateSize() const { + if (_gpuObject.getTexelFormat().isCompressed()) { + qFatal("Compressed textures not yet supported"); + } + + if (_transferrable && _sparseInfo.sparse) { + auto size = _sparseInfo.getSize(); + Backend::updateTextureGPUSparseMemoryUsage(_size, size); + setSize(size); + } else { + setSize(_gpuObject.evalTotalSize(_mipOffset)); + } +} + +void GL45Texture::startTransfer() { + Parent::startTransfer(); + _sparseInfo.update(); + _populatedMip = _maxMip + 1; +} + +bool GL45Texture::continueTransfer() { + size_t maxFace = GL_TEXTURE_CUBE_MAP == _target ? CUBE_NUM_FACES : 1; + if (_populatedMip == _minMip) { + return false; + } + + uint16_t targetMip = _populatedMip - 1; + while (targetMip > 0 && !_gpuObject.isStoredMipFaceAvailable(targetMip)) { + --targetMip; + } + + _sparseInfo.allocateToMip(targetMip); + for (uint8_t face = 0; face < maxFace; ++face) { + auto size = _gpuObject.evalMipDimensions(targetMip); + if (_gpuObject.isStoredMipFaceAvailable(targetMip, face)) { + auto mip = _gpuObject.accessStoredMipFace(targetMip, face); + GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat(), mip->getFormat()); + if (GL_TEXTURE_2D == _target) { + glTextureSubImage2D(_id, targetMip, 0, 0, size.x, size.y, texelFormat.format, texelFormat.type, mip->readData()); + } else if (GL_TEXTURE_CUBE_MAP == _target) { + // DSA ARB does not work on AMD, so use EXT + // unless EXT is not available on the driver + if (glTextureSubImage2DEXT) { + auto target = CUBE_FACE_LAYOUT[face]; + glTextureSubImage2DEXT(_id, target, targetMip, 0, 0, size.x, size.y, texelFormat.format, texelFormat.type, mip->readData()); + } else { + glTextureSubImage3D(_id, targetMip, 0, 0, face, size.x, size.y, 1, texelFormat.format, texelFormat.type, mip->readData()); + } + } else { + Q_ASSERT(false); + } + (void)CHECK_GL_ERROR(); + break; + } + } + _populatedMip = targetMip; + return _populatedMip != _minMip; +} + +void GL45Texture::finishTransfer() { + Parent::finishTransfer(); +} + +void GL45Texture::postTransfer() { + Parent::postTransfer(); +} + +void GL45Texture::stripToMip(uint16_t newMinMip) { + if (newMinMip < _minMip) { + qCWarning(gpugl45logging) << "Cannot decrease the min mip"; + return; + } + + if (_sparseInfo.sparse && newMinMip > _sparseInfo.maxSparseLevel) { + qCWarning(gpugl45logging) << "Cannot increase the min mip into the mip tail"; + return; + } + + // If we weren't generating mips before, we need to now that we're stripping down mip levels. + if (!_gpuObject.isAutogenerateMips()) { + qCDebug(gpugl45logging) << "Force mip generation for texture"; + glGenerateTextureMipmap(_id); + } + + + uint8_t maxFace = (uint8_t)((_target == GL_TEXTURE_CUBE_MAP) ? GLTexture::CUBE_NUM_FACES : 1); + if (_sparseInfo.sparse) { + for (uint16_t mip = _minMip; mip < newMinMip; ++mip) { + auto id = _id; + auto mipDimensions = _gpuObject.evalMipDimensions(mip); + glTexturePageCommitmentEXT(id, mip, 0, 0, 0, mipDimensions.x, mipDimensions.y, maxFace, GL_FALSE); + auto deallocatedPages = _sparseInfo.getPageCount(mipDimensions) * maxFace; + assert(deallocatedPages < _sparseInfo.allocatedPages); + _sparseInfo.allocatedPages -= deallocatedPages; + } + _minMip = newMinMip; + } else { + GLuint oldId = _id; + // Find the distance between the old min mip and the new one + uint16 mipDelta = newMinMip - _minMip; + _mipOffset += mipDelta; + const_cast(_maxMip) -= mipDelta; + auto newLevels = usedMipLevels(); + + // Create and setup the new texture (allocate) + glCreateTextures(_target, 1, &const_cast(_id)); + glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, 0); + glTextureParameteri(_id, GL_TEXTURE_MAX_LEVEL, _maxMip - _minMip); + Vec3u newDimensions = _gpuObject.evalMipDimensions(_mipOffset); + glTextureStorage2D(_id, newLevels, _internalFormat, newDimensions.x, newDimensions.y); + + // Copy the contents of the old texture to the new + GLuint fbo { 0 }; + glCreateFramebuffers(1, &fbo); + glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); + for (uint16 targetMip = _minMip; targetMip <= _maxMip; ++targetMip) { + uint16 sourceMip = targetMip + mipDelta; + Vec3u mipDimensions = _gpuObject.evalMipDimensions(targetMip + _mipOffset); + for (GLenum target : getFaceTargets(_target)) { + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, oldId, sourceMip); + (void)CHECK_GL_ERROR(); + glCopyTextureSubImage2D(_id, targetMip, 0, 0, 0, 0, mipDimensions.x, mipDimensions.y); + (void)CHECK_GL_ERROR(); + } + } + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + glDeleteFramebuffers(1, &fbo); + glDeleteTextures(1, &oldId); + } + + // Re-sync the sampler to force access to the new mip level + syncSampler(); + updateSize(); +} + +bool GL45Texture::derezable() const { + if (_external) { + return false; + } + auto maxMinMip = _sparseInfo.sparse ? _sparseInfo.maxSparseLevel : _maxMip; + return _transferrable && (_targetMinMip < maxMinMip); +} + +size_t GL45Texture::getMipByteCount(uint16_t mip) const { + if (!_sparseInfo.sparse) { + return Parent::getMipByteCount(mip); + } + + auto dimensions = _gpuObject.evalMipDimensions(_targetMinMip); + return _sparseInfo.getPageCount(dimensions) * _sparseInfo.pageBytes; +} + +std::pair GL45Texture::preDerez() { + assert(!_sparseInfo.sparse || _targetMinMip < _sparseInfo.maxSparseLevel); + size_t freedMemory = getMipByteCount(_targetMinMip); + bool liveMip = _populatedMip != INVALID_MIP && _populatedMip <= _targetMinMip; + ++_targetMinMip; + return { freedMemory, liveMip }; +} + +void GL45Texture::derez() { + if (_sparseInfo.sparse) { + assert(_minMip < _sparseInfo.maxSparseLevel); + } + assert(_minMip < _maxMip); + assert(_transferrable); + stripToMip(_minMip + 1); +} + +size_t GL45Texture::getCurrentGpuSize() const { + if (!_sparseInfo.sparse) { + return Parent::getCurrentGpuSize(); + } + + return _sparseInfo.getSize(); +} + +size_t GL45Texture::getTargetGpuSize() const { + if (!_sparseInfo.sparse) { + return Parent::getTargetGpuSize(); + } + + size_t result = 0; + for (auto mip = _targetMinMip; mip <= _sparseInfo.maxSparseLevel; ++mip) { + result += (_sparseInfo.pageBytes * _sparseInfo.getPageCount(_gpuObject.evalMipDimensions(mip))); + } + + return result; +} + +GL45Texture::~GL45Texture() { + if (_sparseInfo.sparse) { + uint8_t maxFace = (uint8_t)((_target == GL_TEXTURE_CUBE_MAP) ? GLTexture::CUBE_NUM_FACES : 1); + auto maxSparseMip = std::min(_maxMip, _sparseInfo.maxSparseLevel); + for (uint16_t mipLevel = _minMip; mipLevel <= maxSparseMip; ++mipLevel) { + auto mipDimensions = _gpuObject.evalMipDimensions(mipLevel); + glTexturePageCommitmentEXT(_texture, mipLevel, 0, 0, 0, mipDimensions.x, mipDimensions.y, maxFace, GL_FALSE); + auto deallocatedPages = _sparseInfo.getPageCount(mipDimensions) * maxFace; + assert(deallocatedPages <= _sparseInfo.allocatedPages); + _sparseInfo.allocatedPages -= deallocatedPages; + } + + if (0 != _sparseInfo.allocatedPages) { + qCWarning(gpugl45logging) << "Allocated pages remaining " << _id << " " << _sparseInfo.allocatedPages; + } + Backend::decrementTextureGPUSparseCount(); + } +} +GL45Texture::GL45Texture(const std::weak_ptr& backend, const Texture& texture) + : GLTexture(backend, texture, allocate(texture)), _sparseInfo(*this), _targetMinMip(_minMip) +{ + + auto theBackend = _backend.lock(); + if (_transferrable && theBackend && theBackend->isTextureManagementSparseEnabled()) { + _sparseInfo.maybeMakeSparse(); + if (_sparseInfo.sparse) { + Backend::incrementTextureGPUSparseCount(); + } + } +} +#endif diff --git a/libraries/gpu/src/gpu/Batch.cpp b/libraries/gpu/src/gpu/Batch.cpp index c15da61800..f822da129b 100644 --- a/libraries/gpu/src/gpu/Batch.cpp +++ b/libraries/gpu/src/gpu/Batch.cpp @@ -292,15 +292,8 @@ void Batch::setUniformBuffer(uint32 slot, const BufferView& view) { setUniformBuffer(slot, view._buffer, view._offset, view._size); } - void Batch::setResourceTexture(uint32 slot, const TexturePointer& texture) { - if (texture && texture->getUsage().isExternal()) { - auto recycler = texture->getExternalRecycler(); - Q_ASSERT(recycler); - } - ADD_COMMAND(setResourceTexture); - _params.emplace_back(_textures.cache(texture)); _params.emplace_back(slot); } diff --git a/libraries/gpu/src/gpu/Framebuffer.cpp b/libraries/gpu/src/gpu/Framebuffer.cpp index e8ccfce3b2..0d3291a74d 100755 --- a/libraries/gpu/src/gpu/Framebuffer.cpp +++ b/libraries/gpu/src/gpu/Framebuffer.cpp @@ -32,7 +32,7 @@ Framebuffer* Framebuffer::create(const std::string& name) { Framebuffer* Framebuffer::create(const std::string& name, const Format& colorBufferFormat, uint16 width, uint16 height) { auto framebuffer = Framebuffer::create(name); - auto colorTexture = TexturePointer(Texture::create2D(colorBufferFormat, width, height, Sampler(Sampler::FILTER_MIN_MAG_POINT))); + auto colorTexture = TexturePointer(Texture::createRenderBuffer(colorBufferFormat, width, height, Sampler(Sampler::FILTER_MIN_MAG_POINT))); colorTexture->setSource("Framebuffer::colorTexture"); framebuffer->setRenderBuffer(0, colorTexture); @@ -43,8 +43,8 @@ Framebuffer* Framebuffer::create(const std::string& name, const Format& colorBuf Framebuffer* Framebuffer::create(const std::string& name, const Format& colorBufferFormat, const Format& depthStencilBufferFormat, uint16 width, uint16 height) { auto framebuffer = Framebuffer::create(name); - auto colorTexture = TexturePointer(Texture::create2D(colorBufferFormat, width, height, Sampler(Sampler::FILTER_MIN_MAG_POINT))); - auto depthTexture = TexturePointer(Texture::create2D(depthStencilBufferFormat, width, height, Sampler(Sampler::FILTER_MIN_MAG_POINT))); + auto colorTexture = TexturePointer(Texture::createRenderBuffer(colorBufferFormat, width, height, Sampler(Sampler::FILTER_MIN_MAG_POINT))); + auto depthTexture = TexturePointer(Texture::createRenderBuffer(depthStencilBufferFormat, width, height, Sampler(Sampler::FILTER_MIN_MAG_POINT))); framebuffer->setRenderBuffer(0, colorTexture); framebuffer->setDepthStencilBuffer(depthTexture, depthStencilBufferFormat); @@ -55,7 +55,7 @@ Framebuffer* Framebuffer::createShadowmap(uint16 width) { auto framebuffer = Framebuffer::create("Shadowmap"); auto depthFormat = Element(gpu::SCALAR, gpu::FLOAT, gpu::DEPTH); // Depth32 texel format - auto depthTexture = TexturePointer(Texture::create2D(depthFormat, width, width)); + auto depthTexture = TexturePointer(Texture::createRenderBuffer(depthFormat, width, width)); Sampler::Desc samplerDesc; samplerDesc._borderColor = glm::vec4(1.0f); samplerDesc._wrapModeU = Sampler::WRAP_BORDER; @@ -143,6 +143,8 @@ int Framebuffer::setRenderBuffer(uint32 slot, const TexturePointer& texture, uin return -1; } + Q_ASSERT(!texture || TextureUsageType::RENDERBUFFER == texture->getUsageType()); + // Check for the slot if (slot >= getMaxNumRenderBuffers()) { return -1; @@ -222,6 +224,8 @@ bool Framebuffer::setDepthStencilBuffer(const TexturePointer& texture, const For return false; } + Q_ASSERT(!texture || TextureUsageType::RENDERBUFFER == texture->getUsageType()); + // Check for the compatibility of size if (texture) { if (!validateTargetCompatibility(*texture)) { diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index 5b0c4c876a..9db3fb60c3 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -253,35 +253,42 @@ bool Texture::Storage::assignMipFaceData(uint16 level, const Element& format, Si return allocated == size; } -Texture* Texture::createExternal2D(const ExternalRecycler& recycler, const Sampler& sampler) { - Texture* tex = new Texture(); +Texture* Texture::createExternal(const ExternalRecycler& recycler, const Sampler& sampler) { + Texture* tex = new Texture(TextureUsageType::EXTERNAL); tex->_type = TEX_2D; tex->_maxMip = 0; tex->_sampler = sampler; - tex->setUsage(Usage::Builder().withExternal().withColor()); tex->setExternalRecycler(recycler); return tex; } +Texture* Texture::createRenderBuffer(const Element& texelFormat, uint16 width, uint16 height, const Sampler& sampler) { + return create(TextureUsageType::RENDERBUFFER, TEX_2D, texelFormat, width, height, 1, 1, 1, sampler); +} + Texture* Texture::create1D(const Element& texelFormat, uint16 width, const Sampler& sampler) { - return create(TEX_1D, texelFormat, width, 1, 1, 1, 1, sampler); + return create(TextureUsageType::RESOURCE, TEX_1D, texelFormat, width, 1, 1, 1, 1, sampler); } Texture* Texture::create2D(const Element& texelFormat, uint16 width, uint16 height, const Sampler& sampler) { - return create(TEX_2D, texelFormat, width, height, 1, 1, 1, sampler); + return create(TextureUsageType::RESOURCE, TEX_2D, texelFormat, width, height, 1, 1, 1, sampler); +} + +Texture* Texture::createStrict(const Element& texelFormat, uint16 width, uint16 height, const Sampler& sampler) { + return create(TextureUsageType::STRICT_RESOURCE, TEX_2D, texelFormat, width, height, 1, 1, 1, sampler); } Texture* Texture::create3D(const Element& texelFormat, uint16 width, uint16 height, uint16 depth, const Sampler& sampler) { - return create(TEX_3D, texelFormat, width, height, depth, 1, 1, sampler); + return create(TextureUsageType::RESOURCE, TEX_3D, texelFormat, width, height, depth, 1, 1, sampler); } Texture* Texture::createCube(const Element& texelFormat, uint16 width, const Sampler& sampler) { - return create(TEX_CUBE, texelFormat, width, width, 1, 1, 1, sampler); + return create(TextureUsageType::RESOURCE, TEX_CUBE, texelFormat, width, width, 1, 1, 1, sampler); } -Texture* Texture::create(Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices, const Sampler& sampler) +Texture* Texture::create(TextureUsageType usageType, Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices, const Sampler& sampler) { - Texture* tex = new Texture(); + Texture* tex = new Texture(usageType); tex->_storage.reset(new Storage()); tex->_type = type; tex->_storage->assignTexture(tex); @@ -293,16 +300,14 @@ Texture* Texture::create(Type type, const Element& texelFormat, uint16 width, ui return tex; } -Texture::Texture(): - Resource() -{ +Texture::Texture(TextureUsageType usageType) : + Resource(), _usageType(usageType) { _textureCPUCount++; } -Texture::~Texture() -{ +Texture::~Texture() { _textureCPUCount--; - if (getUsage().isExternal()) { + if (_usageType == TextureUsageType::EXTERNAL) { Texture::ExternalUpdates externalUpdates; { Lock lock(_externalMutex); diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 856bd4983d..3c6c34c68d 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -139,6 +139,13 @@ protected: Desc _desc; }; +enum class TextureUsageType { + RENDERBUFFER, // Used as attachments to a framebuffer + RESOURCE, // Resource textures, like materials... subject to memory manipulation + STRICT_RESOURCE, // Resource textures not subject to manipulation, like the normal fitting texture + EXTERNAL, +}; + class Texture : public Resource { static std::atomic _textureCPUCount; static std::atomic _textureCPUMemoryUsage; @@ -173,9 +180,9 @@ public: NORMAL, // Texture is a normal map ALPHA, // Texture has an alpha channel ALPHA_MASK, // Texture alpha channel is a Mask 0/1 - EXTERNAL, NUM_FLAGS, }; + typedef std::bitset Flags; // The key is the Flags @@ -199,7 +206,6 @@ public: Builder& withNormal() { _flags.set(NORMAL); return (*this); } Builder& withAlpha() { _flags.set(ALPHA); return (*this); } Builder& withAlphaMask() { _flags.set(ALPHA_MASK); return (*this); } - Builder& withExternal() { _flags.set(EXTERNAL); return (*this); } }; Usage(const Builder& builder) : Usage(builder._flags) {} @@ -208,8 +214,6 @@ public: bool isAlpha() const { return _flags[ALPHA]; } bool isAlphaMask() const { return _flags[ALPHA_MASK]; } - bool isExternal() const { return _flags[EXTERNAL]; } - bool operator==(const Usage& usage) { return (_flags == usage._flags); } bool operator!=(const Usage& usage) { return (_flags != usage._flags); } @@ -298,9 +302,11 @@ public: static Texture* create2D(const Element& texelFormat, uint16 width, uint16 height, const Sampler& sampler = Sampler()); static Texture* create3D(const Element& texelFormat, uint16 width, uint16 height, uint16 depth, const Sampler& sampler = Sampler()); static Texture* createCube(const Element& texelFormat, uint16 width, const Sampler& sampler = Sampler()); - static Texture* createExternal2D(const ExternalRecycler& recycler, const Sampler& sampler = Sampler()); + static Texture* createRenderBuffer(const Element& texelFormat, uint16 width, uint16 height, const Sampler& sampler = Sampler()); + static Texture* createStrict(const Element& texelFormat, uint16 width, uint16 height, const Sampler& sampler = Sampler()); + static Texture* createExternal(const ExternalRecycler& recycler, const Sampler& sampler = Sampler()); - Texture(); + Texture(TextureUsageType usageType); Texture(const Texture& buf); // deep copy of the sysmem texture Texture& operator=(const Texture& buf); // deep copy of the sysmem texture ~Texture(); @@ -325,6 +331,7 @@ public: // Size and format Type getType() const { return _type; } + TextureUsageType getUsageType() const { return _usageType; } bool isColorRenderTarget() const; bool isDepthStencilRenderTarget() const; @@ -476,6 +483,8 @@ public: ExternalUpdates getUpdates() const; protected: + const TextureUsageType _usageType; + // Should only be accessed internally or by the backend sync function mutable Mutex _externalMutex; mutable std::list _externalUpdates; @@ -513,7 +522,7 @@ protected: bool _isIrradianceValid = false; bool _defined = false; - static Texture* create(Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices, const Sampler& sampler); + static Texture* create(TextureUsageType usageType, Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices, const Sampler& sampler); Size resize(Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices); }; diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index f371207981..6a84fc960f 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -120,7 +120,7 @@ const unsigned char OPAQUE_BLACK[] = { 0x00, 0x00, 0x00, 0xFF }; const gpu::TexturePointer& TextureCache::getWhiteTexture() { if (!_whiteTexture) { - _whiteTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element::COLOR_RGBA_32, 1, 1)); + _whiteTexture = gpu::TexturePointer(gpu::Texture::createStrict(gpu::Element::COLOR_RGBA_32, 1, 1)); _whiteTexture->setSource("TextureCache::_whiteTexture"); _whiteTexture->assignStoredMip(0, _whiteTexture->getTexelFormat(), sizeof(OPAQUE_WHITE), OPAQUE_WHITE); } @@ -129,7 +129,7 @@ const gpu::TexturePointer& TextureCache::getWhiteTexture() { const gpu::TexturePointer& TextureCache::getGrayTexture() { if (!_grayTexture) { - _grayTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element::COLOR_RGBA_32, 1, 1)); + _grayTexture = gpu::TexturePointer(gpu::Texture::createStrict(gpu::Element::COLOR_RGBA_32, 1, 1)); _grayTexture->setSource("TextureCache::_grayTexture"); _grayTexture->assignStoredMip(0, _grayTexture->getTexelFormat(), sizeof(OPAQUE_GRAY), OPAQUE_GRAY); } @@ -138,7 +138,7 @@ const gpu::TexturePointer& TextureCache::getGrayTexture() { const gpu::TexturePointer& TextureCache::getBlueTexture() { if (!_blueTexture) { - _blueTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element::COLOR_RGBA_32, 1, 1)); + _blueTexture = gpu::TexturePointer(gpu::Texture::createStrict(gpu::Element::COLOR_RGBA_32, 1, 1)); _blueTexture->setSource("TextureCache::_blueTexture"); _blueTexture->assignStoredMip(0, _blueTexture->getTexelFormat(), sizeof(OPAQUE_BLUE), OPAQUE_BLUE); } @@ -147,7 +147,7 @@ const gpu::TexturePointer& TextureCache::getBlueTexture() { const gpu::TexturePointer& TextureCache::getBlackTexture() { if (!_blackTexture) { - _blackTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element::COLOR_RGBA_32, 1, 1)); + _blackTexture = gpu::TexturePointer(gpu::Texture::createStrict(gpu::Element::COLOR_RGBA_32, 1, 1)); _blackTexture->setSource("TextureCache::_blackTexture"); _blackTexture->assignStoredMip(0, _blackTexture->getTexelFormat(), sizeof(OPAQUE_BLACK), OPAQUE_BLACK); } @@ -157,7 +157,7 @@ const gpu::TexturePointer& TextureCache::getBlackTexture() { const gpu::TexturePointer& TextureCache::getNormalFittingTexture() { if (!_normalFittingTexture) { - _normalFittingTexture = getImageTexture(PathUtils::resourcesPath() + "images/normalFittingScale.dds"); + _normalFittingTexture = getImageTexture(PathUtils::resourcesPath() + "images/normalFittingScale.dds", NetworkTexture::STRICT_TEXTURE); } return _normalFittingTexture; } @@ -227,11 +227,16 @@ NetworkTexture::TextureLoaderFunc getTextureLoaderForType(NetworkTexture::Type t return model::TextureUsage::createMetallicTextureFromImage; break; } + case Type::STRICT_TEXTURE: { + return model::TextureUsage::createStrict2DTextureFromImage; + break; + } case Type::CUSTOM_TEXTURE: { Q_ASSERT(false); return NetworkTexture::TextureLoaderFunc(); break; } + case Type::DEFAULT_TEXTURE: default: { return model::TextureUsage::create2DTextureFromImage; diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h index cb509490c6..749b5a2ebb 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.h +++ b/libraries/model-networking/src/model-networking/TextureCache.h @@ -43,6 +43,7 @@ class NetworkTexture : public Resource, public Texture { public: enum Type { DEFAULT_TEXTURE, + STRICT_TEXTURE, ALBEDO_TEXTURE, NORMAL_TEXTURE, BUMP_TEXTURE, diff --git a/libraries/model/src/model/TextureMap.cpp b/libraries/model/src/model/TextureMap.cpp index d1fbaf767a..6a9446f2ef 100755 --- a/libraries/model/src/model/TextureMap.cpp +++ b/libraries/model/src/model/TextureMap.cpp @@ -235,7 +235,7 @@ void generateFaceMips(gpu::Texture* texture, QImage& image, gpu::Element formatM #endif } -gpu::Texture* TextureUsage::process2DTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName, bool isLinear, bool doCompress, bool generateMips) { +gpu::Texture* TextureUsage::process2DTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName, bool isLinear, bool doCompress, bool generateMips, bool isStrict) { PROFILE_RANGE(resource_parse, "process2DTextureColorFromImage"); bool validAlpha = false; bool alphaAsMask = true; @@ -248,7 +248,11 @@ gpu::Texture* TextureUsage::process2DTextureColorFromImage(const QImage& srcImag gpu::Element formatMip; defineColorTexelFormats(formatGPU, formatMip, image, isLinear, doCompress); - theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); + if (isStrict) { + theTexture = (gpu::Texture::createStrict(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); + } else { + theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); + } theTexture->setSource(srcImageName); auto usage = gpu::Texture::Usage::Builder().withColor(); if (validAlpha) { @@ -269,11 +273,14 @@ gpu::Texture* TextureUsage::process2DTextureColorFromImage(const QImage& srcImag return theTexture; } +gpu::Texture* TextureUsage::createStrict2DTextureFromImage(const QImage& srcImage, const std::string& srcImageName) { + return process2DTextureColorFromImage(srcImage, srcImageName, false, false, true, true); +} + gpu::Texture* TextureUsage::create2DTextureFromImage(const QImage& srcImage, const std::string& srcImageName) { return process2DTextureColorFromImage(srcImage, srcImageName, false, false, true); } - gpu::Texture* TextureUsage::createAlbedoTextureFromImage(const QImage& srcImage, const std::string& srcImageName) { return process2DTextureColorFromImage(srcImage, srcImageName, false, true, true); } diff --git a/libraries/model/src/model/TextureMap.h b/libraries/model/src/model/TextureMap.h index 220ee57a97..a4bb861502 100755 --- a/libraries/model/src/model/TextureMap.h +++ b/libraries/model/src/model/TextureMap.h @@ -32,6 +32,7 @@ public: int _environmentUsage = 0; static gpu::Texture* create2DTextureFromImage(const QImage& image, const std::string& srcImageName); + static gpu::Texture* createStrict2DTextureFromImage(const QImage& image, const std::string& srcImageName); static gpu::Texture* createAlbedoTextureFromImage(const QImage& image, const std::string& srcImageName); static gpu::Texture* createEmissiveTextureFromImage(const QImage& image, const std::string& srcImageName); static gpu::Texture* createNormalTextureFromNormalImage(const QImage& image, const std::string& srcImageName); @@ -47,7 +48,7 @@ public: static const QImage process2DImageColor(const QImage& srcImage, bool& validAlpha, bool& alphaAsMask); static void defineColorTexelFormats(gpu::Element& formatGPU, gpu::Element& formatMip, const QImage& srcImage, bool isLinear, bool doCompress); - static gpu::Texture* process2DTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName, bool isLinear, bool doCompress, bool generateMips); + static gpu::Texture* process2DTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName, bool isLinear, bool doCompress, bool generateMips, bool isStrict = false); static gpu::Texture* processCubeTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName, bool isLinear, bool doCompress, bool generateMips, bool generateIrradiance); }; diff --git a/libraries/render-utils/src/AntialiasingEffect.cpp b/libraries/render-utils/src/AntialiasingEffect.cpp index 2941197e6d..f95d45de04 100644 --- a/libraries/render-utils/src/AntialiasingEffect.cpp +++ b/libraries/render-utils/src/AntialiasingEffect.cpp @@ -52,7 +52,7 @@ const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline() { _antialiasingBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("antialiasing")); auto format = gpu::Element::COLOR_SRGBA_32; // DependencyManager::get()->getLightingTexture()->getTexelFormat(); auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT); - _antialiasingTexture = gpu::TexturePointer(gpu::Texture::create2D(format, width, height, defaultSampler)); + _antialiasingTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(format, width, height, defaultSampler)); _antialiasingBuffer->setRenderBuffer(0, _antialiasingTexture); } diff --git a/libraries/render-utils/src/DeferredFramebuffer.cpp b/libraries/render-utils/src/DeferredFramebuffer.cpp index e8783e0e0d..40c22beba4 100644 --- a/libraries/render-utils/src/DeferredFramebuffer.cpp +++ b/libraries/render-utils/src/DeferredFramebuffer.cpp @@ -53,9 +53,9 @@ void DeferredFramebuffer::allocate() { auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT); - _deferredColorTexture = gpu::TexturePointer(gpu::Texture::create2D(colorFormat, width, height, defaultSampler)); - _deferredNormalTexture = gpu::TexturePointer(gpu::Texture::create2D(linearFormat, width, height, defaultSampler)); - _deferredSpecularTexture = gpu::TexturePointer(gpu::Texture::create2D(colorFormat, width, height, defaultSampler)); + _deferredColorTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(colorFormat, width, height, defaultSampler)); + _deferredNormalTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(linearFormat, width, height, defaultSampler)); + _deferredSpecularTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(colorFormat, width, height, defaultSampler)); _deferredFramebuffer->setRenderBuffer(0, _deferredColorTexture); _deferredFramebuffer->setRenderBuffer(1, _deferredNormalTexture); @@ -65,7 +65,7 @@ void DeferredFramebuffer::allocate() { auto depthFormat = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::DEPTH_STENCIL); // Depth24_Stencil8 texel format if (!_primaryDepthTexture) { - _primaryDepthTexture = gpu::TexturePointer(gpu::Texture::create2D(depthFormat, width, height, defaultSampler)); + _primaryDepthTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(depthFormat, width, height, defaultSampler)); } _deferredFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, depthFormat); @@ -75,7 +75,7 @@ void DeferredFramebuffer::allocate() { auto smoothSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR); - _lightingTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::R11G11B10), width, height, defaultSampler)); + _lightingTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::R11G11B10), width, height, defaultSampler)); _lightingFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("lighting")); _lightingFramebuffer->setRenderBuffer(0, _lightingTexture); _lightingFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, depthFormat); diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index 6f1152ac16..ce340583ee 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -496,14 +496,14 @@ void PreparePrimaryFramebuffer::run(const SceneContextPointer& sceneContext, con auto colorFormat = gpu::Element::COLOR_SRGBA_32; auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT); - auto primaryColorTexture = gpu::TexturePointer(gpu::Texture::create2D(colorFormat, frameSize.x, frameSize.y, defaultSampler)); + auto primaryColorTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(colorFormat, frameSize.x, frameSize.y, defaultSampler)); _primaryFramebuffer->setRenderBuffer(0, primaryColorTexture); auto depthFormat = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::DEPTH_STENCIL); // Depth24_Stencil8 texel format - auto primaryDepthTexture = gpu::TexturePointer(gpu::Texture::create2D(depthFormat, frameSize.x, frameSize.y, defaultSampler)); + auto primaryDepthTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(depthFormat, frameSize.x, frameSize.y, defaultSampler)); _primaryFramebuffer->setDepthStencilBuffer(primaryDepthTexture, depthFormat); } diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index 55a9c8b9e4..d1a7080eca 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -190,7 +190,7 @@ RenderDeferredTask::RenderDeferredTask(RenderFetchCullSortTask::Output items) { { // Grab a texture map representing the different status icons and assign that to the drawStatsuJob auto iconMapPath = PathUtils::resourcesPath() + "icons/statusIconAtlas.svg"; - auto statusIconMap = DependencyManager::get()->getImageTexture(iconMapPath); + auto statusIconMap = DependencyManager::get()->getImageTexture(iconMapPath, NetworkTexture::STRICT_TEXTURE); addJob("DrawStatus", opaques, DrawStatus(statusIconMap)); } } diff --git a/libraries/render-utils/src/SubsurfaceScattering.cpp b/libraries/render-utils/src/SubsurfaceScattering.cpp index 188381b822..25a01bff1b 100644 --- a/libraries/render-utils/src/SubsurfaceScattering.cpp +++ b/libraries/render-utils/src/SubsurfaceScattering.cpp @@ -414,7 +414,7 @@ gpu::TexturePointer SubsurfaceScatteringResource::generateScatteringProfile(Rend const int PROFILE_RESOLUTION = 512; // const auto pixelFormat = gpu::Element::COLOR_SRGBA_32; const auto pixelFormat = gpu::Element::COLOR_R11G11B10; - auto profileMap = gpu::TexturePointer(gpu::Texture::create2D(pixelFormat, PROFILE_RESOLUTION, 1, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP))); + auto profileMap = gpu::TexturePointer(gpu::Texture::createRenderBuffer(pixelFormat, PROFILE_RESOLUTION, 1, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP))); profileMap->setSource("Generated Scattering Profile"); diffuseProfileGPU(profileMap, args); return profileMap; @@ -425,7 +425,7 @@ gpu::TexturePointer SubsurfaceScatteringResource::generatePreIntegratedScatterin const int TABLE_RESOLUTION = 512; // const auto pixelFormat = gpu::Element::COLOR_SRGBA_32; const auto pixelFormat = gpu::Element::COLOR_R11G11B10; - auto scatteringLUT = gpu::TexturePointer(gpu::Texture::create2D(pixelFormat, TABLE_RESOLUTION, TABLE_RESOLUTION, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP))); + auto scatteringLUT = gpu::TexturePointer(gpu::Texture::createRenderBuffer(pixelFormat, TABLE_RESOLUTION, TABLE_RESOLUTION, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP))); //diffuseScatter(scatteringLUT); scatteringLUT->setSource("Generated pre-integrated scattering"); diffuseScatterGPU(profile, scatteringLUT, args); @@ -434,7 +434,7 @@ gpu::TexturePointer SubsurfaceScatteringResource::generatePreIntegratedScatterin gpu::TexturePointer SubsurfaceScatteringResource::generateScatteringSpecularBeckmann(RenderArgs* args) { const int SPECULAR_RESOLUTION = 256; - auto beckmannMap = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element::COLOR_RGBA_32 /*gpu::Element(gpu::SCALAR, gpu::HALF, gpu::RGB)*/, SPECULAR_RESOLUTION, SPECULAR_RESOLUTION, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP))); + auto beckmannMap = gpu::TexturePointer(gpu::Texture::createRenderBuffer(gpu::Element::COLOR_RGBA_32 /*gpu::Element(gpu::SCALAR, gpu::HALF, gpu::RGB)*/, SPECULAR_RESOLUTION, SPECULAR_RESOLUTION, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP))); beckmannMap->setSource("Generated beckmannMap"); computeSpecularBeckmannGPU(beckmannMap, args); return beckmannMap; diff --git a/libraries/render-utils/src/SurfaceGeometryPass.cpp b/libraries/render-utils/src/SurfaceGeometryPass.cpp index f0ac56ac26..3a23e70664 100644 --- a/libraries/render-utils/src/SurfaceGeometryPass.cpp +++ b/libraries/render-utils/src/SurfaceGeometryPass.cpp @@ -72,18 +72,18 @@ void LinearDepthFramebuffer::allocate() { auto height = _frameSize.y; // For Linear Depth: - _linearDepthTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::RGB), width, height, + _linearDepthTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::RGB), width, height, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT))); _linearDepthFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("linearDepth")); _linearDepthFramebuffer->setRenderBuffer(0, _linearDepthTexture); _linearDepthFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, _primaryDepthTexture->getTexelFormat()); // For Downsampling: - _halfLinearDepthTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::RGB), _halfFrameSize.x, _halfFrameSize.y, + _halfLinearDepthTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::RGB), _halfFrameSize.x, _halfFrameSize.y, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT))); _halfLinearDepthTexture->autoGenerateMips(5); - _halfNormalTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element(gpu::VEC3, gpu::NUINT8, gpu::RGB), _halfFrameSize.x, _halfFrameSize.y, + _halfNormalTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(gpu::Element(gpu::VEC3, gpu::NUINT8, gpu::RGB), _halfFrameSize.x, _halfFrameSize.y, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT))); _downsampleFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("halfLinearDepth")); @@ -304,15 +304,15 @@ void SurfaceGeometryFramebuffer::allocate() { auto width = _frameSize.x; auto height = _frameSize.y; - _curvatureTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element::COLOR_RGBA_32, width, height, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT))); + _curvatureTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(gpu::Element::COLOR_RGBA_32, width, height, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT))); _curvatureFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("surfaceGeometry::curvature")); _curvatureFramebuffer->setRenderBuffer(0, _curvatureTexture); - _lowCurvatureTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element::COLOR_RGBA_32, width, height, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT))); + _lowCurvatureTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(gpu::Element::COLOR_RGBA_32, width, height, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT))); _lowCurvatureFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("surfaceGeometry::lowCurvature")); _lowCurvatureFramebuffer->setRenderBuffer(0, _lowCurvatureTexture); - _blurringTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element::COLOR_RGBA_32, width, height, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT))); + _blurringTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(gpu::Element::COLOR_RGBA_32, width, height, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT))); _blurringFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("surfaceGeometry::blurring")); _blurringFramebuffer->setRenderBuffer(0, _blurringTexture); } diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp index 09f3e6dc8c..b759a06aee 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp @@ -255,7 +255,7 @@ void OculusLegacyDisplayPlugin::hmdPresent() { memset(eyePoses, 0, sizeof(ovrPosef) * 2); eyePoses[0].Orientation = eyePoses[1].Orientation = ovrRotation; - GLint texture = getGLBackend()->getTextureID(_compositeFramebuffer->getRenderBuffer(0), false); + GLint texture = getGLBackend()->getTextureID(_compositeFramebuffer->getRenderBuffer(0)); auto sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); glFlush(); if (_hmdWindow->makeCurrent()) { diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index 42f3ece9cd..f9c69da4d6 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -494,9 +494,9 @@ void OpenVrDisplayPlugin::customizeContext() { _compositeInfos[0].texture = _compositeFramebuffer->getRenderBuffer(0); for (size_t i = 0; i < COMPOSITING_BUFFER_SIZE; ++i) { if (0 != i) { - _compositeInfos[i].texture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element::COLOR_RGBA_32, _renderTargetSize.x, _renderTargetSize.y, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT))); + _compositeInfos[i].texture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(gpu::Element::COLOR_RGBA_32, _renderTargetSize.x, _renderTargetSize.y, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT))); } - _compositeInfos[i].textureID = getGLBackend()->getTextureID(_compositeInfos[i].texture, false); + _compositeInfos[i].textureID = getGLBackend()->getTextureID(_compositeInfos[i].texture); } _submitThread->_canvas = _submitCanvas; _submitThread->start(QThread::HighPriority); @@ -624,7 +624,7 @@ void OpenVrDisplayPlugin::compositeLayers() { glFlush(); if (!newComposite.textureID) { - newComposite.textureID = getGLBackend()->getTextureID(newComposite.texture, false); + newComposite.textureID = getGLBackend()->getTextureID(newComposite.texture); } withPresentThreadLock([&] { _submitThread->update(newComposite); @@ -638,7 +638,7 @@ void OpenVrDisplayPlugin::hmdPresent() { if (_threadedSubmit) { _submitThread->waitForPresent(); } else { - GLuint glTexId = getGLBackend()->getTextureID(_compositeFramebuffer->getRenderBuffer(0), false); + GLuint glTexId = getGLBackend()->getTextureID(_compositeFramebuffer->getRenderBuffer(0)); vr::Texture_t vrTexture { (void*)glTexId, vr::API_OpenGL, vr::ColorSpace_Auto }; vr::VRCompositor()->Submit(vr::Eye_Left, &vrTexture, &OPENVR_TEXTURE_BOUNDS_LEFT); vr::VRCompositor()->Submit(vr::Eye_Right, &vrTexture, &OPENVR_TEXTURE_BOUNDS_RIGHT); diff --git a/tests/render-perf/src/main.cpp b/tests/render-perf/src/main.cpp index 7e9d2c426f..522fe79b10 100644 --- a/tests/render-perf/src/main.cpp +++ b/tests/render-perf/src/main.cpp @@ -642,7 +642,6 @@ protected: gpu::Texture::setAllowedGPUMemoryUsage(MB_TO_BYTES(64)); return; - default: break; } diff --git a/tests/render-texture-load/src/main.cpp b/tests/render-texture-load/src/main.cpp index 09a420f018..d924f76232 100644 --- a/tests/render-texture-load/src/main.cpp +++ b/tests/render-texture-load/src/main.cpp @@ -48,6 +48,7 @@ #include #include +#include #include #include #include From 1238edd0d7b8dcf8bfd34f02f3adf494f0578ced Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 9 Feb 2017 10:20:24 -0800 Subject: [PATCH 06/28] Add incremental transfers for large mips --- libraries/gpu-gl/src/gpu/gl45/GL45Backend.h | 3 +- .../src/gpu/gl45/GL45BackendTexture.cpp | 47 ++++++++------ .../gpu/gl45/GL45BackendVariableTexture.cpp | 63 ++++++++++++++++--- 3 files changed, 82 insertions(+), 31 deletions(-) diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h index e44f71e3e5..f811b26d94 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h +++ b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h @@ -38,7 +38,8 @@ public: protected: GL45Texture(const std::weak_ptr& backend, const Texture& texture); void generateMips() const override; - void copyMipFromTexture(uint16_t sourceMip, uint16_t targetMip) const; + void copyMipFaceFromTexture(uint16_t sourceMip, uint16_t targetMip, uint8_t face) const; + void copyMipFaceLinesFromTexture(uint16_t sourceMip, uint16_t targetMip, uint8_t face, uint32_t lineOffset, uint32_t lines, size_t dataOffset) const; virtual void syncSampler() const; }; diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp index c46c07ee37..21c211ca20 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp @@ -120,32 +120,36 @@ void GL45Texture::generateMips() const { (void)CHECK_GL_ERROR(); } -void GL45Texture::copyMipFromTexture(uint16_t sourceMip, uint16_t targetMip) const { +void GL45Texture::copyMipFaceLinesFromTexture(uint16_t sourceMip, uint16_t targetMip, uint8_t face, uint32_t lineOffset, uint32_t lines, size_t dataOffset) const { const auto& texture = _gpuObject; if (!texture.isStoredMipFaceAvailable(sourceMip)) { return; } - size_t maxFace = GLTexture::getFaceCount(_target); - for (uint8_t face = 0; face < maxFace; ++face) { - auto size = texture.evalMipDimensions(sourceMip); - auto mipData = texture.accessStoredMipFace(sourceMip, face); - GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(texture.getTexelFormat(), mipData->getFormat()); - if (GL_TEXTURE_2D == _target) { - glTextureSubImage2D(_id, targetMip, 0, 0, size.x, size.y, texelFormat.format, texelFormat.type, mipData->readData()); - } else if (GL_TEXTURE_CUBE_MAP == _target) { - // DSA ARB does not work on AMD, so use EXT - // unless EXT is not available on the driver - if (glTextureSubImage2DEXT) { - auto target = GLTexture::CUBE_FACE_LAYOUT[face]; - glTextureSubImage2DEXT(_id, target, targetMip, 0, 0, size.x, size.y, texelFormat.format, texelFormat.type, mipData->readData()); - } else { - glTextureSubImage3D(_id, targetMip, 0, 0, face, size.x, size.y, 1, texelFormat.format, texelFormat.type, mipData->readData()); - } + auto mipDimensions = texture.evalMipDimensions(sourceMip); + glm::uvec3 size = { mipDimensions.x, lines, mipDimensions.z }; + auto mipData = texture.accessStoredMipFace(sourceMip, face); + auto sourcePointer = mipData->readData() + dataOffset; + GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(texture.getTexelFormat(), mipData->getFormat()); + if (GL_TEXTURE_2D == _target) { + glTextureSubImage2D(_id, targetMip, 0, lineOffset, size.x, size.y, texelFormat.format, texelFormat.type, sourcePointer); + } else if (GL_TEXTURE_CUBE_MAP == _target) { + // DSA ARB does not work on AMD, so use EXT + // unless EXT is not available on the driver + if (glTextureSubImage2DEXT) { + auto target = GLTexture::CUBE_FACE_LAYOUT[face]; + glTextureSubImage2DEXT(_id, target, targetMip, 0, lineOffset, size.x, size.y, texelFormat.format, texelFormat.type, sourcePointer); } else { - Q_ASSERT(false); + glTextureSubImage3D(_id, targetMip, 0, lineOffset, face, size.x, size.y, 1, texelFormat.format, texelFormat.type, sourcePointer); } - (void)CHECK_GL_ERROR(); + } else { + Q_ASSERT(false); } + (void)CHECK_GL_ERROR(); +} + +void GL45Texture::copyMipFaceFromTexture(uint16_t sourceMip, uint16_t targetMip, uint8_t face) const { + auto size = _gpuObject.evalMipDimensions(sourceMip); + copyMipFaceLinesFromTexture(sourceMip, targetMip, face, 0, size.y, 0); } void GL45Texture::syncSampler() const { @@ -221,7 +225,10 @@ GL45StrictResourceTexture::GL45StrictResourceTexture(const std::weak_ptr= _allocatedMip; --mip) { - // FIXME break down the transfers into chunks so that no single transfer is - // consuming more than X bandwidth - _pendingTransfers.push([mip, this] { - Q_ASSERT(mip >= _allocatedMip); - // FIXME modify the copy mechanism to be incremental - copyMipFromTexture(mip, mip - _allocatedMip); - _populatedMip = mip; + static const uvec3 MAX_TRANSFER_DIMENSIONS { 512, 512, 1 }; + static const size_t MAX_TRANSFER_SIZE = MAX_TRANSFER_DIMENSIONS.x * MAX_TRANSFER_DIMENSIONS.y * 4; + const uint8_t maxFace = GLTexture::getFaceCount(_target); + + uint16_t sourceMip = _populatedMip; + do { + --sourceMip; + auto targetMip = sourceMip - _allocatedMip; + auto mipDimensions = _gpuObject.evalMipDimensions(sourceMip); + for (uint8_t face = 0; face < maxFace; ++face) { + if (!_gpuObject.isStoredMipFaceAvailable(sourceMip, face)) { + continue; + } + + // If the mip is less than the max transfer size, then just do it in one transfer + if (glm::all(glm::lessThanEqual(mipDimensions, MAX_TRANSFER_DIMENSIONS))) { + // Can the mip be transferred in one go + _pendingTransfers.push([=] { + Q_ASSERT(sourceMip >= _allocatedMip); + // FIXME modify the copy mechanism to be incremental + copyMipFaceFromTexture(sourceMip, targetMip, face); + }); + continue; + } + + // break down the transfers into chunks so that no single transfer is + // consuming more than X bandwidth + auto mipData = _gpuObject.accessStoredMipFace(sourceMip, face); + const auto lines = mipDimensions.y; + auto bytesPerLine = (uint32_t)mipData->getSize() / lines; + Q_ASSERT(0 == (mipData->getSize() % lines)); + auto linesPerTransfer = MAX_TRANSFER_SIZE / bytesPerLine; + size_t offset = 0; + uint32_t lineOffset = 0; + while (lineOffset < lines) { + uint32_t linesToCopy = std::min(lines - lineOffset, linesPerTransfer); + uvec3 size { mipDimensions.x, linesToCopy, 1 }; + _pendingTransfers.push([=] { + copyMipFaceLinesFromTexture(sourceMip, targetMip, face, lineOffset, linesToCopy, offset); + }); + lineOffset += linesToCopy; + offset += (linesToCopy * bytesPerLine); + } + } + + // queue up the sampler and populated mip change for after the transfer has completed + _pendingTransfers.push([=] { + _populatedMip = targetMip; syncSampler(); }); - } + } while (sourceMip != _allocatedMip); } // Sparsely allocated, managed size resource textures From fe5c511eeb310f5ef7181a0cf61353f27cff1b59 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 9 Feb 2017 12:09:59 -0800 Subject: [PATCH 07/28] Fix texture count --- libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp index a6ab13c258..67ab1b5bb5 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp @@ -385,6 +385,7 @@ void GL45ResourceTexture::promote() { uint32_t oldSize = _size; // create new texture const_cast(_id) = allocate(_gpuObject); + incrementTextureGPUCount(); uint16_t oldAllocatedMip = _allocatedMip; // allocate storage for new level allocateStorage(_allocatedMip - std::min(_allocatedMip, 2)); @@ -418,6 +419,7 @@ void GL45ResourceTexture::demote() { auto oldId = _id; auto oldSize = _size; const_cast(_id) = allocate(_gpuObject); + incrementTextureGPUCount(); allocateStorage(_allocatedMip + 1); _populatedMip = std::max(_populatedMip, _allocatedMip); uint16_t mips = _gpuObject.evalNumMips(); @@ -482,7 +484,7 @@ void GL45ResourceTexture::populateTransferQueue() { const auto lines = mipDimensions.y; auto bytesPerLine = (uint32_t)mipData->getSize() / lines; Q_ASSERT(0 == (mipData->getSize() % lines)); - auto linesPerTransfer = MAX_TRANSFER_SIZE / bytesPerLine; + uint32_t linesPerTransfer = (uint32_t)(MAX_TRANSFER_SIZE / bytesPerLine); size_t offset = 0; uint32_t lineOffset = 0; while (lineOffset < lines) { From 0d89b3a922c86c9f3598729a4526a1a508020b19 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 9 Feb 2017 15:29:59 -0800 Subject: [PATCH 08/28] Better sampler handling --- libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp | 10 ++-------- .../gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp | 6 ------ 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp index 21c211ca20..23ab9b203c 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp @@ -171,14 +171,8 @@ void GL45Texture::syncSampler() const { glTextureParameteri(_id, GL_TEXTURE_WRAP_R, WRAP_MODES[sampler.getWrapModeW()]); glTextureParameterf(_id, GL_TEXTURE_MAX_ANISOTROPY_EXT, sampler.getMaxAnisotropy()); glTextureParameterfv(_id, GL_TEXTURE_BORDER_COLOR, (const float*)&sampler.getBorderColor()); - -#if 0 - // FIXME account for mip offsets here - auto baseMip = std::max(sampler.getMipOffset(), _minMip); - glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, baseMip); - glTextureParameterf(_id, GL_TEXTURE_MIN_LOD, (float)sampler.getMinMip()); - glTextureParameterf(_id, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.f : sampler.getMaxMip() - _mipOffset)); -#endif + glTextureParameterf(_id, GL_TEXTURE_MIN_LOD, sampler.getMinMip()); + glTextureParameterf(_id, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.f : sampler.getMaxMip())); } using GL45FixedAllocationTexture = GL45Backend::GL45FixedAllocationTexture; diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp index 67ab1b5bb5..b6c72aab77 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp @@ -370,13 +370,7 @@ void GL45ResourceTexture::copyMipsFromTexture() { void GL45ResourceTexture::syncSampler() const { Parent::syncSampler(); - const Sampler& sampler = _gpuObject.getSampler(); - uint16_t maxMip = _gpuObject.evalNumMips() - _allocatedMip; - auto minMip = std::max(sampler.getMipOffset(), sampler.getMinMip()); - minMip = std::min(minMip, maxMip); glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, _populatedMip - _allocatedMip); - glTextureParameterf(_id, GL_TEXTURE_MIN_LOD, (float)minMip); - glTextureParameterf(_id, GL_TEXTURE_MAX_LOD, (float)maxMip); } void GL45ResourceTexture::promote() { From 283ff01038e60e61f3a03b03c05cb6840403da5c Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 9 Feb 2017 18:29:20 -0800 Subject: [PATCH 09/28] Remove duplicate code, polish --- .../src/gpu/gl41/GL41BackendTexture.cpp | 2 +- libraries/gpu-gl/src/gpu/gl45/GL45Backend.h | 12 + .../src/gpu/gl45/GL45BackendTexture.cpp | 1 + .../gpu/gl45/GL45BackendVariableTexture.cpp | 229 ++++++------------ 4 files changed, 82 insertions(+), 162 deletions(-) diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp index 65c4dda202..cf389c83f3 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp @@ -69,7 +69,7 @@ GLTexture* GL41Backend::syncGPUObject(const TexturePointer& texturePointer) { GL41Texture::GL41Texture(const std::weak_ptr& backend, const Texture& texture) : GLTexture(backend, texture, allocate()), _storageStamp { texture.getStamp() }, _size(texture.evalTotalSize()) { - + incrementTextureGPUCount(); withPreservedTexture([&] { GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat()); const Sampler& sampler = _gpuObject.getSampler(); diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h index f811b26d94..e6a61e9498 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h +++ b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h @@ -94,15 +94,27 @@ public: Undersubscribed, }; + using QueuePair = std::pair; + class QueuePairLess { + public: + bool operator()(const QueuePair& a, const QueuePair& b) { + return a.second < b.second; + } + }; + using WorkQueue = std::priority_queue, QueuePairLess>; + protected: static std::atomic _memoryPressureStateStale; static MemoryPressureState _memoryPressureState; static std::list _memoryManagedTextures; + static WorkQueue _workQueue; static const uvec3 INITIAL_MIP_TRANSFER_DIMENSIONS; + static void updateMemoryPressure(); static void processWorkQueues(); static void addMemoryManagedTexture(const TexturePointer& texturePointer); + static void addToWorkQueue(const TexturePointer& texture); static void manageMemory(); diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp index 23ab9b203c..87eb5228a4 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp @@ -107,6 +107,7 @@ using GL45Texture = GL45Backend::GL45Texture; GL45Texture::GL45Texture(const std::weak_ptr& backend, const Texture& texture) : GLTexture(backend, texture, allocate(texture)) { + incrementTextureGPUCount(); } GLuint GL45Texture::allocate(const Texture& texture) { diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp index b6c72aab77..0f14c9cc43 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp @@ -30,11 +30,13 @@ using namespace gpu::gl45; // Variable sized textures using GL45VariableAllocationTexture = GL45Backend::GL45VariableAllocationTexture; using MemoryPressureState = GL45VariableAllocationTexture::MemoryPressureState; +using WorkQueue = GL45VariableAllocationTexture::WorkQueue; std::list GL45VariableAllocationTexture::_memoryManagedTextures; MemoryPressureState GL45VariableAllocationTexture::_memoryPressureState = MemoryPressureState::Idle; std::atomic GL45VariableAllocationTexture::_memoryPressureStateStale { false }; const uvec3 GL45VariableAllocationTexture::INITIAL_MIP_TRANSFER_DIMENSIONS { 64, 64, 1 }; +WorkQueue GL45VariableAllocationTexture::_workQueue; #define OVERSUBSCRIBED_PRESSURE_VALUE 0.95f #define UNDERSUBSCRIBED_PRESSURE_VALUE 0.85f @@ -42,45 +44,29 @@ const uvec3 GL45VariableAllocationTexture::INITIAL_MIP_TRANSFER_DIMENSIONS { 64, static const size_t DEFAULT_ALLOWED_TEXTURE_MEMORY = MB_TO_BYTES(DEFAULT_ALLOWED_TEXTURE_MEMORY_MB); -using QueuePair = std::pair; -class QueuePairLess { -public: - bool operator()(const QueuePair& a, const QueuePair& b) { - return a.second < b.second; - } -}; -class QueuePairGreater { -public: - bool operator()(const QueuePair& a, const QueuePair& b) { - return a.second > b.second; - } -}; -using DemoteQueue = std::priority_queue, QueuePairLess>; -using PromoteQueue = std::priority_queue, QueuePairGreater>; -using TransferQueue = std::queue; -static DemoteQueue demoteQueue; -static PromoteQueue promoteQueue; -static TransferQueue transferQueue; - void GL45VariableAllocationTexture::addMemoryManagedTexture(const TexturePointer& texturePointer) { _memoryManagedTextures.push_back(texturePointer); + addToWorkQueue(texturePointer); +} + +void GL45VariableAllocationTexture::addToWorkQueue(const TexturePointer& texturePointer) { GL45VariableAllocationTexture* object = Backend::getGPUObject(*texturePointer); switch (_memoryPressureState) { case MemoryPressureState::Oversubscribed: if (object->canDemote()) { - demoteQueue.push({ texturePointer, object->size() }); + _workQueue.push({ texturePointer, (float) object->size() }); } break; case MemoryPressureState::Undersubscribed: if (object->canPromote()) { - promoteQueue.push({ texturePointer, object->size() }); + _workQueue.push({ texturePointer, 1.0f / (float)object->size() }); } break; case MemoryPressureState::Transfer: if (object->hasPendingTransfers()) { - transferQueue.push( texturePointer ); + _workQueue.push({ texturePointer, 1.0f / (float)object->_gpuObject.evalMipSize(object->_populatedMip) }); } break; @@ -94,23 +80,32 @@ void GL45VariableAllocationTexture::addMemoryManagedTexture(const TexturePointer void GL45VariableAllocationTexture::updateMemoryPressure() { static size_t lastAllowedMemoryAllocation = gpu::Texture::getAllowedGPUMemoryUsage(); + size_t allowedMemoryAllocation = gpu::Texture::getAllowedGPUMemoryUsage(); + if (0 == allowedMemoryAllocation) { + allowedMemoryAllocation = DEFAULT_ALLOWED_TEXTURE_MEMORY; + } + + // If the user explicitly changed the allowed memory usage, we need to mark ourselves stale + // so that we react if (allowedMemoryAllocation != lastAllowedMemoryAllocation) { _memoryPressureStateStale = true; lastAllowedMemoryAllocation = allowedMemoryAllocation; } - if (!_memoryPressureStateStale) { + if (!_memoryPressureStateStale.exchange(false)) { return; } - _memoryPressureStateStale = false; - // Clear any defunct textures + + // Clear any defunct textures (weak pointers that no longer have a valid texture) _memoryManagedTextures.remove_if([&](const TextureWeakPointer& weakPointer) { return weakPointer.expired(); }); - // Convert weak pointers to strong - std::list strongTextures; { + // Convert weak pointers to strong. This new list may still contain nulls if a texture was + // deleted on another thread between the previous line and this one + std::vector strongTextures; { + strongTextures.reserve(_memoryManagedTextures.size()); std::transform( _memoryManagedTextures.begin(), _memoryManagedTextures.end(), std::back_inserter(strongTextures), @@ -127,8 +122,10 @@ void GL45VariableAllocationTexture::updateMemoryPressure() { if (!texture) { continue; } - idealMemoryAllocation += texture->evalTotalSize(); GL45VariableAllocationTexture* object = Backend::getGPUObject(*texture); + // Track how much the texture thinks it should be using + idealMemoryAllocation += texture->evalTotalSize(); + // Track how much we're actually using totalVariableMemoryAllocation += object->size(); canDemote |= object->canDemote(); canPromote |= object->canPromote(); @@ -136,11 +133,6 @@ void GL45VariableAllocationTexture::updateMemoryPressure() { } size_t unallocated = idealMemoryAllocation - totalVariableMemoryAllocation; - if (0 == allowedMemoryAllocation) { - allowedMemoryAllocation = DEFAULT_ALLOWED_TEXTURE_MEMORY; - } - - float pressure = (float)totalVariableMemoryAllocation / (float)allowedMemoryAllocation; auto newState = MemoryPressureState::Idle; @@ -154,142 +146,59 @@ void GL45VariableAllocationTexture::updateMemoryPressure() { if (newState != _memoryPressureState) { _memoryPressureState = newState; - - demoteQueue = DemoteQueue(); - promoteQueue = PromoteQueue(); - transferQueue = TransferQueue(); - - switch (_memoryPressureState) { - case MemoryPressureState::Idle: - break; - - case MemoryPressureState::Oversubscribed: - for (const auto& texture : strongTextures) { - if (!texture) { - continue; - } - GL45VariableAllocationTexture* object = Backend::getGPUObject(*texture); - if (object->canDemote()) { - demoteQueue.push({ texture, object->size() }); - } - } - break; - - case MemoryPressureState::Undersubscribed: - for (const auto& texture : strongTextures) { - if (!texture) { - continue; - } - GL45VariableAllocationTexture* object = Backend::getGPUObject(*texture); - if (object->canPromote()) { - promoteQueue.push({ texture, object->size() }); - } - } - break; - - case MemoryPressureState::Transfer: - for (const auto& texture : strongTextures) { - if (!texture) { - continue; - } - GL45VariableAllocationTexture* object = Backend::getGPUObject(*texture); - if (object->hasPendingTransfers()) { - transferQueue.push(texture); - } - } - break; - - default: - Q_UNREACHABLE(); - break; + // Clear the existing queue + _workQueue = WorkQueue(); + // Populate the existing textures into the queue + for (const auto& texture : strongTextures) { + addToWorkQueue(texture); } } } void GL45VariableAllocationTexture::processWorkQueues() { - switch (_memoryPressureState) { - case MemoryPressureState::Idle: - break; + if (MemoryPressureState::Idle == _memoryPressureState) { + return; + } - case MemoryPressureState::Oversubscribed: - // Grab the first item off the demote queue - while (!demoteQueue.empty()) { - auto demoteTarget = demoteQueue.top(); - demoteQueue.pop(); - auto texture = demoteTarget.first.lock(); - if (!texture) { - continue; - } + while (!_workQueue.empty()) { + auto workTarget = _workQueue.top(); + _workQueue.pop(); + auto texture = workTarget.first.lock(); + if (!texture) { + continue; + } - GL45VariableAllocationTexture* object = Backend::getGPUObject(*texture); - if (!object->canDemote()) { - continue; - } - - //qDebug() << "QQQ executing demote for " << texture->source().c_str(); - object->demote(); - // if the object can be further demoted, reinsert into the queue - if (object->canDemote()) { - demoteQueue.push({ demoteTarget.first, object->size() }); - } - break; + // Grab the first item off the demote queue + GL45VariableAllocationTexture* object = Backend::getGPUObject(*texture); + if (MemoryPressureState::Oversubscribed == _memoryPressureState) { + if (!object->canDemote()) { + continue; } - if (demoteQueue.empty()) { - _memoryPressureState = MemoryPressureState::Idle; + //qDebug() << "QQQ executing demote for " << texture->source().c_str(); + object->demote(); + } else if (MemoryPressureState::Undersubscribed == _memoryPressureState) { + if (!object->canPromote()) { + continue; } - break; - - case MemoryPressureState::Undersubscribed: - while (!promoteQueue.empty()) { - auto promoteTarget = promoteQueue.top(); - promoteQueue.pop(); - auto texture = promoteTarget.first.lock(); - if (!texture) { - continue; - } - GL45VariableAllocationTexture* object = Backend::getGPUObject(*texture); - if (!object->canPromote()) { - continue; - } - //qDebug() << "QQQ executing promote for " << texture->source().c_str(); - object->promote(); - if (object->canPromote()) { - promoteQueue.push({ promoteTarget.first, object->size() }); - } - break; + //qDebug() << "QQQ executing promote for " << texture->source().c_str(); + object->promote(); + } else if (MemoryPressureState::Transfer == _memoryPressureState) { + if (!object->hasPendingTransfers()) { + continue; } - if (promoteQueue.empty()) { - _memoryPressureState = MemoryPressureState::Idle; - } - break; - - case MemoryPressureState::Transfer: - while (!transferQueue.empty()) { - auto weakTexture = transferQueue.front(); - transferQueue.pop(); - auto texture = weakTexture.lock(); - if (!texture) { - continue; - } - GL45VariableAllocationTexture* object = Backend::getGPUObject(*texture); - if (!object->hasPendingTransfers()) { - continue; - } - //qDebug() << "QQQ executing transfer for " << texture->source().c_str(); - object->executeNextTransfer(); - if (object->hasPendingTransfers()) { - transferQueue.push(weakTexture); - } - break; - } - if (transferQueue.empty()) { - _memoryPressureState = MemoryPressureState::Idle; - } - break; - - default: + //qDebug() << "QQQ executing transfer for " << texture->source().c_str(); + object->executeNextTransfer(); + } else { Q_UNREACHABLE(); - break; + } + + // Reinject into the queue if more work to be done + addToWorkQueue(texture); + break; + } + + if (_workQueue.empty()) { + _memoryPressureState = MemoryPressureState::Idle; } } @@ -379,7 +288,6 @@ void GL45ResourceTexture::promote() { uint32_t oldSize = _size; // create new texture const_cast(_id) = allocate(_gpuObject); - incrementTextureGPUCount(); uint16_t oldAllocatedMip = _allocatedMip; // allocate storage for new level allocateStorage(_allocatedMip - std::min(_allocatedMip, 2)); @@ -413,7 +321,6 @@ void GL45ResourceTexture::demote() { auto oldId = _id; auto oldSize = _size; const_cast(_id) = allocate(_gpuObject); - incrementTextureGPUCount(); allocateStorage(_allocatedMip + 1); _populatedMip = std::max(_populatedMip, _allocatedMip); uint16_t mips = _gpuObject.evalNumMips(); From 35a6359d5976b61fab4f21f17188872a6e6122b5 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 10 Feb 2017 10:56:01 -0800 Subject: [PATCH 10/28] Fixing transfer logic, adding stats display & profile ranges --- interface/resources/qml/Stats.qml | 11 +-- interface/src/ui/Stats.cpp | 3 + interface/src/ui/Stats.h | 2 + libraries/gpu-gl/src/gpu/gl45/GL45Backend.h | 7 +- .../src/gpu/gl45/GL45BackendTexture.cpp | 3 - .../gpu/gl45/GL45BackendVariableTexture.cpp | 70 ++++++++++++++++--- 6 files changed, 72 insertions(+), 24 deletions(-) diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml index faf37d5366..4cba41f6cc 100644 --- a/interface/resources/qml/Stats.qml +++ b/interface/resources/qml/Stats.qml @@ -241,7 +241,7 @@ Item { text: "GPU Textures: "; } StatText { - text: " Sparse Enabled: " + (0 == root.gpuSparseTextureEnabled ? "false" : "true"); + text: " Pressure State: " + root.gpuTextureMemoryPressureState; } StatText { text: " Count: " + root.gpuTextures; @@ -253,14 +253,7 @@ Item { text: " Decimated: " + root.decimatedTextureCount; } StatText { - text: " Sparse Count: " + root.gpuTexturesSparse; - visible: 0 != root.gpuSparseTextureEnabled; - } - StatText { - text: " Virtual Memory: " + root.gpuTextureVirtualMemory + " MB"; - } - StatText { - text: " Commited Memory: " + root.gpuTextureMemory + " MB"; + text: " Resource Memory: " + root.gpuTextureMemory + " MB"; } StatText { text: " Framebuffer Memory: " + root.gpuTextureFramebufferMemory + " MB"; diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index e82f99bed2..7af454b702 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -38,6 +38,8 @@ using namespace std; static Stats* INSTANCE{ nullptr }; +QString getTextureMemoryPressureModeString(); + Stats* Stats::getInstance() { if (!INSTANCE) { Stats::registerType(); @@ -323,6 +325,7 @@ void Stats::updateStats(bool force) { STAT_UPDATE(gpuTextureVirtualMemory, (int)BYTES_TO_MB(gpu::Texture::getTextureGPUVirtualMemoryUsage())); STAT_UPDATE(gpuTextureFramebufferMemory, (int)BYTES_TO_MB(gpu::Texture::getTextureGPUFramebufferMemoryUsage())); STAT_UPDATE(gpuTextureSparseMemory, (int)BYTES_TO_MB(gpu::Texture::getTextureGPUSparseMemoryUsage())); + STAT_UPDATE(gpuTextureMemoryPressureState, getTextureMemoryPressureModeString()); STAT_UPDATE(gpuSparseTextureEnabled, gpuContext->getBackend()->isTextureManagementSparseEnabled() ? 1 : 0); STAT_UPDATE(gpuFreeMemory, (int)BYTES_TO_MB(gpu::Context::getFreeGPUMemory())); STAT_UPDATE(rectifiedTextureCount, (int)RECTIFIED_TEXTURE_COUNT.load()); diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index f501f4b09a..069429d639 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -110,6 +110,7 @@ class Stats : public QQuickItem { STATS_PROPERTY(int, gpuTextureFramebufferMemory, 0) STATS_PROPERTY(int, gpuTextureSparseMemory, 0) STATS_PROPERTY(int, gpuSparseTextureEnabled, 0) + STATS_PROPERTY(QString, gpuTextureMemoryPressureState, QString()) STATS_PROPERTY(int, gpuFreeMemory, 0) STATS_PROPERTY(float, gpuFrameTime, 0) STATS_PROPERTY(float, batchFrameTime, 0) @@ -217,6 +218,7 @@ signals: void gpuTextureVirtualMemoryChanged(); void gpuTextureFramebufferMemoryChanged(); void gpuTextureSparseMemoryChanged(); + void gpuTextureMemoryPressureStateChanged(); void gpuSparseTextureEnabledChanged(); void gpuFreeMemoryChanged(); void gpuFrameTimeChanged(); diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h index e6a61e9498..1983088ca5 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h +++ b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h @@ -103,11 +103,13 @@ public: }; using WorkQueue = std::priority_queue, QueuePairLess>; + static MemoryPressureState _memoryPressureState; protected: static std::atomic _memoryPressureStateStale; - static MemoryPressureState _memoryPressureState; static std::list _memoryManagedTextures; - static WorkQueue _workQueue; + static WorkQueue _transferQueue; + static WorkQueue _promoteQueue; + static WorkQueue _demoteQueue; static const uvec3 INITIAL_MIP_TRANSFER_DIMENSIONS; @@ -115,6 +117,7 @@ public: static void processWorkQueues(); static void addMemoryManagedTexture(const TexturePointer& texturePointer); static void addToWorkQueue(const TexturePointer& texture); + static WorkQueue& getActiveWorkQueue(); static void manageMemory(); diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp index 87eb5228a4..c344b453a9 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp @@ -35,9 +35,6 @@ GLTexture* GL45Backend::syncGPUObject(const TexturePointer& texturePointer) { } const Texture& texture = *texturePointer; - if (std::string("cursor texture") == texture.source()) { - qDebug() << "Loading cursor texture"; - } if (TextureUsageType::EXTERNAL == texture.getUsageType()) { return Parent::syncGPUObject(texturePointer); } diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp index 0f14c9cc43..282ec2161c 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp @@ -36,7 +36,9 @@ std::list GL45VariableAllocationTexture::_memoryManagedTextu MemoryPressureState GL45VariableAllocationTexture::_memoryPressureState = MemoryPressureState::Idle; std::atomic GL45VariableAllocationTexture::_memoryPressureStateStale { false }; const uvec3 GL45VariableAllocationTexture::INITIAL_MIP_TRANSFER_DIMENSIONS { 64, 64, 1 }; -WorkQueue GL45VariableAllocationTexture::_workQueue; +WorkQueue GL45VariableAllocationTexture::_transferQueue; +WorkQueue GL45VariableAllocationTexture::_promoteQueue; +WorkQueue GL45VariableAllocationTexture::_demoteQueue; #define OVERSUBSCRIBED_PRESSURE_VALUE 0.95f #define UNDERSUBSCRIBED_PRESSURE_VALUE 0.85f @@ -54,19 +56,19 @@ void GL45VariableAllocationTexture::addToWorkQueue(const TexturePointer& texture switch (_memoryPressureState) { case MemoryPressureState::Oversubscribed: if (object->canDemote()) { - _workQueue.push({ texturePointer, (float) object->size() }); + _demoteQueue.push({ texturePointer, (float)object->size() }); } break; case MemoryPressureState::Undersubscribed: if (object->canPromote()) { - _workQueue.push({ texturePointer, 1.0f / (float)object->size() }); + _promoteQueue.push({ texturePointer, 1.0f / (float)object->size() }); } break; case MemoryPressureState::Transfer: if (object->hasPendingTransfers()) { - _workQueue.push({ texturePointer, 1.0f / (float)object->_gpuObject.evalMipSize(object->_populatedMip) }); + _transferQueue.push({ texturePointer, 1.0f / (float)object->_gpuObject.evalMipSize(object->_populatedMip) }); } break; @@ -78,6 +80,44 @@ void GL45VariableAllocationTexture::addToWorkQueue(const TexturePointer& texture } } +WorkQueue& GL45VariableAllocationTexture::getActiveWorkQueue() { + static WorkQueue empty; + switch (_memoryPressureState) { + case MemoryPressureState::Oversubscribed: + return _demoteQueue; + + case MemoryPressureState::Undersubscribed: + return _promoteQueue; + + case MemoryPressureState::Transfer: + return _transferQueue; + + default: + break; + } + Q_UNREACHABLE(); + return empty; +} + +// FIXME hack for stats display +QString getTextureMemoryPressureModeString() { + switch (GL45VariableAllocationTexture::_memoryPressureState) { + case MemoryPressureState::Oversubscribed: + return "Oversubscribed"; + + case MemoryPressureState::Undersubscribed: + return "Undersubscribed"; + + case MemoryPressureState::Transfer: + return "Transfer"; + + case MemoryPressureState::Idle: + return "Idle"; + } + Q_UNREACHABLE(); + return "Unknown"; +} + void GL45VariableAllocationTexture::updateMemoryPressure() { static size_t lastAllowedMemoryAllocation = gpu::Texture::getAllowedGPUMemoryUsage(); @@ -97,6 +137,8 @@ void GL45VariableAllocationTexture::updateMemoryPressure() { return; } + PROFILE_RANGE(render_gpu_gl, __FUNCTION__); + // Clear any defunct textures (weak pointers that no longer have a valid texture) _memoryManagedTextures.remove_if([&](const TextureWeakPointer& weakPointer) { return weakPointer.expired(); @@ -147,7 +189,9 @@ void GL45VariableAllocationTexture::updateMemoryPressure() { if (newState != _memoryPressureState) { _memoryPressureState = newState; // Clear the existing queue - _workQueue = WorkQueue(); + _transferQueue = WorkQueue(); + _promoteQueue = WorkQueue(); + _demoteQueue = WorkQueue(); // Populate the existing textures into the queue for (const auto& texture : strongTextures) { addToWorkQueue(texture); @@ -160,9 +204,11 @@ void GL45VariableAllocationTexture::processWorkQueues() { return; } - while (!_workQueue.empty()) { - auto workTarget = _workQueue.top(); - _workQueue.pop(); + auto& workQueue = getActiveWorkQueue(); + PROFILE_RANGE(render_gpu_gl, __FUNCTION__); + while (!workQueue.empty()) { + auto workTarget = workQueue.top(); + workQueue.pop(); auto texture = workTarget.first.lock(); if (!texture) { continue; @@ -197,7 +243,7 @@ void GL45VariableAllocationTexture::processWorkQueues() { break; } - if (_workQueue.empty()) { + if (workQueue.empty()) { _memoryPressureState = MemoryPressureState::Idle; } } @@ -208,6 +254,7 @@ void GL45VariableAllocationTexture::manageMemory() { auto interval = now - lastProcessTime; if (interval > (USECS_PER_MSEC * 20)) { lastProcessTime = now; + PROFILE_RANGE(render_gpu_gl, __FUNCTION__); updateMemoryPressure(); processWorkQueues(); } @@ -283,6 +330,7 @@ void GL45ResourceTexture::syncSampler() const { } void GL45ResourceTexture::promote() { + PROFILE_RANGE(render_gpu_gl, __FUNCTION__); Q_ASSERT(_allocatedMip > 0); GLuint oldId = _id; uint32_t oldSize = _size; @@ -317,6 +365,7 @@ void GL45ResourceTexture::promote() { } void GL45ResourceTexture::demote() { + PROFILE_RANGE(render_gpu_gl, __FUNCTION__); Q_ASSERT(_allocatedMip < _maxAllocatedMip); auto oldId = _id; auto oldSize = _size; @@ -349,6 +398,7 @@ void GL45ResourceTexture::demote() { } void GL45ResourceTexture::populateTransferQueue() { + PROFILE_RANGE(render_gpu_gl, __FUNCTION__); _pendingTransfers = std::queue(); if (_populatedMip <= _allocatedMip) { return; @@ -401,7 +451,7 @@ void GL45ResourceTexture::populateTransferQueue() { // queue up the sampler and populated mip change for after the transfer has completed _pendingTransfers.push([=] { - _populatedMip = targetMip; + _populatedMip = sourceMip; syncSampler(); }); } while (sourceMip != _allocatedMip); From 439cb388f2205ca9733217822301b4b73d00841c Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 10 Feb 2017 11:15:22 -0800 Subject: [PATCH 11/28] Increase the rate of work queue processing --- .../src/gpu/gl45/GL45BackendVariableTexture.cpp | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp index 282ec2161c..22bb26cc10 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp @@ -249,15 +249,9 @@ void GL45VariableAllocationTexture::processWorkQueues() { } void GL45VariableAllocationTexture::manageMemory() { - static auto lastProcessTime = usecTimestampNow(); - auto now = usecTimestampNow(); - auto interval = now - lastProcessTime; - if (interval > (USECS_PER_MSEC * 20)) { - lastProcessTime = now; - PROFILE_RANGE(render_gpu_gl, __FUNCTION__); - updateMemoryPressure(); - processWorkQueues(); - } + PROFILE_RANGE(render_gpu_gl, __FUNCTION__); + updateMemoryPressure(); + processWorkQueues(); } GL45VariableAllocationTexture::GL45VariableAllocationTexture(const std::weak_ptr& backend, const Texture& texture) : GL45Texture(backend, texture) { @@ -404,7 +398,7 @@ void GL45ResourceTexture::populateTransferQueue() { return; } - static const uvec3 MAX_TRANSFER_DIMENSIONS { 512, 512, 1 }; + static const uvec3 MAX_TRANSFER_DIMENSIONS { 1024, 1024, 1 }; static const size_t MAX_TRANSFER_SIZE = MAX_TRANSFER_DIMENSIONS.x * MAX_TRANSFER_DIMENSIONS.y * 4; const uint8_t maxFace = GLTexture::getFaceCount(_target); From 066a6483a0badf9b533d2e77b4b3b7a6e8768971 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 10 Feb 2017 11:23:37 -0800 Subject: [PATCH 12/28] Code comments --- libraries/gpu-gl/src/gpu/gl45/GL45Backend.h | 13 +++++++++++-- .../src/gpu/gl45/GL45BackendVariableTexture.cpp | 3 +++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h index 1983088ca5..d2d17160ba 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h +++ b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h @@ -134,10 +134,20 @@ public: virtual void promote() = 0; virtual void demote() = 0; - uint16 _populatedMip { 0 }; + // The allocated mip level, relative to the number of mips in the gpu::Texture object + // The relationship between a given glMip to the original gpu::Texture mip is always + // glMip + _allocatedMip uint16 _allocatedMip { 0 }; + // The populated mip level, relative to the number of mips in the gpu::Texture object + // This must always be >= the allocated mip + uint16 _populatedMip { 0 }; + // The highest (lowest resolution) mip that we will support, relative to the number + // of mips in the gpu::Texture object uint16 _maxAllocatedMip { 0 }; uint32 _size { 0 }; + // Contains a series of lambdas that when executed will transfer data to the GPU, modify + // the _populatedMip and update the sampler in order to fully populate the allocated texture + // until _populatedMip == _allocatedMip std::queue _pendingTransfers; }; @@ -154,7 +164,6 @@ public: void allocateStorage(uint16 mip); void copyMipsFromTexture(); - private: }; #if 0 diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp index 22bb26cc10..597e35750a 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp @@ -56,18 +56,21 @@ void GL45VariableAllocationTexture::addToWorkQueue(const TexturePointer& texture switch (_memoryPressureState) { case MemoryPressureState::Oversubscribed: if (object->canDemote()) { + // Demote largest first _demoteQueue.push({ texturePointer, (float)object->size() }); } break; case MemoryPressureState::Undersubscribed: if (object->canPromote()) { + // Promote smallest first _promoteQueue.push({ texturePointer, 1.0f / (float)object->size() }); } break; case MemoryPressureState::Transfer: if (object->hasPendingTransfers()) { + // Transfer priority given to smaller mips first _transferQueue.push({ texturePointer, 1.0f / (float)object->_gpuObject.evalMipSize(object->_populatedMip) }); } break; From 75c17e89a27d5ce287b2a1af5191bb5d514326d0 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 13 Feb 2017 12:37:42 -0800 Subject: [PATCH 13/28] Fix OpenGL 4.1 texture loading --- .../gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp index cf389c83f3..efbc6903b1 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp @@ -73,14 +73,21 @@ GL41Texture::GL41Texture(const std::weak_ptr& backend, const Texture& withPreservedTexture([&] { GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat()); const Sampler& sampler = _gpuObject.getSampler(); - auto minMip = sampler.getMinMip(); - auto maxMip = sampler.getMaxMip(); - for (uint16_t l = minMip; l <= maxMip; l++) { + auto numMips = _gpuObject.evalNumMips(); + for (uint16_t mipLevel = 0; mipLevel < numMips; ++mipLevel) { // Get the mip level dimensions, accounting for the downgrade level - Vec3u dimensions = _gpuObject.evalMipDimensions(l); + Vec3u dimensions = _gpuObject.evalMipDimensions(mipLevel); + uint8_t face = 0; for (GLenum target : getFaceTargets(_target)) { - glTexImage2D(target, l - minMip, texelFormat.internalFormat, dimensions.x, dimensions.y, 0, texelFormat.format, texelFormat.type, NULL); + const Byte* mipData = nullptr; + if (_gpuObject.isStoredMipFaceAvailable(mipLevel, face)) { + auto mip = _gpuObject.accessStoredMipFace(mipLevel, face); + mipData = mip->readData(); + texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat(), mip->getFormat()); + } + glTexImage2D(target, mipLevel, texelFormat.internalFormat, dimensions.x, dimensions.y, 0, texelFormat.format, texelFormat.type, mipData); (void)CHECK_GL_ERROR(); + ++face; } } }); From 1f058f069e16976a35f39ed7d587fd0c92289e39 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 14 Feb 2017 17:58:41 -0800 Subject: [PATCH 14/28] First pass at new texture transfer logic --- libraries/gpu-gl/src/gpu/gl45/GL45Backend.h | 48 +++++- .../src/gpu/gl45/GL45BackendTexture.cpp | 24 ++- .../gpu/gl45/GL45BackendVariableTexture.cpp | 163 +++++++++++++++--- 3 files changed, 192 insertions(+), 43 deletions(-) diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h index d2d17160ba..4f299d417f 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h +++ b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h @@ -14,6 +14,7 @@ #include "../gl/GLBackend.h" #include "../gl/GLTexture.h" +#include #define INCREMENTAL_TRANSFER 0 @@ -39,7 +40,7 @@ public: GL45Texture(const std::weak_ptr& backend, const Texture& texture); void generateMips() const override; void copyMipFaceFromTexture(uint16_t sourceMip, uint16_t targetMip, uint8_t face) const; - void copyMipFaceLinesFromTexture(uint16_t sourceMip, uint16_t targetMip, uint8_t face, uint32_t lineOffset, uint32_t lines, size_t dataOffset) const; + void copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum format, GLenum type, const void* sourcePointer) const; virtual void syncSampler() const; }; @@ -95,14 +96,50 @@ public: }; using QueuePair = std::pair; - class QueuePairLess { - public: + struct QueuePairLess { bool operator()(const QueuePair& a, const QueuePair& b) { return a.second < b.second; } }; using WorkQueue = std::priority_queue, QueuePairLess>; + class TransferJob { + using VoidLambda = std::function; + using VoidLambdaQueue = std::queue; + using ThreadPointer = std::shared_ptr; + const GL45VariableAllocationTexture& _parent; + const uint16_t _sourceMip; + const uint16_t _targetMip; + const uint8_t _face; + const uint32_t _lines; + const uint32_t _lineOffset; + // Holds the contents to transfer to the GPU in CPU memory + std::vector _buffer; + // Indicates if a transfer from backing storage to interal storage has started + bool _bufferingStarted { false }; + bool _transferOnly { false }; + bool _bufferingCompleted { false }; + VoidLambda _transferLambda; + VoidLambda _bufferingLambda; + static ThreadPointer _bufferThread; + static Mutex _mutex; + static VoidLambdaQueue _bufferLambdaQueue; + static std::atomic _shutdownBufferingThread; + static void bufferLoop(); + + public: + TransferJob(const GL45VariableAllocationTexture& parent, std::function transferLambda); + TransferJob(const GL45VariableAllocationTexture& parent, uint16_t sourceMip, uint16_t targetMip, uint8_t face, uint32_t lines = 0, uint32_t lineOffset = 0); + bool tryTransfer(); + static void startTransferLoop(); + static void stopTransferLoop(); + + private: + void startBuffering(); + void transfer(); + }; + + using TransferQueue = std::queue; static MemoryPressureState _memoryPressureState; protected: static std::atomic _memoryPressureStateStale; @@ -110,6 +147,7 @@ public: static WorkQueue _transferQueue; static WorkQueue _promoteQueue; static WorkQueue _demoteQueue; + static TexturePointer _currentTransferTexture; static const uvec3 INITIAL_MIP_TRANSFER_DIMENSIONS; @@ -128,7 +166,7 @@ public: bool canPromote() const { return _allocatedMip > 0; } bool canDemote() const { return _allocatedMip < _maxAllocatedMip; } bool hasPendingTransfers() const { return !_pendingTransfers.empty(); } - void executeNextTransfer(); + void executeNextTransfer(const TexturePointer& currentTexture); uint32 size() const override { return _size; } virtual void populateTransferQueue() = 0; virtual void promote() = 0; @@ -148,7 +186,7 @@ public: // Contains a series of lambdas that when executed will transfer data to the GPU, modify // the _populatedMip and update the sampler in order to fully populate the allocated texture // until _populatedMip == _allocatedMip - std::queue _pendingTransfers; + TransferQueue _pendingTransfers; }; class GL45ResourceTexture : public GL45VariableAllocationTexture { diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp index c344b453a9..6dd1d6aea3 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp @@ -118,26 +118,17 @@ void GL45Texture::generateMips() const { (void)CHECK_GL_ERROR(); } -void GL45Texture::copyMipFaceLinesFromTexture(uint16_t sourceMip, uint16_t targetMip, uint8_t face, uint32_t lineOffset, uint32_t lines, size_t dataOffset) const { - const auto& texture = _gpuObject; - if (!texture.isStoredMipFaceAvailable(sourceMip)) { - return; - } - auto mipDimensions = texture.evalMipDimensions(sourceMip); - glm::uvec3 size = { mipDimensions.x, lines, mipDimensions.z }; - auto mipData = texture.accessStoredMipFace(sourceMip, face); - auto sourcePointer = mipData->readData() + dataOffset; - GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(texture.getTexelFormat(), mipData->getFormat()); +void GL45Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum format, GLenum type, const void* sourcePointer) const { if (GL_TEXTURE_2D == _target) { - glTextureSubImage2D(_id, targetMip, 0, lineOffset, size.x, size.y, texelFormat.format, texelFormat.type, sourcePointer); + glTextureSubImage2D(_id, mip, 0, yOffset, size.x, size.y, format, type, sourcePointer); } else if (GL_TEXTURE_CUBE_MAP == _target) { // DSA ARB does not work on AMD, so use EXT // unless EXT is not available on the driver if (glTextureSubImage2DEXT) { auto target = GLTexture::CUBE_FACE_LAYOUT[face]; - glTextureSubImage2DEXT(_id, target, targetMip, 0, lineOffset, size.x, size.y, texelFormat.format, texelFormat.type, sourcePointer); + glTextureSubImage2DEXT(_id, target, mip, 0, yOffset, size.x, size.y, format, type, sourcePointer); } else { - glTextureSubImage3D(_id, targetMip, 0, lineOffset, face, size.x, size.y, 1, texelFormat.format, texelFormat.type, sourcePointer); + glTextureSubImage3D(_id, mip, 0, yOffset, face, size.x, size.y, 1, format, type, sourcePointer); } } else { Q_ASSERT(false); @@ -146,8 +137,13 @@ void GL45Texture::copyMipFaceLinesFromTexture(uint16_t sourceMip, uint16_t targe } void GL45Texture::copyMipFaceFromTexture(uint16_t sourceMip, uint16_t targetMip, uint8_t face) const { + if (!_gpuObject.isStoredMipFaceAvailable(sourceMip)) { + return; + } auto size = _gpuObject.evalMipDimensions(sourceMip); - copyMipFaceLinesFromTexture(sourceMip, targetMip, face, 0, size.y, 0); + auto mipData = _gpuObject.accessStoredMipFace(sourceMip, face); + GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat(), mipData->getFormat()); + copyMipFaceLinesFromTexture(targetMip, face, size, 0, texelFormat.format, texelFormat.type, mipData->readData()); } void GL45Texture::syncSampler() const { diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp index 597e35750a..e26a5c262f 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp @@ -39,6 +39,7 @@ const uvec3 GL45VariableAllocationTexture::INITIAL_MIP_TRANSFER_DIMENSIONS { 64, WorkQueue GL45VariableAllocationTexture::_transferQueue; WorkQueue GL45VariableAllocationTexture::_promoteQueue; WorkQueue GL45VariableAllocationTexture::_demoteQueue; +TexturePointer GL45VariableAllocationTexture::_currentTransferTexture; #define OVERSUBSCRIBED_PRESSURE_VALUE 0.95f #define UNDERSUBSCRIBED_PRESSURE_VALUE 0.85f @@ -46,6 +47,123 @@ WorkQueue GL45VariableAllocationTexture::_demoteQueue; static const size_t DEFAULT_ALLOWED_TEXTURE_MEMORY = MB_TO_BYTES(DEFAULT_ALLOWED_TEXTURE_MEMORY_MB); +using TransferJob = GL45VariableAllocationTexture::TransferJob; + +static const uvec3 MAX_TRANSFER_DIMENSIONS { 1024, 1024, 1 }; +static const size_t MAX_TRANSFER_SIZE = MAX_TRANSFER_DIMENSIONS.x * MAX_TRANSFER_DIMENSIONS.y * 4; + +std::shared_ptr TransferJob::_bufferThread { nullptr }; +std::atomic TransferJob::_shutdownBufferingThread { false }; +Mutex TransferJob::_mutex; +TransferJob::VoidLambdaQueue TransferJob::_bufferLambdaQueue; + +void TransferJob::startTransferLoop() { + if (_bufferThread) { + return; + } + _shutdownBufferingThread = false; + _bufferThread = std::make_shared([] { + TransferJob::bufferLoop(); + }); +} + +void TransferJob::stopTransferLoop() { + if (!_bufferThread) { + return; + } + _shutdownBufferingThread = true; + _bufferThread->join(); + _bufferThread.reset(); + _shutdownBufferingThread = false; +} + +TransferJob::TransferJob(const GL45VariableAllocationTexture& parent, uint16_t sourceMip, uint16_t targetMip, uint8_t face, uint32_t lines, uint32_t lineOffset) + : _parent(parent), _sourceMip(sourceMip), _targetMip(targetMip), _face(face), _lines(lines), _lineOffset(lineOffset) { + + if (0 == lines) { + _bufferingLambda = [this] { + auto mipData = _parent._gpuObject.accessStoredMipFace(_sourceMip, _face); + auto size = mipData->getSize(); + _buffer.resize(size); + memcpy(&_buffer[0], mipData->readData(), size); + _bufferingCompleted = true; + }; + + } else { + _bufferingLambda = [this] { + auto mipData = _parent._gpuObject.accessStoredMipFace(_sourceMip, _face); + auto dimensions = _parent._gpuObject.evalMipDimensions(_sourceMip); + auto mipSize = mipData->getSize(); + auto bytesPerLine = (uint32_t)mipSize / dimensions.y; + auto transferSize = bytesPerLine * _lines; + auto sourceOffset = bytesPerLine * _lineOffset; + _buffer.resize(transferSize); + memcpy(&_buffer[0], mipData->readData() + sourceOffset, transferSize); + _bufferingCompleted = true; + }; + } + + _transferLambda = [this] { + auto mipData = _parent._gpuObject.accessStoredMipFace(_sourceMip, _face); + auto dimensions = _parent._gpuObject.evalMipDimensions(_sourceMip); + GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_parent._gpuObject.getTexelFormat(), mipData->getFormat()); + _parent.copyMipFaceLinesFromTexture(_targetMip, _face, dimensions, _lineOffset, texelFormat.format, texelFormat.type, &_buffer[0]); + _buffer.swap(std::vector()); + }; +} + +TransferJob::TransferJob(const GL45VariableAllocationTexture& parent, std::function transferLambda) + : _parent(parent), _sourceMip(0), _targetMip(0), _face(0), _lines(0), _lineOffset(0), _bufferingCompleted(true), _transferLambda(transferLambda) { + if (!_bufferThread) { + _bufferThread = std::make_shared([] { + TransferJob::bufferLoop(); + }); + } +} + +bool TransferJob::tryTransfer() { + // Are we ready to transfer + if (_bufferingCompleted) { + _transferLambda(); + return true; + } + + startBuffering(); + return false; +} + +void TransferJob::startBuffering() { + if (_bufferingStarted) { + return; + } + _bufferingStarted = true; + { + Lock lock(_mutex); + _bufferLambdaQueue.push(_bufferingLambda); + } +} + +void TransferJob::bufferLoop() { + while (!_shutdownBufferingThread) { + VoidLambdaQueue workingQueue; + { + Lock lock(_mutex); + _bufferLambdaQueue.swap(workingQueue); + } + + if (workingQueue.empty()) { + QThread::msleep(5); + continue; + } + + while (!workingQueue.empty()) { + workingQueue.front()(); + workingQueue.pop(); + } + } +} + + void GL45VariableAllocationTexture::addMemoryManagedTexture(const TexturePointer& texturePointer) { _memoryManagedTextures.push_back(texturePointer); addToWorkQueue(texturePointer); @@ -190,7 +308,14 @@ void GL45VariableAllocationTexture::updateMemoryPressure() { } if (newState != _memoryPressureState) { + if (MemoryPressureState::Transfer == _memoryPressureState) { + TransferJob::stopTransferLoop(); + } _memoryPressureState = newState; + if (MemoryPressureState::Transfer == _memoryPressureState) { + TransferJob::startTransferLoop(); + } + // Clear the existing queue _transferQueue = WorkQueue(); _promoteQueue = WorkQueue(); @@ -223,20 +348,17 @@ void GL45VariableAllocationTexture::processWorkQueues() { if (!object->canDemote()) { continue; } - //qDebug() << "QQQ executing demote for " << texture->source().c_str(); object->demote(); } else if (MemoryPressureState::Undersubscribed == _memoryPressureState) { if (!object->canPromote()) { continue; } - //qDebug() << "QQQ executing promote for " << texture->source().c_str(); object->promote(); } else if (MemoryPressureState::Transfer == _memoryPressureState) { if (!object->hasPendingTransfers()) { continue; } - //qDebug() << "QQQ executing transfer for " << texture->source().c_str(); - object->executeNextTransfer(); + object->executeNextTransfer(texture); } else { Q_UNREACHABLE(); } @@ -265,10 +387,14 @@ GL45VariableAllocationTexture::~GL45VariableAllocationTexture() { Backend::updateTextureGPUMemoryUsage(_size, 0); } -void GL45VariableAllocationTexture::executeNextTransfer() { +void GL45VariableAllocationTexture::executeNextTransfer(const TexturePointer& currentTexture) { if (!_pendingTransfers.empty()) { - _pendingTransfers.front()(); - _pendingTransfers.pop(); + // Keeping hold of a strong pointer during the transfer ensures that the transfer thread cannot try to access a destroyed texture + _currentTransferTexture = currentTexture; + if (_pendingTransfers.front().tryTransfer()) { + _pendingTransfers.pop(); + _currentTransferTexture.reset(); + } } } @@ -394,17 +520,15 @@ void GL45ResourceTexture::demote() { populateTransferQueue(); } + void GL45ResourceTexture::populateTransferQueue() { PROFILE_RANGE(render_gpu_gl, __FUNCTION__); - _pendingTransfers = std::queue(); if (_populatedMip <= _allocatedMip) { return; } + _pendingTransfers = TransferQueue(); - static const uvec3 MAX_TRANSFER_DIMENSIONS { 1024, 1024, 1 }; - static const size_t MAX_TRANSFER_SIZE = MAX_TRANSFER_DIMENSIONS.x * MAX_TRANSFER_DIMENSIONS.y * 4; const uint8_t maxFace = GLTexture::getFaceCount(_target); - uint16_t sourceMip = _populatedMip; do { --sourceMip; @@ -418,11 +542,7 @@ void GL45ResourceTexture::populateTransferQueue() { // If the mip is less than the max transfer size, then just do it in one transfer if (glm::all(glm::lessThanEqual(mipDimensions, MAX_TRANSFER_DIMENSIONS))) { // Can the mip be transferred in one go - _pendingTransfers.push([=] { - Q_ASSERT(sourceMip >= _allocatedMip); - // FIXME modify the copy mechanism to be incremental - copyMipFaceFromTexture(sourceMip, targetMip, face); - }); + _pendingTransfers.emplace(*this, sourceMip, targetMip, face); continue; } @@ -433,24 +553,19 @@ void GL45ResourceTexture::populateTransferQueue() { auto bytesPerLine = (uint32_t)mipData->getSize() / lines; Q_ASSERT(0 == (mipData->getSize() % lines)); uint32_t linesPerTransfer = (uint32_t)(MAX_TRANSFER_SIZE / bytesPerLine); - size_t offset = 0; uint32_t lineOffset = 0; while (lineOffset < lines) { uint32_t linesToCopy = std::min(lines - lineOffset, linesPerTransfer); - uvec3 size { mipDimensions.x, linesToCopy, 1 }; - _pendingTransfers.push([=] { - copyMipFaceLinesFromTexture(sourceMip, targetMip, face, lineOffset, linesToCopy, offset); - }); + _pendingTransfers.emplace(TransferJob(*this, sourceMip, targetMip, face, linesToCopy, lineOffset)); lineOffset += linesToCopy; - offset += (linesToCopy * bytesPerLine); } } // queue up the sampler and populated mip change for after the transfer has completed - _pendingTransfers.push([=] { + _pendingTransfers.emplace(TransferJob(*this, [=] { _populatedMip = sourceMip; syncSampler(); - }); + })); } while (sourceMip != _allocatedMip); } From 115251542d1b4c7cbf7ad95b5a1820f23e1923f9 Mon Sep 17 00:00:00 2001 From: sam Date: Wed, 15 Feb 2017 00:54:16 -0800 Subject: [PATCH 15/28] Adding the ktx library --- libraries/gpu/CMakeLists.txt | 2 +- libraries/ktx/CMakeLists.txt | 3 + libraries/ktx/src/ktx/KTX.cpp | 92 +++++++ libraries/ktx/src/ktx/KTX.h | 397 +++++++++++++++++++++++++++++++ libraries/ktx/src/ktx/Reader.cpp | 159 +++++++++++++ libraries/ktx/src/ktx/Writer.cpp | 21 ++ 6 files changed, 673 insertions(+), 1 deletion(-) create mode 100644 libraries/ktx/CMakeLists.txt create mode 100644 libraries/ktx/src/ktx/KTX.cpp create mode 100644 libraries/ktx/src/ktx/KTX.h create mode 100644 libraries/ktx/src/ktx/Reader.cpp create mode 100644 libraries/ktx/src/ktx/Writer.cpp diff --git a/libraries/gpu/CMakeLists.txt b/libraries/gpu/CMakeLists.txt index 384c5709ee..207431d8c7 100644 --- a/libraries/gpu/CMakeLists.txt +++ b/libraries/gpu/CMakeLists.txt @@ -1,6 +1,6 @@ set(TARGET_NAME gpu) autoscribe_shader_lib(gpu) setup_hifi_library() -link_hifi_libraries(shared) +link_hifi_libraries(shared ktx) target_nsight() diff --git a/libraries/ktx/CMakeLists.txt b/libraries/ktx/CMakeLists.txt new file mode 100644 index 0000000000..404660b247 --- /dev/null +++ b/libraries/ktx/CMakeLists.txt @@ -0,0 +1,3 @@ +set(TARGET_NAME ktx) +setup_hifi_library() +link_hifi_libraries() \ No newline at end of file diff --git a/libraries/ktx/src/ktx/KTX.cpp b/libraries/ktx/src/ktx/KTX.cpp new file mode 100644 index 0000000000..2d5e0bc812 --- /dev/null +++ b/libraries/ktx/src/ktx/KTX.cpp @@ -0,0 +1,92 @@ +// +// KTX.cpp +// ktx/src/ktx +// +// Created by Zach Pomerantz on 2/08/2017. +// Copyright 2017 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 "KTX.h" + +#include //min max and more + +using namespace ktx; + +const Header::Identifier ktx::Header::IDENTIFIER {{ + 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A +}}; + +Header::Header() { + memcpy(identifier, IDENTIFIER.data(), IDENTIFIER_LENGTH); +} + +uint32_t Header::evalMaxDimension() const { + return std::max(pixelWidth, std::max(pixelHeight, pixelDepth)); +} + +uint32_t Header::evalMaxLevel() const { + return 1 + log2(evalMaxDimension()); +} + +uint32_t Header::evalPixelWidth(uint32_t level) const { + return std::max(pixelWidth >> level, 1U); +} +uint32_t Header::evalPixelHeight(uint32_t level) const { + return std::max(pixelHeight >> level, 1U); +} +uint32_t Header::evalPixelDepth(uint32_t level) const { + return std::max(pixelDepth >> level, 1U); +} + +size_t Header::evalPixelSize() const { + return glTypeSize; // Really we should generate the size from the FOrmat etc +} + +size_t Header::evalRowSize(uint32_t level) const { + auto pixelWidth = evalPixelWidth(level); + auto pixSize = evalPixelSize(); + auto netSize = pixelWidth * pixSize; + auto packing = netSize % 4; + return netSize + (packing ? 4 - packing : 0); +} +size_t Header::evalFaceSize(uint32_t level) const { + auto pixelHeight = evalPixelHeight(level); + auto pixelDepth = evalPixelDepth(level); + auto rowSize = evalRowSize(level); + return pixelDepth * pixelHeight * rowSize; +} +size_t Header::evalImageSize(uint32_t level) const { + auto faceSize = evalFaceSize(level); + if (numberOfFaces == 6 && numberOfArrayElements == 0) { + return faceSize; + } else { + return (numberOfArrayElements * numberOfFaces * faceSize); + } +} + + +KTX::KTX() { +} + +void KTX::resetStorage(Storage* storage) { + _storage.reset(storage); +} + +const Header* KTX::getHeader() const { + if (_storage) { + return reinterpret_cast (_storage->_bytes); + } else { + return nullptr; + } +} + +const Byte* KTX::getKeyValueData() const { + if (_storage) { + return (_storage->_bytes + sizeof(Header)); + } else { + return nullptr; + } +} diff --git a/libraries/ktx/src/ktx/KTX.h b/libraries/ktx/src/ktx/KTX.h new file mode 100644 index 0000000000..003d3f0d1b --- /dev/null +++ b/libraries/ktx/src/ktx/KTX.h @@ -0,0 +1,397 @@ +// +// KTX.h +// ktx/src/ktx +// +// Created by Zach Pomerantz on 2/08/2017. +// Copyright 2017 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 +// +#pragma once +#ifndef hifi_ktx_KTX_h +#define hifi_ktx_KTX_h + +#include +#include +#include +#include +#include + +/* KTX Spec: + +Byte[12] identifier +UInt32 endianness +UInt32 glType +UInt32 glTypeSize +UInt32 glFormat +Uint32 glInternalFormat +Uint32 glBaseInternalFormat +UInt32 pixelWidth +UInt32 pixelHeight +UInt32 pixelDepth +UInt32 numberOfArrayElements +UInt32 numberOfFaces +UInt32 numberOfMipmapLevels +UInt32 bytesOfKeyValueData + +for each keyValuePair that fits in bytesOfKeyValueData + UInt32 keyAndValueByteSize + Byte keyAndValue[keyAndValueByteSize] + Byte valuePadding[3 - ((keyAndValueByteSize + 3) % 4)] +end + +for each mipmap_level in numberOfMipmapLevels* + UInt32 imageSize; + for each array_element in numberOfArrayElements* + for each face in numberOfFaces + for each z_slice in pixelDepth* + for each row or row_of_blocks in pixelHeight* + for each pixel or block_of_pixels in pixelWidth + Byte data[format-specific-number-of-bytes]** + end + end + end + Byte cubePadding[0-3] + end + end + Byte mipPadding[3 - ((imageSize + 3) % 4)] +end + +* Replace with 1 if this field is 0. + +** Uncompressed texture data matches a GL_UNPACK_ALIGNMENT of 4. +*/ + + + +namespace ktx { + + enum GLType : uint32_t { + COMPRESSED_TYPE = 0, + + // GL 4.4 Table 8.2 + UNSIGNED_BYTE = 0x1401, + BYTE = 0x1400, + UNSIGNED_SHORT = 0x1403, + SHORT = 0x1402, + UNSIGNED_INT = 0x1405, + INT = 0x1404, + HALF_FLOAT = 0x140B, + FLOAT = 0x1406, + UNSIGNED_BYTE_3_3_2 = 0x8032, + UNSIGNED_BYTE_2_3_3_REV = 0x8362, + UNSIGNED_SHORT_5_6_5 = 0x8363, + UNSIGNED_SHORT_5_6_5_REV = 0x8364, + UNSIGNED_SHORT_4_4_4_4 = 0x8033, + UNSIGNED_SHORT_4_4_4_4_REV = 0x8365, + UNSIGNED_SHORT_5_5_5_1 = 0x8034, + UNSIGNED_SHORT_1_5_5_5_REV = 0x8366, + UNSIGNED_INT_8_8_8_8 = 0x8035, + UNSIGNED_INT_8_8_8_8_REV = 0x8367, + UNSIGNED_INT_10_10_10_2 = 0x8036, + UNSIGNED_INT_2_10_10_10_REV = 0x8368, + UNSIGNED_INT_24_8 = 0x84FA, + UNSIGNED_INT_10F_11F_11F_REV = 0x8C3B, + UNSIGNED_INT_5_9_9_9_REV = 0x8C3E, + FLOAT_32_UNSIGNED_INT_24_8_REV = 0x8DAD, + + NUM_GLTYPES = 25, + }; + + enum GLFormat : uint32_t { + COMPRESSED_FORMAT = 0, + + // GL 4.4 Table 8.3 + STENCIL_INDEX = 0x1901, + DEPTH_COMPONENT = 0x1902, + DEPTH_STENCIL = 0x84F9, + + RED = 0x1903, + GREEN = 0x1904, + BLUE = 0x1905, + RG = 0x8227, + RGB = 0x1907, + RGBA = 0x1908, + BGR = 0x80E0, + BGRA = 0x80E1, + + RG_INTEGER = 0x8228, + RED_INTEGER = 0x8D94, + GREEN_INTEGER = 0x8D95, + BLUE_INTEGER = 0x8D96, + RGB_INTEGER = 0x8D98, + RGBA_INTEGER = 0x8D99, + BGR_INTEGER = 0x8D9A, + BGRA_INTEGER = 0x8D9B, + + NUM_GLFORMATS = 20, + }; + + enum GLInternalFormat_Uncompressed : uint32_t { + // GL 4.4 Table 8.12 + R8 = 0x8229, + R8_SNORM = 0x8F94, + + R16 = 0x822A, + R16_SNORM = 0x8F98, + + RG8 = 0x822B, + RG8_SNORM = 0x8F95, + + RG16 = 0x822C, + RG16_SNORM = 0x8F99, + + R3_G3_B2 = 0x2A10, + RGB4 = 0x804F, + RGB5 = 0x8050, + RGB565 = 0x8D62, + + RGB8 = 0x8051, + RGB8_SNORM = 0x8F96, + RGB10 = 0x8052, + RGB12 = 0x8053, + + RGB16 = 0x8054, + RGB16_SNORM = 0x8F9A, + + RGBA2 = 0x8055, + RGBA4 = 0x8056, + RGB5_A1 = 0x8057, + RGBA8 = 0x8058, + RGBA8_SNORM = 0x8F97, + + RGB10_A2 = 0x8059, + RGB10_A2UI = 0x906F, + + RGBA12 = 0x805A, + RGBA16 = 0x805B, + RGBA16_SNORM = 0x8F9B, + + SRGB8 = 0x8C41, + SRGB8_ALPHA8 = 0x8C43, + + R16F = 0x822D, + RG16F = 0x822F, + RGB16F = 0x881B, + RGBA16F = 0x881A, + + R32F = 0x822E, + RG32F = 0x8230, + RGB32F = 0x8815, + RGBA32F = 0x8814, + + R11F_G11F_B10F = 0x8C3A, + RGB9_E5 = 0x8C3D, + + + R8I = 0x8231, + R8UI = 0x8232, + R16I = 0x8233, + R16UI = 0x8234, + R32I = 0x8235, + R32UI = 0x8236, + RG8I = 0x8237, + RG8UI = 0x8238, + RG16I = 0x8239, + RG16UI = 0x823A, + RG32I = 0x823B, + RG32UI = 0x823C, + + RGB8I = 0x8D8F, + RGB8UI = 0x8D7D, + RGB16I = 0x8D89, + RGB16UI = 0x8D77, + + RGB32I = 0x8D83, + RGB32UI = 0x8D71, + RGBA8I = 0x8D8E, + RGBA8UI = 0x8D7C, + RGBA16I = 0x8D88, + RGBA16UI = 0x8D76, + RGBA32I = 0x8D82, + + RGBA32UI = 0x8D70, + + // GL 4.4 Table 8.13 + DEPTH_COMPONENT16 = 0x81A5, + DEPTH_COMPONENT24 = 0x81A6, + DEPTH_COMPONENT32 = 0x81A7, + + DEPTH_COMPONENT32F = 0x8CAC, + DEPTH24_STENCIL8 = 0x88F0, + DEPTH32F_STENCIL8 = 0x8CAD, + + STENCIL_INDEX1 = 0x8D46, + STENCIL_INDEX4 = 0x8D47, + STENCIL_INDEX8 = 0x8D48, + STENCIL_INDEX16 = 0x8D49, + + NUM_UNCOMPRESSED_GLINTERNALFORMATS = 74, + }; + + enum GLInternalFormat_Compressed : uint32_t { + // GL 4.4 Table 8.14 + COMPRESSED_RED = 0x8225, + COMPRESSED_RG = 0x8226, + COMPRESSED_RGB = 0x84ED, + COMPRESSED_RGBA = 0x84EE, + + COMPRESSED_SRGB = 0x8C48, + COMPRESSED_SRGB_ALPHA = 0x8C49, + + COMPRESSED_RED_RGTC1 = 0x8DBB, + COMPRESSED_SIGNED_RED_RGTC1 = 0x8DBC, + COMPRESSED_RG_RGTC2 = 0x8DBD, + COMPRESSED_SIGNED_RG_RGTC2 = 0x8DBE, + + COMPRESSED_RGBA_BPTC_UNORM = 0x8E8C, + COMPRESSED_SRGB_ALPHA_BPTC_UNORM = 0x8E8D, + COMPRESSED_RGB_BPTC_SIGNED_FLOAT = 0x8E8E, + COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT = 0x8E8F, + + COMPRESSED_RGB8_ETC2 = 0x9274, + COMPRESSED_SRGB8_ETC2 = 0x9275, + COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 = 0x9276, + COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 = 0x9277, + COMPRESSED_RGBA8_ETC2_EAC = 0x9278, + COMPRESSED_SRGB8_ALPHA8_ETC2_EAC = 0x9279, + + COMPRESSED_R11_EAC = 0x9270, + COMPRESSED_SIGNED_R11_EAC = 0x9271, + COMPRESSED_RG11_EAC = 0x9272, + COMPRESSED_SIGNED_RG11_EAC = 0x9273, + + NUM_COMPRESSED_GLINTERNALFORMATS = 24, + }; + + enum GLBaseInternalFormat : uint32_t { + // GL 4.4 Table 8.11 + BIF_DEPTH_COMPONENT = 0x1902, + BIF_DEPTH_STENCIL = 0x84F9, + BIF_RED = 0x1903, + BIF_RG = 0x8227, + BIF_RGB = 0x1907, + BIF_RGBA = 0x1908, + BIF_STENCIL_INDEX = 0x1901, + + NUM_GLBASEINTERNALFORMATS = 7, + }; + + enum CubeMapFace { + POS_X = 0, + NEG_X = 1, + POS_Y = 2, + NEG_Y = 3, + POS_Z = 4, + NEG_Z = 5, + NUM_CUBEMAPFACES = 6, + }; + + using Byte = uint8_t; + + // Chunk of data + struct Storage { + size_t _size {0}; + Byte* _bytes {nullptr}; + + Byte* data() { + return _bytes; + } + const Byte* data() const { + return _bytes; + } + size_t size() const { return _size; } + + ~Storage() { if (_bytes) { delete _bytes; } } + + Storage() {} + Storage(size_t size) : + _size(size) + { + if (_size) { _bytes = new Byte[_size]; } + } + + Storage(size_t size, Byte* bytes) : + _size(size) + { + if (_size && _bytes) { _bytes = bytes; } + } + }; + + // Header + struct Header { + static const size_t IDENTIFIER_LENGTH = 12; + using Identifier = std::array; + static const Identifier IDENTIFIER; + + static const uint32_t ENDIAN_TEST = 0x04030201; + static const uint32_t REVERSE_ENDIAN_TEST = 0x01020304; + + Header(); + + Byte identifier[IDENTIFIER_LENGTH]; + uint32_t endianness { ENDIAN_TEST }; + uint32_t glType; + uint32_t glTypeSize; + uint32_t glFormat; + uint32_t glInternalFormat; + uint32_t glBaseInternalFormat; + uint32_t pixelWidth; + uint32_t pixelHeight; + uint32_t pixelDepth; + uint32_t numberOfArrayElements; + uint32_t numberOfFaces; + uint32_t numberOfMipmapLevels; + uint32_t bytesOfKeyValueData; + + uint32_t evalMaxDimension() const; + uint32_t evalMaxLevel() const; + uint32_t evalPixelWidth(uint32_t level) const; + uint32_t evalPixelHeight(uint32_t level) const; + uint32_t evalPixelDepth(uint32_t level) const; + + size_t evalPixelSize() const; + size_t evalRowSize(uint32_t level) const; + size_t evalFaceSize(uint32_t level) const; + size_t evalImageSize(uint32_t level) const; + + }; + + // Key Values + using KeyValue = std::pair; + using KeyValues = std::list; + + + struct Mip { + uint32_t imageSize; + const Byte* _bytes; + }; + using Mips = std::vector; + + class KTX { + void resetStorage(Storage* src); + + public: + + KTX(); + + bool read(const Storage* src); + bool read(Storage* src); + + std::unique_ptr _storage; + + const Header* getHeader() const; + const Byte* getKeyValueData() const; + + KeyValues _keyValues; + + Mips _mips; + + static bool checkStorageHeader(const Storage& storage); + static KTX* create(const Storage* src); + }; + +} + +#endif // hifi_ktx_KTX_h diff --git a/libraries/ktx/src/ktx/Reader.cpp b/libraries/ktx/src/ktx/Reader.cpp new file mode 100644 index 0000000000..8ec4de7686 --- /dev/null +++ b/libraries/ktx/src/ktx/Reader.cpp @@ -0,0 +1,159 @@ +// +// Reader.cpp +// ktx/src/ktx +// +// Created by Zach Pomerantz on 2/08/2017. +// Copyright 2017 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 "KTX.h" + +#include +#include + +namespace ktx { + class Exception: public std::exception { + public: + Exception(std::string explanation) : _explanation(explanation) {} + const char* what() const override { + return ("KTX deserialization error: " + _explanation).c_str(); + } + private: + std::string _explanation; + }; + + bool checkEndianness(uint32_t endianness, bool& matching) { + switch (endianness) { + case Header::ENDIAN_TEST: { + matching = true; + return true; + } + break; + case Header::REVERSE_ENDIAN_TEST: + { + matching = false; + return true; + } + break; + default: + return false; + } + } + + bool checkIdentifier(const Byte* identifier) { + return memcmp(identifier, Header::IDENTIFIER.data(), Header::IDENTIFIER_LENGTH); + } + + KeyValues getKeyValues(size_t length, const Byte* src) { + KeyValues keyValues; + size_t offset = 0; + + while (offset < length) { + // determine byte size + uint32_t keyValueByteSize; + memcpy(&keyValueByteSize, src, sizeof(uint32_t)); + if (keyValueByteSize > length - offset) { + throw Exception("invalid key-value size"); + } + + // find the first null character \0 + int keyLength = 0; + while (reinterpret_cast(src[++keyLength]) != '\0') { + if (keyLength == keyValueByteSize) { + // key must be null-terminated, and there must be space for the value + throw Exception("invalid key-value " + std::string(reinterpret_cast(src), keyLength)); + } + } + + // populate the key-value + keyValues.emplace_back( + std::move(std::string(reinterpret_cast(src), keyLength)), + std::move(std::string(reinterpret_cast(src + keyLength), keyValueByteSize - keyLength))); + + // advance offset/src + uint32_t keyValuePadding = 3 - ((keyValueByteSize + 3) % 4); + offset += keyValueByteSize + keyValuePadding; + src += keyValueByteSize + keyValuePadding; + } + + return keyValues; + } + + bool KTX::read(Storage* src) { + resetStorage(src); + + return true; + } + + bool KTX::checkStorageHeader(const Storage& src) { + try { + size_t srcSize = src.size(); + const uint8_t* srcBytes = src.data(); + + // validation + if (srcSize < sizeof(Header)) { + throw Exception("length is too short for header"); + } + const Header* header = reinterpret_cast(srcBytes); + + if (!checkIdentifier(header->identifier)) { + throw Exception("identifier field invalid"); + } + + bool endianMatch { true }; + if (!checkEndianness(header->endianness, endianMatch)) { + throw Exception("endianness field has invalid value"); + } + + // TODO: endian conversion if !endianMatch - for now, this is for local use and is unnecessary + + + // TODO: calculated bytesOfTexData + if (srcSize < (sizeof(Header) + header->bytesOfKeyValueData)) { + throw Exception("length is too short for metadata"); + } + + size_t bytesOfTexData = 0; + if (srcSize < (sizeof(Header) + header->bytesOfKeyValueData + bytesOfTexData)) { + + throw Exception("length is too short for data"); + } + + + // read metadata + KeyValues keyValues = getKeyValues(header->bytesOfKeyValueData, srcBytes + sizeof(Header)); + + // prepare gpu::Texture using header & key-values + // TODO + + // read data + // TODO + + return true; + } catch (Exception& e) { + qWarning(e.what()); + return false; + } + } + + KTX* KTX::create(const Storage* data) { + try { + if (!checkStorageHeader(*data)) { + + } + + auto result = new KTX(); + result->resetStorage(const_cast(data)); + + // read metadata + KeyValues keyValues = getKeyValues(result->getHeader()->bytesOfKeyValueData, result->getKeyValueData()); + + return nullptr; + } catch (Exception& e) { + qWarning(e.what()); + return nullptr; + } + } +} \ No newline at end of file diff --git a/libraries/ktx/src/ktx/Writer.cpp b/libraries/ktx/src/ktx/Writer.cpp new file mode 100644 index 0000000000..d502a4a29a --- /dev/null +++ b/libraries/ktx/src/ktx/Writer.cpp @@ -0,0 +1,21 @@ +// +// Writer.cpp +// ktx/src/ktx +// +// Created by Zach Pomerantz on 2/08/2017. +// Copyright 2017 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 "KTX.h" + + +namespace ktx { + /* size_t serialize(const gpu::Texture& texture, uint8_t* data) { + return 0; + }*/ + /* KTX serialize(const gpu::Texture& texture) { + return KTX(0, nullptr); + }*/ +} From 894f1b8e665cd6f428a9a3a9bf2c2381e9bd943d Mon Sep 17 00:00:00 2001 From: samcake Date: Wed, 15 Feb 2017 16:24:06 -0800 Subject: [PATCH 16/28] DOne with the parsing side of things --- libraries/ktx/src/ktx/KTX.cpp | 29 +++++++++++- libraries/ktx/src/ktx/KTX.h | 49 +++++++++++++++------ libraries/ktx/src/ktx/Reader.cpp | 75 ++++++++++++++++++++++---------- 3 files changed, 114 insertions(+), 39 deletions(-) diff --git a/libraries/ktx/src/ktx/KTX.cpp b/libraries/ktx/src/ktx/KTX.cpp index 2d5e0bc812..4ce4c94c69 100644 --- a/libraries/ktx/src/ktx/KTX.cpp +++ b/libraries/ktx/src/ktx/KTX.cpp @@ -49,8 +49,8 @@ size_t Header::evalRowSize(uint32_t level) const { auto pixelWidth = evalPixelWidth(level); auto pixSize = evalPixelSize(); auto netSize = pixelWidth * pixSize; - auto packing = netSize % 4; - return netSize + (packing ? 4 - packing : 0); + auto packing = netSize % PACKING_SIZE; + return netSize + (packing ? PACKING_SIZE - packing : 0); } size_t Header::evalFaceSize(uint32_t level) const { auto pixelHeight = evalPixelHeight(level); @@ -83,6 +83,23 @@ const Header* KTX::getHeader() const { } } + +size_t KTX::getKeyValueDataSize() const { + if (_storage) { + return getHeader()->bytesOfKeyValueData; + } else { + return 0; + } +} + +size_t KTX::getTexelsDataSize() const { + if (_storage) { + return _storage->size() - sizeof(Header) + getKeyValueDataSize(); + } else { + return 0; + } +} + const Byte* KTX::getKeyValueData() const { if (_storage) { return (_storage->_bytes + sizeof(Header)); @@ -90,3 +107,11 @@ const Byte* KTX::getKeyValueData() const { return nullptr; } } + +const Byte* KTX::getTexelsData() const { + if (_storage) { + return (_storage->_bytes + sizeof(Header) + getKeyValueDataSize()); + } else { + return nullptr; + } +} diff --git a/libraries/ktx/src/ktx/KTX.h b/libraries/ktx/src/ktx/KTX.h index 003d3f0d1b..b6a9240972 100644 --- a/libraries/ktx/src/ktx/KTX.h +++ b/libraries/ktx/src/ktx/KTX.h @@ -66,6 +66,7 @@ end namespace ktx { + const uint32_t PACKING_SIZE { sizeof(uint32_t) }; enum GLType : uint32_t { COMPRESSED_TYPE = 0, @@ -317,6 +318,17 @@ namespace ktx { { if (_size && _bytes) { _bytes = bytes; } } + Storage(size_t size, const Byte* bytes) : + Storage(size) + { + if (_size && _bytes && bytes) { + memcpy(_bytes, bytes, size); + } + } + Storage(const Storage& src) : + Storage(src.size(), src.data()) + {} + }; // Header @@ -364,8 +376,14 @@ namespace ktx { struct Mip { - uint32_t imageSize; + uint32_t _imageSize; + uint32_t _padding; const Byte* _bytes; + + Mip(uint32_t imageSize, uint32_t padding, const Byte* bytes) : + _imageSize(imageSize), + _padding(padding), + _bytes(bytes) {} }; using Mips = std::vector; @@ -375,21 +393,26 @@ namespace ktx { public: KTX(); + ~KTX(); - bool read(const Storage* src); - bool read(Storage* src); - - std::unique_ptr _storage; - - const Header* getHeader() const; - const Byte* getKeyValueData() const; - - KeyValues _keyValues; - - Mips _mips; + // parse a block of memory and create a KTX object from it + static std::unique_ptr create(const Storage& src); + static std::unique_ptr create(std::unique_ptr& src); static bool checkStorageHeader(const Storage& storage); - static KTX* create(const Storage* src); + + // Access raw pointers to the main sections of the KTX + const Header* getHeader() const; + const Byte* getKeyValueData() const; + const Byte* getTexelsData() const; + + size_t getKeyValueDataSize() const; + size_t getTexelsDataSize() const; + + + std::unique_ptr _storage; + KeyValues _keyValues; + Mips _mips; }; } diff --git a/libraries/ktx/src/ktx/Reader.cpp b/libraries/ktx/src/ktx/Reader.cpp index 8ec4de7686..ff682d5bdb 100644 --- a/libraries/ktx/src/ktx/Reader.cpp +++ b/libraries/ktx/src/ktx/Reader.cpp @@ -73,7 +73,7 @@ namespace ktx { std::move(std::string(reinterpret_cast(src + keyLength), keyValueByteSize - keyLength))); // advance offset/src - uint32_t keyValuePadding = 3 - ((keyValueByteSize + 3) % 4); + uint32_t keyValuePadding = 3 - ((keyValueByteSize + 3) % PACKING_SIZE); offset += keyValueByteSize + keyValuePadding; src += keyValueByteSize + keyValuePadding; } @@ -81,16 +81,39 @@ namespace ktx { return keyValues; } - bool KTX::read(Storage* src) { - resetStorage(src); + Mips getMipsTable(const Header& header, size_t mipsDataSize, const Byte* mipsData) { + Mips mips; + auto currentPtr = mipsData; + auto numMips = header.numberOfMipmapLevels + 1; - return true; + // Keep identifying new mip as long as we can at list query the next imageSize + while ((currentPtr - mipsData) + sizeof(uint32_t) <= (mipsDataSize)) { + + // Grab the imageSize coming up + auto imageSize = *reinterpret_cast(currentPtr); + currentPtr += sizeof(uint32_t); + + // If enough data ahead then capture the pointer + if ((currentPtr - mipsData) + imageSize <= (mipsDataSize)) { + auto padding = imageSize % PACKING_SIZE; + padding = (padding ? 4 - padding : 0); + + mips.emplace_back(Mip(imageSize, padding, currentPtr)); + + currentPtr += imageSize + padding; + } else { + break; + } + } + + return mips; } + bool KTX::checkStorageHeader(const Storage& src) { try { - size_t srcSize = src.size(); - const uint8_t* srcBytes = src.data(); + auto srcSize = src.size(); + auto srcBytes = src.data(); // validation if (srcSize < sizeof(Header)) { @@ -121,16 +144,6 @@ namespace ktx { throw Exception("length is too short for data"); } - - // read metadata - KeyValues keyValues = getKeyValues(header->bytesOfKeyValueData, srcBytes + sizeof(Header)); - - // prepare gpu::Texture using header & key-values - // TODO - - // read data - // TODO - return true; } catch (Exception& e) { qWarning(e.what()); @@ -138,20 +151,34 @@ namespace ktx { } } - KTX* KTX::create(const Storage* data) { + std::unique_ptr KTX::create(const Storage& src) { + auto srcCopy = std::make_unique(src); + + return create(srcCopy); + } + + std::unique_ptr KTX::create(std::unique_ptr& src) { + if (!src) { + return nullptr; + } + try { - if (!checkStorageHeader(*data)) { - + if (!checkStorageHeader(*src)) { + } - auto result = new KTX(); - result->resetStorage(const_cast(data)); + std::unique_ptr result(new KTX()); + result->resetStorage(src.release()); // read metadata - KeyValues keyValues = getKeyValues(result->getHeader()->bytesOfKeyValueData, result->getKeyValueData()); + result->_keyValues = getKeyValues(result->getHeader()->bytesOfKeyValueData, result->getKeyValueData()); - return nullptr; - } catch (Exception& e) { + // populate mip table + result->_mips = getMipsTable(*result->getHeader(), result->getTexelsDataSize(), result->getTexelsData()); + + return result; + } + catch (Exception& e) { qWarning(e.what()); return nullptr; } From cf9fc17c4d640041a16a42296e72ea11a3f472bd Mon Sep 17 00:00:00 2001 From: samcake Date: Wed, 15 Feb 2017 17:04:40 -0800 Subject: [PATCH 17/28] Starting to work on the writer side --- libraries/ktx/src/ktx/KTX.cpp | 10 +++++-- libraries/ktx/src/ktx/KTX.h | 23 ++++++++++------ libraries/ktx/src/ktx/Reader.cpp | 42 ++++++++++++++--------------- libraries/ktx/src/ktx/Writer.cpp | 46 +++++++++++++++++++++++++++----- 4 files changed, 84 insertions(+), 37 deletions(-) diff --git a/libraries/ktx/src/ktx/KTX.cpp b/libraries/ktx/src/ktx/KTX.cpp index 4ce4c94c69..d2872e76b5 100644 --- a/libraries/ktx/src/ktx/KTX.cpp +++ b/libraries/ktx/src/ktx/KTX.cpp @@ -15,6 +15,11 @@ using namespace ktx; +uint32_t evalPadding(size_t byteSize) { + auto padding = byteSize % PACKING_SIZE; + return (padding ? PACKING_SIZE - padding : 0); +} + const Header::Identifier ktx::Header::IDENTIFIER {{ 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A }}; @@ -49,8 +54,8 @@ size_t Header::evalRowSize(uint32_t level) const { auto pixelWidth = evalPixelWidth(level); auto pixSize = evalPixelSize(); auto netSize = pixelWidth * pixSize; - auto packing = netSize % PACKING_SIZE; - return netSize + (packing ? PACKING_SIZE - packing : 0); + auto padding = evalPadding(netSize); + return netSize + padding; } size_t Header::evalFaceSize(uint32_t level) const { auto pixelHeight = evalPixelHeight(level); @@ -115,3 +120,4 @@ const Byte* KTX::getTexelsData() const { return nullptr; } } + diff --git a/libraries/ktx/src/ktx/KTX.h b/libraries/ktx/src/ktx/KTX.h index b6a9240972..5b72c4c1ce 100644 --- a/libraries/ktx/src/ktx/KTX.h +++ b/libraries/ktx/src/ktx/KTX.h @@ -67,6 +67,8 @@ end namespace ktx { const uint32_t PACKING_SIZE { sizeof(uint32_t) }; + using Byte = uint8_t; + uint32_t evalPadding(size_t byteSize); enum GLType : uint32_t { COMPRESSED_TYPE = 0, @@ -289,7 +291,6 @@ namespace ktx { NUM_CUBEMAPFACES = 6, }; - using Byte = uint8_t; // Chunk of data struct Storage { @@ -375,31 +376,37 @@ namespace ktx { using KeyValues = std::list; - struct Mip { + struct Image { uint32_t _imageSize; uint32_t _padding; const Byte* _bytes; - Mip(uint32_t imageSize, uint32_t padding, const Byte* bytes) : + Image(uint32_t imageSize, uint32_t padding, const Byte* bytes) : _imageSize(imageSize), _padding(padding), _bytes(bytes) {} }; - using Mips = std::vector; + using Images = std::vector; + class KTX { void resetStorage(Storage* src); + KTX(); public: - KTX(); ~KTX(); - // parse a block of memory and create a KTX object from it + // Define a KTX object manually to write it somewhere (in a file on disk?) + // This path allocate the Storage where to store header, keyvalues and copy mips + // Then COPY all the data + static std::unique_ptr create(const Header& header, const KeyValues& keyValues, const Images& images); + + // Parse a block of memory and create a KTX object from it static std::unique_ptr create(const Storage& src); static std::unique_ptr create(std::unique_ptr& src); - static bool checkStorageHeader(const Storage& storage); + static bool checkHeaderFromStorage(const Storage& storage); // Access raw pointers to the main sections of the KTX const Header* getHeader() const; @@ -412,7 +419,7 @@ namespace ktx { std::unique_ptr _storage; KeyValues _keyValues; - Mips _mips; + Images _images; }; } diff --git a/libraries/ktx/src/ktx/Reader.cpp b/libraries/ktx/src/ktx/Reader.cpp index ff682d5bdb..2b45d30786 100644 --- a/libraries/ktx/src/ktx/Reader.cpp +++ b/libraries/ktx/src/ktx/Reader.cpp @@ -14,9 +14,9 @@ #include namespace ktx { - class Exception: public std::exception { + class ReaderException: public std::exception { public: - Exception(std::string explanation) : _explanation(explanation) {} + ReaderException(std::string explanation) : _explanation(explanation) {} const char* what() const override { return ("KTX deserialization error: " + _explanation).c_str(); } @@ -55,7 +55,7 @@ namespace ktx { uint32_t keyValueByteSize; memcpy(&keyValueByteSize, src, sizeof(uint32_t)); if (keyValueByteSize > length - offset) { - throw Exception("invalid key-value size"); + throw ReaderException("invalid key-value size"); } // find the first null character \0 @@ -63,7 +63,7 @@ namespace ktx { while (reinterpret_cast(src[++keyLength]) != '\0') { if (keyLength == keyValueByteSize) { // key must be null-terminated, and there must be space for the value - throw Exception("invalid key-value " + std::string(reinterpret_cast(src), keyLength)); + throw ReaderException("invalid key-value " + std::string(reinterpret_cast(src), keyLength)); } } @@ -81,8 +81,8 @@ namespace ktx { return keyValues; } - Mips getMipsTable(const Header& header, size_t mipsDataSize, const Byte* mipsData) { - Mips mips; + Images getImagesTable(const Header& header, size_t mipsDataSize, const Byte* mipsData) { + Images images; auto currentPtr = mipsData; auto numMips = header.numberOfMipmapLevels + 1; @@ -95,10 +95,9 @@ namespace ktx { // If enough data ahead then capture the pointer if ((currentPtr - mipsData) + imageSize <= (mipsDataSize)) { - auto padding = imageSize % PACKING_SIZE; - padding = (padding ? 4 - padding : 0); + auto padding = evalPadding(imageSize); - mips.emplace_back(Mip(imageSize, padding, currentPtr)); + images.emplace_back(Image(imageSize, padding, currentPtr)); currentPtr += imageSize + padding; } else { @@ -106,28 +105,28 @@ namespace ktx { } } - return mips; + return images; } - bool KTX::checkStorageHeader(const Storage& src) { + bool KTX::checkHeaderFromStorage(const Storage& src) { try { auto srcSize = src.size(); auto srcBytes = src.data(); // validation if (srcSize < sizeof(Header)) { - throw Exception("length is too short for header"); + throw ReaderException("length is too short for header"); } const Header* header = reinterpret_cast(srcBytes); if (!checkIdentifier(header->identifier)) { - throw Exception("identifier field invalid"); + throw ReaderException("identifier field invalid"); } bool endianMatch { true }; if (!checkEndianness(header->endianness, endianMatch)) { - throw Exception("endianness field has invalid value"); + throw ReaderException("endianness field has invalid value"); } // TODO: endian conversion if !endianMatch - for now, this is for local use and is unnecessary @@ -135,17 +134,18 @@ namespace ktx { // TODO: calculated bytesOfTexData if (srcSize < (sizeof(Header) + header->bytesOfKeyValueData)) { - throw Exception("length is too short for metadata"); + throw ReaderException("length is too short for metadata"); } size_t bytesOfTexData = 0; if (srcSize < (sizeof(Header) + header->bytesOfKeyValueData + bytesOfTexData)) { - throw Exception("length is too short for data"); + throw ReaderException("length is too short for data"); } return true; - } catch (Exception& e) { + } + catch (ReaderException& e) { qWarning(e.what()); return false; } @@ -163,7 +163,7 @@ namespace ktx { } try { - if (!checkStorageHeader(*src)) { + if (!checkHeaderFromStorage(*src)) { } @@ -173,12 +173,12 @@ namespace ktx { // read metadata result->_keyValues = getKeyValues(result->getHeader()->bytesOfKeyValueData, result->getKeyValueData()); - // populate mip table - result->_mips = getMipsTable(*result->getHeader(), result->getTexelsDataSize(), result->getTexelsData()); + // populate image table + result->_images = getImagesTable(*result->getHeader(), result->getTexelsDataSize(), result->getTexelsData()); return result; } - catch (Exception& e) { + catch (ReaderException& e) { qWarning(e.what()); return nullptr; } diff --git a/libraries/ktx/src/ktx/Writer.cpp b/libraries/ktx/src/ktx/Writer.cpp index d502a4a29a..a2b3d55178 100644 --- a/libraries/ktx/src/ktx/Writer.cpp +++ b/libraries/ktx/src/ktx/Writer.cpp @@ -12,10 +12,44 @@ namespace ktx { - /* size_t serialize(const gpu::Texture& texture, uint8_t* data) { - return 0; - }*/ - /* KTX serialize(const gpu::Texture& texture) { - return KTX(0, nullptr); - }*/ + + class WriterException : public std::exception { + public: + WriterException(std::string explanation) : _explanation(explanation) {} + const char* what() const override { + return ("KTX serialization error: " + _explanation).c_str(); + } + private: + std::string _explanation; + }; + + std::unique_ptr generateStorage(const Header& header, const KeyValues& keyValues, const Images& images) { + size_t storageSize = sizeof(Header); + auto numMips = header.numberOfMipmapLevels + 1; + + for (uint32_t l = 0; l < numMips; l++) { + if (images.size() > l) { + + storageSize += images[l]._imageSize; + storageSize += images[l]._imageSize; + } + + } + + } + + std::unique_ptr KTX::create(const Header& header, const KeyValues& keyValues, const Images& images) { + + std::unique_ptr result(new KTX()); + result->resetStorage(generateStorage(header, keyValues, images).release()); + + // read metadata + result->_keyValues = keyValues; + + // populate image table + result->_images = images; + + return result; + } + } From d56f982decec045c8f012bf33af7532b1edfc123 Mon Sep 17 00:00:00 2001 From: sam Date: Thu, 16 Feb 2017 02:45:53 -0800 Subject: [PATCH 18/28] Maybe saving the first ktx textures, testing the save pipeline --- interface/CMakeLists.txt | 2 +- libraries/gpu/src/gpu/Texture.cpp | 25 +++++++++ libraries/gpu/src/gpu/Texture.h | 8 +++ libraries/ktx/src/ktx/KTX.cpp | 38 ++++++++----- libraries/ktx/src/ktx/KTX.h | 71 ++++++++++++++++-------- libraries/ktx/src/ktx/Reader.cpp | 6 +- libraries/ktx/src/ktx/Writer.cpp | 55 ++++++++++++++++-- libraries/model/CMakeLists.txt | 2 +- libraries/model/src/model/TextureMap.cpp | 17 ++++++ libraries/render-utils/CMakeLists.txt | 2 +- libraries/render/CMakeLists.txt | 2 +- 11 files changed, 181 insertions(+), 47 deletions(-) diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 868a2cf933..03b7a6b7e9 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -178,7 +178,7 @@ endif() # link required hifi libraries link_hifi_libraries( - shared octree gpu gl gpu-gl procedural model render + shared octree ktx gpu gl gpu-gl procedural model render recording fbx networking model-networking entities avatars audio audio-client animation script-engine physics render-utils entities-renderer ui auto-updater diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index 9db3fb60c3..21c32cc55e 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -1006,3 +1006,28 @@ Texture::ExternalUpdates Texture::getUpdates() const { } return result; } + +#include + +ktx::KTXUniquePointer Texture::serialize(const Texture& texture) { + + ktx::Header header; + header.setUncompressed(ktx::GLType::UNSIGNED_BYTE, 4, ktx::GLFormat::BGRA, ktx::GLInternalFormat_Uncompressed::RGBA8, ktx::GLBaseInternalFormat::RGBA); + header.pixelWidth = texture.getWidth(); + header.pixelHeight = texture.getHeight(); + header.numberOfMipmapLevels = texture.mipLevels(); + + ktx::Images images; + for (int level = 0; level < header.numberOfMipmapLevels; level++) { + auto mip = texture.accessStoredMipFace(level); + if (mip) { + images.emplace_back(ktx::Image(mip->getSize(), 0, mip->readData())); + } + } + + auto ktxBuffer = ktx::KTX::create(header, ktx::KeyValues(), images); + return ktxBuffer; +} +TexturePointer Texture::unserialize(const ktx::KTXUniquePointer& srcData) { + return nullptr; +} diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 3c6c34c68d..fba4c7079f 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -20,6 +20,11 @@ #include "Forward.h" #include "Resource.h" +namespace ktx { + class KTX; + using KTXUniquePointer = std::unique_ptr; +} + namespace gpu { // THe spherical harmonics is a nice tool for cubemap, so if required, the irradiance SH can be automatically generated @@ -482,6 +487,9 @@ public: ExternalUpdates getUpdates() const; + static ktx::KTXUniquePointer serialize(const Texture& texture); + static TexturePointer unserialize(const ktx::KTXUniquePointer& srcData); + protected: const TextureUsageType _usageType; diff --git a/libraries/ktx/src/ktx/KTX.cpp b/libraries/ktx/src/ktx/KTX.cpp index d2872e76b5..f8f315d784 100644 --- a/libraries/ktx/src/ktx/KTX.cpp +++ b/libraries/ktx/src/ktx/KTX.cpp @@ -15,11 +15,12 @@ using namespace ktx; -uint32_t evalPadding(size_t byteSize) { +uint32_t Header::evalPadding(size_t byteSize) { auto padding = byteSize % PACKING_SIZE; - return (padding ? PACKING_SIZE - padding : 0); + return (uint32_t) (padding ? PACKING_SIZE - padding : 0); } + const Header::Identifier ktx::Header::IDENTIFIER {{ 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A }}; @@ -29,7 +30,7 @@ Header::Header() { } uint32_t Header::evalMaxDimension() const { - return std::max(pixelWidth, std::max(pixelHeight, pixelDepth)); + return std::max(getPixelWidth(), std::max(getPixelHeight(), getPixelDepth())); } uint32_t Header::evalMaxLevel() const { @@ -37,13 +38,13 @@ uint32_t Header::evalMaxLevel() const { } uint32_t Header::evalPixelWidth(uint32_t level) const { - return std::max(pixelWidth >> level, 1U); + return std::max(getPixelWidth() >> level, 1U); } uint32_t Header::evalPixelHeight(uint32_t level) const { - return std::max(pixelHeight >> level, 1U); + return std::max(getPixelHeight() >> level, 1U); } uint32_t Header::evalPixelDepth(uint32_t level) const { - return std::max(pixelDepth >> level, 1U); + return std::max(getPixelDepth() >> level, 1U); } size_t Header::evalPixelSize() const { @@ -51,24 +52,24 @@ size_t Header::evalPixelSize() const { } size_t Header::evalRowSize(uint32_t level) const { - auto pixelWidth = evalPixelWidth(level); + auto pixWidth = evalPixelWidth(level); auto pixSize = evalPixelSize(); - auto netSize = pixelWidth * pixSize; + auto netSize = pixWidth * pixSize; auto padding = evalPadding(netSize); return netSize + padding; } size_t Header::evalFaceSize(uint32_t level) const { - auto pixelHeight = evalPixelHeight(level); - auto pixelDepth = evalPixelDepth(level); + auto pixHeight = evalPixelHeight(level); + auto pixDepth = evalPixelDepth(level); auto rowSize = evalRowSize(level); - return pixelDepth * pixelHeight * rowSize; + return pixDepth * pixHeight * rowSize; } size_t Header::evalImageSize(uint32_t level) const { auto faceSize = evalFaceSize(level); if (numberOfFaces == 6 && numberOfArrayElements == 0) { return faceSize; } else { - return (numberOfArrayElements * numberOfFaces * faceSize); + return (getNumberOfSlices() * numberOfFaces * faceSize); } } @@ -76,6 +77,9 @@ size_t Header::evalImageSize(uint32_t level) const { KTX::KTX() { } +KTX::~KTX() { +} + void KTX::resetStorage(Storage* storage) { _storage.reset(storage); } @@ -99,7 +103,8 @@ size_t KTX::getKeyValueDataSize() const { size_t KTX::getTexelsDataSize() const { if (_storage) { - return _storage->size() - sizeof(Header) + getKeyValueDataSize(); + //return _storage->size() - (sizeof(Header) + getKeyValueDataSize()); + return (_storage->_bytes + _storage->_size) - getTexelsData(); } else { return 0; } @@ -121,3 +126,10 @@ const Byte* KTX::getTexelsData() const { } } +Byte* KTX::getTexelsData() { + if (_storage) { + return (_storage->_bytes + sizeof(Header) + getKeyValueDataSize()); + } else { + return nullptr; + } +} diff --git a/libraries/ktx/src/ktx/KTX.h b/libraries/ktx/src/ktx/KTX.h index 5b72c4c1ce..1006124693 100644 --- a/libraries/ktx/src/ktx/KTX.h +++ b/libraries/ktx/src/ktx/KTX.h @@ -68,9 +68,8 @@ end namespace ktx { const uint32_t PACKING_SIZE { sizeof(uint32_t) }; using Byte = uint8_t; - uint32_t evalPadding(size_t byteSize); - enum GLType : uint32_t { + enum class GLType : uint32_t { COMPRESSED_TYPE = 0, // GL 4.4 Table 8.2 @@ -102,7 +101,7 @@ namespace ktx { NUM_GLTYPES = 25, }; - enum GLFormat : uint32_t { + enum class GLFormat : uint32_t { COMPRESSED_FORMAT = 0, // GL 4.4 Table 8.3 @@ -131,7 +130,7 @@ namespace ktx { NUM_GLFORMATS = 20, }; - enum GLInternalFormat_Uncompressed : uint32_t { + enum class GLInternalFormat_Uncompressed : uint32_t { // GL 4.4 Table 8.12 R8 = 0x8229, R8_SNORM = 0x8F94, @@ -233,7 +232,7 @@ namespace ktx { NUM_UNCOMPRESSED_GLINTERNALFORMATS = 74, }; - enum GLInternalFormat_Compressed : uint32_t { + enum class GLInternalFormat_Compressed : uint32_t { // GL 4.4 Table 8.14 COMPRESSED_RED = 0x8225, COMPRESSED_RG = 0x8226, @@ -268,15 +267,15 @@ namespace ktx { NUM_COMPRESSED_GLINTERNALFORMATS = 24, }; - enum GLBaseInternalFormat : uint32_t { + enum class GLBaseInternalFormat : uint32_t { // GL 4.4 Table 8.11 - BIF_DEPTH_COMPONENT = 0x1902, - BIF_DEPTH_STENCIL = 0x84F9, - BIF_RED = 0x1903, - BIF_RG = 0x8227, - BIF_RGB = 0x1907, - BIF_RGBA = 0x1908, - BIF_STENCIL_INDEX = 0x1901, + DEPTH_COMPONENT = 0x1902, + DEPTH_STENCIL = 0x84F9, + RED = 0x1903, + RG = 0x8227, + RGB = 0x1907, + RGBA = 0x1908, + STENCIL_INDEX = 0x1901, NUM_GLBASEINTERNALFORMATS = 7, }; @@ -341,22 +340,33 @@ namespace ktx { static const uint32_t ENDIAN_TEST = 0x04030201; static const uint32_t REVERSE_ENDIAN_TEST = 0x01020304; + static uint32_t evalPadding(size_t byteSize); + Header(); Byte identifier[IDENTIFIER_LENGTH]; uint32_t endianness { ENDIAN_TEST }; + uint32_t glType; - uint32_t glTypeSize; + uint32_t glTypeSize { 0 }; uint32_t glFormat; uint32_t glInternalFormat; uint32_t glBaseInternalFormat; - uint32_t pixelWidth; - uint32_t pixelHeight; - uint32_t pixelDepth; - uint32_t numberOfArrayElements; - uint32_t numberOfFaces; - uint32_t numberOfMipmapLevels; - uint32_t bytesOfKeyValueData; + + uint32_t pixelWidth { 0 }; + uint32_t pixelHeight { 0 }; + uint32_t pixelDepth { 0 }; + uint32_t numberOfArrayElements { 0 }; + uint32_t numberOfFaces { 1 }; + uint32_t numberOfMipmapLevels { 1 }; + + uint32_t bytesOfKeyValueData { 0 }; + + uint32_t getPixelWidth() const { return (pixelWidth ? pixelWidth : 1); } + uint32_t getPixelHeight() const { return (pixelHeight ? pixelHeight : 1); } + uint32_t getPixelDepth() const { return (pixelDepth ? pixelDepth : 1); } + uint32_t getNumberOfSlices() const { return (numberOfArrayElements ? numberOfArrayElements : 1); } + uint32_t getNumberOfLevels() const { return (numberOfMipmapLevels ? numberOfMipmapLevels : 1); } uint32_t evalMaxDimension() const; uint32_t evalMaxLevel() const; @@ -369,6 +379,20 @@ namespace ktx { size_t evalFaceSize(uint32_t level) const; size_t evalImageSize(uint32_t level) const; + void setUncompressed(GLType type, uint32_t typeSize, GLFormat format, GLInternalFormat_Uncompressed internalFormat, GLBaseInternalFormat baseInternalFormat) { + glType = (uint32_t) type; + glTypeSize = 0; + glFormat = (uint32_t) format; + glInternalFormat = (uint32_t) internalFormat; + glBaseInternalFormat = (uint32_t) baseInternalFormat; + } + void setCompressed(GLInternalFormat_Compressed internalFormat, GLBaseInternalFormat baseInternalFormat) { + glType = (uint32_t) GLType::COMPRESSED_TYPE; + glTypeSize = 1; + glFormat = (uint32_t) GLFormat::COMPRESSED_FORMAT; + glInternalFormat = (uint32_t) internalFormat; + glBaseInternalFormat = (uint32_t) baseInternalFormat; + } }; // Key Values @@ -392,6 +416,9 @@ namespace ktx { class KTX { void resetStorage(Storage* src); + void resetHeader(const Header& header); + void resetImages(const Images& images); + KTX(); public: @@ -412,11 +439,11 @@ namespace ktx { const Header* getHeader() const; const Byte* getKeyValueData() const; const Byte* getTexelsData() const; + Byte* getTexelsData(); size_t getKeyValueDataSize() const; size_t getTexelsDataSize() const; - std::unique_ptr _storage; KeyValues _keyValues; Images _images; diff --git a/libraries/ktx/src/ktx/Reader.cpp b/libraries/ktx/src/ktx/Reader.cpp index 2b45d30786..9586a9c033 100644 --- a/libraries/ktx/src/ktx/Reader.cpp +++ b/libraries/ktx/src/ktx/Reader.cpp @@ -84,18 +84,18 @@ namespace ktx { Images getImagesTable(const Header& header, size_t mipsDataSize, const Byte* mipsData) { Images images; auto currentPtr = mipsData; - auto numMips = header.numberOfMipmapLevels + 1; + auto numMips = header.getNumberOfLevels(); // Keep identifying new mip as long as we can at list query the next imageSize while ((currentPtr - mipsData) + sizeof(uint32_t) <= (mipsDataSize)) { // Grab the imageSize coming up - auto imageSize = *reinterpret_cast(currentPtr); + size_t imageSize = *reinterpret_cast(currentPtr); currentPtr += sizeof(uint32_t); // If enough data ahead then capture the pointer if ((currentPtr - mipsData) + imageSize <= (mipsDataSize)) { - auto padding = evalPadding(imageSize); + auto padding = Header::evalPadding(imageSize); images.emplace_back(Image(imageSize, padding, currentPtr)); diff --git a/libraries/ktx/src/ktx/Writer.cpp b/libraries/ktx/src/ktx/Writer.cpp index a2b3d55178..ae2dfc2d6d 100644 --- a/libraries/ktx/src/ktx/Writer.cpp +++ b/libraries/ktx/src/ktx/Writer.cpp @@ -25,17 +25,60 @@ namespace ktx { std::unique_ptr generateStorage(const Header& header, const KeyValues& keyValues, const Images& images) { size_t storageSize = sizeof(Header); - auto numMips = header.numberOfMipmapLevels + 1; + auto numMips = header.getNumberOfLevels(); for (uint32_t l = 0; l < numMips; l++) { if (images.size() > l) { - - storageSize += images[l]._imageSize; + storageSize += sizeof(uint32_t); storageSize += images[l]._imageSize; + storageSize += Header::evalPadding(images[l]._imageSize); } - } + std::unique_ptr storage(new Storage(storageSize)); + return storage; + } + + + void KTX::resetHeader(const Header& header) { + if (!_storage) { + return; + } + memcpy(_storage->_bytes, &header, sizeof(Header)); + } + void KTX::resetImages(const Images& srcImages) { + auto imagesDataPtr = getTexelsData(); + if (!imagesDataPtr) { + return; + } + auto allocatedImagesDataSize = getTexelsDataSize(); + size_t currentDataSize = 0; + auto currentPtr = imagesDataPtr; + + _images.clear(); + + + for (uint32_t l = 0; l < srcImages.size(); l++) { + if (currentDataSize + sizeof(uint32_t) < allocatedImagesDataSize) { + size_t imageSize = srcImages[l]._imageSize; + *(reinterpret_cast (currentPtr)) = imageSize; + currentPtr += sizeof(uint32_t); + currentDataSize += sizeof(uint32_t); + + // If enough data ahead then capture the copy source pointer + if (currentDataSize + imageSize <= (allocatedImagesDataSize)) { + + auto copied = memcpy(currentPtr, srcImages[l]._bytes, imageSize); + + auto padding = Header::evalPadding(imageSize); + + _images.emplace_back(Image(imageSize, padding, currentPtr)); + + currentPtr += imageSize + padding; + currentDataSize += imageSize + padding; + } + } + } } std::unique_ptr KTX::create(const Header& header, const KeyValues& keyValues, const Images& images) { @@ -43,11 +86,13 @@ namespace ktx { std::unique_ptr result(new KTX()); result->resetStorage(generateStorage(header, keyValues, images).release()); + result->resetHeader(header); + // read metadata result->_keyValues = keyValues; // populate image table - result->_images = images; + result->resetImages(images); return result; } diff --git a/libraries/model/CMakeLists.txt b/libraries/model/CMakeLists.txt index 63f632e484..021aa3d027 100755 --- a/libraries/model/CMakeLists.txt +++ b/libraries/model/CMakeLists.txt @@ -1,5 +1,5 @@ set(TARGET_NAME model) AUTOSCRIBE_SHADER_LIB(gpu model) setup_hifi_library() -link_hifi_libraries(shared gpu) +link_hifi_libraries(shared ktx gpu) diff --git a/libraries/model/src/model/TextureMap.cpp b/libraries/model/src/model/TextureMap.cpp index 6a9446f2ef..24777b7dd6 100755 --- a/libraries/model/src/model/TextureMap.cpp +++ b/libraries/model/src/model/TextureMap.cpp @@ -10,6 +10,8 @@ // #include "TextureMap.h" +#include + #include #include #include @@ -268,8 +270,23 @@ gpu::Texture* TextureUsage::process2DTextureColorFromImage(const QImage& srcImag if (generateMips) { ::generateMips(theTexture, image, formatMip, false); } + auto theKTX = Texture::serialize(*theTexture); + if (theKTX) { + // save that! + std::string filename("C://temp//ktxCache//texmex"); + filename += std::to_string((size_t) theTexture); + filename += ".ktx"; + + FILE* file = fopen (filename.c_str(),"wb"); + if (file != nullptr) { + fwrite(theKTX->_storage->data(), 1, theKTX->_storage->size(), file); + fclose (file); + } + + } } + return theTexture; } diff --git a/libraries/render-utils/CMakeLists.txt b/libraries/render-utils/CMakeLists.txt index ecafb8f565..3bf389973a 100644 --- a/libraries/render-utils/CMakeLists.txt +++ b/libraries/render-utils/CMakeLists.txt @@ -3,7 +3,7 @@ AUTOSCRIBE_SHADER_LIB(gpu model render) # pull in the resources.qrc file qt5_add_resources(QT_RESOURCES_FILE "${CMAKE_CURRENT_SOURCE_DIR}/res/fonts/fonts.qrc") setup_hifi_library(Widgets OpenGL Network Qml Quick Script) -link_hifi_libraries(shared gpu model model-networking render animation fbx entities) +link_hifi_libraries(shared ktx gpu model model-networking render animation fbx entities) if (NOT ANDROID) target_nsight() diff --git a/libraries/render/CMakeLists.txt b/libraries/render/CMakeLists.txt index 735bb7f086..8fd05bd320 100644 --- a/libraries/render/CMakeLists.txt +++ b/libraries/render/CMakeLists.txt @@ -3,6 +3,6 @@ AUTOSCRIBE_SHADER_LIB(gpu model) setup_hifi_library() # render needs octree only for getAccuracyAngle(float, int) -link_hifi_libraries(shared gpu model octree) +link_hifi_libraries(shared ktx gpu model octree) target_nsight() From 3f0ea1c889f13045a560ecb683c00046e680d29a Mon Sep 17 00:00:00 2001 From: samcake Date: Thu, 16 Feb 2017 13:27:16 -0800 Subject: [PATCH 19/28] Address review comments --- libraries/gpu/src/gpu/Texture_ktx.cpp | 42 ++++++++ libraries/ktx/src/ktx/KTX.h | 17 +++- libraries/ktx/src/ktx/Reader.cpp | 6 +- libraries/ktx/src/ktx/Writer.cpp | 118 ++++++++++++++++------- libraries/model/src/model/TextureMap.cpp | 15 ++- 5 files changed, 156 insertions(+), 42 deletions(-) create mode 100644 libraries/gpu/src/gpu/Texture_ktx.cpp diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp new file mode 100644 index 0000000000..d06454b933 --- /dev/null +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -0,0 +1,42 @@ +// +// Texture_ktx.cpp +// libraries/gpu/src/gpu +// +// Created by Sam Gateau on 2/16/2017. +// Copyright 2014 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 "Texture.h" + +#include +using namespace gpu; + +ktx::KTXUniquePointer Texture::serialize(const Texture& texture) { + + ktx::Header header; + header.setUncompressed(ktx::GLType::UNSIGNED_BYTE, 4, ktx::GLFormat::BGRA, ktx::GLInternalFormat_Uncompressed::RGBA8, ktx::GLBaseInternalFormat::RGBA); + header.pixelWidth = texture.getWidth(); + header.pixelHeight = texture.getHeight(); + header.numberOfMipmapLevels = texture.mipLevels(); + + ktx::Images images; + for (int level = 0; level < header.numberOfMipmapLevels; level++) { + auto mip = texture.accessStoredMipFace(level); + if (mip) { + images.emplace_back(ktx::Image(mip->getSize(), 0, mip->readData())); + } + } + + auto ktxBuffer = ktx::KTX::create(header, images); + return ktxBuffer; +} +TexturePointer Texture::unserialize(const ktx::KTXUniquePointer& srcData) { + + const auto& header = *srcData->getHeader(); + + return nullptr; +} \ No newline at end of file diff --git a/libraries/ktx/src/ktx/KTX.h b/libraries/ktx/src/ktx/KTX.h index 1006124693..41032c8222 100644 --- a/libraries/ktx/src/ktx/KTX.h +++ b/libraries/ktx/src/ktx/KTX.h @@ -427,7 +427,22 @@ namespace ktx { // Define a KTX object manually to write it somewhere (in a file on disk?) // This path allocate the Storage where to store header, keyvalues and copy mips // Then COPY all the data - static std::unique_ptr create(const Header& header, const KeyValues& keyValues, const Images& images); + static std::unique_ptr create(const Header& header, const Images& images, const KeyValues& keyValues = KeyValues()); + + // Instead of creating a full Copy of the src data in a KTX object, the write serialization can be performed with the + // following two functions + // size_t sizeNeeded = KTX::evalStorageSize(header, images); + // + // //allocate a buffer of size "sizeNeeded" or map a file with enough capacity + // Byte* destBytes = new Byte[sizeNeeded]; + // + // // THen perform the writing of the src data to the destinnation buffer + // write(destBytes, sizeNeeded, header, images); + // + // This is exactly what is done in the create function + static size_t evalStorageSize(const Header& header, const Images& images, const KeyValues& keyValues = KeyValues()); + static size_t write(Byte* destBytes, size_t destByteSize, const Header& header, const Images& images, const KeyValues& keyValues = KeyValues()); + static Images writeImages(Byte* destBytes, size_t destByteSize, const Images& images); // Parse a block of memory and create a KTX object from it static std::unique_ptr create(const Storage& src); diff --git a/libraries/ktx/src/ktx/Reader.cpp b/libraries/ktx/src/ktx/Reader.cpp index 9586a9c033..4099aa6ef8 100644 --- a/libraries/ktx/src/ktx/Reader.cpp +++ b/libraries/ktx/src/ktx/Reader.cpp @@ -16,12 +16,12 @@ namespace ktx { class ReaderException: public std::exception { public: - ReaderException(std::string explanation) : _explanation(explanation) {} + ReaderException(const std::string& explanation) : _explanation("KTX deserialization error: " + explanation) {} const char* what() const override { - return ("KTX deserialization error: " + _explanation).c_str(); + return _explanation.c_str(); } private: - std::string _explanation; + const std::string _explanation; }; bool checkEndianness(uint32_t endianness, bool& matching) { diff --git a/libraries/ktx/src/ktx/Writer.cpp b/libraries/ktx/src/ktx/Writer.cpp index ae2dfc2d6d..75836ad5b5 100644 --- a/libraries/ktx/src/ktx/Writer.cpp +++ b/libraries/ktx/src/ktx/Writer.cpp @@ -15,30 +15,14 @@ namespace ktx { class WriterException : public std::exception { public: - WriterException(std::string explanation) : _explanation(explanation) {} + WriterException(const std::string& explanation) : _explanation("KTX serialization error: " + explanation) {} const char* what() const override { - return ("KTX serialization error: " + _explanation).c_str(); + return _explanation.c_str(); } private: - std::string _explanation; + const std::string _explanation; }; - std::unique_ptr generateStorage(const Header& header, const KeyValues& keyValues, const Images& images) { - size_t storageSize = sizeof(Header); - auto numMips = header.getNumberOfLevels(); - - for (uint32_t l = 0; l < numMips; l++) { - if (images.size() > l) { - storageSize += sizeof(uint32_t); - storageSize += images[l]._imageSize; - storageSize += Header::evalPadding(images[l]._imageSize); - } - } - - std::unique_ptr storage(new Storage(storageSize)); - return storage; - } - void KTX::resetHeader(const Header& header) { if (!_storage) { @@ -52,10 +36,84 @@ namespace ktx { return; } auto allocatedImagesDataSize = getTexelsDataSize(); + + // Just copy in our storage + _images = writeImages(imagesDataPtr, allocatedImagesDataSize, srcImages); + } + + std::unique_ptr KTX::create(const Header& header, const Images& images, const KeyValues& keyValues) { + auto storageSize = evalStorageSize(header, images, keyValues); + + std::unique_ptr result(new KTX()); + + result->resetStorage(new Storage(storageSize)); + + result->resetHeader(header); + + // read metadata + result->_keyValues = keyValues; + + // populate image table + result->resetImages(images); + + return result; + } + + size_t KTX::evalStorageSize(const Header& header, const Images& images, const KeyValues& keyValues) { + size_t storageSize = sizeof(Header); + + if (header.bytesOfKeyValueData && !keyValues.empty()) { + + } + + auto numMips = header.getNumberOfLevels(); + for (uint32_t l = 0; l < numMips; l++) { + if (images.size() > l) { + storageSize += sizeof(uint32_t); + storageSize += images[l]._imageSize; + storageSize += Header::evalPadding(images[l]._imageSize); + } + } + return storageSize; + } + + size_t KTX::write(Byte* destBytes, size_t destByteSize, const Header& header, const Images& srcImages, const KeyValues& keyValues) { + // Check again that we have enough destination capacity + if (!destBytes || (destByteSize < evalStorageSize(header, srcImages, keyValues))) { + return 0; + } + + auto currentDestPtr = destBytes; + // Header + auto destHeader = reinterpret_cast(currentDestPtr); + memcpy(currentDestPtr, &header, sizeof(Header)); + currentDestPtr += sizeof(Header); + + // KeyValues + // Skip for now + if (header.bytesOfKeyValueData && !keyValues.empty()) { + + } + destHeader->bytesOfKeyValueData = 0; + currentDestPtr += destHeader->bytesOfKeyValueData; + + // Images + auto destImages = writeImages(currentDestPtr, destByteSize - sizeof(Header) - destHeader->bytesOfKeyValueData, srcImages); + // We chould check here that the amoutn of dest IMages generated is the same as the source + + return destByteSize; + } + + Images KTX::writeImages(Byte* destBytes, size_t destByteSize, const Images& srcImages) { + Images destImages; + auto imagesDataPtr = destBytes; + if (!imagesDataPtr) { + return destImages; + } + auto allocatedImagesDataSize = destByteSize; size_t currentDataSize = 0; auto currentPtr = imagesDataPtr; - _images.clear(); for (uint32_t l = 0; l < srcImages.size(); l++) { @@ -72,29 +130,15 @@ namespace ktx { auto padding = Header::evalPadding(imageSize); - _images.emplace_back(Image(imageSize, padding, currentPtr)); + destImages.emplace_back(Image(imageSize, padding, currentPtr)); currentPtr += imageSize + padding; currentDataSize += imageSize + padding; } } } - } - - std::unique_ptr KTX::create(const Header& header, const KeyValues& keyValues, const Images& images) { - - std::unique_ptr result(new KTX()); - result->resetStorage(generateStorage(header, keyValues, images).release()); - - result->resetHeader(header); - - // read metadata - result->_keyValues = keyValues; - - // populate image table - result->resetImages(images); - - return result; + + return destImages; } } diff --git a/libraries/model/src/model/TextureMap.cpp b/libraries/model/src/model/TextureMap.cpp index 24777b7dd6..cadd985c22 100755 --- a/libraries/model/src/model/TextureMap.cpp +++ b/libraries/model/src/model/TextureMap.cpp @@ -15,6 +15,9 @@ #include #include #include +#include +#include +#include #include @@ -273,7 +276,17 @@ gpu::Texture* TextureUsage::process2DTextureColorFromImage(const QImage& srcImag auto theKTX = Texture::serialize(*theTexture); if (theKTX) { // save that! - std::string filename("C://temp//ktxCache//texmex"); + QString path("hifi_ktx/"); + QFileInfo originalFileInfo(path); + QString docsLocation = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); + path = docsLocation + "/" + path; + QFileInfo info(path); + if (!info.absoluteDir().exists()) { + QString originalRelativePath = originalFileInfo.path(); + QDir(docsLocation).mkpath(originalRelativePath); + } + + std::string filename(path.toStdString()); filename += std::to_string((size_t) theTexture); filename += ".ktx"; From 6771cc31e1c39928c30ea880e088b407e15e254d Mon Sep 17 00:00:00 2001 From: sam Date: Thu, 16 Feb 2017 16:03:55 -0800 Subject: [PATCH 20/28] Adding the reading path --- libraries/gpu/src/gpu/Texture.h | 2 +- libraries/gpu/src/gpu/Texture_ktx.cpp | 16 ++++++++++-- libraries/model/src/model/TextureMap.cpp | 32 +++++++++++++++++++++--- 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index fba4c7079f..acf063e365 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -488,7 +488,7 @@ public: ExternalUpdates getUpdates() const; static ktx::KTXUniquePointer serialize(const Texture& texture); - static TexturePointer unserialize(const ktx::KTXUniquePointer& srcData); + static Texture* unserialize(const ktx::KTXUniquePointer& srcData); protected: const TextureUsageType _usageType; diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp index d06454b933..6380b23903 100644 --- a/libraries/gpu/src/gpu/Texture_ktx.cpp +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -34,9 +34,21 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture) { auto ktxBuffer = ktx::KTX::create(header, images); return ktxBuffer; } -TexturePointer Texture::unserialize(const ktx::KTXUniquePointer& srcData) { +Texture* Texture::unserialize(const ktx::KTXUniquePointer& srcData) { + if (!srcData) { + return nullptr; + } const auto& header = *srcData->getHeader(); - return nullptr; + Format pixelFormat = Format::COLOR_RGBA_32; + + auto tex = Texture::create2D(pixelFormat, header.getPixelWidth(), header.getPixelHeight()); + uint16_t level = 0; + for (auto& image : srcData->_images) { + tex->assignStoredMip(level, pixelFormat, image._imageSize, image._bytes); + level++; + } + + return tex; } \ No newline at end of file diff --git a/libraries/model/src/model/TextureMap.cpp b/libraries/model/src/model/TextureMap.cpp index cadd985c22..4f8a43412e 100755 --- a/libraries/model/src/model/TextureMap.cpp +++ b/libraries/model/src/model/TextureMap.cpp @@ -290,12 +290,36 @@ gpu::Texture* TextureUsage::process2DTextureColorFromImage(const QImage& srcImag filename += std::to_string((size_t) theTexture); filename += ".ktx"; - FILE* file = fopen (filename.c_str(),"wb"); - if (file != nullptr) { - fwrite(theKTX->_storage->data(), 1, theKTX->_storage->size(), file); - fclose (file); + { + FILE* file = fopen (filename.c_str(),"wb"); + if (file != nullptr) { + fwrite(theKTX->_storage->data(), 1, theKTX->_storage->size(), file); + fclose (file); + } } + { + FILE* file = fopen (filename.c_str(),"rb"); + if (file != nullptr) { + // obtain file size: + fseek (file , 0 , SEEK_END); + auto size = ftell(file); + rewind(file); + + std::unique_ptr storage(new ktx::Storage(size)); + fread(storage->_bytes, 1, storage->_size, file); + fclose (file); + + //then create a new texture out of the ktx + auto theNewTexure = Texture::unserialize(ktx::KTX::create(storage)); + + if (theNewTexure) { + auto srcTexture = theTexture; + theTexture = theNewTexure; + delete srcTexture; + } + } + } } } From 0d2e764bfd26cea93c5fdb1f4c4b28969be14bff Mon Sep 17 00:00:00 2001 From: sam Date: Thu, 16 Feb 2017 17:28:02 -0800 Subject: [PATCH 21/28] CLeaning the read case --- libraries/gpu/src/gpu/Format.cpp | 4 + libraries/gpu/src/gpu/Format.h | 2 + libraries/gpu/src/gpu/Texture_ktx.cpp | 7 +- libraries/ktx/src/ktx/KTX.h | 4 +- libraries/ktx/src/ktx/Reader.cpp | 105 +++++++++++--------------- 5 files changed, 57 insertions(+), 65 deletions(-) diff --git a/libraries/gpu/src/gpu/Format.cpp b/libraries/gpu/src/gpu/Format.cpp index 2a8185bf94..0f5e85c907 100644 --- a/libraries/gpu/src/gpu/Format.cpp +++ b/libraries/gpu/src/gpu/Format.cpp @@ -12,6 +12,10 @@ using namespace gpu; const Element Element::COLOR_RGBA_32{ VEC4, NUINT8, RGBA }; const Element Element::COLOR_SRGBA_32{ VEC4, NUINT8, SRGBA }; + +const Element Element::COLOR_BGRA_32{ VEC4, NUINT8, BGRA }; +const Element Element::COLOR_SBGRA_32{ VEC4, NUINT8, SBGRA }; + const Element Element::COLOR_R11G11B10{ SCALAR, FLOAT, R11G11B10 }; const Element Element::VEC4F_COLOR_RGBA{ VEC4, FLOAT, RGBA }; const Element Element::VEC2F_UV{ VEC2, FLOAT, UV }; diff --git a/libraries/gpu/src/gpu/Format.h b/libraries/gpu/src/gpu/Format.h index 13809a41e6..c7dc2eed8d 100644 --- a/libraries/gpu/src/gpu/Format.h +++ b/libraries/gpu/src/gpu/Format.h @@ -229,6 +229,8 @@ public: static const Element COLOR_RGBA_32; static const Element COLOR_SRGBA_32; + static const Element COLOR_BGRA_32; + static const Element COLOR_SBGRA_32; static const Element COLOR_R11G11B10; static const Element VEC4F_COLOR_RGBA; static const Element VEC2F_UV; diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp index 6380b23903..623a3493da 100644 --- a/libraries/gpu/src/gpu/Texture_ktx.cpp +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -24,7 +24,7 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture) { header.numberOfMipmapLevels = texture.mipLevels(); ktx::Images images; - for (int level = 0; level < header.numberOfMipmapLevels; level++) { + for (uint32_t level = 0; level < header.numberOfMipmapLevels; level++) { auto mip = texture.accessStoredMipFace(level); if (mip) { images.emplace_back(ktx::Image(mip->getSize(), 0, mip->readData())); @@ -41,12 +41,13 @@ Texture* Texture::unserialize(const ktx::KTXUniquePointer& srcData) { const auto& header = *srcData->getHeader(); - Format pixelFormat = Format::COLOR_RGBA_32; + Format mipFormat = Format::COLOR_SBGRA_32; + Format pixelFormat = Format::COLOR_SRGBA_32; auto tex = Texture::create2D(pixelFormat, header.getPixelWidth(), header.getPixelHeight()); uint16_t level = 0; for (auto& image : srcData->_images) { - tex->assignStoredMip(level, pixelFormat, image._imageSize, image._bytes); + tex->assignStoredMip(level, mipFormat, image._imageSize, image._bytes); level++; } diff --git a/libraries/ktx/src/ktx/KTX.h b/libraries/ktx/src/ktx/KTX.h index 41032c8222..3a4c19971a 100644 --- a/libraries/ktx/src/ktx/KTX.h +++ b/libraries/ktx/src/ktx/KTX.h @@ -445,10 +445,10 @@ namespace ktx { static Images writeImages(Byte* destBytes, size_t destByteSize, const Images& images); // Parse a block of memory and create a KTX object from it - static std::unique_ptr create(const Storage& src); static std::unique_ptr create(std::unique_ptr& src); - static bool checkHeaderFromStorage(const Storage& storage); + static bool checkHeaderFromStorage(size_t srcSize, const Byte* srcBytes); + static Images parseImages(const Header& header, size_t srcSize, const Byte* srcBytes); // Access raw pointers to the main sections of the KTX const Header* getHeader() const; diff --git a/libraries/ktx/src/ktx/Reader.cpp b/libraries/ktx/src/ktx/Reader.cpp index 4099aa6ef8..00c0c4e19a 100644 --- a/libraries/ktx/src/ktx/Reader.cpp +++ b/libraries/ktx/src/ktx/Reader.cpp @@ -38,12 +38,17 @@ namespace ktx { } break; default: + throw ReaderException("endianness field has invalid value"); return false; } } bool checkIdentifier(const Byte* identifier) { - return memcmp(identifier, Header::IDENTIFIER.data(), Header::IDENTIFIER_LENGTH); + if (!(0 == memcmp(identifier, Header::IDENTIFIER.data(), Header::IDENTIFIER_LENGTH))) { + throw ReaderException("identifier field invalid"); + return false; + } + return true; } KeyValues getKeyValues(size_t length, const Byte* src) { @@ -81,53 +86,18 @@ namespace ktx { return keyValues; } - Images getImagesTable(const Header& header, size_t mipsDataSize, const Byte* mipsData) { - Images images; - auto currentPtr = mipsData; - auto numMips = header.getNumberOfLevels(); - - // Keep identifying new mip as long as we can at list query the next imageSize - while ((currentPtr - mipsData) + sizeof(uint32_t) <= (mipsDataSize)) { - - // Grab the imageSize coming up - size_t imageSize = *reinterpret_cast(currentPtr); - currentPtr += sizeof(uint32_t); - - // If enough data ahead then capture the pointer - if ((currentPtr - mipsData) + imageSize <= (mipsDataSize)) { - auto padding = Header::evalPadding(imageSize); - - images.emplace_back(Image(imageSize, padding, currentPtr)); - - currentPtr += imageSize + padding; - } else { - break; - } - } - - return images; - } - - - bool KTX::checkHeaderFromStorage(const Storage& src) { + bool KTX::checkHeaderFromStorage(size_t srcSize, const Byte* srcBytes) { try { - auto srcSize = src.size(); - auto srcBytes = src.data(); - // validation if (srcSize < sizeof(Header)) { throw ReaderException("length is too short for header"); } const Header* header = reinterpret_cast(srcBytes); - if (!checkIdentifier(header->identifier)) { - throw ReaderException("identifier field invalid"); - } + checkIdentifier(header->identifier); bool endianMatch { true }; - if (!checkEndianness(header->endianness, endianMatch)) { - throw ReaderException("endianness field has invalid value"); - } + checkEndianness(header->endianness, endianMatch); // TODO: endian conversion if !endianMatch - for now, this is for local use and is unnecessary @@ -151,10 +121,31 @@ namespace ktx { } } - std::unique_ptr KTX::create(const Storage& src) { - auto srcCopy = std::make_unique(src); + Images KTX::parseImages(const Header& header, size_t srcSize, const Byte* srcBytes) { + Images images; + auto currentPtr = srcBytes; + auto numMips = header.getNumberOfLevels(); - return create(srcCopy); + // Keep identifying new mip as long as we can at list query the next imageSize + while ((currentPtr - srcBytes) + sizeof(uint32_t) <= (srcSize)) { + + // Grab the imageSize coming up + size_t imageSize = *reinterpret_cast(currentPtr); + currentPtr += sizeof(uint32_t); + + // If enough data ahead then capture the pointer + if ((currentPtr - srcBytes) + imageSize <= (srcSize)) { + auto padding = Header::evalPadding(imageSize); + + images.emplace_back(Image(imageSize, padding, currentPtr)); + + currentPtr += imageSize + padding; + } else { + break; + } + } + + return images; } std::unique_ptr KTX::create(std::unique_ptr& src) { @@ -162,25 +153,19 @@ namespace ktx { return nullptr; } - try { - if (!checkHeaderFromStorage(*src)) { - - } - - std::unique_ptr result(new KTX()); - result->resetStorage(src.release()); - - // read metadata - result->_keyValues = getKeyValues(result->getHeader()->bytesOfKeyValueData, result->getKeyValueData()); - - // populate image table - result->_images = getImagesTable(*result->getHeader(), result->getTexelsDataSize(), result->getTexelsData()); - - return result; - } - catch (ReaderException& e) { - qWarning(e.what()); + if (!checkHeaderFromStorage(src->size(), src->data())) { return nullptr; } + + std::unique_ptr result(new KTX()); + result->resetStorage(src.release()); + + // read metadata + // result->_keyValues = getKeyValues(result->getHeader()->bytesOfKeyValueData, result->getKeyValueData()); + + // populate image table + result->_images = parseImages(*result->getHeader(), result->getTexelsDataSize(), result->getTexelsData()); + + return result; } } \ No newline at end of file From b4745657e0378cf3fdb66b4c5c7bf1401a1aba19 Mon Sep 17 00:00:00 2001 From: sam Date: Fri, 17 Feb 2017 01:15:27 -0800 Subject: [PATCH 22/28] progressing on io with ktx --- libraries/gpu/src/gpu/Texture.cpp | 24 ++--- libraries/gpu/src/gpu/Texture.h | 17 ++-- libraries/gpu/src/gpu/Texture_ktx.cpp | 84 ++++++++++++++-- libraries/ktx/src/ktx/KTX.h | 19 +++- libraries/model/src/model/TextureMap.cpp | 120 +++++++++++++---------- 5 files changed, 185 insertions(+), 79 deletions(-) diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index 21c32cc55e..664a2273a6 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -263,27 +263,27 @@ Texture* Texture::createExternal(const ExternalRecycler& recycler, const Sampler } Texture* Texture::createRenderBuffer(const Element& texelFormat, uint16 width, uint16 height, const Sampler& sampler) { - return create(TextureUsageType::RENDERBUFFER, TEX_2D, texelFormat, width, height, 1, 1, 1, sampler); + return create(TextureUsageType::RENDERBUFFER, TEX_2D, texelFormat, width, height, 1, 1, 0, sampler); } Texture* Texture::create1D(const Element& texelFormat, uint16 width, const Sampler& sampler) { - return create(TextureUsageType::RESOURCE, TEX_1D, texelFormat, width, 1, 1, 1, 1, sampler); + return create(TextureUsageType::RESOURCE, TEX_1D, texelFormat, width, 1, 1, 1, 0, sampler); } Texture* Texture::create2D(const Element& texelFormat, uint16 width, uint16 height, const Sampler& sampler) { - return create(TextureUsageType::RESOURCE, TEX_2D, texelFormat, width, height, 1, 1, 1, sampler); + return create(TextureUsageType::RESOURCE, TEX_2D, texelFormat, width, height, 1, 1, 0, sampler); } Texture* Texture::createStrict(const Element& texelFormat, uint16 width, uint16 height, const Sampler& sampler) { - return create(TextureUsageType::STRICT_RESOURCE, TEX_2D, texelFormat, width, height, 1, 1, 1, sampler); + return create(TextureUsageType::STRICT_RESOURCE, TEX_2D, texelFormat, width, height, 1, 1, 0, sampler); } Texture* Texture::create3D(const Element& texelFormat, uint16 width, uint16 height, uint16 depth, const Sampler& sampler) { - return create(TextureUsageType::RESOURCE, TEX_3D, texelFormat, width, height, depth, 1, 1, sampler); + return create(TextureUsageType::RESOURCE, TEX_3D, texelFormat, width, height, depth, 1, 0, sampler); } Texture* Texture::createCube(const Element& texelFormat, uint16 width, const Sampler& sampler) { - return create(TextureUsageType::RESOURCE, TEX_CUBE, texelFormat, width, width, 1, 1, 1, sampler); + return create(TextureUsageType::RESOURCE, TEX_CUBE, texelFormat, width, width, 1, 1, 0, sampler); } Texture* Texture::create(TextureUsageType usageType, Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices, const Sampler& sampler) @@ -326,7 +326,7 @@ Texture::~Texture() { } Texture::Size Texture::resize(Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices) { - if (width && height && depth && numSamples && numSlices) { + if (width && height && depth && numSamples) { bool changed = false; if ( _type != type) { @@ -387,20 +387,20 @@ Texture::Size Texture::resize(Type type, const Element& texelFormat, uint16 widt } Texture::Size Texture::resize1D(uint16 width, uint16 numSamples) { - return resize(TEX_1D, getTexelFormat(), width, 1, 1, numSamples, 1); + return resize(TEX_1D, getTexelFormat(), width, 1, 1, numSamples, 0); } Texture::Size Texture::resize2D(uint16 width, uint16 height, uint16 numSamples) { - return resize(TEX_2D, getTexelFormat(), width, height, 1, numSamples, 1); + return resize(TEX_2D, getTexelFormat(), width, height, 1, numSamples, 0); } Texture::Size Texture::resize3D(uint16 width, uint16 height, uint16 depth, uint16 numSamples) { - return resize(TEX_3D, getTexelFormat(), width, height, depth, numSamples, 1); + return resize(TEX_3D, getTexelFormat(), width, height, depth, numSamples, 0); } Texture::Size Texture::resizeCube(uint16 width, uint16 numSamples) { - return resize(TEX_CUBE, getTexelFormat(), width, 1, 1, numSamples, 1); + return resize(TEX_CUBE, getTexelFormat(), width, 1, 1, numSamples, 0); } Texture::Size Texture::reformat(const Element& texelFormat) { - return resize(_type, texelFormat, getWidth(), getHeight(), getDepth(), getNumSamples(), getNumSlices()); + return resize(_type, texelFormat, getWidth(), getHeight(), getDepth(), getNumSamples(), _numSlices); } bool Texture::isColorRenderTarget() const { diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index acf063e365..9c8ebd627c 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -359,7 +359,12 @@ public: uint32 getNumTexels() const { return _width * _height * _depth * getNumFaces(); } - uint16 getNumSlices() const { return _numSlices; } + // The texture is an array if the _numSlices is not 0. + // otherwise, if _numSLices is 0, then the texture is NOT an array + // The number of slices returned is 1 at the minimum (if not an array) or the actual _numSlices. + bool isArray() const { return _numSlices > 0; } + uint16 getNumSlices() const { return (isArray() ? _numSlices : 1); } + uint16 getNumSamples() const { return _numSamples; } @@ -511,12 +516,12 @@ protected: uint32 _size = 0; Element _texelFormat; - uint16 _width = 1; - uint16 _height = 1; - uint16 _depth = 1; + uint16 _width { 1 }; + uint16 _height { 1 }; + uint16 _depth { 1 }; - uint16 _numSamples = 1; - uint16 _numSlices = 1; + uint16 _numSamples { 1 }; + uint16 _numSlices { 0 }; // if _numSlices is 0, the texture is not an "Array", the getNumSlices reported is 1 uint16 _maxMip { 0 }; uint16 _minMip { 0 }; diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp index 623a3493da..c36c0f14d8 100644 --- a/libraries/gpu/src/gpu/Texture_ktx.cpp +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -16,12 +16,57 @@ using namespace gpu; ktx::KTXUniquePointer Texture::serialize(const Texture& texture) { - ktx::Header header; + + // From texture format to ktx format description + auto texelFormat = texture.getTexelFormat(); + if ( !( (texelFormat == Format::COLOR_RGBA_32) + || (texelFormat == Format::COLOR_SRGBA_32) + )) + return nullptr; + header.setUncompressed(ktx::GLType::UNSIGNED_BYTE, 4, ktx::GLFormat::BGRA, ktx::GLInternalFormat_Uncompressed::RGBA8, ktx::GLBaseInternalFormat::RGBA); - header.pixelWidth = texture.getWidth(); - header.pixelHeight = texture.getHeight(); - header.numberOfMipmapLevels = texture.mipLevels(); + + // Set Dimensions + switch (texture.getType()) { + case TEX_1D: { + if (texture.isArray()) { + header.set1DArray(texture.getWidth(), texture.getNumSlices()); + } else { + header.set1D(texture.getWidth()); + } + break; + } + case TEX_2D: { + if (texture.isArray()) { + header.set2DArray(texture.getWidth(), texture.getHeight(), texture.getNumSlices()); + } else { + header.set2D(texture.getWidth(), texture.getHeight()); + } + break; + } + case TEX_3D: { + if (texture.isArray()) { + header.set3DArray(texture.getWidth(), texture.getHeight(), texture.getDepth(), texture.getNumSlices()); + } else { + header.set3D(texture.getWidth(), texture.getHeight(), texture.getDepth()); + } + break; + } + case TEX_CUBE: { + if (texture.isArray()) { + header.setCubeArray(texture.getWidth(), texture.getHeight(), texture.getNumSlices()); + } else { + header.setCube(texture.getWidth(), texture.getHeight()); + } + break; + } + default: + return nullptr; + } + + // Number level of mips coming + header.numberOfMipmapLevels = texture.maxMip(); ktx::Images images; for (uint32_t level = 0; level < header.numberOfMipmapLevels; level++) { @@ -34,17 +79,42 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture) { auto ktxBuffer = ktx::KTX::create(header, images); return ktxBuffer; } + Texture* Texture::unserialize(const ktx::KTXUniquePointer& srcData) { if (!srcData) { return nullptr; } - const auto& header = *srcData->getHeader(); Format mipFormat = Format::COLOR_SBGRA_32; - Format pixelFormat = Format::COLOR_SRGBA_32; + Format texelFormat = Format::COLOR_SRGBA_32; - auto tex = Texture::create2D(pixelFormat, header.getPixelWidth(), header.getPixelHeight()); + // Find Texture Type based on dimensions + Type type = TEX_1D; + if (header.pixelWidth == 0) { + return nullptr; + } else if (header.pixelHeight == 0) { + type = TEX_1D; + } else if (header.pixelDepth == 0) { + if (header.numberOfFaces == ktx::NUM_CUBEMAPFACES) { + type = TEX_CUBE; + } else { + type = TEX_2D; + } + } else { + type = TEX_3D; + } + + auto tex = Texture::create( type, + texelFormat, + header.getPixelWidth(), + header.getPixelHeight(), + header.getPixelDepth(), + 1, // num Samples + header.getNumberOfSlices(), + Sampler()); + + // Assing the mips availables uint16_t level = 0; for (auto& image : srcData->_images) { tex->assignStoredMip(level, mipFormat, image._imageSize, image._bytes); diff --git a/libraries/ktx/src/ktx/KTX.h b/libraries/ktx/src/ktx/KTX.h index 3a4c19971a..8e79d90dc0 100644 --- a/libraries/ktx/src/ktx/KTX.h +++ b/libraries/ktx/src/ktx/KTX.h @@ -353,7 +353,7 @@ namespace ktx { uint32_t glInternalFormat; uint32_t glBaseInternalFormat; - uint32_t pixelWidth { 0 }; + uint32_t pixelWidth { 1 }; uint32_t pixelHeight { 0 }; uint32_t pixelDepth { 0 }; uint32_t numberOfArrayElements { 0 }; @@ -393,6 +393,23 @@ namespace ktx { glInternalFormat = (uint32_t) internalFormat; glBaseInternalFormat = (uint32_t) baseInternalFormat; } + + void setDimensions(uint32_t width, uint32_t height = 0, uint32_t depth = 0, uint32_t numSlices = 0, uint32_t numFaces = 1) { + pixelWidth = (width > 0 ? width : 1); + pixelHeight = height; + pixelDepth = depth; + numberOfArrayElements = numSlices; + numberOfFaces = ((numFaces == 1) || (numFaces == NUM_CUBEMAPFACES) ? numFaces : 1); + } + void set1D(uint32_t width) { setDimensions(width); } + void set1DArray(uint32_t width, uint32_t numSlices) { setDimensions(width, 0, 0, (numSlices > 0 ? numSlices : 1)); } + void set2D(uint32_t width, uint32_t height) { setDimensions(width, height); } + void set2DArray(uint32_t width, uint32_t height, uint32_t numSlices) { setDimensions(width, height, 0, (numSlices > 0 ? numSlices : 1)); } + void set3D(uint32_t width, uint32_t height, uint32_t depth) { setDimensions(width, height, depth); } + void set3DArray(uint32_t width, uint32_t height, uint32_t depth, uint32_t numSlices) { setDimensions(width, height, depth, (numSlices > 0 ? numSlices : 1)); } + void setCube(uint32_t width, uint32_t height) { setDimensions(width, height, 0, 0, NUM_CUBEMAPFACES); } + void setCubeArray(uint32_t width, uint32_t height, uint32_t numSlices) { setDimensions(width, height, 0, (numSlices > 0 ? numSlices : 1), NUM_CUBEMAPFACES); } + }; // Key Values diff --git a/libraries/model/src/model/TextureMap.cpp b/libraries/model/src/model/TextureMap.cpp index 4f8a43412e..03605bd6ef 100755 --- a/libraries/model/src/model/TextureMap.cpp +++ b/libraries/model/src/model/TextureMap.cpp @@ -85,6 +85,61 @@ QImage processSourceImage(const QImage& srcImage, bool cubemap) { return srcImage; } +gpu::Texture* cacheTexture(const std::string& name, gpu::Texture* srcTexture, bool write = true, bool read = true) { + if (!srcTexture) { + return nullptr; + } + gpu::Texture* returnedTexture = srcTexture; + + auto theKTX = Texture::serialize(*srcTexture); + if (theKTX) { + // Prepare cache directory + QString path("hifi_ktx/"); + QFileInfo originalFileInfo(path); + QString docsLocation = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); + path = docsLocation + "/" + path; + QFileInfo info(path); + if (!info.absoluteDir().exists()) { + QString originalRelativePath = originalFileInfo.path(); + QDir(docsLocation).mkpath(originalRelativePath); + } + std::string filename(path.toStdString()); + filename += name; + filename += ".ktx"; + + if (write) { + FILE* file = fopen (filename.c_str(),"wb"); + if (file != nullptr) { + fwrite(theKTX->_storage->data(), 1, theKTX->_storage->size(), file); + fclose (file); + } + } + + if (read) { + FILE* file = fopen (filename.c_str(),"rb"); + if (file != nullptr) { + // obtain file size: + fseek (file , 0 , SEEK_END); + auto size = ftell(file); + rewind(file); + + std::unique_ptr storage(new ktx::Storage(size)); + fread(storage->_bytes, 1, storage->_size, file); + fclose (file); + + //then create a new texture out of the ktx + auto theNewTexure = Texture::unserialize(ktx::KTX::create(storage)); + + if (theNewTexure) { + returnedTexture = theNewTexure; + delete srcTexture; + } + } + } + } + return returnedTexture; +} + void TextureMap::setTextureSource(TextureSourcePointer& textureSource) { _textureSource = textureSource; } @@ -273,57 +328,10 @@ gpu::Texture* TextureUsage::process2DTextureColorFromImage(const QImage& srcImag if (generateMips) { ::generateMips(theTexture, image, formatMip, false); } - auto theKTX = Texture::serialize(*theTexture); - if (theKTX) { - // save that! - QString path("hifi_ktx/"); - QFileInfo originalFileInfo(path); - QString docsLocation = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); - path = docsLocation + "/" + path; - QFileInfo info(path); - if (!info.absoluteDir().exists()) { - QString originalRelativePath = originalFileInfo.path(); - QDir(docsLocation).mkpath(originalRelativePath); - } - std::string filename(path.toStdString()); - filename += std::to_string((size_t) theTexture); - filename += ".ktx"; - - { - FILE* file = fopen (filename.c_str(),"wb"); - if (file != nullptr) { - fwrite(theKTX->_storage->data(), 1, theKTX->_storage->size(), file); - fclose (file); - } - } - - { - FILE* file = fopen (filename.c_str(),"rb"); - if (file != nullptr) { - // obtain file size: - fseek (file , 0 , SEEK_END); - auto size = ftell(file); - rewind(file); - - std::unique_ptr storage(new ktx::Storage(size)); - fread(storage->_bytes, 1, storage->_size, file); - fclose (file); - - //then create a new texture out of the ktx - auto theNewTexure = Texture::unserialize(ktx::KTX::create(storage)); - - if (theNewTexure) { - auto srcTexture = theTexture; - theTexture = theNewTexure; - delete srcTexture; - } - } - } - } + theTexture = cacheTexture(std::to_string((size_t) theTexture), theTexture); } - return theTexture; } @@ -367,6 +375,8 @@ gpu::Texture* TextureUsage::createNormalTextureFromNormalImage(const QImage& src theTexture->setSource(srcImageName); theTexture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits()); generateMips(theTexture, image, formatMip, true); + + theTexture = cacheTexture(std::to_string((size_t) theTexture), theTexture); } return theTexture; @@ -453,6 +463,8 @@ gpu::Texture* TextureUsage::createNormalTextureFromBumpImage(const QImage& srcIm theTexture->setSource(srcImageName); theTexture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits()); generateMips(theTexture, image, formatMip, true); + + theTexture = cacheTexture(std::to_string((size_t) theTexture), theTexture); } return theTexture; @@ -486,8 +498,8 @@ gpu::Texture* TextureUsage::createRoughnessTextureFromImage(const QImage& srcIma theTexture->setSource(srcImageName); theTexture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits()); generateMips(theTexture, image, formatMip, true); - - // FIXME queue for transfer to GPU and block on completion + + theTexture = cacheTexture(std::to_string((size_t) theTexture), theTexture); } return theTexture; @@ -525,8 +537,8 @@ gpu::Texture* TextureUsage::createRoughnessTextureFromGlossImage(const QImage& s theTexture->setSource(srcImageName); theTexture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits()); generateMips(theTexture, image, formatMip, true); - - // FIXME queue for transfer to GPU and block on completion + + theTexture = cacheTexture(std::to_string((size_t) theTexture), theTexture); } return theTexture; @@ -562,7 +574,7 @@ gpu::Texture* TextureUsage::createMetallicTextureFromImage(const QImage& srcImag theTexture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits()); generateMips(theTexture, image, formatMip, true); - // FIXME queue for transfer to GPU and block on completion + theTexture = cacheTexture(std::to_string((size_t) theTexture), theTexture); } return theTexture; @@ -893,6 +905,8 @@ gpu::Texture* TextureUsage::processCubeTextureColorFromImage(const QImage& srcIm PROFILE_RANGE(resource_parse, "generateIrradiance"); theTexture->generateIrradiance(); } + + theTexture = cacheTexture(std::to_string((size_t) theTexture), theTexture); } } From d1b91cb436d1e602452aa358477108bef61ab86a Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 17 Feb 2017 09:18:51 -0800 Subject: [PATCH 23/28] Adding abstracted storage for in-memory or file backed data --- libraries/shared/src/shared/Storage.cpp | 86 +++++++++++++++++++++++++ libraries/shared/src/shared/Storage.h | 65 +++++++++++++++++++ tests/shared/src/StorageTests.cpp | 75 +++++++++++++++++++++ tests/shared/src/StorageTests.h | 32 +++++++++ 4 files changed, 258 insertions(+) create mode 100644 libraries/shared/src/shared/Storage.cpp create mode 100644 libraries/shared/src/shared/Storage.h create mode 100644 tests/shared/src/StorageTests.cpp create mode 100644 tests/shared/src/StorageTests.h diff --git a/libraries/shared/src/shared/Storage.cpp b/libraries/shared/src/shared/Storage.cpp new file mode 100644 index 0000000000..fe17d678c7 --- /dev/null +++ b/libraries/shared/src/shared/Storage.cpp @@ -0,0 +1,86 @@ +// +// Created by Bradley Austin Davis on 2016/02/17 +// Copyright 2013-2017 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 "Storage.h" + +#include + +using namespace storage; + +MemoryStoragePointer Storage::toMemoryStorage() const { + return std::make_unique(size(), data()); +} + +FileStoragePointer Storage::toFileStorage(const QString& filename) const { + return FileStorage::create(filename, size(), data()); +} + +MemoryStorage::MemoryStorage(size_t size, const uint8_t* data) { + _data.resize(size); + memcpy(_data.data(), data, size); +} + +const uint8_t* MemoryStorage::data() const { + return _data.data(); +} + +size_t MemoryStorage::size() const { + return _data.size(); +} + +FileStoragePointer FileStorage::create(const QString& filename, size_t size, const uint8_t* data) { + QFile file(filename); + if (!file.open(QFile::ReadWrite | QIODevice::Truncate)) { + throw std::runtime_error("Unable to open file for writing"); + } + if (!file.resize(size)) { + throw std::runtime_error("Unable to resize file"); + } + { + auto mapped = file.map(0, size); + if (!mapped) { + throw std::runtime_error("Unable to map file"); + } + memcpy(mapped, data, size); + if (!file.unmap(mapped)) { + throw std::runtime_error("Unable to unmap file"); + } + } + file.close(); + return std::make_unique(filename); +} + +FileStorage::FileStorage(const QString& filename) : _file(filename) { + if (!_file.open(QFile::ReadOnly)) { + throw std::runtime_error("Unable to open file"); + } + _mapped = _file.map(0, _file.size()); + if (!_mapped) { + throw std::runtime_error("Unable to map file"); + } +} + +FileStorage::~FileStorage() { + if (_mapped) { + if (!_file.unmap(_mapped)) { + throw std::runtime_error("Unable to unmap file"); + } + } + if (_file.isOpen()) { + _file.close(); + } +} + + +const uint8_t* FileStorage::data() const { + return _mapped; +} + +size_t FileStorage::size() const { + return _file.size(); +} diff --git a/libraries/shared/src/shared/Storage.h b/libraries/shared/src/shared/Storage.h new file mode 100644 index 0000000000..3198172b06 --- /dev/null +++ b/libraries/shared/src/shared/Storage.h @@ -0,0 +1,65 @@ +// +// Created by Bradley Austin Davis on 2016/02/17 +// Copyright 2013-2017 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 +// + +#pragma once +#ifndef hifi_Storage_h +#define hifi_Storage_h + +#include +#include +#include +#include +#include + +namespace storage { + class Storage; + using StoragePointer = std::unique_ptr; + class MemoryStorage; + using MemoryStoragePointer = std::unique_ptr; + class FileStorage; + using FileStoragePointer = std::unique_ptr; + + class Storage { + public: + virtual ~Storage() {} + virtual const uint8_t* data() const = 0; + virtual size_t size() const = 0; + + FileStoragePointer toFileStorage(const QString& filename) const; + MemoryStoragePointer toMemoryStorage() const; + }; + + class MemoryStorage : public Storage { + public: + MemoryStorage(size_t size, const uint8_t* data); + const uint8_t* data() const override; + size_t size() const override; + private: + std::vector _data; + }; + + class FileStorage : public Storage { + public: + static FileStoragePointer create(const QString& filename, size_t size, const uint8_t* data); + FileStorage(const QString& filename); + ~FileStorage(); + // Prevent copying + FileStorage(const FileStorage& other) = delete; + FileStorage& operator=(const FileStorage& other) = delete; + + const uint8_t* data() const override; + size_t size() const override; + private: + QFile _file; + uint8_t* _mapped { nullptr }; + }; + + +} + +#endif // hifi_Storage_h diff --git a/tests/shared/src/StorageTests.cpp b/tests/shared/src/StorageTests.cpp new file mode 100644 index 0000000000..fa538f6911 --- /dev/null +++ b/tests/shared/src/StorageTests.cpp @@ -0,0 +1,75 @@ +// +// Created by Bradley Austin Davis on 2016/02/17 +// Copyright 2013-2017 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 "StorageTests.h" + +QTEST_MAIN(StorageTests) + +using namespace storage; + +StorageTests::StorageTests() { + for (size_t i = 0; i < _testData.size(); ++i) { + _testData[i] = (uint8_t)rand(); + } + _testFile = QDir::tempPath() + "/" + QUuid::createUuid().toString(); +} + +StorageTests::~StorageTests() { + QFileInfo fileInfo(_testFile); + if (fileInfo.exists()) { + QFile(_testFile).remove(); + } +} + + +void StorageTests::testConversion() { + { + QFileInfo fileInfo(_testFile); + QCOMPARE(fileInfo.exists(), false); + } + StoragePointer storagePointer = std::make_unique(_testData.size(), _testData.data()); + QCOMPARE(storagePointer->size(), (quint64)_testData.size()); + QCOMPARE(memcmp(_testData.data(), storagePointer->data(), _testData.size()), 0); + // Convert to a file + storagePointer = storagePointer->toFileStorage(_testFile); + { + QFileInfo fileInfo(_testFile); + QCOMPARE(fileInfo.exists(), true); + QCOMPARE(fileInfo.size(), (qint64)_testData.size()); + } + QCOMPARE(storagePointer->size(), (quint64)_testData.size()); + QCOMPARE(memcmp(_testData.data(), storagePointer->data(), _testData.size()), 0); + + // Convert to memory + storagePointer = storagePointer->toMemoryStorage(); + QCOMPARE(storagePointer->size(), (quint64)_testData.size()); + QCOMPARE(memcmp(_testData.data(), storagePointer->data(), _testData.size()), 0); + { + // ensure the file is unaffected + QFileInfo fileInfo(_testFile); + QCOMPARE(fileInfo.exists(), true); + QCOMPARE(fileInfo.size(), (qint64)_testData.size()); + } + + // truncate the data as a new memory object + auto newSize = _testData.size() / 2; + storagePointer = std::make_unique(newSize, storagePointer->data()); + QCOMPARE(storagePointer->size(), (quint64)newSize); + QCOMPARE(memcmp(_testData.data(), storagePointer->data(), newSize), 0); + + // Convert back to file + storagePointer = storagePointer->toFileStorage(_testFile); + QCOMPARE(storagePointer->size(), (quint64)newSize); + QCOMPARE(memcmp(_testData.data(), storagePointer->data(), newSize), 0); + { + // ensure the file is truncated + QFileInfo fileInfo(_testFile); + QCOMPARE(fileInfo.exists(), true); + QCOMPARE(fileInfo.size(), (qint64)newSize); + } +} diff --git a/tests/shared/src/StorageTests.h b/tests/shared/src/StorageTests.h new file mode 100644 index 0000000000..6a2c153223 --- /dev/null +++ b/tests/shared/src/StorageTests.h @@ -0,0 +1,32 @@ +// +// Created by Bradley Austin Davis on 2016/02/17 +// Copyright 2013-2017 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 +// + +#ifndef hifi_StorageTests_h +#define hifi_StorageTests_h + +#include + +#include +#include + +class StorageTests : public QObject { + Q_OBJECT + +public: + StorageTests(); + ~StorageTests(); + +private slots: + void testConversion(); + +private: + std::array _testData; + QString _testFile; +}; + +#endif // hifi_StorageTests_h From 2d7ba45667ff00369cfcb429a88d93e216ace21b Mon Sep 17 00:00:00 2001 From: samcake Date: Fri, 17 Feb 2017 12:01:33 -0800 Subject: [PATCH 24/28] FIx creationg issue , need a usageType at creation --- libraries/gpu/src/gpu/Texture.cpp | 25 ------------------------ libraries/gpu/src/gpu/Texture.h | 2 +- libraries/gpu/src/gpu/Texture_ktx.cpp | 5 +++-- libraries/model/src/model/TextureMap.cpp | 2 +- 4 files changed, 5 insertions(+), 29 deletions(-) diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index 664a2273a6..b2cb41e9bb 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -1006,28 +1006,3 @@ Texture::ExternalUpdates Texture::getUpdates() const { } return result; } - -#include - -ktx::KTXUniquePointer Texture::serialize(const Texture& texture) { - - ktx::Header header; - header.setUncompressed(ktx::GLType::UNSIGNED_BYTE, 4, ktx::GLFormat::BGRA, ktx::GLInternalFormat_Uncompressed::RGBA8, ktx::GLBaseInternalFormat::RGBA); - header.pixelWidth = texture.getWidth(); - header.pixelHeight = texture.getHeight(); - header.numberOfMipmapLevels = texture.mipLevels(); - - ktx::Images images; - for (int level = 0; level < header.numberOfMipmapLevels; level++) { - auto mip = texture.accessStoredMipFace(level); - if (mip) { - images.emplace_back(ktx::Image(mip->getSize(), 0, mip->readData())); - } - } - - auto ktxBuffer = ktx::KTX::create(header, ktx::KeyValues(), images); - return ktxBuffer; -} -TexturePointer Texture::unserialize(const ktx::KTXUniquePointer& srcData) { - return nullptr; -} diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 9c8ebd627c..cc351de787 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -493,7 +493,7 @@ public: ExternalUpdates getUpdates() const; static ktx::KTXUniquePointer serialize(const Texture& texture); - static Texture* unserialize(const ktx::KTXUniquePointer& srcData); + static Texture* unserialize(TextureUsageType usageType, const ktx::KTXUniquePointer& srcData); protected: const TextureUsageType _usageType; diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp index c36c0f14d8..d63bffb74f 100644 --- a/libraries/gpu/src/gpu/Texture_ktx.cpp +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -80,7 +80,7 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture) { return ktxBuffer; } -Texture* Texture::unserialize(const ktx::KTXUniquePointer& srcData) { +Texture* Texture::unserialize(TextureUsageType usageType, const ktx::KTXUniquePointer& srcData) { if (!srcData) { return nullptr; } @@ -105,7 +105,8 @@ Texture* Texture::unserialize(const ktx::KTXUniquePointer& srcData) { type = TEX_3D; } - auto tex = Texture::create( type, + auto tex = Texture::create( usageType, + type, texelFormat, header.getPixelWidth(), header.getPixelHeight(), diff --git a/libraries/model/src/model/TextureMap.cpp b/libraries/model/src/model/TextureMap.cpp index 03605bd6ef..e4e358c581 100755 --- a/libraries/model/src/model/TextureMap.cpp +++ b/libraries/model/src/model/TextureMap.cpp @@ -128,7 +128,7 @@ gpu::Texture* cacheTexture(const std::string& name, gpu::Texture* srcTexture, bo fclose (file); //then create a new texture out of the ktx - auto theNewTexure = Texture::unserialize(ktx::KTX::create(storage)); + auto theNewTexure = Texture::unserialize(gpu::TextureUsageType::RESOURCE, ktx::KTX::create(storage)); if (theNewTexure) { returnedTexture = theNewTexure; From e6c6cd2825bd476958917d72bfd79f62b801b5e1 Mon Sep 17 00:00:00 2001 From: samcake Date: Fri, 17 Feb 2017 12:24:04 -0800 Subject: [PATCH 25/28] cleaner unserialization --- libraries/model/src/model/TextureMap.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/model/src/model/TextureMap.cpp b/libraries/model/src/model/TextureMap.cpp index e4e358c581..3f10f66256 100755 --- a/libraries/model/src/model/TextureMap.cpp +++ b/libraries/model/src/model/TextureMap.cpp @@ -128,7 +128,7 @@ gpu::Texture* cacheTexture(const std::string& name, gpu::Texture* srcTexture, bo fclose (file); //then create a new texture out of the ktx - auto theNewTexure = Texture::unserialize(gpu::TextureUsageType::RESOURCE, ktx::KTX::create(storage)); + auto theNewTexure = Texture::unserialize(srcTexture->getUsageType(), ktx::KTX::create(storage)); if (theNewTexure) { returnedTexture = theNewTexure; From 285222d4907ebd45efe1a2f17ff9ce8f60d9250f Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 17 Feb 2017 12:49:55 -0800 Subject: [PATCH 26/28] Add view storage --- libraries/shared/src/shared/Storage.cpp | 29 +++++++++---------------- libraries/shared/src/shared/Storage.h | 21 +++++++++++++----- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/libraries/shared/src/shared/Storage.cpp b/libraries/shared/src/shared/Storage.cpp index fe17d678c7..d3f1f3c5d3 100644 --- a/libraries/shared/src/shared/Storage.cpp +++ b/libraries/shared/src/shared/Storage.cpp @@ -12,8 +12,16 @@ using namespace storage; +ViewStoragePointer Storage::createView(size_t viewSize, size_t offset) const { + auto selfSize = size(); + if ((viewSize + offset) > selfSize) { + throw std::runtime_error("Unable to map file"); + } + return ViewStoragePointer(new ViewStorage(viewSize, data() + offset)); +} + MemoryStoragePointer Storage::toMemoryStorage() const { - return std::make_unique(size(), data()); + return MemoryStoragePointer(new MemoryStorage(size(), data())); } FileStoragePointer Storage::toFileStorage(const QString& filename) const { @@ -25,14 +33,6 @@ MemoryStorage::MemoryStorage(size_t size, const uint8_t* data) { memcpy(_data.data(), data, size); } -const uint8_t* MemoryStorage::data() const { - return _data.data(); -} - -size_t MemoryStorage::size() const { - return _data.size(); -} - FileStoragePointer FileStorage::create(const QString& filename, size_t size, const uint8_t* data) { QFile file(filename); if (!file.open(QFile::ReadWrite | QIODevice::Truncate)) { @@ -52,7 +52,7 @@ FileStoragePointer FileStorage::create(const QString& filename, size_t size, con } } file.close(); - return std::make_unique(filename); + return FileStoragePointer(new FileStorage(filename)); } FileStorage::FileStorage(const QString& filename) : _file(filename) { @@ -75,12 +75,3 @@ FileStorage::~FileStorage() { _file.close(); } } - - -const uint8_t* FileStorage::data() const { - return _mapped; -} - -size_t FileStorage::size() const { - return _file.size(); -} diff --git a/libraries/shared/src/shared/Storage.h b/libraries/shared/src/shared/Storage.h index 3198172b06..8096b631ed 100644 --- a/libraries/shared/src/shared/Storage.h +++ b/libraries/shared/src/shared/Storage.h @@ -23,13 +23,15 @@ namespace storage { using MemoryStoragePointer = std::unique_ptr; class FileStorage; using FileStoragePointer = std::unique_ptr; + class ViewStorage; + using ViewStoragePointer = std::unique_ptr; class Storage { public: virtual ~Storage() {} virtual const uint8_t* data() const = 0; virtual size_t size() const = 0; - + ViewStoragePointer createView(size_t size, size_t offset = 0) const; FileStoragePointer toFileStorage(const QString& filename) const; MemoryStoragePointer toMemoryStorage() const; }; @@ -37,8 +39,8 @@ namespace storage { class MemoryStorage : public Storage { public: MemoryStorage(size_t size, const uint8_t* data); - const uint8_t* data() const override; - size_t size() const override; + const uint8_t* data() const override { return _data.data(); } + size_t size() const override { return _data.size(); } private: std::vector _data; }; @@ -52,13 +54,22 @@ namespace storage { FileStorage(const FileStorage& other) = delete; FileStorage& operator=(const FileStorage& other) = delete; - const uint8_t* data() const override; - size_t size() const override; + const uint8_t* data() const override { return _mapped; } + size_t size() const override { return _file.size(); } private: QFile _file; uint8_t* _mapped { nullptr }; }; + class ViewStorage : public Storage { + public: + ViewStorage(size_t size, const uint8_t* data) : _size(size), _data(data) {} + const uint8_t* data() const override { return _data; } + size_t size() const override { return _size; } + private: + const size_t _size; + const uint8_t* _data; + }; } From eafe0a04d5666b5dd5be01c936be482889c785ec Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 17 Feb 2017 13:00:38 -0800 Subject: [PATCH 27/28] Fix transfer buffering --- libraries/gpu-gl/src/gpu/gl45/GL45Backend.h | 9 +-- .../gpu/gl45/GL45BackendVariableTexture.cpp | 55 +++++++++++-------- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h index 4f299d417f..29e5a59ec5 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h +++ b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h @@ -108,16 +108,10 @@ public: using VoidLambdaQueue = std::queue; using ThreadPointer = std::shared_ptr; const GL45VariableAllocationTexture& _parent; - const uint16_t _sourceMip; - const uint16_t _targetMip; - const uint8_t _face; - const uint32_t _lines; - const uint32_t _lineOffset; // Holds the contents to transfer to the GPU in CPU memory std::vector _buffer; // Indicates if a transfer from backing storage to interal storage has started bool _bufferingStarted { false }; - bool _transferOnly { false }; bool _bufferingCompleted { false }; VoidLambda _transferLambda; VoidLambda _bufferingLambda; @@ -128,6 +122,7 @@ public: static void bufferLoop(); public: + TransferJob(const TransferJob& other) = delete; TransferJob(const GL45VariableAllocationTexture& parent, std::function transferLambda); TransferJob(const GL45VariableAllocationTexture& parent, uint16_t sourceMip, uint16_t targetMip, uint8_t face, uint32_t lines = 0, uint32_t lineOffset = 0); bool tryTransfer(); @@ -139,7 +134,7 @@ public: void transfer(); }; - using TransferQueue = std::queue; + using TransferQueue = std::queue>; static MemoryPressureState _memoryPressureState; protected: static std::atomic _memoryPressureStateStale; diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp index e26a5c262f..8c93ed6a65 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp @@ -78,11 +78,18 @@ void TransferJob::stopTransferLoop() { } TransferJob::TransferJob(const GL45VariableAllocationTexture& parent, uint16_t sourceMip, uint16_t targetMip, uint8_t face, uint32_t lines, uint32_t lineOffset) - : _parent(parent), _sourceMip(sourceMip), _targetMip(targetMip), _face(face), _lines(lines), _lineOffset(lineOffset) { + : _parent(parent) { + + auto transferDimensions = _parent._gpuObject.evalMipDimensions(sourceMip); + GLenum format; + GLenum type; + auto mipData = _parent._gpuObject.accessStoredMipFace(sourceMip, face); + GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_parent._gpuObject.getTexelFormat(), mipData->getFormat()); + format = texelFormat.format; + type = texelFormat.type; if (0 == lines) { - _bufferingLambda = [this] { - auto mipData = _parent._gpuObject.accessStoredMipFace(_sourceMip, _face); + _bufferingLambda = [=] { auto size = mipData->getSize(); _buffer.resize(size); memcpy(&_buffer[0], mipData->readData(), size); @@ -90,38 +97,39 @@ TransferJob::TransferJob(const GL45VariableAllocationTexture& parent, uint16_t s }; } else { - _bufferingLambda = [this] { - auto mipData = _parent._gpuObject.accessStoredMipFace(_sourceMip, _face); - auto dimensions = _parent._gpuObject.evalMipDimensions(_sourceMip); + transferDimensions.y = lines; + _bufferingLambda = [=] { + auto dimensions = _parent._gpuObject.evalMipDimensions(sourceMip); auto mipSize = mipData->getSize(); auto bytesPerLine = (uint32_t)mipSize / dimensions.y; - auto transferSize = bytesPerLine * _lines; - auto sourceOffset = bytesPerLine * _lineOffset; + auto transferSize = bytesPerLine * lines; + auto sourceOffset = bytesPerLine * lineOffset; _buffer.resize(transferSize); memcpy(&_buffer[0], mipData->readData() + sourceOffset, transferSize); _bufferingCompleted = true; }; } - _transferLambda = [this] { - auto mipData = _parent._gpuObject.accessStoredMipFace(_sourceMip, _face); - auto dimensions = _parent._gpuObject.evalMipDimensions(_sourceMip); - GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_parent._gpuObject.getTexelFormat(), mipData->getFormat()); - _parent.copyMipFaceLinesFromTexture(_targetMip, _face, dimensions, _lineOffset, texelFormat.format, texelFormat.type, &_buffer[0]); + _transferLambda = [=] { + _parent.copyMipFaceLinesFromTexture(targetMip, face, transferDimensions, lineOffset, format, type, _buffer.data()); _buffer.swap(std::vector()); }; } TransferJob::TransferJob(const GL45VariableAllocationTexture& parent, std::function transferLambda) - : _parent(parent), _sourceMip(0), _targetMip(0), _face(0), _lines(0), _lineOffset(0), _bufferingCompleted(true), _transferLambda(transferLambda) { - if (!_bufferThread) { - _bufferThread = std::make_shared([] { - TransferJob::bufferLoop(); - }); - } + : _parent(parent), _bufferingCompleted(true), _transferLambda(transferLambda) { } bool TransferJob::tryTransfer() { + // Disable threaded texture transfer for now +#if 1 + if (!_bufferingCompleted) { + _bufferingLambda(); + _bufferingCompleted = true; + } + _transferLambda(); + return true; +#else // Are we ready to transfer if (_bufferingCompleted) { _transferLambda(); @@ -130,6 +138,7 @@ bool TransferJob::tryTransfer() { startBuffering(); return false; +#endif } void TransferJob::startBuffering() { @@ -391,7 +400,7 @@ void GL45VariableAllocationTexture::executeNextTransfer(const TexturePointer& cu if (!_pendingTransfers.empty()) { // Keeping hold of a strong pointer during the transfer ensures that the transfer thread cannot try to access a destroyed texture _currentTransferTexture = currentTexture; - if (_pendingTransfers.front().tryTransfer()) { + if (_pendingTransfers.front()->tryTransfer()) { _pendingTransfers.pop(); _currentTransferTexture.reset(); } @@ -542,7 +551,7 @@ void GL45ResourceTexture::populateTransferQueue() { // If the mip is less than the max transfer size, then just do it in one transfer if (glm::all(glm::lessThanEqual(mipDimensions, MAX_TRANSFER_DIMENSIONS))) { // Can the mip be transferred in one go - _pendingTransfers.emplace(*this, sourceMip, targetMip, face); + _pendingTransfers.emplace(new TransferJob(*this, sourceMip, targetMip, face)); continue; } @@ -556,13 +565,13 @@ void GL45ResourceTexture::populateTransferQueue() { uint32_t lineOffset = 0; while (lineOffset < lines) { uint32_t linesToCopy = std::min(lines - lineOffset, linesPerTransfer); - _pendingTransfers.emplace(TransferJob(*this, sourceMip, targetMip, face, linesToCopy, lineOffset)); + _pendingTransfers.emplace(new TransferJob(*this, sourceMip, targetMip, face, linesToCopy, lineOffset)); lineOffset += linesToCopy; } } // queue up the sampler and populated mip change for after the transfer has completed - _pendingTransfers.emplace(TransferJob(*this, [=] { + _pendingTransfers.emplace(new TransferJob(*this, [=] { _populatedMip = sourceMip; syncSampler(); })); From 61ce66a03956c8a68a73ce11f99bdea6b6af8667 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 17 Feb 2017 13:01:46 -0800 Subject: [PATCH 28/28] Switching texture backing to opaque storage type --- libraries/gpu/src/gpu/Texture.cpp | 47 +++++---------------------- libraries/gpu/src/gpu/Texture.h | 27 +++++++-------- libraries/gpu/src/gpu/Texture_ktx.cpp | 2 +- 3 files changed, 20 insertions(+), 56 deletions(-) diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index b2cb41e9bb..833647bbda 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -122,36 +122,12 @@ uint8 Texture::NUM_FACES_PER_TYPE[NUM_TYPES] = { 1, 1, 1, 6 }; Texture::Pixels::Pixels(const Element& format, Size size, const Byte* bytes) : _format(format), - _sysmem(size, bytes), - _isGPULoaded(false) { - Texture::updateTextureCPUMemoryUsage(0, _sysmem.getSize()); + _storage(new storage::MemoryStorage(size, bytes)) { + Texture::updateTextureCPUMemoryUsage(0, _storage->size()); } Texture::Pixels::~Pixels() { - Texture::updateTextureCPUMemoryUsage(_sysmem.getSize(), 0); -} - -Texture::Size Texture::Pixels::resize(Size pSize) { - auto prevSize = _sysmem.getSize(); - auto newSize = _sysmem.resize(pSize); - Texture::updateTextureCPUMemoryUsage(prevSize, newSize); - return newSize; -} - -Texture::Size Texture::Pixels::setData(const Element& format, Size size, const Byte* bytes ) { - _format = format; - auto prevSize = _sysmem.getSize(); - auto newSize = _sysmem.setData(size, bytes); - Texture::updateTextureCPUMemoryUsage(prevSize, newSize); - _isGPULoaded = false; - return newSize; -} - -void Texture::Pixels::notifyGPULoaded() { - _isGPULoaded = true; - auto prevSize = _sysmem.getSize(); - auto newSize = _sysmem.resize(0); - Texture::updateTextureCPUMemoryUsage(prevSize, newSize); + Texture::updateTextureCPUMemoryUsage(_storage->size(), 0); } void Texture::Storage::assignTexture(Texture* texture) { @@ -183,14 +159,6 @@ const Texture::PixelsPointer Texture::Storage::getMipFace(uint16 level, uint8 fa return PixelsPointer(); } -void Texture::Storage::notifyMipFaceGPULoaded(uint16 level, uint8 face) const { - PixelsPointer mipFace = getMipFace(level, face); - // Free the mips - if (mipFace) { - mipFace->notifyGPULoaded(); - } -} - bool Texture::Storage::isMipAvailable(uint16 level, uint8 face) const { PixelsPointer mipFace = getMipFace(level, face); return (mipFace && mipFace->getSize()); @@ -229,7 +197,8 @@ bool Texture::Storage::assignMipData(uint16 level, const Element& format, Size s auto faceBytes = bytes; Size allocated = 0; for (auto& face : mip) { - allocated += face->setData(format, sizePerFace, faceBytes); + face.reset(new Pixels(format, size, bytes)); + allocated += size; faceBytes += sizePerFace; } @@ -242,11 +211,11 @@ bool Texture::Storage::assignMipData(uint16 level, const Element& format, Size s bool Texture::Storage::assignMipFaceData(uint16 level, const Element& format, Size size, const Byte* bytes, uint8 face) { allocateMip(level); - auto mip = _mips[level]; + auto& mip = _mips[level]; Size allocated = 0; if (face < mip.size()) { - auto mipFace = mip[face]; - allocated += mipFace->setData(format, size, bytes); + mip[face].reset(new Pixels(format, size, bytes)); + allocated += size; bumpStamp(); } diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index cc351de787..0a5afe78c3 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -17,6 +17,8 @@ #include #include +#include + #include "Forward.h" #include "Resource.h" @@ -224,26 +226,26 @@ public: bool operator!=(const Usage& usage) { return (_flags != usage._flags); } }; + class Pixels { public: + using StoragePointer = storage::StoragePointer; + Pixels() {} Pixels(const Pixels& pixels) = default; Pixels(const Element& format, Size size, const Byte* bytes); + Pixels(const Element& format, StoragePointer& storage) : _format(format), _storage(storage.release()) {} ~Pixels(); - const Byte* readData() const { return _sysmem.readData(); } - Size getSize() const { return _sysmem.getSize(); } - Size resize(Size pSize); - Size setData(const Element& format, Size size, const Byte* bytes ); + const Byte* readData() const { return _storage->data(); } + Size getSize() const { return _storage->size(); } const Element& getFormat() const { return _format; } - - void notifyGPULoaded(); - + + protected: Element _format; - Sysmem _sysmem; - bool _isGPULoaded; + StoragePointer _storage; friend class Texture; }; @@ -296,10 +298,6 @@ public: const Texture* getTexture() const { return _texture; } friend class Texture; - - // THis should be only called by the Texture from the Backend to notify the storage that the specified mip face pixels - // have been uploaded to the GPU memory. IT is possible for the storage to free the system memory then - virtual void notifyMipFaceGPULoaded(uint16 level, uint8 face) const; }; @@ -481,9 +479,6 @@ public: const Sampler& getSampler() const { return _sampler; } Stamp getSamplerStamp() const { return _samplerStamp; } - // Only callable by the Backend - void notifyMipFaceGPULoaded(uint16 level, uint8 face = 0) const { return _storage->notifyMipFaceGPULoaded(level, face); } - void setExternalTexture(uint32 externalId, void* externalFence); void setExternalRecycler(const ExternalRecycler& recycler); ExternalRecycler getExternalRecycler() const; diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp index d63bffb74f..404aca77a4 100644 --- a/libraries/gpu/src/gpu/Texture_ktx.cpp +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -72,7 +72,7 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture) { for (uint32_t level = 0; level < header.numberOfMipmapLevels; level++) { auto mip = texture.accessStoredMipFace(level); if (mip) { - images.emplace_back(ktx::Image(mip->getSize(), 0, mip->readData())); + images.emplace_back(ktx::Image((uint32_t)mip->getSize(), 0, mip->readData())); } }