mirror of
https://thingvellir.net/git/overte
synced 2025-03-27 23:52:03 +01:00
Support external GL textures with proper fencing
This commit is contained in:
parent
ca1a7135eb
commit
ca3572f991
20 changed files with 308 additions and 89 deletions
|
@ -40,15 +40,6 @@ ApplicationOverlay::ApplicationOverlay()
|
|||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
_domainStatusBorder = geometryCache->allocateID();
|
||||
_magnifierBorder = geometryCache->allocateID();
|
||||
|
||||
// Once we move UI rendering and screen rendering to different
|
||||
// threads, we need to use a sync object to deteremine when
|
||||
// the current UI texture is no longer being read from, and only
|
||||
// then release it back to the UI for re-use
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
connect(offscreenUi.data(), &OffscreenUi::textureUpdated, this, [&](GLuint textureId) {
|
||||
_uiTexture = textureId;
|
||||
});
|
||||
}
|
||||
|
||||
ApplicationOverlay::~ApplicationOverlay() {
|
||||
|
@ -96,18 +87,32 @@ void ApplicationOverlay::renderOverlay(RenderArgs* renderArgs) {
|
|||
|
||||
void ApplicationOverlay::renderQmlUi(RenderArgs* renderArgs) {
|
||||
PROFILE_RANGE(__FUNCTION__);
|
||||
if (_uiTexture) {
|
||||
gpu::Batch& batch = *renderArgs->_batch;
|
||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
|
||||
geometryCache->useSimpleDrawPipeline(batch);
|
||||
batch.setProjectionTransform(mat4());
|
||||
batch.setModelTransform(Transform());
|
||||
batch.resetViewTransform();
|
||||
batch._glActiveBindTexture(GL_TEXTURE0, GL_TEXTURE_2D, _uiTexture);
|
||||
|
||||
geometryCache->renderUnitQuad(batch, glm::vec4(1));
|
||||
if (!_uiTexture) {
|
||||
_uiTexture = gpu::TexturePointer(gpu::Texture::createExternal2D([](uint32_t recycleTexture, void* recycleFence){
|
||||
DependencyManager::get<OffscreenUi>()->releaseTexture({ recycleTexture, recycleFence });
|
||||
}));
|
||||
_uiTexture->setSource(__FUNCTION__);
|
||||
}
|
||||
// Once we move UI rendering and screen rendering to different
|
||||
// threads, we need to use a sync object to deteremine when
|
||||
// the current UI texture is no longer being read from, and only
|
||||
// then release it back to the UI for re-use
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
|
||||
OffscreenQmlSurface::TextureAndFence newTextureAndFence;
|
||||
bool newTextureAvailable = offscreenUi->fetchTexture(newTextureAndFence);
|
||||
if (newTextureAvailable) {
|
||||
_uiTexture->setExternalTexture(newTextureAndFence.first, newTextureAndFence.second);
|
||||
}
|
||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
gpu::Batch& batch = *renderArgs->_batch;
|
||||
geometryCache->useSimpleDrawPipeline(batch);
|
||||
batch.setProjectionTransform(mat4());
|
||||
batch.setModelTransform(Transform());
|
||||
batch.resetViewTransform();
|
||||
batch.setResourceTexture(0, _uiTexture);
|
||||
geometryCache->renderUnitQuad(batch, glm::vec4(1));
|
||||
}
|
||||
|
||||
void ApplicationOverlay::renderAudioScope(RenderArgs* renderArgs) {
|
||||
|
|
|
@ -40,13 +40,13 @@ private:
|
|||
|
||||
float _alpha{ 1.0f };
|
||||
float _trailingAudioLoudness{ 0.0f };
|
||||
uint32_t _uiTexture{ 0 };
|
||||
|
||||
int _domainStatusBorder;
|
||||
int _magnifierBorder;
|
||||
|
||||
ivec2 _previousBorderSize{ -1 };
|
||||
|
||||
gpu::TexturePointer _uiTexture;
|
||||
gpu::TexturePointer _overlayDepthTexture;
|
||||
gpu::TexturePointer _overlayColorTexture;
|
||||
gpu::FramebufferPointer _overlayFramebuffer;
|
||||
|
|
|
@ -80,9 +80,6 @@ void Web3DOverlay::render(RenderArgs* args) {
|
|||
_webSurface->resume();
|
||||
_webSurface->getRootItem()->setProperty("url", _url);
|
||||
_webSurface->resize(QSize(_resolution.x, _resolution.y));
|
||||
_connection = QObject::connect(_webSurface, &OffscreenQmlSurface::textureUpdated, [&](GLuint textureId) {
|
||||
_texture = textureId;
|
||||
});
|
||||
currentContext->makeCurrent(currentSurface);
|
||||
}
|
||||
|
||||
|
@ -97,14 +94,21 @@ void Web3DOverlay::render(RenderArgs* args) {
|
|||
transform.postScale(vec3(getDimensions(), 1.0f));
|
||||
}
|
||||
|
||||
Q_ASSERT(args->_batch);
|
||||
gpu::Batch& batch = *args->_batch;
|
||||
if (_texture) {
|
||||
batch._glActiveBindTexture(GL_TEXTURE0, GL_TEXTURE_2D, _texture);
|
||||
} else {
|
||||
batch.setResourceTexture(0, DependencyManager::get<TextureCache>()->getWhiteTexture());
|
||||
if (!_texture) {
|
||||
_texture = gpu::TexturePointer(gpu::Texture::createExternal2D([this](uint32_t recycleTexture, void* recycleFence) {
|
||||
_webSurface->releaseTexture({ recycleTexture, recycleFence });
|
||||
}));
|
||||
_texture->setSource(__FUNCTION__);
|
||||
}
|
||||
OffscreenQmlSurface::TextureAndFence newTextureAndFence;
|
||||
bool newTextureAvailable = _webSurface->fetchTexture(newTextureAndFence);
|
||||
if (newTextureAvailable) {
|
||||
_texture->setExternalTexture(newTextureAndFence.first, newTextureAndFence.second);
|
||||
}
|
||||
|
||||
Q_ASSERT(args->_batch);
|
||||
gpu::Batch& batch = *args->_batch;
|
||||
batch.setResourceTexture(0, _texture);
|
||||
batch.setModelTransform(transform);
|
||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
if (color.a < OPAQUE_ALPHA_THRESHOLD) {
|
||||
|
|
|
@ -43,7 +43,7 @@ public:
|
|||
private:
|
||||
OffscreenQmlSurface* _webSurface{ nullptr };
|
||||
QMetaObject::Connection _connection;
|
||||
uint32_t _texture{ 0 };
|
||||
gpu::TexturePointer _texture;
|
||||
QString _url;
|
||||
float _dpi;
|
||||
vec2 _resolution{ 640, 480 };
|
||||
|
|
|
@ -140,9 +140,6 @@ bool RenderableWebEntityItem::buildWebSurface(EntityTreeRenderer* renderer) {
|
|||
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
|
||||
_webSurface->getRootContext()->setContextProperty("desktop", QVariant());
|
||||
_webSurface->getRootContext()->setContextProperty("webEntity", _webEntityAPIHelper);
|
||||
_connection = QObject::connect(_webSurface, &OffscreenQmlSurface::textureUpdated, [&](GLuint textureId) {
|
||||
_texture = textureId;
|
||||
});
|
||||
// Restore the original GL context
|
||||
currentContext->makeCurrent(currentSurface);
|
||||
|
||||
|
@ -217,20 +214,31 @@ void RenderableWebEntityItem::render(RenderArgs* args) {
|
|||
// without worrying about excessive overhead.
|
||||
_webSurface->resize(QSize(windowSize.x, windowSize.y));
|
||||
|
||||
if (!_texture) {
|
||||
_texture = gpu::TexturePointer(gpu::Texture::createExternal2D([this](uint32_t recycleTexture, void* recycleFence) {
|
||||
_webSurface->releaseTexture({ recycleTexture, recycleFence });
|
||||
}));
|
||||
_texture->setSource(__FUNCTION__);
|
||||
}
|
||||
OffscreenQmlSurface::TextureAndFence newTextureAndFence;
|
||||
bool newTextureAvailable = _webSurface->fetchTexture(newTextureAndFence);
|
||||
if (newTextureAvailable) {
|
||||
_texture->setExternalTexture(newTextureAndFence.first, newTextureAndFence.second);
|
||||
}
|
||||
|
||||
PerformanceTimer perfTimer("RenderableWebEntityItem::render");
|
||||
Q_ASSERT(getType() == EntityTypes::Web);
|
||||
static const glm::vec2 texMin(0.0f), texMax(1.0f), topLeft(-0.5f), bottomRight(0.5f);
|
||||
|
||||
Q_ASSERT(args->_batch);
|
||||
gpu::Batch& batch = *args->_batch;
|
||||
|
||||
bool success;
|
||||
batch.setModelTransform(getTransformToCenter(success));
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
if (_texture) {
|
||||
batch._glActiveBindTexture(GL_TEXTURE0, GL_TEXTURE_2D, _texture);
|
||||
}
|
||||
batch.setResourceTexture(0, _texture);
|
||||
|
||||
float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f;
|
||||
|
||||
|
|
|
@ -80,8 +80,8 @@ private:
|
|||
|
||||
OffscreenQmlSurface* _webSurface{ nullptr };
|
||||
QMetaObject::Connection _connection;
|
||||
uint32_t _texture{ 0 };
|
||||
ivec2 _lastPress{ INT_MIN };
|
||||
gpu::TexturePointer _texture;
|
||||
ivec2 _lastPress { INT_MIN };
|
||||
bool _pressed{ false };
|
||||
QTouchEvent _lastTouchEvent { QEvent::TouchUpdate };
|
||||
uint64_t _lastRenderTime{ 0 };
|
||||
|
|
|
@ -34,7 +34,6 @@
|
|||
#include <NetworkAccessManager.h>
|
||||
|
||||
#include "OffscreenGLCanvas.h"
|
||||
#include "GLEscrow.h"
|
||||
#include "GLHelpers.h"
|
||||
#include "GLLogging.h"
|
||||
|
||||
|
@ -265,6 +264,14 @@ private:
|
|||
// Helper methods
|
||||
void setupFbo();
|
||||
bool allowNewFrame(uint8_t fps);
|
||||
bool fetchTexture(OffscreenQmlSurface::TextureAndFence& textureAndFence);
|
||||
void releaseTexture(const OffscreenQmlSurface::TextureAndFence& textureAndFence);
|
||||
|
||||
// Texture management
|
||||
std::mutex _textureMutex;
|
||||
GLuint _latestTexture { 0 };
|
||||
GLsync _latestTextureFence { 0 };
|
||||
std::list<OffscreenQmlSurface::TextureAndFence> _returnedTextures;
|
||||
|
||||
// Rendering members
|
||||
OffscreenGLCanvas _canvas;
|
||||
|
@ -274,7 +281,6 @@ private:
|
|||
GLuint _fbo { 0 };
|
||||
GLuint _depthStencil { 0 };
|
||||
RawTextureRecycler _textures { true };
|
||||
GLTextureEscrow _escrow;
|
||||
|
||||
uint64_t _lastRenderTime{ 0 };
|
||||
uvec2 _size{ 1920, 1080 };
|
||||
|
@ -406,9 +412,6 @@ void OffscreenQmlRenderThread::init() {
|
|||
|
||||
_renderControl->initialize(_canvas.getContext());
|
||||
setupFbo();
|
||||
_escrow.setRecycler([this](GLuint texture){
|
||||
_textures.recycleTexture(texture);
|
||||
});
|
||||
}
|
||||
|
||||
void OffscreenQmlRenderThread::cleanup() {
|
||||
|
@ -485,27 +488,93 @@ void OffscreenQmlRenderThread::render() {
|
|||
|
||||
_quickWindow->setRenderTarget(_fbo, QSize(_size.x, _size.y));
|
||||
|
||||
// Clear out any pending textures to be returned
|
||||
{
|
||||
std::list<OffscreenQmlSurface::TextureAndFence> returnedTextures;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_textureMutex);
|
||||
returnedTextures.swap(_returnedTextures);
|
||||
}
|
||||
if (!returnedTextures.empty()) {
|
||||
for (const auto& textureAndFence : returnedTextures) {
|
||||
GLsync fence = static_cast<GLsync>(textureAndFence.second);
|
||||
if (fence) {
|
||||
glWaitSync(fence, 0, GL_TIMEOUT_IGNORED);
|
||||
glDeleteSync(fence);
|
||||
}
|
||||
_textures.recycleTexture(textureAndFence.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
GLuint texture = _textures.getNextTexture();
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo);
|
||||
glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0);
|
||||
PROFILE_RANGE("qml_render->rendercontrol")
|
||||
_renderControl->render();
|
||||
|
||||
PROFILE_RANGE("qml_render->rendercontrol")
|
||||
_renderControl->render();
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_textureMutex);
|
||||
// If the most recent texture was unused, we can directly recycle it
|
||||
if (_latestTextureFence) {
|
||||
}
|
||||
if (_latestTexture) {
|
||||
_textures.recycleTexture(_latestTexture);
|
||||
glDeleteSync(_latestTextureFence);
|
||||
_latestTexture = 0;
|
||||
_latestTextureFence = 0;
|
||||
}
|
||||
|
||||
_latestTextureFence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
_latestTexture = texture;
|
||||
// Fence will be used in another thread / context, so a flush is required
|
||||
glFlush();
|
||||
}
|
||||
|
||||
_quickWindow->resetOpenGLState();
|
||||
_escrow.submit(texture);
|
||||
_lastRenderTime = usecTimestampNow();
|
||||
} catch (std::runtime_error& error) {
|
||||
qWarning() << "Failed to render QML: " << error.what();
|
||||
}
|
||||
}
|
||||
|
||||
bool OffscreenQmlRenderThread::fetchTexture(OffscreenQmlSurface::TextureAndFence& textureAndFence) {
|
||||
textureAndFence = { 0, 0 };
|
||||
|
||||
std::unique_lock<std::mutex> lock(_textureMutex);
|
||||
if (0 == _latestTexture) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure writes to the latest texture are complete before before returning it for reading
|
||||
Q_ASSERT(0 != _latestTextureFence);
|
||||
textureAndFence = { _latestTexture, _latestTextureFence };
|
||||
_latestTextureFence = 0;
|
||||
_latestTexture = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void OffscreenQmlRenderThread::releaseTexture(const OffscreenQmlSurface::TextureAndFence& textureAndFence) {
|
||||
std::unique_lock<std::mutex> lock(_textureMutex);
|
||||
_returnedTextures.push_back(textureAndFence);
|
||||
}
|
||||
|
||||
bool OffscreenQmlRenderThread::allowNewFrame(uint8_t fps) {
|
||||
// If we already have a pending texture, don't render another one
|
||||
// i.e. don't render faster than the consumer context, since it wastes
|
||||
// GPU cycles on producing output that will never be seen
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_textureMutex);
|
||||
if (0 != _latestTexture) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto minRenderInterval = USECS_PER_SECOND / fps;
|
||||
auto lastInterval = usecTimestampNow() - _lastRenderTime;
|
||||
return (lastInterval > minRenderInterval);
|
||||
|
@ -726,13 +795,18 @@ void OffscreenQmlSurface::updateQuick() {
|
|||
// Lock the GUI size while syncing
|
||||
QMutexLocker locker(&(_renderer->_mutex));
|
||||
_renderer->_queue.add(RENDER);
|
||||
// FIXME need to find a better way to handle the render lockout than this locking of the main thread
|
||||
_renderer->_waitCondition.wait(&(_renderer->_mutex));
|
||||
_render = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (_renderer->_escrow.fetchSignaledAndRelease(_currentTexture)) {
|
||||
emit textureUpdated(_currentTexture);
|
||||
}
|
||||
bool OffscreenQmlSurface::fetchTexture(TextureAndFence& texture) {
|
||||
return _renderer->fetchTexture(texture);
|
||||
}
|
||||
|
||||
void OffscreenQmlSurface::releaseTexture(const TextureAndFence& texture) {
|
||||
_renderer->releaseTexture(texture);
|
||||
}
|
||||
|
||||
QPointF OffscreenQmlSurface::mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject) {
|
||||
|
@ -752,7 +826,6 @@ QPointF OffscreenQmlSurface::mapToVirtualScreen(const QPointF& originalPoint, QO
|
|||
return _mouseTranslator(originalPoint);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
//
|
||||
// Event handling customization
|
||||
|
|
|
@ -71,8 +71,17 @@ public:
|
|||
QPointF mapToVirtualScreen(const QPointF& originalPoint, QObject* originalWidget);
|
||||
bool eventFilter(QObject* originalDestination, QEvent* event) override;
|
||||
|
||||
using TextureAndFence = std::pair<uint32_t, void*>;
|
||||
// Checks to see if a new texture is available. If one is, the function returns true and
|
||||
// textureAndFence will be populated with the texture ID and a fence which will be signalled
|
||||
// when the texture is safe to read.
|
||||
// Returns false if no new texture is available
|
||||
bool fetchTexture(TextureAndFence& textureAndFence);
|
||||
// Release a previously acquired texture, along with a fence which indicates when reads from the
|
||||
// texture have completed.
|
||||
void releaseTexture(const TextureAndFence& textureAndFence);
|
||||
|
||||
signals:
|
||||
void textureUpdated(unsigned int texture);
|
||||
void focusObjectChanged(QObject* newFocus);
|
||||
void focusTextChanged(bool focusText);
|
||||
|
||||
|
@ -100,7 +109,6 @@ private:
|
|||
QQmlComponent* _qmlComponent{ nullptr };
|
||||
QQuickItem* _rootItem{ nullptr };
|
||||
QTimer _updateTimer;
|
||||
uint32_t _currentTexture{ 0 };
|
||||
bool _render{ false };
|
||||
bool _polish{ true };
|
||||
bool _paused{ true };
|
||||
|
|
|
@ -119,8 +119,6 @@ GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] =
|
|||
(&::gpu::gl::GLBackend::do_startNamedCall),
|
||||
(&::gpu::gl::GLBackend::do_stopNamedCall),
|
||||
|
||||
(&::gpu::gl::GLBackend::do_glActiveBindTexture),
|
||||
|
||||
(&::gpu::gl::GLBackend::do_glUniform1i),
|
||||
(&::gpu::gl::GLBackend::do_glUniform1f),
|
||||
(&::gpu::gl::GLBackend::do_glUniform2f),
|
||||
|
@ -388,14 +386,6 @@ void GLBackend::do_popProfileRange(const Batch& batch, size_t paramOffset) {
|
|||
// As long as we don;t use several versions of shaders we can avoid this more complex code path
|
||||
// #define GET_UNIFORM_LOCATION(shaderUniformLoc) _pipeline._programShader->getUniformLocation(shaderUniformLoc, isStereo());
|
||||
#define GET_UNIFORM_LOCATION(shaderUniformLoc) shaderUniformLoc
|
||||
void GLBackend::do_glActiveBindTexture(const Batch& batch, size_t paramOffset) {
|
||||
glActiveTexture(batch._params[paramOffset + 2]._uint);
|
||||
glBindTexture(
|
||||
GET_UNIFORM_LOCATION(batch._params[paramOffset + 1]._uint),
|
||||
batch._params[paramOffset + 0]._uint);
|
||||
|
||||
(void)CHECK_GL_ERROR();
|
||||
}
|
||||
|
||||
void GLBackend::do_glUniform1i(const Batch& batch, size_t paramOffset) {
|
||||
if (_pipeline._program == 0) {
|
||||
|
@ -568,6 +558,11 @@ void GLBackend::releaseBuffer(GLuint id, Size size) const {
|
|||
_buffersTrash.push_back({ id, size });
|
||||
}
|
||||
|
||||
void GLBackend::releaseExternalTexture(GLuint id, const Texture::ExternalRecycler& recycler) const {
|
||||
Lock lock(_trashMutex);
|
||||
_externalTexturesTrash.push_back({ id, recycler });
|
||||
}
|
||||
|
||||
void GLBackend::releaseTexture(GLuint id, Size size) const {
|
||||
Lock lock(_trashMutex);
|
||||
_texturesTrash.push_back({ id, size });
|
||||
|
@ -662,6 +657,19 @@ void GLBackend::recycle() const {
|
|||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::list<std::pair<GLuint, Texture::ExternalRecycler>> externalTexturesTrash;
|
||||
{
|
||||
Lock lock(_trashMutex);
|
||||
std::swap(_externalTexturesTrash, externalTexturesTrash);
|
||||
}
|
||||
for (auto pair : externalTexturesTrash) {
|
||||
auto fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
pair.second(pair.first, fence);
|
||||
decrementTextureGPUCount();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::list<GLuint> programsTrash;
|
||||
{
|
||||
|
|
|
@ -130,8 +130,6 @@ public:
|
|||
// TODO: As long as we have gl calls explicitely issued from interface
|
||||
// code, we need to be able to record and batch these calls. THe long
|
||||
// term strategy is to get rid of any GL calls in favor of the HIFI GPU API
|
||||
virtual void do_glActiveBindTexture(const Batch& batch, size_t paramOffset) final;
|
||||
|
||||
virtual void do_glUniform1i(const Batch& batch, size_t paramOffset) final;
|
||||
virtual void do_glUniform1f(const Batch& batch, size_t paramOffset) final;
|
||||
virtual void do_glUniform2f(const Batch& batch, size_t paramOffset) final;
|
||||
|
@ -170,6 +168,7 @@ public:
|
|||
virtual bool isTextureReady(const TexturePointer& texture);
|
||||
|
||||
virtual void releaseBuffer(GLuint id, Size size) const;
|
||||
virtual void releaseExternalTexture(GLuint id, const Texture::ExternalRecycler& recycler) const;
|
||||
virtual void releaseTexture(GLuint id, Size size) const;
|
||||
virtual void releaseFramebuffer(GLuint id) const;
|
||||
virtual void releaseShader(GLuint id) const;
|
||||
|
@ -194,6 +193,7 @@ protected:
|
|||
mutable Mutex _trashMutex;
|
||||
mutable std::list<std::pair<GLuint, Size>> _buffersTrash;
|
||||
mutable std::list<std::pair<GLuint, Size>> _texturesTrash;
|
||||
mutable std::list<std::pair<GLuint, Texture::ExternalRecycler>> _externalTexturesTrash;
|
||||
mutable std::list<GLuint> _framebuffersTrash;
|
||||
mutable std::list<GLuint> _shadersTrash;
|
||||
mutable std::list<GLuint> _programsTrash;
|
||||
|
|
|
@ -136,6 +136,7 @@ float GLTexture::getMemoryPressure() {
|
|||
// Create the texture and allocate storage
|
||||
GLTexture::GLTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture, GLuint id, bool transferrable) :
|
||||
GLObject(backend, texture, id),
|
||||
_external(false),
|
||||
_source(texture.source()),
|
||||
_storageStamp(texture.getStamp()),
|
||||
_target(getGLTextureType(texture)),
|
||||
|
@ -152,10 +153,38 @@ GLTexture::GLTexture(const std::weak_ptr<GLBackend>& backend, const Texture& tex
|
|||
Backend::setGPUObject(texture, this);
|
||||
}
|
||||
|
||||
GLTexture::GLTexture(const std::weak_ptr<GLBackend>& 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)
|
||||
{
|
||||
Backend::setGPUObject(texture, this);
|
||||
|
||||
// FIXME Is this necessary?
|
||||
//withPreservedTexture([this] {
|
||||
// syncSampler();
|
||||
// if (_gpuObject.isAutogenerateMips()) {
|
||||
// generateMips();
|
||||
// }
|
||||
//});
|
||||
}
|
||||
|
||||
GLTexture::~GLTexture() {
|
||||
if (_id) {
|
||||
auto backend = _backend.lock();
|
||||
if (backend) {
|
||||
auto backend = _backend.lock();
|
||||
if (backend) {
|
||||
if (_external) {
|
||||
auto recycler = _gpuObject.getExternalRecycler();
|
||||
assert(recycler);
|
||||
backend->releaseExternalTexture(_id, recycler);
|
||||
} else if (_id) {
|
||||
backend->releaseTexture(_id, _size);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,42 @@ public:
|
|||
template <typename GLTextureType>
|
||||
static GLTextureType* 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<GLsync>(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<GLTextureType>(texture);
|
||||
}
|
||||
|
||||
if (!texture.isDefined()) {
|
||||
// NO texture definition yet so let's avoid thinking
|
||||
return nullptr;
|
||||
|
@ -110,6 +146,8 @@ public:
|
|||
|
||||
~GLTexture();
|
||||
|
||||
// Is this texture generated outside the GPU library?
|
||||
const bool _external;
|
||||
const GLuint& _texture { _id };
|
||||
const std::string _source;
|
||||
const Stamp _storageStamp;
|
||||
|
@ -159,6 +197,7 @@ protected:
|
|||
std::atomic<GLSyncState> _syncState { GLSyncState::Idle };
|
||||
|
||||
GLTexture(const std::weak_ptr<gl::GLBackend>& backend, const Texture& texture, GLuint id, bool transferrable);
|
||||
GLTexture(const std::weak_ptr<gl::GLBackend>& backend, const Texture& texture, GLuint id);
|
||||
|
||||
void setSyncState(GLSyncState syncState) { _syncState = syncState; }
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ public:
|
|||
using Parent = GLTexture;
|
||||
GLuint allocate();
|
||||
public:
|
||||
GL41Texture(const std::weak_ptr<GLBackend>& backend, const Texture& buffer, GLuint externalId);
|
||||
GL41Texture(const std::weak_ptr<GLBackend>& backend, const Texture& buffer, bool transferrable);
|
||||
|
||||
protected:
|
||||
|
|
|
@ -38,7 +38,13 @@ GLTexture* GL41Backend::syncGPUObject(const TexturePointer& texture, bool transf
|
|||
return GL41Texture::sync<GL41Texture>(*this, texture, transfer);
|
||||
}
|
||||
|
||||
GL41Texture::GL41Texture(const std::weak_ptr<GLBackend>& backend, const Texture& texture, bool transferrable) : GLTexture(backend, texture, allocate(), transferrable) {}
|
||||
GL41Texture::GL41Texture(const std::weak_ptr<GLBackend>& backend, const Texture& texture, GLuint externalId)
|
||||
: GLTexture(backend, texture, externalId) {
|
||||
}
|
||||
|
||||
GL41Texture::GL41Texture(const std::weak_ptr<GLBackend>& backend, const Texture& texture, bool transferrable)
|
||||
: GLTexture(backend, texture, allocate(), transferrable) {
|
||||
}
|
||||
|
||||
void GL41Texture::generateMips() const {
|
||||
withPreservedTexture([&] {
|
||||
|
|
|
@ -33,6 +33,7 @@ public:
|
|||
static const uint32_t DEFAULT_PAGE_DIMENSION = 128;
|
||||
static const uint32_t DEFAULT_MAX_SPARSE_LEVEL = 0xFFFF;
|
||||
public:
|
||||
GL45Texture(const std::weak_ptr<GLBackend>& backend, const Texture& texture, GLuint externalId);
|
||||
GL45Texture(const std::weak_ptr<GLBackend>& backend, const Texture& texture, bool transferrable);
|
||||
~GL45Texture();
|
||||
|
||||
|
|
|
@ -243,6 +243,10 @@ GLuint GL45Backend::getTextureID(const TexturePointer& texture, bool transfer) {
|
|||
return GL45Texture::getId<GL45Texture>(*this, texture, transfer);
|
||||
}
|
||||
|
||||
GL45Texture::GL45Texture(const std::weak_ptr<GLBackend>& backend, const Texture& texture, GLuint externalId)
|
||||
: GLTexture(backend, texture, externalId), _sparseInfo(*this), _transferState(*this) {
|
||||
}
|
||||
|
||||
GL45Texture::GL45Texture(const std::weak_ptr<GLBackend>& backend, const Texture& texture, bool transferrable)
|
||||
: GLTexture(backend, texture, allocate(texture), transferrable), _sparseInfo(*this), _transferState(*this) {
|
||||
|
||||
|
@ -252,7 +256,10 @@ GL45Texture::GL45Texture(const std::weak_ptr<GLBackend>& backend, const Texture&
|
|||
}
|
||||
|
||||
GL45Texture::~GL45Texture() {
|
||||
qCDebug(gpugl45logging) << "Destroying texture " << _id << " from source " << _source.c_str();
|
||||
// External textures cycle very quickly, so don't spam the log with messages about them.
|
||||
if (!_gpuObject.getUsage().isExternal()) {
|
||||
qCDebug(gpugl45logging) << "Destroying texture " << _id << " from source " << _source.c_str();
|
||||
}
|
||||
if (_sparseInfo.sparse) {
|
||||
// Remove this texture from the candidate list of derezzable textures
|
||||
{
|
||||
|
|
|
@ -294,6 +294,11 @@ void Batch::setUniformBuffer(uint32 slot, const BufferView& view) {
|
|||
|
||||
|
||||
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));
|
||||
|
@ -506,18 +511,6 @@ void Batch::popProfileRange() {
|
|||
#endif
|
||||
}
|
||||
|
||||
#define GL_TEXTURE0 0x84C0
|
||||
|
||||
void Batch::_glActiveBindTexture(uint32 unit, uint32 target, uint32 texture) {
|
||||
// clean the cache on the texture unit we are going to use so the next call to setResourceTexture() at the same slot works fine
|
||||
setResourceTexture(unit - GL_TEXTURE0, nullptr);
|
||||
|
||||
ADD_COMMAND(glActiveBindTexture);
|
||||
_params.emplace_back(texture);
|
||||
_params.emplace_back(target);
|
||||
_params.emplace_back(unit);
|
||||
}
|
||||
|
||||
void Batch::_glUniform1i(int32 location, int32 v0) {
|
||||
if (location < 0) {
|
||||
return;
|
||||
|
@ -680,4 +673,4 @@ void Batch::flush() {
|
|||
}
|
||||
buffer->flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -229,9 +229,6 @@ public:
|
|||
// term strategy is to get rid of any GL calls in favor of the HIFI GPU API
|
||||
// For now, instead of calling the raw gl Call, use the equivalent call on the batch so the call is beeing recorded
|
||||
// THe implementation of these functions is in GLBackend.cpp
|
||||
|
||||
void _glActiveBindTexture(unsigned int unit, unsigned int target, unsigned int texture);
|
||||
|
||||
void _glUniform1i(int location, int v0);
|
||||
void _glUniform1f(int location, float v0);
|
||||
void _glUniform2f(int location, float v0, float v1);
|
||||
|
@ -314,8 +311,6 @@ public:
|
|||
// TODO: As long as we have gl calls explicitely issued from interface
|
||||
// code, we need to be able to record and batch these calls. THe long
|
||||
// term strategy is to get rid of any GL calls in favor of the HIFI GPU API
|
||||
COMMAND_glActiveBindTexture,
|
||||
|
||||
COMMAND_glUniform1i,
|
||||
COMMAND_glUniform1f,
|
||||
COMMAND_glUniform2f,
|
||||
|
|
|
@ -238,6 +238,16 @@ 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();
|
||||
tex->_type = TEX_2D;
|
||||
tex->_maxMip = 0;
|
||||
tex->_sampler = sampler;
|
||||
tex->setUsage(Usage::Builder().withExternal().withColor());
|
||||
tex->setExternalRecycler(recycler);
|
||||
return tex;
|
||||
}
|
||||
|
||||
Texture* Texture::create1D(const Element& texelFormat, uint16 width, const Sampler& sampler) {
|
||||
return create(TEX_1D, texelFormat, width, 1, 1, 1, 1, sampler);
|
||||
}
|
||||
|
@ -925,3 +935,16 @@ Vec3u Texture::evalMipDimensions(uint16 level) const {
|
|||
return glm::max(dimensions, Vec3u(1));
|
||||
}
|
||||
|
||||
void Texture::setExternalTexture(uint32 externalId, void* externalFence) {
|
||||
Lock lock(_externalMutex);
|
||||
_externalUpdates.push_back({ externalId, externalFence });
|
||||
}
|
||||
|
||||
Texture::ExternalUpdates Texture::getUpdates() const {
|
||||
Texture::ExternalUpdates result;
|
||||
{
|
||||
Lock lock(_externalMutex);
|
||||
_externalUpdates.swap(result);
|
||||
}
|
||||
return result;
|
||||
}
|
|
@ -163,6 +163,10 @@ public:
|
|||
static void setEnableSparseTextures(bool enabled);
|
||||
static void setEnableIncrementalTextureTransfers(bool enabled);
|
||||
|
||||
using ExternalRecycler = std::function<void(uint32, void*)>;
|
||||
using ExternalIdAndFence = std::pair<uint32, void*>;
|
||||
using ExternalUpdates = std::list<ExternalIdAndFence>;
|
||||
|
||||
class Usage {
|
||||
public:
|
||||
enum FlagBit {
|
||||
|
@ -170,7 +174,7 @@ 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<NUM_FLAGS> Flags;
|
||||
|
@ -196,6 +200,7 @@ 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) {}
|
||||
|
||||
|
@ -204,6 +209,7 @@ 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); }
|
||||
|
@ -293,6 +299,7 @@ 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());
|
||||
|
||||
Texture();
|
||||
Texture(const Texture& buf); // deep copy of the sysmem texture
|
||||
|
@ -458,9 +465,21 @@ public:
|
|||
// 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 = recycler; }
|
||||
ExternalRecycler getExternalRecycler() const { return _externalRecycler; }
|
||||
|
||||
const GPUObjectPointer gpuObject {};
|
||||
|
||||
ExternalUpdates getUpdates() const;
|
||||
|
||||
protected:
|
||||
// Should only be accessed internally or by the backend sync function
|
||||
mutable Mutex _externalMutex;
|
||||
mutable std::list<ExternalIdAndFence> _externalUpdates;
|
||||
ExternalRecycler _externalRecycler;
|
||||
|
||||
|
||||
// Not strictly necessary, but incredibly useful for debugging
|
||||
std::string _source;
|
||||
std::unique_ptr< Storage > _storage;
|
||||
|
|
Loading…
Reference in a new issue