summaryrefslogtreecommitdiffstats
path: root/src/render/renderers/opengl
diff options
context:
space:
mode:
authorPaul Lemire <paul.lemire@kdab.com>2019-02-21 13:01:38 +0100
committerPaul Lemire <paul.lemire@kdab.com>2019-03-20 13:29:55 +0000
commitb9ac4faf28cc27849a392019564209b319f90568 (patch)
treee36fae9592ad81dd26ab483b6d1d6a6562b7ac71 /src/render/renderers/opengl
parent3c4feadc1f71d2e7cd1fa0e00d439b740bd9289d (diff)
Make it possible to partially update a texture
- Introduce QTextureDataUpdate which contains information about the update - QAbstractTexture::updateTexture function added - Add manual test texture-updates-cpp - Add unit tests for GLTexture and complete other texture tests Change-Id: I1b792f2075830ce05cc8e04cc68110141b5571d6 Reviewed-by: Sean Harmer <sean.harmer@kdab.com>
Diffstat (limited to 'src/render/renderers/opengl')
-rw-r--r--src/render/renderers/opengl/renderer/renderer.cpp4
-rw-r--r--src/render/renderers/opengl/textures/gltexture.cpp215
-rw-r--r--src/render/renderers/opengl/textures/gltexture_p.h55
3 files changed, 199 insertions, 75 deletions
diff --git a/src/render/renderers/opengl/renderer/renderer.cpp b/src/render/renderers/opengl/renderer/renderer.cpp
index be08abbd0..a3c0b93da 100644
--- a/src/render/renderers/opengl/renderer/renderer.cpp
+++ b/src/render/renderers/opengl/renderer/renderer.cpp
@@ -1388,6 +1388,10 @@ void Renderer::updateTexture(Texture *texture)
if (dirtyFlags.testFlag(Texture::DirtyDataGenerator))
glTexture->setGenerator(texture->dataGenerator());
+ // Will make the texture requestUpload
+ if (dirtyFlags.testFlag(Texture::DirtyPendingDataUpdates))
+ glTexture->addTextureDataUpdates(texture->takePendingTextureDataUpdates());
+
// Unset the dirty flag on the texture
texture->unsetDirty();
}
diff --git a/src/render/renderers/opengl/textures/gltexture.cpp b/src/render/renderers/opengl/textures/gltexture.cpp
index 4561e5ebf..7a49304e3 100644
--- a/src/render/renderers/opengl/textures/gltexture.cpp
+++ b/src/render/renderers/opengl/textures/gltexture.cpp
@@ -41,6 +41,8 @@
#include "gltexture_p.h"
#include <private/qdebug_p.h>
+#include <private/qopengltexture_p.h>
+#include <private/qopengltexturehelper_p.h>
#include <QDebug>
#include <QOpenGLFunctions>
#include <QOpenGLTexture>
@@ -85,16 +87,37 @@ void uploadGLData(QOpenGLTexture *glTex,
}
}
+// For partial sub image uploads
+void uploadGLData(QOpenGLTexture *glTex,
+ int mipLevel, int layer, QOpenGLTexture::CubeMapFace cubeFace,
+ int xOffset, int yOffset, int zOffset,
+ const QByteArray &bytes, const QTextureImageDataPtr &data)
+{
+ if (data->isCompressed()) {
+ qWarning() << Q_FUNC_INFO << "Uploading non full sized Compressed Data not supported yet";
+ } else {
+ QOpenGLPixelTransferOptions uploadOptions;
+ uploadOptions.setAlignment(1);
+ glTex->setData(xOffset, yOffset, zOffset,
+ data->width(), data->height(), data->depth(),
+ mipLevel, layer, cubeFace, data->layers(),
+ data->pixelFormat(), data->pixelType(),
+ bytes.constData(), &uploadOptions);
+ }
+}
+
} // anonymous
GLTexture::GLTexture()
- : m_gl(nullptr)
+ : m_dirtyFlags(None)
+ , m_gl(nullptr)
, m_renderBuffer(nullptr)
, m_dataFunctor()
, m_pendingDataFunctor(nullptr)
, m_sharedTextureId(-1)
, m_externalRendering(false)
+ , m_wasTextureRecreated(false)
{
}
@@ -110,18 +133,19 @@ void GLTexture::destroy()
delete m_renderBuffer;
m_renderBuffer = nullptr;
- m_dirtyFlags.store(0);
+ m_dirtyFlags = None;
m_sharedTextureId = -1;
m_externalRendering = false;
+ m_wasTextureRecreated = false;
m_dataFunctor.reset();
m_pendingDataFunctor = nullptr;
- m_actualTarget = QAbstractTexture::Target1D;
m_properties = {};
m_parameters = {};
m_textureData.reset();
m_images.clear();
m_imageData.clear();
+ m_pendingTextureDataUpdates.clear();
}
bool GLTexture::loadTextureDataFromGenerator()
@@ -129,10 +153,27 @@ bool GLTexture::loadTextureDataFromGenerator()
m_textureData = m_dataFunctor->operator()();
// if there is a texture generator, most properties will be defined by it
if (m_textureData) {
- if (m_properties.target != QAbstractTexture::TargetAutomatic)
- qWarning() << "[Qt3DRender::GLTexture] When a texture provides a generator, it's target is expected to be TargetAutomatic";
+ const QAbstractTexture::Target target = m_textureData->target();
+
+ // If both target and functor return Automatic we are still
+ // probably loading the texture, return false
+ if (m_properties.target == QAbstractTexture::TargetAutomatic &&
+ target == QAbstractTexture::TargetAutomatic) {
+ m_textureData.reset();
+ return false;
+ }
+
+ if (m_properties.target != QAbstractTexture::TargetAutomatic &&
+ target != QAbstractTexture::TargetAutomatic &&
+ m_properties.target != target) {
+ qWarning() << Q_FUNC_INFO << "Generator and Properties not requesting the same texture target";
+ m_textureData.reset();
+ return false;
+ }
- m_actualTarget = m_textureData->target();
+ // We take target type from generator if it wasn't explicitly set by the user
+ if (m_properties.target == QAbstractTexture::TargetAutomatic)
+ m_properties.target = target;
m_properties.width = m_textureData->width();
m_properties.height = m_textureData->height();
m_properties.depth = m_textureData->depth();
@@ -190,26 +231,25 @@ void GLTexture::loadTextureDataFromImages()
}
}
+// Called from RenderThread
GLTexture::TextureUpdateInfo GLTexture::createOrUpdateGLTexture()
{
- QMutexLocker locker(&m_textureMutex);
- bool needUpload = false;
TextureUpdateInfo textureInfo;
-
m_properties.status = QAbstractTexture::Error;
+ m_wasTextureRecreated = false;
const bool hasSharedTextureId = m_sharedTextureId > 0;
// Only load texture data if we are not using a sharedTextureId
+ // Check if dataFunctor or images have changed
if (!hasSharedTextureId) {
- // on the first invocation in the render thread, make sure to
- // evaluate the texture data generator output
- // (this might change some property values)
- if (m_dataFunctor && !m_textureData) {
+ // If dataFunctor exists and we have no data and it hasnĀ“t run yet
+ if (m_dataFunctor && !m_textureData && m_dataFunctor.get() != m_pendingDataFunctor ) {
const bool successfullyLoadedTextureData = loadTextureDataFromGenerator();
+ // If successful, m_textureData has content
if (successfullyLoadedTextureData) {
setDirtyFlag(Properties, true);
- needUpload = true;
+ setDirtyFlag(TextureData, true);
} else {
if (m_pendingDataFunctor != m_dataFunctor.get()) {
qWarning() << "[Qt3DRender::GLTexture] No QTextureData generated from Texture Generator yet. Texture will be invalid for this frame";
@@ -220,56 +260,77 @@ GLTexture::TextureUpdateInfo GLTexture::createOrUpdateGLTexture()
}
}
- // additional texture images may be defined through image data generators
- if (testDirtyFlag(TextureData)) {
+ // If images have changed, clear previous images data
+ // and regenerate m_imageData for the images
+ if (testDirtyFlag(TextureImageData)) {
m_imageData.clear();
loadTextureDataFromImages();
- needUpload = true;
+ // Mark for upload if we actually have something to upload
+ if (!m_imageData.empty()) {
+ setDirtyFlag(TextureData, true);
+ }
+ // Reset image flag
+ setDirtyFlag(TextureImageData, false);
}
+ }
- // don't try to create the texture if the format was not set
- if (m_properties.format == QAbstractTexture::Automatic) {
- textureInfo.properties.status = QAbstractTexture::Error;
- return textureInfo;
- }
+ // Don't try to create the texture if the target or format was still not set
+ // Format should either be set by user or if Automatic
+ // by either the dataGenerator of the texture or the first Image
+ // Target should explicitly be set by the user or the dataGenerator
+ if (m_properties.target == QAbstractTexture::TargetAutomatic ||
+ m_properties.format == QAbstractTexture::Automatic ||
+ m_properties.format == QAbstractTexture::NoFormat) {
+ textureInfo.properties.status = QAbstractTexture::Error;
+ return textureInfo;
}
- // if the properties changed, we need to re-allocate the texture
+ // If the properties changed or texture has become a shared texture from a
+ // 3rd party engine, we need to destroy and maybe re-allocate the texture
if (testDirtyFlag(Properties) || testDirtyFlag(SharedTextureId)) {
delete m_gl;
m_gl = nullptr;
textureInfo.wasUpdated = true;
- // If we are destroyed because of some property change but still our content data
- // make sure we are marked for upload
- if (m_textureData || !m_imageData.empty())
- needUpload = true;
+ // If we are destroyed because of some property change but still have (some) of
+ // our content data make sure we are marked for upload
+ // TO DO: We should actually check if the textureData is still correct
+ // in regard to the size, target and format of the texture though.
+ if (!testDirtyFlag(SharedTextureId) &&
+ (m_textureData || !m_imageData.empty() || !m_pendingTextureDataUpdates.empty()))
+ setDirtyFlag(TextureData, true);
}
m_properties.status = QAbstractTexture::Ready;
- if (hasSharedTextureId && testDirtyFlag(SharedTextureId)) {
+ if (testDirtyFlag(SharedTextureId) || hasSharedTextureId) {
// Update m_properties by doing introspection on the texture
- introspectPropertiesFromSharedTextureId();
+ if (hasSharedTextureId)
+ introspectPropertiesFromSharedTextureId();
+ setDirtyFlag(SharedTextureId, false);
} else {
// We only build a QOpenGLTexture if we have no shared textureId set
if (!m_gl) {
m_gl = buildGLTexture();
if (!m_gl) {
+ qWarning() << "[Qt3DRender::GLTexture] failed to create texture";
textureInfo.properties.status = QAbstractTexture::Error;
return textureInfo;
}
m_gl->allocateStorage();
if (!m_gl->isStorageAllocated()) {
+ qWarning() << "[Qt3DRender::GLTexture] failed to allocate texture";
textureInfo.properties.status = QAbstractTexture::Error;
return textureInfo;
}
+ m_wasTextureRecreated = true;
}
textureInfo.texture = m_gl;
// need to (re-)upload texture data?
- if (needUpload) {
+ const bool needsUpload = testDirtyFlag(TextureData);
+ if (needsUpload) {
uploadGLTextureData();
setDirtyFlag(TextureData, false);
}
@@ -277,24 +338,18 @@ GLTexture::TextureUpdateInfo GLTexture::createOrUpdateGLTexture()
// need to set texture parameters?
if (testDirtyFlag(Properties) || testDirtyFlag(Parameters)) {
updateGLTextureParameters();
+ setDirtyFlag(Properties, false);
+ setDirtyFlag(Parameters, false);
}
}
textureInfo.properties = m_properties;
- // un-set properties and parameters. The TextureData flag might have been set by another thread
- // in the meantime, so don't clear that.
- setDirtyFlag(Properties, false);
- setDirtyFlag(Parameters, false);
- setDirtyFlag(SharedTextureId, false);
-
return textureInfo;
}
RenderBuffer *GLTexture::getOrCreateRenderBuffer()
{
- QMutexLocker locker(&m_textureMutex);
-
if (m_dataFunctor && !m_textureData) {
m_textureData = m_dataFunctor->operator()();
if (m_textureData) {
@@ -338,7 +393,6 @@ void GLTexture::cleanup()
void GLTexture::setParameters(const TextureParameters &params)
{
- QMutexLocker locker(&m_textureMutex);
if (m_parameters != params) {
m_parameters = params;
setDirtyFlag(Parameters);
@@ -347,10 +401,8 @@ void GLTexture::setParameters(const TextureParameters &params)
void GLTexture::setProperties(const TextureProperties &props)
{
- QMutexLocker locker(&m_textureMutex);
if (m_properties != props) {
m_properties = props;
- m_actualTarget = props.target;
setDirtyFlag(Properties);
}
}
@@ -371,7 +423,7 @@ void GLTexture::setImages(const QVector<Image> &images)
if (!same) {
m_images = images;
- requestUpload();
+ requestImageUpload();
}
}
@@ -379,6 +431,7 @@ void GLTexture::setGenerator(const QTextureGeneratorPtr &generator)
{
m_textureData.reset();
m_dataFunctor = generator;
+ m_pendingDataFunctor = nullptr;
requestUpload();
}
@@ -390,6 +443,12 @@ void GLTexture::setSharedTextureId(int textureId)
}
}
+void GLTexture::addTextureDataUpdates(const QVector<QTextureDataUpdate> &updates)
+{
+ m_pendingTextureDataUpdates += updates;
+ requestUpload();
+}
+
// Return nullptr if
// - context cannot be obtained
// - texture hasn't yet been loaded
@@ -401,14 +460,15 @@ QOpenGLTexture *GLTexture::buildGLTexture()
return nullptr;
}
- if (m_actualTarget == QAbstractTexture::TargetAutomatic) {
+ const QAbstractTexture::Target actualTarget = m_properties.target;
+ if (actualTarget == QAbstractTexture::TargetAutomatic) {
// If the target is automatic at this point, it means that the texture
// hasn't been loaded yet (case of remote urls) and that loading failed
// and that target format couldn't be deduced
return nullptr;
}
- QOpenGLTexture* glTex = new QOpenGLTexture(static_cast<QOpenGLTexture::Target>(m_actualTarget));
+ QOpenGLTexture* glTex = new QOpenGLTexture(static_cast<QOpenGLTexture::Target>(actualTarget));
// m_format may not be ES2 compatible. Now it's time to convert it, if necessary.
QAbstractTexture::TextureFormat format = m_properties.format;
@@ -444,19 +504,19 @@ QOpenGLTexture *GLTexture::buildGLTexture()
}
glTex->setFormat(m_properties.format == QAbstractTexture::Automatic ?
- QOpenGLTexture::NoFormat :
- static_cast<QOpenGLTexture::TextureFormat>(format));
+ QOpenGLTexture::NoFormat :
+ static_cast<QOpenGLTexture::TextureFormat>(format));
glTex->setSize(m_properties.width, m_properties.height, m_properties.depth);
// Set layers count if texture array
- if (m_actualTarget == QAbstractTexture::Target1DArray ||
- m_actualTarget == QAbstractTexture::Target2DArray ||
- m_actualTarget == QAbstractTexture::Target2DMultisampleArray ||
- m_actualTarget == QAbstractTexture::TargetCubeMapArray) {
+ if (actualTarget == QAbstractTexture::Target1DArray ||
+ actualTarget == QAbstractTexture::Target2DArray ||
+ actualTarget == QAbstractTexture::Target2DMultisampleArray ||
+ actualTarget == QAbstractTexture::TargetCubeMapArray) {
glTex->setLayers(m_properties.layers);
}
- if (m_actualTarget == QAbstractTexture::Target2DMultisample ||
- m_actualTarget == QAbstractTexture::Target2DMultisampleArray) {
+ if (actualTarget == QAbstractTexture::Target2DMultisample ||
+ actualTarget == QAbstractTexture::Target2DMultisampleArray) {
// Set samples count if multisampled texture
// (multisampled textures don't have mipmaps)
glTex->setSamples(m_properties.samples);
@@ -516,16 +576,59 @@ void GLTexture::uploadGLTextureData()
// Free up image data once content has been uploaded
// Note: if data functor stores the data, this won't really free anything though
m_imageData.clear();
+
+ // Update data from TextureUpdates
+ const QVector<QTextureDataUpdate> textureDataUpdates = std::move(m_pendingTextureDataUpdates);
+ for (const QTextureDataUpdate &update : textureDataUpdates) {
+ const QTextureImageDataPtr imgData = update.data();
+
+ if (!imgData) {
+ qWarning() << Q_FUNC_INFO << "QTextureDataUpdate no QTextureImageData set";
+ continue;
+ }
+
+ const int xOffset = update.x();
+ const int yOffset = update.y();
+ const int zOffset = update.z();
+ const int xExtent = xOffset + imgData->width();
+ const int yExtent = yOffset + imgData->height();
+ const int zExtent = zOffset + imgData->depth();
+
+ // Check update is compatible with our texture
+ if (xOffset >= m_gl->width() ||
+ yOffset >= m_gl->height() ||
+ zOffset >= m_gl->depth() ||
+ xExtent > m_gl->width() ||
+ yExtent > m_gl->height() ||
+ zExtent > m_gl->depth() ||
+ update.mipLevel() >= m_gl->mipLevels() ||
+ update.layer() >= m_gl->layers()) {
+ qWarning() << Q_FUNC_INFO << "QTextureDataUpdate incompatible with texture";
+ continue;
+ }
+
+ const QByteArray bytes = (QTextureImageDataPrivate::get(imgData.get())->m_data);
+ // Here the bytes in the QTextureImageData contain data for a single
+ // layer, face or mip level, unlike the QTextureGenerator case where
+ // they are in a single blob. Hence QTextureImageData::data() is not suitable.
+
+ uploadGLData(m_gl,
+ update.mipLevel(), update.layer(),
+ static_cast<QOpenGLTexture::CubeMapFace>(update.face()),
+ xOffset, yOffset, zOffset,
+ bytes, imgData);
+ }
}
void GLTexture::updateGLTextureParameters()
{
+ const QAbstractTexture::Target actualTarget = m_properties.target;
m_gl->setWrapMode(QOpenGLTexture::DirectionS, static_cast<QOpenGLTexture::WrapMode>(m_parameters.wrapModeX));
- if (m_actualTarget != QAbstractTexture::Target1D &&
- m_actualTarget != QAbstractTexture::Target1DArray &&
- m_actualTarget != QAbstractTexture::TargetBuffer)
+ if (actualTarget != QAbstractTexture::Target1D &&
+ actualTarget != QAbstractTexture::Target1DArray &&
+ actualTarget != QAbstractTexture::TargetBuffer)
m_gl->setWrapMode(QOpenGLTexture::DirectionT, static_cast<QOpenGLTexture::WrapMode>(m_parameters.wrapModeY));
- if (m_actualTarget == QAbstractTexture::Target3D)
+ if (actualTarget == QAbstractTexture::Target3D)
m_gl->setWrapMode(QOpenGLTexture::DirectionR, static_cast<QOpenGLTexture::WrapMode>(m_parameters.wrapModeZ));
m_gl->setMinMagFilters(static_cast<QOpenGLTexture::Filter>(m_parameters.minificationFilter),
static_cast<QOpenGLTexture::Filter>(m_parameters.magnificationFilter));
diff --git a/src/render/renderers/opengl/textures/gltexture_p.h b/src/render/renderers/opengl/textures/gltexture_p.h
index 4dd78513a..ca851712d 100644
--- a/src/render/renderers/opengl/textures/gltexture_p.h
+++ b/src/render/renderers/opengl/textures/gltexture_p.h
@@ -99,6 +99,15 @@ public:
GLTexture();
~GLTexture();
+ enum DirtyFlag {
+ None = 0,
+ TextureData = (1 << 0), // texture data needs uploading to GPU
+ Properties = (1 << 1), // texture needs to be (re-)created
+ Parameters = (1 << 2), // texture parameters need to be (re-)set
+ SharedTextureId = (1 << 3), // texture id from shared context
+ TextureImageData = (1 << 4) // texture image data needs uploading
+ };
+
/**
* Helper class to hold the defining properties of TextureImages
*/
@@ -156,11 +165,16 @@ public:
void cleanup();
- bool isDirty()
+ bool isDirty() const
{
- return m_dirtyFlags.load() != 0;
+ return m_dirtyFlags != None;
}
+ bool hasTextureData() const { return !m_textureData.isNull(); }
+ bool hasImagesData() const { return !m_imageData.isEmpty(); }
+
+ QFlags<DirtyFlag> dirtyFlags() const { return m_dirtyFlags; }
+
QMutex *externalRenderingLock()
{
return &m_externalRenderingMutex;
@@ -176,38 +190,41 @@ public:
return m_externalRendering;
}
+ // Purely for unit testing purposes
+ bool wasTextureRecreated() const
+ {
+ return m_wasTextureRecreated;
+ }
+
void setParameters(const TextureParameters &params);
void setProperties(const TextureProperties &props);
void setImages(const QVector<Image> &images);
void setGenerator(const QTextureGeneratorPtr &generator);
void setSharedTextureId(int textureId);
+ void addTextureDataUpdates(const QVector<QTextureDataUpdate> &updates);
-private:
-
- enum DirtyFlag {
- TextureData = 0x01, // one or more image generators have been executed, data needs uploading to GPU
- Properties = 0x02, // texture needs to be (re-)created
- Parameters = 0x04, // texture parameters need to be (re-)set
- SharedTextureId = 0x08 // texture id from shared context
- };
+ QVector<QTextureDataUpdate> textureDataUpdates() const { return m_pendingTextureDataUpdates; }
+ QTextureGeneratorPtr dataGenerator() const { return m_dataFunctor; }
+private:
+ void requestImageUpload()
+ {
+ m_dirtyFlags |= TextureImageData;
+ }
void requestUpload()
{
- setDirtyFlag(TextureData, true);
+ m_dirtyFlags |= TextureData;
}
bool testDirtyFlag(DirtyFlag flag)
{
- return m_dirtyFlags.load() & flag;
+ return m_dirtyFlags.testFlag(flag);
}
void setDirtyFlag(DirtyFlag flag, bool value = true)
{
- if (value)
- m_dirtyFlags |= flag;
- else
- m_dirtyFlags &= ~static_cast<int>(flag);
+ m_dirtyFlags.setFlag(flag, value);
}
QOpenGLTexture *buildGLTexture();
@@ -218,14 +235,12 @@ private:
void introspectPropertiesFromSharedTextureId();
void destroyResources();
- QAtomicInt m_dirtyFlags;
- QMutex m_textureMutex;
+ QFlags<DirtyFlag> m_dirtyFlags;
QMutex m_externalRenderingMutex;
QOpenGLTexture *m_gl;
RenderBuffer *m_renderBuffer;
// target which is actually used for GL texture
- QAbstractTexture::Target m_actualTarget;
TextureProperties m_properties;
TextureParameters m_parameters;
@@ -236,9 +251,11 @@ private:
// cache actual image data generated by the functors
QTextureDataPtr m_textureData;
QVector<QTextureImageDataPtr> m_imageData;
+ QVector<QTextureDataUpdate> m_pendingTextureDataUpdates;
int m_sharedTextureId;
bool m_externalRendering;
+ bool m_wasTextureRecreated;
};
} // namespace Render