From 6c301949167d21f55854da341dd15a4bb9483e26 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 15 Apr 2016 18:07:37 -0700 Subject: [PATCH] Support lowering the minimum mip of a texture at runtime --- cmake/externals/glew/CMakeLists.txt | 1 - interface/src/Menu.cpp | 35 ++ interface/src/Menu.h | 7 + libraries/gpu/src/gpu/Format.h | 24 +- libraries/gpu/src/gpu/Forward.h | 82 ++-- libraries/gpu/src/gpu/GLBackend.cpp | 67 ++++ libraries/gpu/src/gpu/GLBackend.h | 46 ++- libraries/gpu/src/gpu/GLBackendTexture.cpp | 373 +++++++++++++----- .../gpu/src/gpu/GLBackendTextureTransfer.cpp | 35 +- .../gpu/src/gpu/GLBackendTextureTransfer.h | 25 +- libraries/gpu/src/gpu/Texture.cpp | 32 +- libraries/gpu/src/gpu/Texture.h | 29 +- tests/gpu-test/src/main.cpp | 12 +- 13 files changed, 547 insertions(+), 221 deletions(-) diff --git a/cmake/externals/glew/CMakeLists.txt b/cmake/externals/glew/CMakeLists.txt index 9a0b7bec7c..c9ad5f837a 100644 --- a/cmake/externals/glew/CMakeLists.txt +++ b/cmake/externals/glew/CMakeLists.txt @@ -4,7 +4,6 @@ if (ANDROID) set(ANDROID_CMAKE_ARGS "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" "-DANDROID_NATIVE_API_LEVEL=19") endif () -#message ("Foo ${CMAKE_CURRENT_BINARY_DIR}/project/src/glew/build/cmake") include(ExternalProject) ExternalProject_Add( ${EXTERNAL_NAME} diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 1fa4a6fe29..c5fa52e49d 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -360,6 +360,41 @@ Menu::Menu() { resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionThird, 0, false)); resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionQuarter, 0, false)); + //const QString = "Automatic Texture Memory"; + //const QString = "64 MB"; + //const QString = "256 MB"; + //const QString = "512 MB"; + //const QString = "1024 MB"; + //const QString = "2048 MB"; + + // Developer > Render > Resolution + MenuWrapper* textureMenu = renderOptionsMenu->addMenu(MenuOption::RenderMaxTextureMemory); + QActionGroup* textureGroup = new QActionGroup(textureMenu); + textureGroup->setExclusive(true); + textureGroup->addAction(addCheckableActionToQMenuAndActionHash(textureMenu, MenuOption::RenderMaxTextureAutomatic, 0, true)); + textureGroup->addAction(addCheckableActionToQMenuAndActionHash(textureMenu, MenuOption::RenderMaxTexture64MB, 0, false)); + textureGroup->addAction(addCheckableActionToQMenuAndActionHash(textureMenu, MenuOption::RenderMaxTexture256MB, 0, false)); + textureGroup->addAction(addCheckableActionToQMenuAndActionHash(textureMenu, MenuOption::RenderMaxTexture512MB, 0, false)); + textureGroup->addAction(addCheckableActionToQMenuAndActionHash(textureMenu, MenuOption::RenderMaxTexture1024MB, 0, false)); + textureGroup->addAction(addCheckableActionToQMenuAndActionHash(textureMenu, MenuOption::RenderMaxTexture2048MB, 0, false)); + connect(textureGroup, &QActionGroup::triggered, [textureGroup] { + auto checked = textureGroup->checkedAction(); + auto text = checked->text(); + gpu::Context::Size newMaxTextureMemory { 0 }; + if (MenuOption::RenderMaxTexture64MB == text) { + newMaxTextureMemory = MB_TO_BYTES(64); + } else if (MenuOption::RenderMaxTexture256MB == text) { + newMaxTextureMemory = MB_TO_BYTES(256); + } else if (MenuOption::RenderMaxTexture512MB == text) { + newMaxTextureMemory = MB_TO_BYTES(512); + } else if (MenuOption::RenderMaxTexture1024MB == text) { + newMaxTextureMemory = MB_TO_BYTES(1024); + } else if (MenuOption::RenderMaxTexture2048MB == text) { + newMaxTextureMemory = MB_TO_BYTES(2048); + } + gpu::Texture::setAllowedGPUMemoryUsage(newMaxTextureMemory); + }); + // Developer > Render > LOD Tools addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::LodTools, 0, dialogsManager.data(), SLOT(lodTools())); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 600632d125..b1bcf91e10 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -150,6 +150,13 @@ namespace MenuOption { const QString RenderFocusIndicator = "Show Eye Focus"; const QString RenderLookAtTargets = "Show Look-at Targets"; const QString RenderLookAtVectors = "Show Look-at Vectors"; + const QString RenderMaxTextureMemory = "Maximum Texture Memory"; + const QString RenderMaxTextureAutomatic = "Automatic Texture Memory"; + const QString RenderMaxTexture64MB = "64 MB"; + const QString RenderMaxTexture256MB = "256 MB"; + const QString RenderMaxTexture512MB = "512 MB"; + const QString RenderMaxTexture1024MB = "1024 MB"; + const QString RenderMaxTexture2048MB = "2048 MB"; const QString RenderResolution = "Scale Resolution"; const QString RenderResolutionOne = "1"; const QString RenderResolutionTwoThird = "2/3"; diff --git a/libraries/gpu/src/gpu/Format.h b/libraries/gpu/src/gpu/Format.h index 940b0eb85b..9c08e45bec 100644 --- a/libraries/gpu/src/gpu/Format.h +++ b/libraries/gpu/src/gpu/Format.h @@ -14,7 +14,7 @@ #include #include -#include +#include "Forward.h" namespace gpu { @@ -37,28 +37,6 @@ private: friend class Backend; }; -typedef int Stamp; - -typedef unsigned int uint32; -typedef int int32; -typedef unsigned short uint16; -typedef short int16; -typedef unsigned char uint8; -typedef char int8; - -typedef unsigned char Byte; - -typedef size_t Offset; - -typedef glm::mat4 Mat4; -typedef glm::mat3 Mat3; -typedef glm::vec4 Vec4; -typedef glm::ivec4 Vec4i; -typedef glm::vec3 Vec3; -typedef glm::vec2 Vec2; -typedef glm::ivec2 Vec2i; -typedef glm::uvec2 Vec2u; - // Description of a scalar type enum Type { diff --git a/libraries/gpu/src/gpu/Forward.h b/libraries/gpu/src/gpu/Forward.h index 0fa315ef1c..07cef0925a 100644 --- a/libraries/gpu/src/gpu/Forward.h +++ b/libraries/gpu/src/gpu/Forward.h @@ -9,69 +9,77 @@ #ifndef hifi_gpu_Forward_h #define hifi_gpu_Forward_h +#include +#include +#include + +#include + namespace gpu { class Batch; class Backend; class Context; - typedef std::shared_ptr ContextPointer; + using ContextPointer = std::shared_ptr; class GPUObject; - typedef int Stamp; - typedef uint32_t uint32; - typedef int32_t int32; - typedef uint16_t uint16; - typedef int16_t int16; - typedef uint8_t uint8; - typedef int8_t int8; + using Stamp = int; + using uint32 = uint32_t; + using int32 = int32_t; + using uint16 = uint16_t; + using int16 = int16_t; + using uint8 = uint8_t; + using int8 = int8_t; - typedef uint8 Byte; - typedef uint32 Offset; - typedef std::vector Offsets; + using Byte = uint8; + using Offset = size_t; + using Offsets = std::vector; - typedef glm::mat4 Mat4; - typedef glm::mat3 Mat3; - typedef glm::vec4 Vec4; - typedef glm::ivec4 Vec4i; - typedef glm::vec3 Vec3; - typedef glm::vec2 Vec2; - typedef glm::ivec2 Vec2i; - typedef glm::uvec2 Vec2u; + using Mat4 = glm::mat4; + using Mat3 = glm::mat3; + using Vec4 = glm::vec4; + using Vec4i = glm::ivec4; + using Vec3 = glm::vec3; + using Vec2 = glm::vec2; + using Vec2i = glm::ivec2; + using Vec2u = glm::uvec2; + using Vec3u = glm::uvec3; + using Vec3u = glm::uvec3; class Element; - typedef Element Format; + using Format = Element; class Swapchain; - typedef std::shared_ptr SwapchainPointer; + using SwapchainPointer = std::shared_ptr; class Framebuffer; - typedef std::shared_ptr FramebufferPointer; + using FramebufferPointer = std::shared_ptr; class Pipeline; - typedef std::shared_ptr PipelinePointer; - typedef std::vector Pipelines; + using PipelinePointer = std::shared_ptr; + using Pipelines = std::vector; class Query; - typedef std::shared_ptr QueryPointer; - typedef std::vector Queries; + using QueryPointer = std::shared_ptr; + using Queries = std::vector; class Resource; class Buffer; - typedef std::shared_ptr BufferPointer; - typedef std::vector Buffers; + using BufferPointer = std::shared_ptr; + using Buffers = std::vector; class BufferView; class Shader; - typedef Shader::Pointer ShaderPointer; - typedef std::vector Shaders; + using ShaderPointer = std::shared_ptr; + using Shaders = std::vector; class State; - typedef std::shared_ptr StatePointer; - typedef std::vector States; + using StatePointer = std::shared_ptr; + using States = std::vector; class Stream; class BufferStream; - typedef std::shared_ptr BufferStreamPointer; + using BufferStreamPointer = std::shared_ptr; class Texture; class SphericalHarmonics; - typedef std::shared_ptr SHPointer; + using SHPointer = std::shared_ptr; class Sampler; class Texture; - typedef std::shared_ptr TexturePointer; - typedef std::vector Textures; + using TexturePointer = std::shared_ptr; + using Textures = std::vector; class TextureView; - typedef std::vector TextureViews; + using TextureViews = std::vector; } #endif diff --git a/libraries/gpu/src/gpu/GLBackend.cpp b/libraries/gpu/src/gpu/GLBackend.cpp index b4217a009a..3b237052c6 100644 --- a/libraries/gpu/src/gpu/GLBackend.cpp +++ b/libraries/gpu/src/gpu/GLBackend.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #if defined(NSIGHT_FOUND) #include "nvToolsExt.h" @@ -126,6 +127,72 @@ void GLBackend::init() { }); } + + +//Information on the current memory resources available can be queried +//by specifying VBO_FREE_MEMORY_ATI, , or +//RENDERBUFFER_FREE_MEMORY_ATI as the value parameter to GetIntergerv. +//These return the memory status for pools of memory used for vertex +//buffer objects, textures, and render buffers respectively.The +//memory status is not meant to be an exact measurement of the system's +//current status(though it may be in some implementations), but it is +//instead meant to represent the present load such that an application +//can make decisions on how aggressive it can be on the allocation of +//resources without overloading the system.The query returns a 4 - tuple +//integer where the values are in Kbyte and have the following meanings : +// +//param[0] - total memory free in the pool +//param[1] - largest available free block in the pool +//param[2] - total auxiliary memory free +//param[3] - largest auxiliary free block + + +Context::Size GLBackend::getDedicatedMemory() { + static Context::Size dedicatedMemory { 0 }; + static std::once_flag once; + std::call_once(once, [&] { +#ifdef Q_OS_WIN + if (!dedicatedMemory && wglGetGPUIDsAMD && wglGetGPUInfoAMD) { + UINT maxCount = wglGetGPUIDsAMD(0, 0); + std::vector ids; + ids.resize(maxCount); + wglGetGPUIDsAMD(maxCount, &ids[0]); + GLuint memTotal; + wglGetGPUInfoAMD(ids[0], WGL_GPU_RAM_AMD, GL_UNSIGNED_INT, sizeof(GLuint), &memTotal); + dedicatedMemory = MB_TO_BYTES(memTotal); + } +#endif + + if (!dedicatedMemory) { + GLint atiGpuMemory[4]; + // not really total memory, but close enough if called early enough in the application lifecycle + glGetIntegerv(GL_TEXTURE_FREE_MEMORY_ATI, atiGpuMemory); + if (GL_NO_ERROR == glGetError()) { + dedicatedMemory = KB_TO_BYTES(atiGpuMemory[0]); + } + } + + if (!dedicatedMemory) { + GLint nvGpuMemory { 0 }; + glGetIntegerv(GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX, &nvGpuMemory); + if (GL_NO_ERROR == glGetError()) { + dedicatedMemory = KB_TO_BYTES(nvGpuMemory); + } + } + + // FIXME Pending Howard's PR + //if (!dedicatedMemory) { + // auto gpuIdent = GPUIdent::getInstance(); + // if (gpuIdent && gpuIdent->isValid()) { + // auto gpuMb = gpuIdent->getMemory(); + // maxMemory = ((size_t)gpuMb) << MB_TO_BYTES_SHIFT; + // } + //} + }); + + return dedicatedMemory; +} + Backend* GLBackend::createBackend() { return new GLBackend(); } diff --git a/libraries/gpu/src/gpu/GLBackend.h b/libraries/gpu/src/gpu/GLBackend.h index 05afb28bf1..6c22ee0222 100644 --- a/libraries/gpu/src/gpu/GLBackend.h +++ b/libraries/gpu/src/gpu/GLBackend.h @@ -37,6 +37,8 @@ class GLBackend : public Backend { explicit GLBackend(bool syncCache); GLBackend(); public: + static Context::Size getDedicatedMemory(); + virtual ~GLBackend(); virtual void render(Batch& batch); @@ -82,11 +84,35 @@ public: const Stamp _storageStamp; Stamp _contentStamp { 0 }; const GLenum _target; + const uint16 _maxMip; + const uint16 _minMip; + const bool _transferrable; - GLTexture(const gpu::Texture& gpuTexture); + struct DownsampleSource { + using Pointer = std::shared_ptr; + DownsampleSource(GLTexture& oldTexture); + ~DownsampleSource(); + const GLuint _texture; + const uint16 _minMip; + const uint16 _maxMip; + }; + + DownsampleSource::Pointer _downsampleSource; + + GLTexture(bool transferrable, const gpu::Texture& gpuTexture); + GLTexture(GLTexture& originalTexture, const gpu::Texture& gpuTexture); ~GLTexture(); + // 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(); + + void withPreservedTexture(std::function f); + void createTexture(); + void allocateStorage(); GLuint size() const { return _size; } GLuint virtualSize() const { return _virtualSize; } @@ -118,26 +144,34 @@ public: // Is the texture in a state where it can be rendered with no work? bool isReady() const; + // Is this texture pushing us over the memory limit? + bool isOverMaxMemory() const; + // Move the image bits from the CPU to the GPU void transfer() const; // Execute any post-move operations that must occur only on the main thread void postTransfer(); + uint16 usedMipLevels() const { return (_maxMip - _minMip) + 1; } + static const size_t CUBE_NUM_FACES = 6; static const GLenum CUBE_FACE_LAYOUT[6]; private: + friend class GLTextureTransferHelper; + + GLTexture(bool transferrable, const gpu::Texture& gpuTexture, bool init); // at creation the true texture is created in GL // it becomes public only when ready. GLuint _privateTexture{ 0 }; - void setSize(GLuint size); - void setVirtualSize(GLuint size); + const std::vector& getFaceTargets() const; - GLuint _size; // true size as reported by the gl api - GLuint _virtualSize; // theorical size as expected - GLuint _numLevels{ 0 }; + void setSize(GLuint size); + + const GLuint _virtualSize; // theorical size as expected + GLuint _size { 0 }; // true size as reported by the gl api void transferMip(uint16_t mipLevel, uint8_t face = 0) const; diff --git a/libraries/gpu/src/gpu/GLBackendTexture.cpp b/libraries/gpu/src/gpu/GLBackendTexture.cpp index dfb854143b..e3152b1fdd 100755 --- a/libraries/gpu/src/gpu/GLBackendTexture.cpp +++ b/libraries/gpu/src/gpu/GLBackendTexture.cpp @@ -10,6 +10,8 @@ // #include "GPULogging.h" +#include +#include #include #include "GLBackendShared.h" @@ -35,44 +37,164 @@ GLenum gpuToGLTextureType(const Texture& texture) { } GLuint allocateSingleTexture() { + Backend::incrementTextureGPUCount(); GLuint result; glGenTextures(1, &result); return result; } + +// FIXME placeholder for texture memory over-use +#define DEFAULT_MAX_MEMORY_MB 256 + +float GLBackend::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) { + auto totalGpuMemory = GLBackend::getDedicatedMemory(); + + // If no limit has been explicitly set, and the dedicated memory can't be determined, + // just use a fallback fixed value of 256 MB + if (!totalGpuMemory) { + totalGpuMemory = MB_TO_BYTES(DEFAULT_MAX_MEMORY_MB); + } + + // Allow 75% of all available GPU memory to be consumed by textures + // FIXME overly conservative? + availableTextureMemory = (totalGpuMemory >> 2) * 3; + } + + // Return the consumed texture memory divided by the available texture memory. + auto consumedGpuMemory = Context::getTextureGPUMemoryUsage(); + return (float)consumedGpuMemory / (float)availableTextureMemory; +} + +GLBackend::GLTexture::DownsampleSource::DownsampleSource(GLTexture& oldTexture) : + _texture(oldTexture._privateTexture), + _minMip(oldTexture._minMip), + _maxMip(oldTexture._maxMip) +{ + // Take ownership of the GL texture + oldTexture._texture = oldTexture._privateTexture = 0; +} + +GLBackend::GLTexture::DownsampleSource::~DownsampleSource() { + if (_texture) { + Backend::decrementTextureGPUCount(); + glDeleteTextures(1, &_texture); + } +} + const GLenum GLBackend::GLTexture::CUBE_FACE_LAYOUT[6] = { GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z }; -// Create the texture and allocate storage -GLBackend::GLTexture::GLTexture(const Texture& texture) : +static std::map _textureCountByMips; +static uint16 _currentMaxMipCount { 0 }; + +GLBackend::GLTexture::GLTexture(bool transferrable, const Texture& texture, bool init) : _storageStamp(texture.getStamp()), _target(gpuToGLTextureType(texture)), - _size(0), - _virtualSize(0), - _numLevels(texture.maxMip() + 1), + _maxMip(texture.maxMip()), + _minMip(texture.minMip()), + _transferrable(transferrable), + _virtualSize(texture.evalTotalSize()), + _size(_virtualSize), _gpuTexture(texture) -{ - Backend::incrementTextureGPUCount(); - Backend::setGPUObject(texture, this); +{ + Q_UNUSED(init); - - // updateSize(); - GLuint virtualSize = _gpuTexture.evalTotalSize(); - setVirtualSize(virtualSize); - setSize(virtualSize); + if (_transferrable) { + uint16 mipCount = usedMipLevels(); + _currentMaxMipCount = std::max(_currentMaxMipCount, mipCount); + if (!_textureCountByMips.count(mipCount)) { + _textureCountByMips[mipCount] = 1; + } else { + ++_textureCountByMips[mipCount]; + } + } else { + withPreservedTexture([&] { + createTexture(); + }); + _contentStamp = _gpuTexture.getDataStamp(); + postTransfer(); + } + + Backend::updateTextureGPUMemoryUsage(0, _size); + Backend::updateTextureGPUVirtualMemoryUsage(0, _virtualSize); } -void GLBackend::GLTexture::createTexture() { - _privateTexture = allocateSingleTexture(); +// Create the texture and allocate storage +GLBackend::GLTexture::GLTexture(bool transferrable, const Texture& texture) : + GLTexture(transferrable, texture, true) +{ + Backend::setGPUObject(texture, this); +} - GLsizei width = _gpuTexture.getWidth(); - GLsizei height = _gpuTexture.getHeight(); +// Create the texture and copy from the original higher resolution version +GLBackend::GLTexture::GLTexture(GLTexture& originalTexture, const gpu::Texture& texture) : + GLTexture(originalTexture._transferrable, texture, true) +{ + if (!originalTexture._texture) { + qFatal("Invalid original texture"); + } + Q_ASSERT(_minMip >= originalTexture._minMip); + // Our downsampler takes ownership of the texture + _downsampleSource = std::make_shared(originalTexture); + _texture = _downsampleSource->_texture; - GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuTexture.getTexelFormat()); + // Set the GPU object last because that implicitly destroys the originalTexture object + Backend::setGPUObject(texture, this); +} +GLBackend::GLTexture::~GLTexture() { + if (_privateTexture != 0) { + Backend::decrementTextureGPUCount(); + glDeleteTextures(1, &_privateTexture); + } + + if (_transferrable) { + uint16 mipCount = usedMipLevels(); + Q_ASSERT(_textureCountByMips.count(mipCount)); + if (0 == --_textureCountByMips[mipCount]) { + _textureCountByMips.erase(mipCount); + if (mipCount == _currentMaxMipCount) { + _currentMaxMipCount = _textureCountByMips.rbegin()->first; + } + } + } + + Backend::updateTextureGPUMemoryUsage(_size, 0); + Backend::updateTextureGPUVirtualMemoryUsage(_virtualSize, 0); +} + +const std::vector& GLBackend::GLTexture::getFaceTargets() const { + static std::vector cubeFaceTargets { + 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 + }; + static std::vector faceTargets { + GL_TEXTURE_2D + }; + switch (_target) { + case GL_TEXTURE_2D: + return faceTargets; + case GL_TEXTURE_CUBE_MAP: + return cubeFaceTargets; + default: + Q_UNREACHABLE(); + break; + } + Q_UNREACHABLE(); + return faceTargets; +} + +void GLBackend::GLTexture::withPreservedTexture(std::function f) { GLint boundTex = -1; switch (_target) { case GL_TEXTURE_2D: @@ -88,47 +210,46 @@ void GLBackend::GLTexture::createTexture() { } (void)CHECK_GL_ERROR(); - glBindTexture(_target, _privateTexture); - - (void)CHECK_GL_ERROR(); - // Fixme: this usage of TexStorage doesn;t work wtih compressed texture, altuogh it should. - // GO through the process of allocating the correct storage - if (GLEW_VERSION_4_2 && !_gpuTexture.getTexelFormat().isCompressed()) { - glTexStorage2D(_target, _numLevels, texelFormat.internalFormat, width, height); - (void)CHECK_GL_ERROR(); - } else { - glTexParameteri(_target, GL_TEXTURE_BASE_LEVEL, 0); - glTexParameteri(_target, GL_TEXTURE_MAX_LEVEL, _numLevels - 1); - for (uint16_t l = 0; l < _numLevels; l++) { - if (_gpuTexture.getType() == gpu::Texture::TEX_CUBE) { - for (size_t face = 0; face < CUBE_NUM_FACES; face++) { - glTexImage2D(CUBE_FACE_LAYOUT[face], l, texelFormat.internalFormat, width, height, 0, texelFormat.format, texelFormat.type, NULL); - } - } else { - glTexImage2D(_target, l, texelFormat.internalFormat, width, height, 0, texelFormat.format, texelFormat.type, NULL); - } - width = std::max(1, (width / 2)); - height = std::max(1, (height / 2)); - } - (void)CHECK_GL_ERROR(); - } - - syncSampler(_gpuTexture.getSampler(), _gpuTexture.getType(), this); - (void)CHECK_GL_ERROR(); - + f(); glBindTexture(_target, boundTex); (void)CHECK_GL_ERROR(); } -GLBackend::GLTexture::~GLTexture() { - if (_privateTexture != 0) { - glDeleteTextures(1, &_privateTexture); +void GLBackend::GLTexture::createTexture() { + _privateTexture = allocateSingleTexture(); + + glBindTexture(_target, _privateTexture); + (void)CHECK_GL_ERROR(); + + allocateStorage(); + (void)CHECK_GL_ERROR(); + + syncSampler(_gpuTexture.getSampler(), _gpuTexture.getType(), this); + (void)CHECK_GL_ERROR(); +} + +void GLBackend::GLTexture::allocateStorage() { + GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuTexture.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 && !_gpuTexture.getTexelFormat().isCompressed()) { + // Get the dimensions, accounting for the downgrade level + Vec3u dimensions = _gpuTexture.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 = _gpuTexture.evalMipDimensions(l); + for (GLenum target : getFaceTargets()) { + glTexImage2D(target, l - _minMip, texelFormat.internalFormat, dimensions.x, dimensions.y, 0, texelFormat.format, texelFormat.type, NULL); + (void)CHECK_GL_ERROR(); + } + } } - - Backend::updateTextureGPUMemoryUsage(_size, 0); - Backend::updateTextureGPUVirtualMemoryUsage(_virtualSize, 0); - Backend::decrementTextureGPUCount(); } @@ -137,16 +258,10 @@ void GLBackend::GLTexture::setSize(GLuint size) { _size = size; } -void GLBackend::GLTexture::setVirtualSize(GLuint size) { - Backend::updateTextureGPUVirtualMemoryUsage(_virtualSize, size); - _virtualSize = size; -} - void GLBackend::GLTexture::updateSize() { - GLuint virtualSize = _gpuTexture.evalTotalSize(); - setVirtualSize(virtualSize); + setSize(_virtualSize); if (!_texture) { - setSize(virtualSize); + return; } if (_gpuTexture.getTexelFormat().isCompressed()) { @@ -161,7 +276,7 @@ void GLBackend::GLTexture::updateSize() { (void)CHECK_GL_ERROR(); if (gpuSize) { - for (GLuint level = 0; level < _numLevels; level++) { + for (GLuint level = _minMip; level < _maxMip; level++) { GLint levelSize{ 0 }; glGetTexLevelParameteriv(proxyType, level, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &levelSize); levelSize *= numFaces; @@ -172,24 +287,32 @@ void GLBackend::GLTexture::updateSize() { gpuSize += levelSize; } (void)CHECK_GL_ERROR(); - setSize(gpuSize); - } else { - setSize(virtualSize); - } - - } else { - setSize(virtualSize); - } + return; + } + } } - bool GLBackend::GLTexture::isInvalid() const { return _storageStamp < _gpuTexture.getStamp(); } bool GLBackend::GLTexture::isOutdated() const { - return _contentStamp < _gpuTexture.getDataStamp(); + return GLTexture::Idle == _syncState && _contentStamp < _gpuTexture.getDataStamp(); +} + +bool GLBackend::GLTexture::isOverMaxMemory() const { + // FIXME switch to using the max mip count used from the previous frame + if (usedMipLevels() < _currentMaxMipCount) { + return false; + } + Q_ASSERT(usedMipLevels() == _currentMaxMipCount); + + if (getMemoryPressure() < 1.0f) { + return false; + } + + return true; } bool GLBackend::GLTexture::isReady() const { @@ -203,23 +326,28 @@ bool GLBackend::GLTexture::isReady() const { auto syncState = _syncState.load(); if (isOutdated()) { - return Pending == syncState; + return Idle != syncState; } - return Idle == syncState; + if (Idle != syncState) { + return false; + } + + return true; } // Move content bits from the CPU to the GPU for a given mip / face void GLBackend::GLTexture::transferMip(uint16_t mipLevel, uint8_t face) const { auto mip = _gpuTexture.accessStoredMipFace(mipLevel, face); GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuTexture.getTexelFormat(), mip->getFormat()); + //GLenum target = getFaceTargets()[face]; GLenum target = _target == GL_TEXTURE_2D ? GL_TEXTURE_2D : CUBE_FACE_LAYOUT[face]; - uvec2 size = uvec2(_gpuTexture.getWidth(), _gpuTexture.getHeight()); - size >>= mipLevel; + auto size = _gpuTexture.evalMipDimensions(mipLevel); glTexSubImage2D(target, mipLevel, 0, 0, size.x, size.y, texelFormat.format, texelFormat.type, mip->readData()); (void)CHECK_GL_ERROR(); } +// This should never happen on the main thread // Move content bits from the CPU to the GPU void GLBackend::GLTexture::transfer() const { PROFILE_RANGE(__FUNCTION__); @@ -229,15 +357,39 @@ void GLBackend::GLTexture::transfer() const { return; } - //_secretTexture glBindTexture(_target, _privateTexture); - // glBindTexture(_target, _texture); - // GO through the process of allocating the correct storage and/or update the content - switch (_gpuTexture.getType()) { - case Texture::TEX_2D: - for (uint16_t i = 0; i < Sampler::MAX_MIP_LEVEL; ++i) { - if (_gpuTexture.isStoredMipFaceAvailable(i)) { - transferMip(i); + (void)CHECK_GL_ERROR(); + + if (_downsampleSource) { + GLuint fbo { 0 }; + glGenFramebuffers(1, &fbo); + (void)CHECK_GL_ERROR(); + glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); + (void)CHECK_GL_ERROR(); + // Find the distance between the old min mip and the new one + uint16 mipOffset = _minMip - _downsampleSource->_minMip; + for (uint16 i = _minMip; i <= _maxMip; ++i) { + uint16 targetMip = i - _minMip; + uint16 sourceMip = targetMip + mipOffset; + Vec3u dimensions = _gpuTexture.evalMipDimensions(i); + for (GLenum target : getFaceTargets()) { + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, _downsampleSource->_texture, sourceMip); + (void)CHECK_GL_ERROR(); + glCopyTexSubImage2D(target, targetMip, 0, 0, 0, 0, dimensions.x, dimensions.y); + (void)CHECK_GL_ERROR(); + } + } + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + glDeleteFramebuffers(1, &fbo); + } else { + // GO through the process of allocating the correct storage and/or update the content + switch (_gpuTexture.getType()) { + case Texture::TEX_2D: + { + for (uint16_t i = _minMip; i <= _maxMip; ++i) { + if (_gpuTexture.isStoredMipFaceAvailable(i)) { + transferMip(i); + } } } break; @@ -256,8 +408,8 @@ void GLBackend::GLTexture::transfer() const { default: qCWarning(gpulogging) << __FUNCTION__ << " case for Texture Type " << _gpuTexture.getType() << " not supported"; break; + } } - if (_gpuTexture.isAutogenerateMips()) { glGenerateMipmap(_target); (void)CHECK_GL_ERROR(); @@ -271,6 +423,8 @@ void GLBackend::GLTexture::postTransfer() { // The public gltexture becaomes available _texture = _privateTexture; + _downsampleSource.reset(); + // At this point the mip pixels have been loaded, we can notify the gpu texture to abandon it's memory switch (_gpuTexture.getType()) { case Texture::TEX_2D: @@ -307,39 +461,38 @@ GLBackend::GLTexture* GLBackend::syncGPUObject(const TexturePointer& texturePoin // If the object hasn't been created, or the object definition is out of date, drop and re-create GLTexture* object = Backend::getGPUObject(texture); - if (object && object->isReady()) { - return object; - } - - // Object isn't ready, check what we need to do... // Create the texture if need be (force re-creation if the storage stamp changes // for easier use of immutable storage) if (!object || object->isInvalid()) { - // This automatically destroys the old texture - object = new GLTexture(texture); + // This automatically any previous texture + object = new GLTexture(needTransfer, texture); } // Object maybe doens't neet to be tranasferred after creation - if (!needTransfer) { - object->createTexture(); - object->_contentStamp = texturePointer->getDataStamp(); + if (!object->_transferrable) { + return object; + } + + // If we just did a transfer, return the object after doing post-transfer work + if (GLTexture::Transferred == object->getSyncState()) { object->postTransfer(); return object; } - // Object might be outdated, if so, start the transfer - // (outdated objects that are already in transfer will have reported 'true' for ready() - if (object->isOutdated()) { - Backend::incrementTextureGPUTransferCount(); + if (object->isReady()) { + // Do we need to reduce texture memory usage? + if (object->isOverMaxMemory() && texturePointer->incremementMinMip()) { + // This automatically destroys the old texture + object = new GLTexture(*object, texture); + _textureTransferHelper->transferTexture(texturePointer); + } + } else 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); } - if (GLTexture::Transferred == object->getSyncState()) { - Backend::decrementTextureGPUTransferCount(); - object->postTransfer(); - } - return object; } @@ -359,8 +512,14 @@ GLuint GLBackend::getTextureID(const TexturePointer& texture, bool sync) { } else { object = Backend::getGPUObject(*texture); } - if (object && object->getSyncState() == GLTexture::Idle) { - return object->_texture; + if (object) { + if (object->getSyncState() == GLTexture::Idle) { + return object->_texture; + } else if (object->_downsampleSource) { + return object->_downsampleSource->_texture; + } else { + return 0; + } } else { return 0; } @@ -425,7 +584,7 @@ void GLBackend::syncSampler(const Sampler& sampler, Texture::Type type, const GL glTexParameteri(object->_target, GL_TEXTURE_WRAP_R, wrapModes[sampler.getWrapModeW()]); glTexParameterfv(object->_target, GL_TEXTURE_BORDER_COLOR, (const float*)&sampler.getBorderColor()); - glTexParameteri(object->_target, GL_TEXTURE_BASE_LEVEL, sampler.getMipOffset()); + glTexParameteri(object->_target, GL_TEXTURE_BASE_LEVEL, (uint16)sampler.getMipOffset()); glTexParameterf(object->_target, GL_TEXTURE_MIN_LOD, (float)sampler.getMinMip()); glTexParameterf(object->_target, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.f : sampler.getMaxMip())); glTexParameterf(object->_target, GL_TEXTURE_MAX_ANISOTROPY_EXT, sampler.getMaxAnisotropy()); diff --git a/libraries/gpu/src/gpu/GLBackendTextureTransfer.cpp b/libraries/gpu/src/gpu/GLBackendTextureTransfer.cpp index f49eb3d7fa..80848f6073 100644 --- a/libraries/gpu/src/gpu/GLBackendTextureTransfer.cpp +++ b/libraries/gpu/src/gpu/GLBackendTextureTransfer.cpp @@ -11,11 +11,8 @@ #include "GLBackendShared.h" #ifdef THREADED_TEXTURE_TRANSFER - #include #include - - #endif using namespace gpu; @@ -46,12 +43,20 @@ GLTextureTransferHelper::~GLTextureTransferHelper() { void GLTextureTransferHelper::transferTexture(const gpu::TexturePointer& texturePointer) { GLBackend::GLTexture* object = Backend::getGPUObject(*texturePointer); + Backend::incrementTextureGPUTransferCount(); #ifdef THREADED_TEXTURE_TRANSFER - TextureTransferPackage package{ texturePointer, 0}; + GLsync fence { 0 }; + //fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + //glFlush(); + + TextureTransferPackage package { texturePointer, fence }; object->setSyncState(GLBackend::GLTexture::Pending); queueItem(package); #else - object->transfer(); + object->withPreservedTexture([&] { + do_transfer(*object); + }); + object->_contentStamp = texturePointer->getDataStamp(); object->setSyncState(GLBackend::GLTexture::Transferred); #endif } @@ -70,6 +75,12 @@ void GLTextureTransferHelper::shutdown() { #endif } +void GLTextureTransferHelper::do_transfer(GLBackend::GLTexture& texture) { + texture.createTexture(); + texture.transfer(); + texture.updateSize(); + Backend::decrementTextureGPUTransferCount(); +} bool GLTextureTransferHelper::processQueueItems(const Queue& messages) { for (auto package : messages) { @@ -79,14 +90,16 @@ bool GLTextureTransferHelper::processQueueItems(const Queue& messages) { continue; } + if (package.fence) { + glClientWaitSync(package.fence, GL_SYNC_FLUSH_COMMANDS_BIT, GL_TIMEOUT_IGNORED); + glDeleteSync(package.fence); + package.fence = 0; + } + GLBackend::GLTexture* object = Backend::getGPUObject(*texturePointer); - object->createTexture(); - - object->transfer(); - - object->updateSize(); - + do_transfer(*object); glBindTexture(object->_target, 0); + auto writeSync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); glClientWaitSync(writeSync, GL_SYNC_FLUSH_COMMANDS_BIT, GL_TIMEOUT_IGNORED); glDeleteSync(writeSync); diff --git a/libraries/gpu/src/gpu/GLBackendTextureTransfer.h b/libraries/gpu/src/gpu/GLBackendTextureTransfer.h index 1046fc9883..6deaf1e49c 100644 --- a/libraries/gpu/src/gpu/GLBackendTextureTransfer.h +++ b/libraries/gpu/src/gpu/GLBackendTextureTransfer.h @@ -32,33 +32,10 @@ protected: void setup() override; void shutdown() override; bool processQueueItems(const Queue& messages) override; - void transferTextureSynchronous(const gpu::Texture& texture); + void do_transfer(GLBackend::GLTexture& texturePointer); private: QSharedPointer _canvas; }; -template -void withPreservedTexture(GLenum target, F f) { - GLint boundTex = -1; - switch (target) { - case GL_TEXTURE_2D: - glGetIntegerv(GL_TEXTURE_BINDING_2D, &boundTex); - break; - - case GL_TEXTURE_CUBE_MAP: - glGetIntegerv(GL_TEXTURE_BINDING_CUBE_MAP, &boundTex); - break; - - default: - qFatal("Unsupported texture type"); - } - (void)CHECK_GL_ERROR(); - - f(); - - glBindTexture(target, boundTex); - (void)CHECK_GL_ERROR(); -} - } diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index 2a19dc926b..9f10f2e2b6 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -12,6 +12,9 @@ #include "Texture.h" #include + +#include + #include "GPULogging.h" #include "Context.h" @@ -21,6 +24,7 @@ static int TexturePointerMetaTypeId = qRegisterMetaType(); std::atomic Texture::_textureCPUCount{ 0 }; std::atomic Texture::_textureCPUMemoryUsage{ 0 }; +std::atomic Texture::_allowedCPUMemoryUsage { 0 }; void Texture::updateTextureCPUMemoryUsage(Size prevObjectSize, Size newObjectSize) { if (prevObjectSize == newObjectSize) { @@ -57,6 +61,15 @@ uint32_t Texture::getTextureGPUTransferCount() { return Context::getTextureGPUTransferCount(); } +Texture::Size Texture::getAllowedGPUMemoryUsage() { + return _allowedCPUMemoryUsage; +} + +void Texture::setAllowedGPUMemoryUsage(Size size) { + qDebug() << "New MAX texture memory " << BYTES_TO_MB(size) << " MB"; + _allowedCPUMemoryUsage = size; +} + uint8 Texture::NUM_FACES_PER_TYPE[NUM_TYPES] = { 1, 1, 1, 6 }; Texture::Pixels::Pixels(const Element& format, Size size, const Byte* bytes) : @@ -333,10 +346,6 @@ uint16 Texture::evalNumMips() const { return 1 + (uint16) val; } -uint16 Texture::maxMip() const { - return _maxMip; -} - bool Texture::assignStoredMip(uint16 level, const Element& format, Size size, const Byte* bytes) { // Check that level accessed make sense if (level != 0) { @@ -870,3 +879,18 @@ bool TextureSource::isDefined() const { } } +bool Texture::setMinMip(uint16 newMinMip) { + uint16 oldMinMip = _minMip; + _minMip = std::min(std::max(_minMip, newMinMip), _maxMip); + return oldMinMip != _minMip; +} + +bool Texture::incremementMinMip(uint16 count) { + return setMinMip(_minMip + count); +} + +Vec3u Texture::evalMipDimensions(uint16 level) const { + auto dimensions = getDimensions(); + dimensions >>= level; + return glm::max(dimensions, Vec3u(1)); +} diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 69d50617bf..8f075d906b 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -11,14 +11,15 @@ #ifndef hifi_gpu_Texture_h #define hifi_gpu_Texture_h -#include "Resource.h" - #include //min max and more #include #include #include +#include "Forward.h" +#include "Resource.h" + namespace gpu { // THe spherical harmonics is a nice tool for cubemap, so if required, the irradiance SH can be automatically generated @@ -141,6 +142,7 @@ protected: class Texture : public Resource { static std::atomic _textureCPUCount; static std::atomic _textureCPUMemoryUsage; + static std::atomic _allowedCPUMemoryUsage; static void updateTextureCPUMemoryUsage(Size prevObjectSize, Size newObjectSize); public: static uint32_t getTextureCPUCount(); @@ -149,6 +151,8 @@ public: static Size getTextureGPUMemoryUsage(); static Size getTextureGPUVirtualMemoryUsage(); static uint32_t getTextureGPUTransferCount(); + static Size getAllowedGPUMemoryUsage(); + static void setAllowedGPUMemoryUsage(Size size); class Usage { public: @@ -313,6 +317,7 @@ public: const Element& getTexelFormat() const { return _texelFormat; } bool hasBorder() const { return false; } + Vec3u getDimensions() const { return Vec3u(_width, _height, _depth); } uint16 getWidth() const { return _width; } uint16 getHeight() const { return _height; } uint16 getDepth() const { return _depth; } @@ -346,6 +351,8 @@ public: // Eval the size that the mips level SHOULD have // not the one stored in the Texture + static const uint MIN_DIMENSION = 1; + Vec3u evalMipDimensions(uint16 level) const; uint16 evalMipWidth(uint16 level) const { return std::max(_width >> level, 1); } uint16 evalMipHeight(uint16 level) const { return std::max(_height >> level, 1); } uint16 evalMipDepth(uint16 level) const { return std::max(_depth >> level, 1); } @@ -363,7 +370,7 @@ public: uint32 evalTotalSize() const { uint32 size = 0; - uint16 minMipLevel = 0; + uint16 minMipLevel = minMip(); uint16 maxMipLevel = maxMip(); for (uint16 l = minMipLevel; l <= maxMipLevel; l++) { size += evalMipSize(l); @@ -371,10 +378,19 @@ public: return size * getNumSlices(); } - // max mip is in the range [ 1 if no sub mips, log2(max(width, height, depth))] + // max mip is in the range [ 0 if no sub mips, log2(max(width, height, depth))] // if autoGenerateMip is on => will provide the maxMIp level specified // else provide the deepest mip level provided through assignMip - uint16 maxMip() const; + uint16 maxMip() const { return _maxMip; } + + uint16 minMip() const { return _minMip; } + + uint16 mipLevels() const { return _maxMip + 1; } + + uint16 usedMipLevels() const { return (_maxMip - _minMip) + 1; } + + bool setMinMip(uint16 newMinMip); + bool incremementMinMip(uint16 count = 1); // Generate the mips automatically // But the sysmem version is not available @@ -451,7 +467,8 @@ protected: uint16 _numSamples = 1; uint16 _numSlices = 1; - uint16 _maxMip = 0; + uint16 _maxMip { 0 }; + uint16 _minMip { 0 }; Type _type = TEX_1D; diff --git a/tests/gpu-test/src/main.cpp b/tests/gpu-test/src/main.cpp index 47852104a2..91b61fb289 100644 --- a/tests/gpu-test/src/main.cpp +++ b/tests/gpu-test/src/main.cpp @@ -445,7 +445,7 @@ public: positionView = gpu::BufferView(vertexBuffer, 0, vertexBuffer->getSize(), SHAPE_VERTEX_STRIDE, POSITION_ELEMENT); textureView = gpu::BufferView(vertexBuffer, SHAPE_TEXTURES_OFFSET, vertexBuffer->getSize(), SHAPE_VERTEX_STRIDE, TEXTURE_ELEMENT); texture = DependencyManager::get()->getImageTexture("C:/Users/bdavis/Git/openvr/samples/bin/cube_texture.png"); - //texture = DependencyManager::get()->getImageTexture("H:/test.png"); + // texture = DependencyManager::get()->getImageTexture("H:/test.png"); //texture = DependencyManager::get()->getImageTexture("H:/crate_blue.fbm/lambert8SG_Normal_OpenGL.png"); auto shader = makeShader(VERTEX_SHADER, FRAGMENT_SHADER, gpu::Shader::BindingSet {}); @@ -456,6 +456,14 @@ public: vertexFormat->setAttribute(gpu::Stream::POSITION); vertexFormat->setAttribute(gpu::Stream::TEXCOORD); }); + + static auto start = usecTimestampNow(); + auto now = usecTimestampNow(); + if ((now - start) > USECS_PER_SECOND * 1) { + start = now; + texture->incremementMinMip(); + } + batch.setPipeline(pipeline); batch.setInputBuffer(gpu::Stream::POSITION, positionView); batch.setInputBuffer(gpu::Stream::TEXCOORD, textureView); @@ -493,7 +501,7 @@ public: //drawFloorGrid(batch); //drawSimpleShapes(batch); - drawCenterShape(batch); + //drawCenterShape(batch); drawTerrain(batch); _context->render(batch);