Large refactor of texture loading.

In this new implementation, everything is loaded on a separate thread,
but the textures are initialized on the main OpenGL thread.

This is all to work around the fact that you cannot create a separate
GL context in GLUT.
This commit is contained in:
2018-04-12 16:52:09 +02:00
parent 30aa25a09e
commit 265bc61b98
14 changed files with 158 additions and 53 deletions

View File

@@ -14,3 +14,8 @@ void fmri::Drawable::patchTransparancy()
(*it)[3] = alpha;
}
}
void fmri::Drawable::glLoad()
{
// Do nothing
}

View File

@@ -12,6 +12,7 @@ namespace fmri
virtual void draw(float time) = 0;
virtual void patchTransparancy();
virtual void glLoad();
protected:
std::vector<Color> colorBuffer;

View File

@@ -15,7 +15,7 @@ void ImageInteractionAnimation::draw(float step)
ImageInteractionAnimation::ImageInteractionAnimation(const DType *data, const std::vector<int> &shape, const std::vector<float> &prevPositions,
const std::vector<float> &curPositions) :
texture(loadTexture(data, shape[2], shape[1] * shape[3], shape[1])),
texture(data, shape[2], shape[1] * shape[3], GL_LUMINANCE, shape[1]),
startingPositions(MultiImageVisualisation::getVertices(prevPositions)),
deltas(MultiImageVisualisation::getVertices(curPositions)),
textureCoordinates(MultiImageVisualisation::getTexCoords(shape[1]))
@@ -26,3 +26,10 @@ ImageInteractionAnimation::ImageInteractionAnimation(const DType *data, const st
deltas[i] = LAYER_X_OFFSET;
}
}
void ImageInteractionAnimation::glLoad()
{
Drawable::glLoad();
texture.configure(GL_TEXTURE_2D);
}

View File

@@ -11,7 +11,8 @@ namespace fmri
public:
ImageInteractionAnimation(const DType *data, const std::vector<int> &shape, const std::vector<float> &prevPositions,
const std::vector<float> &curPositions);
virtual void draw(float step);
void draw(float step) override;
void glLoad() override;
private:
Texture texture;

View File

@@ -19,8 +19,10 @@ using namespace std;
* @param data Layer data to generate an image for.
* @return A normalized RGB image, stored in a vector.
*/
static vector<float> getRGBImage(const LayerData &data)
static unique_ptr<float[]> getRGBImage(const LayerData &data)
{
CHECK_EQ(data.shape().size(), 4) << "Should be image-like-layer.";
CHECK_EQ(data.shape()[0], 1) << "Should be a single image";
vector<cv::Mat> channels;
const int numPixels = data.shape()[2] * data.shape()[3];
for (auto i : Range(3)) {
@@ -29,7 +31,7 @@ static vector<float> getRGBImage(const LayerData &data)
}
cv::Mat channel(data.shape()[3], data.shape()[2], CV_32FC1);
copy(data.data() + i * numPixels, data.data() + (i + 1) * numPixels, channel.begin<float>());
copy_n(data.data() + i * numPixels, numPixels, channel.begin<float>());
channels.push_back(channel);
}
@@ -40,19 +42,18 @@ static vector<float> getRGBImage(const LayerData &data)
outImage = outImage.reshape(1);
vector<float> final(outImage.begin<float>(), outImage.end<float>());
rescale(final.begin(), final.end(), 0.f, 1.f);
auto final = make_unique<float[]>(data.numEntries());
std::copy_n(outImage.begin<float>(), data.numEntries(), final.get());
return final;
}
InputLayerVisualisation::InputLayerVisualisation(const LayerData &data)
InputLayerVisualisation::InputLayerVisualisation(const LayerData &data) :
width(data.shape().at(2)),
height(data.shape().at(3)),
texture(getRGBImage(data), width, height, GL_RGB)
{
CHECK_EQ(data.shape().size(), 4) << "Should be image-like-layer." << endl;
auto imageData = getRGBImage(data);
const auto images = data.shape()[0], channels = data.shape()[1], width = data.shape()[2], height = data.shape()[3];
CHECK_EQ(images, 1) << "Should be single image" << endl;
const auto channels = data.shape()[1];
targetWidth = width / 5.f;
targetHeight = width / 5.f;
@@ -61,9 +62,6 @@ InputLayerVisualisation::InputLayerVisualisation(const LayerData &data)
for (auto i : Range(3, 3 * channels)) {
nodePositions_.push_back(nodePositions_[i % 3]);
}
texture.configure(GL_TEXTURE_2D);
gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, width, height, GL_RGB, GL_FLOAT, imageData.data());
}
void InputLayerVisualisation::draw(float)
@@ -86,3 +84,10 @@ void InputLayerVisualisation::draw(float)
drawImageTiles(4, vertices, texCoords, texture, alpha);
}
void InputLayerVisualisation::glLoad()
{
Drawable::glLoad();
texture.configure(GL_TEXTURE_2D);
}

