Support lowering the minimum mip of a texture at runtime

This commit is contained in:
Brad Davis 2016-04-15 18:07:37 -07:00
parent b85b8db337
commit 6c30194916
13 changed files with 547 additions and 221 deletions

View file

@ -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}

View file

@ -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()));

View file

@ -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";

View file

@ -14,7 +14,7 @@
#include <assert.h>
#include <memory>
#include <glm/glm.hpp>
#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 {

View file

@ -9,69 +9,77 @@
#ifndef hifi_gpu_Forward_h
#define hifi_gpu_Forward_h
#include <stdint.h>
#include <memory>
#include <vector>
#include <glm/glm.hpp>
namespace gpu {
class Batch;
class Backend;
class Context;
typedef std::shared_ptr<Context> ContextPointer;
using ContextPointer = std::shared_ptr<Context>;
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<Offset> Offsets;
using Byte = uint8;
using Offset = size_t;
using Offsets = std::vector<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;
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<Swapchain> SwapchainPointer;
using SwapchainPointer = std::shared_ptr<Swapchain>;
class Framebuffer;
typedef std::shared_ptr<Framebuffer> FramebufferPointer;
using FramebufferPointer = std::shared_ptr<Framebuffer>;
class Pipeline;
typedef std::shared_ptr<Pipeline> PipelinePointer;
typedef std::vector<PipelinePointer> Pipelines;
using PipelinePointer = std::shared_ptr<Pipeline>;
using Pipelines = std::vector<PipelinePointer>;
class Query;
typedef std::shared_ptr<Query> QueryPointer;
typedef std::vector<QueryPointer> Queries;
using QueryPointer = std::shared_ptr<Query>;
using Queries = std::vector<QueryPointer>;
class Resource;
class Buffer;
typedef std::shared_ptr<Buffer> BufferPointer;
typedef std::vector<BufferPointer> Buffers;
using BufferPointer = std::shared_ptr<Buffer>;
using Buffers = std::vector<BufferPointer>;
class BufferView;
class Shader;
typedef Shader::Pointer ShaderPointer;
typedef std::vector<ShaderPointer> Shaders;
using ShaderPointer = std::shared_ptr<Shader>;
using Shaders = std::vector<ShaderPointer>;
class State;
typedef std::shared_ptr<State> StatePointer;
typedef std::vector<StatePointer> States;
using StatePointer = std::shared_ptr<State>;
using States = std::vector<StatePointer>;
class Stream;
class BufferStream;
typedef std::shared_ptr<BufferStream> BufferStreamPointer;
using BufferStreamPointer = std::shared_ptr<BufferStream>;
class Texture;
class SphericalHarmonics;
typedef std::shared_ptr<SphericalHarmonics> SHPointer;
using SHPointer = std::shared_ptr<SphericalHarmonics>;
class Sampler;
class Texture;
typedef std::shared_ptr<Texture> TexturePointer;
typedef std::vector<TexturePointer> Textures;
using TexturePointer = std::shared_ptr<Texture>;
using Textures = std::vector<TexturePointer>;
class TextureView;
typedef std::vector<TextureView> TextureViews;
using TextureViews = std::vector<TextureView>;
}
#endif

View file

@ -15,6 +15,7 @@
#include <list>
#include <glm/gtc/type_ptr.hpp>
#include <GPUIdent.h>
#include <NumericalConstants.h>
#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<UINT> 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();
}

View file

@ -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>;
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<void()> 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<GLenum>& 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;

View file

@ -10,6 +10,8 @@
//
#include "GPULogging.h"
#include <unordered_set>
#include <unordered_map>
#include <QtCore/QThread>
#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<uint16, size_t> _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<DownsampleSource>(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<GLenum>& GLBackend::GLTexture::getFaceTargets() const {
static std::vector<GLenum> 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<GLenum> 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<void()> 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<GLBackend::GLTexture>(texture);
if (object && object->isReady()) {
return object;
}
// Object isn't ready, check what we need to do...
// Create the texture if need be (force re-creation if the storage stamp changes
// for easier use of immutable storage)
if (!object || object->isInvalid()) {
// This automatically destroys the old texture
object = new GLTexture(texture);
// 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<GLBackend::GLTexture>(*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());

View file

@ -11,11 +11,8 @@
#include "GLBackendShared.h"
#ifdef THREADED_TEXTURE_TRANSFER
#include <gl/OffscreenGLCanvas.h>
#include <gl/QOpenGLContextWrapper.h>
#endif
using namespace gpu;
@ -46,12 +43,20 @@ GLTextureTransferHelper::~GLTextureTransferHelper() {
void GLTextureTransferHelper::transferTexture(const gpu::TexturePointer& texturePointer) {
GLBackend::GLTexture* object = Backend::getGPUObject<GLBackend::GLTexture>(*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<GLBackend::GLTexture>(*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);

View file

@ -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<OffscreenGLCanvas> _canvas;
};
template <typename F>
void withPreservedTexture(GLenum target, F f) {
GLint boundTex = -1;
switch (target) {
case GL_TEXTURE_2D:
glGetIntegerv(GL_TEXTURE_BINDING_2D, &boundTex);
break;
case GL_TEXTURE_CUBE_MAP:
glGetIntegerv(GL_TEXTURE_BINDING_CUBE_MAP, &boundTex);
break;
default:
qFatal("Unsupported texture type");
}
(void)CHECK_GL_ERROR();
f();
glBindTexture(target, boundTex);
(void)CHECK_GL_ERROR();
}
}

View file

@ -12,6 +12,9 @@
#include "Texture.h"
#include <glm/gtc/constants.hpp>
#include <NumericalConstants.h>
#include "GPULogging.h"
#include "Context.h"
@ -21,6 +24,7 @@ static int TexturePointerMetaTypeId = qRegisterMetaType<TexturePointer>();
std::atomic<uint32_t> Texture::_textureCPUCount{ 0 };
std::atomic<Texture::Size> Texture::_textureCPUMemoryUsage{ 0 };
std::atomic<Texture::Size> 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));
}

View file

@ -11,14 +11,15 @@
#ifndef hifi_gpu_Texture_h
#define hifi_gpu_Texture_h
#include "Resource.h"
#include <algorithm> //min max and more
#include <bitset>
#include <QMetaType>
#include <QUrl>
#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<uint32_t> _textureCPUCount;
static std::atomic<Size> _textureCPUMemoryUsage;
static std::atomic<Size> _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;

View file

@ -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<TextureCache>()->getImageTexture("C:/Users/bdavis/Git/openvr/samples/bin/cube_texture.png");
//texture = DependencyManager::get<TextureCache>()->getImageTexture("H:/test.png");
// texture = DependencyManager::get<TextureCache>()->getImageTexture("H:/test.png");
//texture = DependencyManager::get<TextureCache>()->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);