View File

@@ -13,10 +13,15 @@ namespace fmri
void draw(float time) override;
void glLoad() override;
private:
Texture texture;
float targetWidth;
float targetHeight;
int width;
int height;
Texture texture;
std::vector<float> textureBuffer;
};
}

View File

@@ -6,19 +6,16 @@
using namespace fmri;
using namespace std;
MultiImageVisualisation::MultiImageVisualisation(const fmri::LayerData &layer)
MultiImageVisualisation::MultiImageVisualisation(const fmri::LayerData &layer) :
texture(layer.data(), layer.shape().at(2), layer.shape().at(3) * layer.shape().at(1), GL_LUMINANCE, layer.shape().at(1))
{
auto dimensions = layer.shape();
CHECK_EQ(4, dimensions.size()) << "Should be image-like layer";
const auto images = dimensions[0],
channels = dimensions[1],
width = dimensions[2],
height = dimensions[3];
channels = dimensions[1];
CHECK_EQ(1, images) << "Only single input image is supported" << endl;
texture = loadTexture(layer.data(), width, channels * height, channels);
initNodePositions<Ordering::SQUARE>(channels, 3);
vertexBuffer = getVertices(nodePositions_);
texCoordBuffer = getTexCoords(channels);
@@ -65,3 +62,10 @@ std::vector<float> MultiImageVisualisation::getTexCoords(int n)
return coords;
}
void MultiImageVisualisation::glLoad()
{
Drawable::glLoad();
texture.configure(GL_TEXTURE_2D);
}

View File

@@ -22,6 +22,8 @@ namespace fmri
void draw(float time) override;
void glLoad() override;
static vector<float> getVertices(const std::vector<float> &nodePositions, float scaling = 1);
static std::vector<float> getTexCoords(int n);

View File

@@ -41,5 +41,13 @@ Texture PoolingLayerAnimation::loadTextureForData(const LayerData &data)
CHECK_EQ(data.shape()[0], 1) << "Only single images supported";
auto channels = data.shape()[1], width = data.shape()[2], height = data.shape()[3];
return loadTexture(data.data(), width, height * channels, channels);
return Texture(data.data(), width, height * channels, GL_LUMINANCE, channels);
}
void PoolingLayerAnimation::glLoad()
{
Drawable::glLoad();
original.configure(GL_TEXTURE_2D);
downSampled.configure(GL_TEXTURE_2D);
}

View File

@@ -14,6 +14,7 @@ namespace fmri
const std::vector<float> &curPositions);
void draw(float timeStep) override;
void glLoad() override;
private:
Texture original;

View File

@@ -223,9 +223,7 @@ void RenderingState::loadSimulationData(const std::map<string, LayerInfo> &info,
void RenderingState::queueUpdate()
{
loadingFuture = std::async(std::launch::async, []() {
// Currently causes a segfault, due to threaded OpenGL.
// Possible solution: don't do GL things while loading.
//RenderingState::instance().updateVisualisers();
RenderingState::instance().updateVisualisers();
});
isLoading = true;
}
@@ -456,7 +454,7 @@ float RenderingState::layerAlpha() const
void RenderingState::idleFunc()
{
if (isLoading && loadingFuture.valid()) {
auto result = loadingFuture.wait_for(std::chrono::milliseconds(40));
auto result = loadingFuture.wait_for(std::chrono::milliseconds(16));
switch (result) {
case std::future_status::deferred:
LOG(ERROR) << "loading status was deferred, invalid state!";
@@ -468,7 +466,8 @@ void RenderingState::idleFunc()
case std::future_status::ready:
loadingFuture.get();
//isLoading = false;
loadGLItems();
isLoading = false;
break;
}
} else {
@@ -484,3 +483,9 @@ void RenderingState::idleFunc()
}
glutPostRedisplay();
}
void RenderingState::loadGLItems()
{
std::for_each(layerVisualisations.begin(), layerVisualisations.end(), [](auto& x) { x->glLoad(); });
std::for_each(interactionAnimations.begin(), interactionAnimations.end(), [](auto& x) { if (x) x->glLoad(); });
}

View File

@@ -102,5 +102,7 @@ namespace fmri
void renderLoadingScreen() const;
void renderVisualisation(float time) const;
void loadGLItems();
};
}

View File

@@ -1,17 +1,11 @@
#include <algorithm>
#include <glog/logging.h>
#include <GL/glu.h>
#include "Texture.hpp"
#include "utils.hpp"
using namespace fmri;
Texture::Texture() noexcept
{
glGenTextures(1, &id);
}
Texture::Texture(GLuint id) noexcept : id(id)
{
}
Texture::~Texture()
{
if (id != 0) {
@@ -22,21 +16,23 @@ Texture::~Texture()
Texture &Texture::operator=(Texture && other) noexcept
{
std::swap(id, other.id);
std::swap(width, other.width);
std::swap(height, other.height);
std::swap(data, other.data);
std::swap(format, other.format);
return *this;
}
Texture::Texture(Texture && other) noexcept
{
std::swap(id, other.id);
}
void Texture::bind(GLenum target) const
{
CHECK_NE(id, 0) << "Texture doesn't hold a reference!";
glBindTexture(target, id);
}
void Texture::configure(GLenum target) const
void Texture::configure(GLenum target)
{
ensureReference();
CHECK(data) << "No valid data to configure with";
bind(target);
const float color[] = {1, 0, 1}; // Background color for textures.
@@ -48,4 +44,64 @@ void Texture::configure(GLenum target) const
// Set up texture scaling
glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); // Use mipmapping for scaling down
glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Use nearest pixel when scaling up.
gluBuild2DMipmaps(target, format, width, height, format, GL_FLOAT, data.get());
data.reset(); // Release data buffer.
}
void Texture::ensureReference()
{
if (id == 0) {
glGenTextures(1, &id);
CHECK_NE(id, 0) << "Failed to allocate a texture.";
}
}
Texture::Texture(const float *data, int width, int height, GLuint format, int subImages) :
Texture(std::make_unique<float[]>(width * height), width, height, format, subImages)
{
std::copy_n(data, width * height, this->data.get());
preCalc(subImages);
}
Texture::Texture(std::unique_ptr<float[]> &&data, int width, int height, GLuint format, int subImages) :
id(0),
width(width),
height(height),
format(format),
data(std::move(data))
{
preCalc(subImages);
}
void Texture::preCalc(int subImages)
{
CHECK_EQ(height % subImages, 0) << "Image should be properly divisible!";
// Rescale images
const auto step = width * height * getStride() / subImages;
auto cur = data.get();
for (auto i = 0; i < subImages; ++i) {
rescale(cur, cur + step, 0, 1);
std::advance(cur, step);
}
}
int Texture::getStride()
{
switch (format) {
case GL_RGB:
return 3;
case GL_LUMINANCE:
return 1;
default:
LOG(ERROR) << "Stride unknown for format: " << format;
exit(1);
}
}
Texture::Texture() noexcept :
id(0)
{
}

View File

@@ -1,5 +1,6 @@
#pragma once
#include <memory>
#include <GL/gl.h>
namespace fmri
@@ -13,19 +14,12 @@ namespace fmri
class Texture
{
public:
/**
* Allocate a new texture
*/
Texture() noexcept;
Texture(const float* data, int width, int height, GLuint format, int subImages = 1);
Texture(std::unique_ptr<float[]> &&data, int width, int height, GLuint format, int subImages = 1);
Texture(Texture &&) noexcept;
Texture(const Texture &) = delete;
/**
* Own an existing texture
* @param id original texture ID.
*/
explicit Texture(GLuint id) noexcept;
~Texture();
Texture &operator=(Texture &&) noexcept;
@@ -36,10 +30,19 @@ namespace fmri
* @param target valid target for glBindTexture.
*/
void bind(GLenum target) const;
void configure(GLenum target) const;
void configure(GLenum target);
private:
GLuint id;
int width;
int height;
GLuint format;
std::unique_ptr<float[]> data;
void ensureReference();
void preCalc(int subImages);
int getStride();
};
}