1010 lines
31 KiB
C++
1010 lines
31 KiB
C++
#include <QtGui>
|
|
#include <mitsuba/mitsuba.h>
|
|
#if defined(__OSX__)
|
|
#include <OpenGL/glew.h>
|
|
#else
|
|
#include <GL/glew.h>
|
|
#endif
|
|
#include "glwidget.h"
|
|
#include "preview.h"
|
|
#include <mitsuba/render/renderjob.h>
|
|
|
|
GLWidget::GLWidget(QWidget *parent) :
|
|
QGLWidget(parent), m_context(NULL) {
|
|
setAutoBufferSwap(false);
|
|
setFocusPolicy(Qt::StrongFocus);
|
|
m_clock = new Timer();
|
|
m_movementTimer = new QTimer(parent);
|
|
m_movementTimer->setInterval(20);
|
|
m_movementTimer->setSingleShot(false);
|
|
m_redrawTimer = new QTimer(parent);
|
|
m_redrawTimer->setInterval(200);
|
|
connect(m_movementTimer, SIGNAL(timeout()), this, SLOT(timerImpulse()));
|
|
connect(m_redrawTimer, SIGNAL(timeout()), this, SLOT(updateGL()));
|
|
m_renderer = Renderer::create(NULL);
|
|
m_device = new QtDevice(this);
|
|
m_preview = new PreviewThread(m_device, m_renderer);
|
|
connect(m_preview, SIGNAL(caughtException(const QString &)),
|
|
this, SLOT(onException(const QString &)), Qt::QueuedConnection);
|
|
connect(m_preview, SIGNAL(statusMessage(const QString &)),
|
|
this, SIGNAL(statusMessage(const QString &)), Qt::QueuedConnection);
|
|
m_invertMouse = false;
|
|
m_navigationMode = EFlythroughFixedYaw;
|
|
m_ignoreMouseEvent = QPoint(0, 0);
|
|
m_didSetCursor = false;
|
|
m_softwareFallback = false;
|
|
m_ignoreResizeEvents = false;
|
|
m_ignoreScrollEvents = false;
|
|
setAcceptDrops(true);
|
|
}
|
|
|
|
GLWidget::~GLWidget() {
|
|
if (m_preview)
|
|
m_preview->quit();
|
|
}
|
|
|
|
void GLWidget::onException(const QString &what) {
|
|
QString errorString("A critical exception occurred in the preview rendering thread. "
|
|
"Please make sure that you are using the most recent graphics drivers. "
|
|
"Mitsuba will now switch "
|
|
"to a slow software fallback mode, which only supports "
|
|
"the rendering preview but no tonemapping and no "
|
|
"real-time preview/navigation. "
|
|
"The encountered error was:\n\n%1");
|
|
QMessageBox::critical(this, tr("Critical exception"),
|
|
errorString.arg(what), QMessageBox::Ok);
|
|
m_softwareFallback = true;
|
|
}
|
|
|
|
void GLWidget::initializeGL() {
|
|
/* Load the Mitsuba logo into a texture */
|
|
QResource res("/resources/logo.png");
|
|
SAssert(res.isValid());
|
|
ref<Stream> mStream = new MemoryStream(res.size());
|
|
mStream->write(res.data(), res.size());
|
|
mStream->setPos(0);
|
|
ref<Bitmap> bitmap = new Bitmap(Bitmap::EPNG, mStream);
|
|
m_logoSize = Vector2(bitmap->getWidth(), bitmap->getHeight());
|
|
m_device->init();
|
|
m_renderer->init(m_device);
|
|
m_logoTexture = m_renderer->createGPUTexture("Logo", bitmap);
|
|
m_logoTexture->setFilterType(GPUTexture::ENearest);
|
|
m_logoTexture->setMipMapped(false);
|
|
|
|
std::vector<std::string> missingExtensions;
|
|
|
|
if (!m_renderer->getCapabilities()->isSupported(
|
|
RendererCapabilities::EShadingLanguage))
|
|
missingExtensions.push_back("GLSL");
|
|
if (!m_renderer->getCapabilities()->isSupported(
|
|
RendererCapabilities::ERenderToTexture))
|
|
missingExtensions.push_back("Render-To-Texture");
|
|
if (!m_renderer->getCapabilities()->isSupported(
|
|
RendererCapabilities::EFloatingPointTextures))
|
|
missingExtensions.push_back("Floating point textures");
|
|
if (!m_renderer->getCapabilities()->isSupported(
|
|
RendererCapabilities::EFloatingPointBuffer))
|
|
missingExtensions.push_back("Floating point render buffers");
|
|
if (!m_renderer->getCapabilities()->isSupported(
|
|
RendererCapabilities::EVertexBufferObjects))
|
|
missingExtensions.push_back("Vertex buffer objects");
|
|
|
|
if (missingExtensions.size() > 0) {
|
|
std::ostringstream oss;
|
|
oss << "You machine is missing the following required "
|
|
"OpenGL capabilities: ";
|
|
for (size_t i=0; i<missingExtensions.size(); ++i) {
|
|
oss << missingExtensions[i];
|
|
if (i+1 < missingExtensions.size())
|
|
oss << ", ";
|
|
}
|
|
oss << ". Please make sure that you are using the most "
|
|
<< "recent graphics drivers.\n\nMitsuba will now switch "
|
|
<< "to a slow software fallback mode, which only supports "
|
|
<< "the rendering preview but no tonemapping and no "
|
|
<< "real-time preview/navigation.";
|
|
m_errorString = QString(oss.str().c_str());
|
|
// Don't redraw as often, since this is now quite costly
|
|
m_redrawTimer->setInterval(1000);
|
|
m_softwareFallback = true;
|
|
} else {
|
|
m_gammaTonemap = m_renderer->createGPUProgram("Tonemapper [Gamma]");
|
|
m_reinhardTonemap = m_renderer->createGPUProgram("Tonemapper [Reinhard et al. 2002]");
|
|
|
|
m_gammaTonemap->setSource(GPUProgram::EVertexProgram,
|
|
"void main() {\n"
|
|
" gl_Position = ftransform();\n"
|
|
" gl_TexCoord[0] = gl_MultiTexCoord0;\n"
|
|
"}\n"
|
|
);
|
|
|
|
m_gammaTonemap->setSource(GPUProgram::EFragmentProgram,
|
|
"uniform sampler2D source;\n"
|
|
"uniform float invWhitePoint, invGamma;\n"
|
|
"uniform bool sRGB;\n"
|
|
"\n"
|
|
"float toSRGB(float value) {\n"
|
|
" if (value < 0.0031308)\n"
|
|
" return 12.92 * value;\n"
|
|
" return 1.055 * pow(value, 0.41666) - 0.055;\n"
|
|
"}\n"
|
|
"\n"
|
|
"void main() {\n"
|
|
" vec4 color = texture2D(source, gl_TexCoord[0].xy) * invWhitePoint;\n"
|
|
" if (sRGB)\n"
|
|
" gl_FragColor = vec4(toSRGB(color.r), toSRGB(color.g), toSRGB(color.b), 1);"
|
|
" else\n"
|
|
" gl_FragColor = vec4(pow(color.rgb, vec3(invGamma)), 1);\n"
|
|
"}\n"
|
|
);
|
|
|
|
m_reinhardTonemap->setSource(GPUProgram::EVertexProgram,
|
|
"void main() {\n"
|
|
" gl_Position = ftransform();\n"
|
|
" gl_TexCoord[0] = gl_MultiTexCoord0;\n"
|
|
"}\n"
|
|
);
|
|
|
|
m_reinhardTonemap->setSource(GPUProgram::EFragmentProgram,
|
|
"uniform sampler2D source;\n"
|
|
"uniform float key, invWpSqr, invGamma, multiplier;\n"
|
|
"uniform bool sRGB;\n"
|
|
"\n"
|
|
"float toSRGB(float value) {\n"
|
|
" if (value < 0.0031308)\n"
|
|
" return 12.92 * value;\n"
|
|
" return 1.055 * pow(value, 0.41666) - 0.055;\n"
|
|
"}\n"
|
|
"\n"
|
|
"void main() {\n"
|
|
" const mat3 rgb2xyz = mat3(0.412453, 0.357580, 0.180423,\n"
|
|
" 0.212671, 0.715160, 0.072169,\n"
|
|
" 0.019334, 0.119193, 0.950227);\n"
|
|
"\n"
|
|
" const mat3 xyz2rgb = mat3(3.240479, -1.537150, -0.498535,\n"
|
|
" -0.969256, 1.875991, 0.041556,\n"
|
|
" 0.055648, -0.204043, 1.057311);\n"
|
|
"\n"
|
|
" vec4 color = texture2D(source, gl_TexCoord[0].xy)*multiplier;\n"
|
|
" vec3 xyz = rgb2xyz * color.rgb;\n"
|
|
" float normalization = 1.0/(xyz.x + xyz.y + xyz.z);\n"
|
|
" vec3 Yxy = vec3(xyz.x*normalization, xyz.y*normalization, xyz.y);\n"
|
|
" float Lp = Yxy.z*key;\n"
|
|
" Yxy.z = Lp * (1.0 + Lp*invWpSqr) / (1.0+Lp);\n"
|
|
" xyz = vec3(Yxy.x * (Yxy.z/Yxy.y), Yxy.z, (Yxy.z/Yxy.y) * (1.0 - Yxy.x - Yxy.y));\n"
|
|
" color.rgb = xyz2rgb * xyz;\n"
|
|
" if (sRGB)\n"
|
|
" gl_FragColor = vec4(toSRGB(color.r), toSRGB(color.g), toSRGB(color.b), 1);\n"
|
|
" else\n"
|
|
" gl_FragColor = vec4(pow(color.rgb, vec3(invGamma)), 1);\n"
|
|
"}\n"
|
|
);
|
|
|
|
m_luminanceProgram = m_renderer->createGPUProgram("Log-luminance program");
|
|
m_luminanceProgram->setSource(GPUProgram::EVertexProgram,
|
|
"void main() {\n"
|
|
" gl_Position = ftransform();\n"
|
|
" gl_TexCoord[0] = gl_MultiTexCoord0;\n"
|
|
"}\n"
|
|
);
|
|
|
|
m_luminanceProgram->setSource(GPUProgram::EFragmentProgram,
|
|
"uniform sampler2D source;\n"
|
|
"uniform float multiplier;\n"
|
|
"\n"
|
|
"void main() {\n"
|
|
" vec4 color = texture2D(source, gl_TexCoord[0].xy);\n"
|
|
" float luminance = multiplier * (color.r * 0.212671 + color.g * 0.715160 + color.b * 0.072169);\n"
|
|
" if (luminance < 0.0 || luminance != luminance) luminance = 0.0; // catch NaNs and negative numbers\n"
|
|
" float logLuminance = log(0.001+luminance);\n"
|
|
" gl_FragColor = vec4(logLuminance, luminance, 0.0, 1.0);"
|
|
"}\n"
|
|
);
|
|
|
|
m_downsamplingProgram = m_renderer->createGPUProgram("Downsampling program");
|
|
m_downsamplingProgram->setSource(GPUProgram::EVertexProgram,
|
|
"uniform vec2 targetSize;\n"
|
|
"void main() {\n"
|
|
" gl_Position = ftransform();\n"
|
|
" gl_TexCoord[0].xy = vec2(gl_MultiTexCoord0.x * targetSize.x, \n"
|
|
" gl_MultiTexCoord0.y * targetSize.y);\n"
|
|
"}\n"
|
|
);
|
|
|
|
m_downsamplingProgram->setSource(GPUProgram::EFragmentProgram,
|
|
"uniform sampler2D source;\n"
|
|
"uniform vec2 activeRegionSize;\n"
|
|
"uniform vec2 invSourceSize;\n"
|
|
"\n"
|
|
"/* Perform a texture lookup by pixel coordinates */\n"
|
|
"vec4 lookupPixel(vec2 coords) {\n"
|
|
" coords = coords + vec2(0.5, 0.5);\n"
|
|
" if (coords.x < 0.0 || coords.y < 0.0 ||\n"
|
|
" coords.x > activeRegionSize.x || coords.y > activeRegionSize.y)\n"
|
|
" return vec4(0);\n"
|
|
" else\n"
|
|
" return texture2D(source, coords*invSourceSize);\n"
|
|
"}\n"
|
|
"\n"
|
|
"/* Find the max. luminance and the sum of all log-luminance values */\n"
|
|
"void main() {\n"
|
|
" vec2 pos = (gl_TexCoord[0].xy-vec2(.5, .5))*2.0;\n"
|
|
" vec2 pixel0 = lookupPixel(pos).rg,\n"
|
|
" pixel1 = lookupPixel(pos + vec2(1, 0)).rg,\n"
|
|
" pixel2 = lookupPixel(pos + vec2(0, 1)).rg,\n"
|
|
" pixel3 = lookupPixel(pos + vec2(1, 1)).rg;\n"
|
|
" gl_FragColor.r = pixel0.r + pixel1.r + pixel2.r + pixel3.r;\n"
|
|
" gl_FragColor.g = max(pixel0.g, max(pixel1.g, max(pixel2.g, pixel3.g)));\n"
|
|
" gl_FragColor.ba = vec2(0, 1);\n"
|
|
"}\n"
|
|
);
|
|
|
|
if (!m_preview->isRunning()) {
|
|
#if defined(WIN32)
|
|
wglMakeCurrent(NULL, NULL);
|
|
#endif
|
|
m_preview->start();
|
|
m_preview->waitUntilStarted();
|
|
#if defined(WIN32)
|
|
makeCurrent();
|
|
#endif
|
|
}
|
|
|
|
m_gammaTonemap->init();
|
|
m_reinhardTonemap->init();
|
|
m_downsamplingProgram->init();
|
|
m_luminanceProgram->init();
|
|
}
|
|
m_logoTexture->init();
|
|
m_redrawTimer->start();
|
|
}
|
|
|
|
void GLWidget::setScene(SceneContext *context) {
|
|
if (m_movementTimer->isActive())
|
|
m_movementTimer->stop();
|
|
m_context = context;
|
|
|
|
if (context && context->scene == NULL)
|
|
context = NULL;
|
|
m_preview->setSceneContext(context, true, false);
|
|
m_framebufferChanged = true;
|
|
m_mouseButtonDown = false;
|
|
m_leftKeyDown = m_rightKeyDown = m_upKeyDown = m_downKeyDown = false;
|
|
updateGeometry();
|
|
updateScrollBars();
|
|
updateGL();
|
|
}
|
|
|
|
void GLWidget::refreshScene() {
|
|
m_framebufferChanged = true;
|
|
resetPreview();
|
|
updateGeometry();
|
|
updateScrollBars();
|
|
if (m_context->mode == EPreview)
|
|
resumePreview();
|
|
}
|
|
|
|
void GLWidget::setPathLength(int length) {
|
|
if (m_context->pathLength != length) {
|
|
m_context->pathLength = length;
|
|
resetPreview();
|
|
}
|
|
}
|
|
|
|
void GLWidget::setShadowMapResolution(int res) {
|
|
if (res != m_context->shadowMapResolution) {
|
|
m_context->shadowMapResolution = res;
|
|
resetPreview();
|
|
}
|
|
}
|
|
|
|
void GLWidget::setDiffuseSources(bool value) {
|
|
if (value != m_context->diffuseSources) {
|
|
m_context->diffuseSources = value;
|
|
resetPreview();
|
|
}
|
|
}
|
|
|
|
void GLWidget::setDiffuseReceivers(bool value) {
|
|
if (value != m_context->diffuseReceivers) {
|
|
m_context->diffuseReceivers = value;
|
|
resetPreview();
|
|
}
|
|
}
|
|
|
|
void GLWidget::setPreviewMethod(EPreviewMethod method) {
|
|
if (method != m_context->previewMethod) {
|
|
m_context->previewMethod = method;
|
|
resetPreview();
|
|
}
|
|
}
|
|
|
|
void GLWidget::setClamping(Float clamping) {
|
|
if (clamping != m_context->clamping) {
|
|
m_context->clamping = clamping;
|
|
resetPreview();
|
|
}
|
|
}
|
|
|
|
void GLWidget::setGamma(bool srgb, Float gamma) {
|
|
if (srgb != m_context->srgb || gamma != m_context->gamma) {
|
|
m_context->srgb = srgb;
|
|
m_context->gamma = gamma;
|
|
updateGL();
|
|
}
|
|
}
|
|
|
|
void GLWidget::setToneMappingMethod(EToneMappingMethod method) {
|
|
if (method != m_context->toneMappingMethod) {
|
|
m_context->toneMappingMethod = method;
|
|
updateGL();
|
|
}
|
|
}
|
|
|
|
void GLWidget::setExposure(Float exposure) {
|
|
if (exposure != m_context->exposure) {
|
|
m_context->exposure = exposure;
|
|
updateGL();
|
|
}
|
|
}
|
|
|
|
void GLWidget::setReinhardKey(Float value) {
|
|
if (value != m_context->reinhardKey) {
|
|
m_context->reinhardKey = value;
|
|
updateGL();
|
|
}
|
|
}
|
|
|
|
void GLWidget::setReinhardBurn(Float value) {
|
|
if (value != m_context->reinhardBurn) {
|
|
m_context->reinhardBurn = value;
|
|
updateGL();
|
|
}
|
|
}
|
|
|
|
void GLWidget::downloadFramebuffer() {
|
|
bool createdFramebuffer = false;
|
|
|
|
if (!m_preview->isRunning()) {
|
|
m_context->framebuffer->clear();
|
|
return;
|
|
}
|
|
|
|
makeCurrent();
|
|
if (m_framebuffer == NULL ||
|
|
m_framebuffer->getBitmap() != m_context->framebuffer) {
|
|
if (m_framebuffer)
|
|
m_framebuffer->cleanup();
|
|
m_framebuffer = m_renderer->createGPUTexture("Framebuffer",
|
|
m_context->framebuffer);
|
|
m_framebuffer->setMipMapped(false);
|
|
m_framebuffer->setFilterType(GPUTexture::ENearest);
|
|
createdFramebuffer = true;
|
|
}
|
|
|
|
PreviewQueueEntry entry = m_preview->acquireBuffer();
|
|
|
|
Point3i size = entry.buffer->getSize();
|
|
ref<Bitmap> sourceBitmap = new Bitmap(size.x, size.y, 128);
|
|
m_renderer->finish();
|
|
entry.buffer->download(sourceBitmap);
|
|
|
|
// Scale by the number of developed VPLs
|
|
const float *sourceData = sourceBitmap->getFloatData();
|
|
float *targetData = m_context->framebuffer->getFloatData();
|
|
float factor = 1.0f/entry.vplSampleOffset;
|
|
|
|
for (size_t pos=0, total = size.x*size.y*4; pos<total; ++pos)
|
|
targetData[pos] = sourceData[pos] * factor;
|
|
|
|
if (m_context->mode == EPreview && (m_context->previewMethod == ERayTrace
|
|
|| m_context->previewMethod == ERayTraceCoherent)) {
|
|
/* Set alpha channel to 1 */
|
|
for (size_t pos=0, total = size.x*size.y*4; pos<total; pos+=4)
|
|
targetData[pos+3] = 1.0f;
|
|
}
|
|
|
|
if (createdFramebuffer)
|
|
m_framebuffer->init();
|
|
else
|
|
m_framebuffer->refresh();
|
|
m_preview->releaseBuffer(entry);
|
|
}
|
|
|
|
QSize GLWidget::sizeHint() const {
|
|
QSize minimumSize(440, 170);
|
|
if (m_context) {
|
|
return QSize(std::max(minimumSize.width(), m_context->framebuffer->getWidth()),
|
|
std::max(minimumSize.height(), m_context->framebuffer->getHeight()));
|
|
} else {
|
|
return minimumSize;
|
|
}
|
|
}
|
|
|
|
void GLWidget::focusOutEvent(QFocusEvent *event) {
|
|
m_leftKeyDown = m_rightKeyDown = m_upKeyDown = m_downKeyDown = false;
|
|
if (m_movementTimer->isActive())
|
|
m_movementTimer->stop();
|
|
}
|
|
|
|
void GLWidget::timerImpulse() {
|
|
if (!m_context || !m_context->scene || !m_preview->isRunning()) {
|
|
m_movementTimer->stop();
|
|
return;
|
|
}
|
|
Camera *camera = m_context->scene->getCamera();
|
|
Float moveSpeed = m_context->movementScale
|
|
* m_clock->getMilliseconds();
|
|
m_clock->reset();
|
|
|
|
if (m_leftKeyDown)
|
|
camera->setViewTransform(
|
|
Transform::translate(Vector(moveSpeed,0,0))
|
|
* camera->getViewTransform());
|
|
if (m_rightKeyDown)
|
|
camera->setViewTransform(
|
|
Transform::translate(Vector(-moveSpeed,0,0))
|
|
* camera->getViewTransform());
|
|
if (m_downKeyDown)
|
|
camera->setViewTransform(
|
|
Transform::translate(Vector(0,0,moveSpeed))
|
|
* camera->getViewTransform());
|
|
if (m_upKeyDown)
|
|
camera->setViewTransform(
|
|
Transform::translate(Vector(0,0,-moveSpeed))
|
|
* camera->getViewTransform());
|
|
|
|
if (m_context->renderJob) {
|
|
m_context->renderJob->cancel();
|
|
m_context->cancelled = true;
|
|
m_context->cancelMode = EPreview;
|
|
return;
|
|
} else if (m_context->mode != EPreview) {
|
|
m_context->mode = EPreview;
|
|
// causes updateUI to be called in the main window
|
|
emit stopRendering();
|
|
}
|
|
|
|
resetPreview();
|
|
}
|
|
|
|
void GLWidget::resetPreview() {
|
|
if (!m_context || !m_context->scene || !m_preview->isRunning())
|
|
return;
|
|
bool motion = m_leftKeyDown || m_rightKeyDown ||
|
|
m_upKeyDown || m_downKeyDown || m_mouseButtonDown;
|
|
m_preview->setSceneContext(m_context, false, motion);
|
|
updateGL();
|
|
}
|
|
|
|
void GLWidget::keyPressEvent(QKeyEvent *event) {
|
|
if (event->key() == Qt::Key_Escape)
|
|
emit quit();
|
|
if (event->key() == Qt::Key_R)
|
|
emit beginRendering();
|
|
|
|
if (event->isAutoRepeat() || !m_context)
|
|
return;
|
|
|
|
switch (event->key()) {
|
|
case Qt::Key_PageUp: m_context->movementScale *= 2; break;
|
|
case Qt::Key_PageDown: m_context->movementScale /= 2; break;
|
|
case Qt::Key_A:
|
|
case Qt::Key_Left:
|
|
m_leftKeyDown = true; break;
|
|
case Qt::Key_D:
|
|
case Qt::Key_Right:
|
|
m_rightKeyDown = true; break;
|
|
case Qt::Key_W:
|
|
case Qt::Key_Up:
|
|
m_upKeyDown = true; break;
|
|
case Qt::Key_S:
|
|
case Qt::Key_Down:
|
|
m_downKeyDown = true; break;
|
|
}
|
|
if (!m_movementTimer->isActive() && (m_leftKeyDown || m_rightKeyDown || m_upKeyDown || m_downKeyDown)) {
|
|
m_clock->reset();
|
|
m_movementTimer->start();
|
|
}
|
|
}
|
|
|
|
void GLWidget::keyReleaseEvent(QKeyEvent *event) {
|
|
if (event->isAutoRepeat() || !m_context || !m_preview->isRunning())
|
|
return;
|
|
switch (event->key()) {
|
|
case Qt::Key_A:
|
|
case Qt::Key_Left:
|
|
m_leftKeyDown = false; break;
|
|
case Qt::Key_D:
|
|
case Qt::Key_Right:
|
|
m_rightKeyDown = false; break;
|
|
case Qt::Key_W:
|
|
case Qt::Key_Up:
|
|
m_upKeyDown = false; break;
|
|
case Qt::Key_S:
|
|
case Qt::Key_Down:
|
|
m_downKeyDown = false; break;
|
|
}
|
|
|
|
if (!(m_leftKeyDown || m_rightKeyDown || m_upKeyDown || m_downKeyDown)) {
|
|
if (m_movementTimer->isActive()) {
|
|
m_movementTimer->stop();
|
|
resetPreview();
|
|
}
|
|
}
|
|
}
|
|
|
|
void GLWidget::mouseMoveEvent(QMouseEvent *event) {
|
|
if (!m_preview->isRunning())
|
|
return;
|
|
|
|
QPoint previousPos = m_mousePos,
|
|
rel = event->pos() - m_mousePos;
|
|
m_mousePos = event->pos();
|
|
|
|
if (!m_context || !m_context->scene || !m_mouseButtonDown || rel == QPoint(0,0))
|
|
return;
|
|
|
|
// if (m_ignoreMouseEvent == rel) {
|
|
if (m_ignoreMouseEvent != QPoint(0, 0)) {
|
|
m_ignoreMouseEvent = QPoint(0, 0);
|
|
return;
|
|
}
|
|
|
|
if (!m_didSetCursor) {
|
|
QApplication::setOverrideCursor(Qt::BlankCursor);
|
|
m_didSetCursor = true;
|
|
}
|
|
|
|
PinholeCamera *camera = static_cast<PinholeCamera *>(m_context->scene->getCamera());
|
|
Point p = camera->getInverseViewTransform()(Point(0,0,0));
|
|
Vector direction = camera->getInverseViewTransform()(Vector(0,0,1));
|
|
Vector up;
|
|
|
|
if (m_navigationMode == EFlythrough)
|
|
up = camera->getInverseViewTransform()(Vector(0,1,0));
|
|
else if (m_navigationMode == EFlythroughFixedYaw)
|
|
up = m_context->up;
|
|
else
|
|
SLog(EError, "Unknown navigation mode encountered!");
|
|
|
|
bool didMove = false;
|
|
|
|
if (event->buttons() & Qt::LeftButton) {
|
|
Float yaw = -.03f * rel.x() * m_mouseSensitivity;
|
|
Float pitch = -.03f * rel.y() * m_mouseSensitivity;
|
|
if (m_invertMouse)
|
|
pitch *= -1;
|
|
|
|
Transform trafo = Transform::rotate(Vector(0,1,0), yaw)
|
|
* Transform::rotate(Vector(1,0,0), pitch)
|
|
* camera->getViewTransform();
|
|
direction = trafo.inverse()(Vector(0,0,1));
|
|
|
|
if (camera->getViewTransform().det3x3() < 0) {
|
|
camera->setInverseViewTransform(Transform::lookAt(p, p+direction, up));
|
|
} else {
|
|
camera->setInverseViewTransform(
|
|
Transform::lookAt(p, p+direction, up) *
|
|
Transform::scale(Vector(-1,1,1))
|
|
);
|
|
}
|
|
didMove = true;
|
|
}
|
|
|
|
if (event->buttons() & Qt::RightButton) {
|
|
Float roll = rel.x() * m_mouseSensitivity * .02f;
|
|
Float fovChange = rel.y() * m_mouseSensitivity * .03f;
|
|
|
|
if (camera->getViewTransform().det3x3() < 0) {
|
|
m_context->up = Transform::rotate(direction, roll)(up);
|
|
camera->setInverseViewTransform(Transform::lookAt(p, p+direction, m_context->up));
|
|
} else {
|
|
m_context->up = Transform::rotate(direction, -roll)(up);
|
|
camera->setInverseViewTransform(
|
|
Transform::lookAt(p, p+direction, m_context->up) *
|
|
Transform::scale(Vector(-1,1,1))
|
|
);
|
|
}
|
|
|
|
camera->setFov(std::min(std::max((Float) 1.0f, camera->getFov() + fovChange), (Float) 100.0f));
|
|
camera->configure();
|
|
|
|
didMove = true;
|
|
}
|
|
|
|
if (event->buttons() & Qt::MidButton) {
|
|
camera->setViewTransform(
|
|
Transform::translate(Vector(-(Float) rel.x(), (Float) rel.y(), 0)
|
|
* m_mouseSensitivity * .6f * m_context->movementScale)
|
|
* camera->getViewTransform());
|
|
didMove = true;
|
|
}
|
|
|
|
QPoint global = mapToGlobal(m_mousePos);
|
|
QDesktopWidget *desktop = QApplication::desktop();
|
|
|
|
if (global.x() < 50 || global.y() < 50 ||
|
|
global.x() > desktop->width()-50 ||
|
|
global.y() > desktop->height()-50) {
|
|
QPoint target(desktop->width()/2, desktop->height()/2);
|
|
m_ignoreMouseEvent = target - global;
|
|
QCursor::setPos(target);
|
|
}
|
|
|
|
if (didMove)
|
|
timerImpulse();
|
|
}
|
|
|
|
void GLWidget::wheelEvent(QWheelEvent *event) {
|
|
QScrollBar *bar = event->orientation() == Qt::Vertical
|
|
? m_vScroll : m_hScroll;
|
|
|
|
if (!bar->isVisible() || !m_preview->isRunning())
|
|
return;
|
|
|
|
int oldStep = bar->singleStep();
|
|
bar->setSingleStep(event->delta()/4);
|
|
#if defined(__OSX__)
|
|
/* Support two-finger swipes */
|
|
if (std::abs(event->delta()) < 8*15)
|
|
bar->setSingleStep(event->delta());
|
|
#endif
|
|
|
|
bar->triggerAction(event->delta() > 0 ?
|
|
QAbstractSlider::SliderSingleStepSub
|
|
: QAbstractSlider::SliderSingleStepAdd);
|
|
bar->setSingleStep(oldStep);
|
|
event->accept();
|
|
}
|
|
|
|
void GLWidget::mousePressEvent(QMouseEvent *event) {
|
|
if (!m_preview->isRunning())
|
|
return;
|
|
m_mousePos = event->pos();
|
|
m_initialMousePos = mapToGlobal(m_mousePos);
|
|
m_mouseButtonDown = true;
|
|
}
|
|
|
|
void GLWidget::mouseReleaseEvent(QMouseEvent *event) {
|
|
if (!m_preview->isRunning())
|
|
return;
|
|
if (event->buttons() == 0) {
|
|
m_mouseButtonDown = false;
|
|
if (m_didSetCursor) {
|
|
resetPreview();
|
|
QApplication::restoreOverrideCursor();
|
|
QCursor::setPos(m_initialMousePos);
|
|
m_didSetCursor = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void GLWidget::paintGL() {
|
|
m_renderer->setDepthMask(false);
|
|
m_renderer->setDepthTest(false);
|
|
if (m_context == NULL) {
|
|
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
|
m_renderer->clear();
|
|
m_renderer->setBlendMode(Renderer::EBlendAlpha);
|
|
m_renderer->blitTexture(m_logoTexture);
|
|
m_renderer->setBlendMode(Renderer::EBlendNone);
|
|
} else if (m_context != NULL) {
|
|
Point3i size;
|
|
glClearColor(0.2f, 0.2f, 0.2f, 1.0f);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
PreviewQueueEntry entry;
|
|
GPUTexture *buffer = NULL;
|
|
|
|
if (m_context->mode == EPreview) {
|
|
if (!m_preview->isRunning()) {
|
|
/* No preview thread running - just show a grey screen */
|
|
swapBuffers();
|
|
return;
|
|
}
|
|
bool motion = m_leftKeyDown || m_rightKeyDown ||
|
|
m_upKeyDown || m_downKeyDown || m_mouseButtonDown;
|
|
entry = m_preview->acquireBuffer(motion ? -1 : 50);
|
|
if (entry.buffer == NULL) {
|
|
/* Unsuccessful at acquiring a buffer in the
|
|
alloted time - just leave and keep the current display */
|
|
return;
|
|
}
|
|
size = entry.buffer->getSize();
|
|
buffer = entry.buffer;
|
|
} else if (m_context->mode == ERender) {
|
|
if (m_framebuffer == NULL ||
|
|
m_framebuffer->getBitmap()->getWidth() != m_context->framebuffer->getWidth() ||
|
|
m_framebuffer->getBitmap()->getHeight() != m_context->framebuffer->getHeight() ||
|
|
(m_softwareFallback && m_framebuffer->getBitmap() != m_fallbackBitmap) ||
|
|
(!m_softwareFallback && m_framebuffer->getBitmap() != m_context->framebuffer)) {
|
|
if (m_framebuffer)
|
|
m_framebuffer->cleanup();
|
|
if (!m_softwareFallback) {
|
|
m_framebuffer = m_renderer->createGPUTexture("Framebuffer",
|
|
m_context->framebuffer);
|
|
} else {
|
|
m_fallbackBitmap = new Bitmap(m_context->framebuffer->getWidth(),
|
|
m_context->framebuffer->getHeight(), 24);
|
|
m_fallbackBitmap->clear();
|
|
m_framebuffer = m_renderer->createGPUTexture("Framebuffer",
|
|
m_fallbackBitmap);
|
|
}
|
|
m_framebuffer->setMipMapped(false);
|
|
m_framebuffer->setFilterType(GPUTexture::ENearest);
|
|
m_framebuffer->init();
|
|
}
|
|
|
|
if (m_framebufferChanged) {
|
|
if (m_softwareFallback) {
|
|
/* Manually generate a gamma-corrected image
|
|
on the CPU (with gamma=2.2) - this will be slow! */
|
|
Bitmap *source = m_context->framebuffer;
|
|
float *sourceData = source->getFloatData();
|
|
uint8_t *targetData = m_fallbackBitmap->getData();
|
|
for (int y=0; y<source->getHeight(); ++y) {
|
|
for (int x=0; x<source->getWidth(); ++x) {
|
|
const float invGammaValue = 0.45455f;
|
|
*targetData++ = (uint8_t) std::max(std::min(std::pow(*sourceData++, invGammaValue) * 255.0f, 255.0f), 0.0f);
|
|
*targetData++ = (uint8_t) std::max(std::min(std::pow(*sourceData++, invGammaValue) * 255.0f, 255.0f), 0.0f);
|
|
*targetData++ = (uint8_t) std::max(std::min(std::pow(*sourceData++, invGammaValue) * 255.0f, 255.0f), 0.0f);
|
|
sourceData++;
|
|
}
|
|
}
|
|
} else {
|
|
}
|
|
m_framebuffer->refresh();
|
|
m_framebufferChanged = false;
|
|
}
|
|
|
|
size = m_framebuffer->getSize();
|
|
buffer = m_framebuffer;
|
|
}
|
|
|
|
if (m_softwareFallback) {
|
|
buffer->bind();
|
|
m_renderer->setColor(Spectrum(1.0f));
|
|
m_renderer->blitTexture(buffer, false,
|
|
!m_hScroll->isVisible(), !m_vScroll->isVisible(),
|
|
-m_context->scrollOffset);
|
|
buffer->unbind();
|
|
} else if (m_context->toneMappingMethod == EGamma) {
|
|
Float invWhitePoint = std::pow((Float) 2.0f, m_context->exposure);
|
|
if (m_context->mode == EPreview)
|
|
invWhitePoint /= entry.vplSampleOffset;
|
|
|
|
buffer->bind();
|
|
m_gammaTonemap->bind();
|
|
m_gammaTonemap->setParameter("source", buffer);
|
|
m_gammaTonemap->setParameter("invWhitePoint", invWhitePoint);
|
|
m_gammaTonemap->setParameter("invGamma", 1/m_context->gamma);
|
|
m_gammaTonemap->setParameter("sRGB", m_context->srgb);
|
|
m_renderer->blitTexture(buffer, m_context->mode == EPreview,
|
|
!m_hScroll->isVisible(), !m_vScroll->isVisible(),
|
|
-m_context->scrollOffset);
|
|
m_gammaTonemap->unbind();
|
|
buffer->unbind();
|
|
} else if (m_context->toneMappingMethod == EReinhard) {
|
|
if (m_luminanceBuffer[0] == NULL || m_luminanceBuffer[0]->getSize() != Point3i(size.x, size.y, 1)) {
|
|
for (int i=0; i<2; ++i) {
|
|
m_luminanceBuffer[i] = m_renderer->createGPUTexture(formatString("Luminance buffer %i", i));
|
|
m_luminanceBuffer[i]->setFormat(GPUTexture::EFloat32RGB);
|
|
m_luminanceBuffer[i]->setSize(Point3i(size.x, size.y, 1));
|
|
m_luminanceBuffer[i]->setFilterType(GPUTexture::ENearest);
|
|
m_luminanceBuffer[i]->setFrameBufferType(GPUTexture::EColorBuffer);
|
|
m_luminanceBuffer[i]->setMipMapped(false);
|
|
m_luminanceBuffer[i]->init();
|
|
}
|
|
}
|
|
|
|
Float multiplier = 1.0;
|
|
if (m_context->mode == EPreview)
|
|
multiplier /= entry.vplSampleOffset;
|
|
|
|
/* Compute the luminace & log luminance */
|
|
m_luminanceBuffer[0]->activateTarget();
|
|
m_luminanceBuffer[0]->clear();
|
|
buffer->bind();
|
|
m_luminanceProgram->bind();
|
|
m_luminanceProgram->setParameter("source", buffer);
|
|
m_luminanceProgram->setParameter("multiplier", multiplier);
|
|
m_renderer->blitQuad(m_context->mode == EPreview);
|
|
m_luminanceProgram->unbind();
|
|
buffer->unbind();
|
|
m_luminanceBuffer[0]->releaseTarget();
|
|
|
|
/* Keep downsampling until we are left with a 1x1 pixel
|
|
sum over the whole image */
|
|
int source = 0, target = 1;
|
|
Vector2i sourceSize(size.x, size.y);
|
|
m_downsamplingProgram->bind();
|
|
m_downsamplingProgram->setParameter("invSourceSize", Vector2(1.0f/size.x, 1.0f/size.y));
|
|
while (sourceSize != Vector2i(1,1)) {
|
|
target = 1-source;
|
|
Vector2i targetSize((int) std::ceil(sourceSize.x/2.0f), (int) std::ceil(sourceSize.y/2.0f));
|
|
m_luminanceBuffer[target]->activateTarget();
|
|
m_luminanceBuffer[source]->bind();
|
|
m_downsamplingProgram->setParameter("source", m_luminanceBuffer[source]);
|
|
m_downsamplingProgram->setParameter("activeRegionSize", Vector2(sourceSize.x, sourceSize.y));
|
|
m_downsamplingProgram->setParameter("targetSize", Vector2(targetSize.x, targetSize.y));
|
|
m_luminanceBuffer[target]->setTargetRegion(Point2i(0, 0), targetSize);
|
|
m_renderer->blitQuad(true);
|
|
m_luminanceBuffer[source]->unbind();
|
|
m_luminanceBuffer[target]->releaseTarget();
|
|
sourceSize = targetSize;
|
|
source = target;
|
|
}
|
|
m_downsamplingProgram->unbind();
|
|
Float logLuminance, maxLuminance, unused;
|
|
m_luminanceBuffer[target]->getPixel(0, 0).toLinearRGB(logLuminance, maxLuminance, unused);
|
|
logLuminance = std::exp(logLuminance / (size.x*size.y));
|
|
if (ubi_isnan(logLuminance) || std::isinf(logLuminance)) {
|
|
SLog(EWarn, "Could not determine the average log-luminance, since the image contains NaNs/infs/negative values");
|
|
logLuminance = 1;
|
|
}
|
|
|
|
buffer->bind();
|
|
m_reinhardTonemap->bind();
|
|
m_reinhardTonemap->setParameter("source", buffer);
|
|
m_reinhardTonemap->setParameter("key", m_context->reinhardKey/logLuminance);
|
|
m_reinhardTonemap->setParameter("multiplier", multiplier);
|
|
m_reinhardTonemap->setParameter("invWpSqr", std::pow((Float) 2, m_context->reinhardBurn));
|
|
m_reinhardTonemap->setParameter("invGamma", 1/m_context->gamma);
|
|
m_reinhardTonemap->setParameter("sRGB", m_context->srgb);
|
|
m_renderer->blitTexture(buffer, m_context->mode == EPreview,
|
|
!m_hScroll->isVisible(), !m_vScroll->isVisible(),
|
|
-m_context->scrollOffset);
|
|
m_reinhardTonemap->unbind();
|
|
buffer->unbind();
|
|
}
|
|
|
|
if (m_context->mode == EPreview)
|
|
m_preview->releaseBuffer(entry);
|
|
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glLoadIdentity();
|
|
|
|
GLint viewport[4];
|
|
glGetIntegerv(GL_VIEWPORT, viewport);
|
|
Vector2 scrSize(viewport[2], viewport[3]);
|
|
|
|
if (scrSize.x > size.x || scrSize.y > size.y) {
|
|
/* Draw a border to highlight the region occupied by the image */
|
|
Vector2i upperLeft((scrSize.x - size.x)/2,
|
|
(scrSize.y - size.y)/2);
|
|
Vector2i lowerRight = upperLeft + Vector2i(size.x, size.y);
|
|
|
|
glColor4f(0.4f, 0.4f, 0.4f, 1.0f);
|
|
glBegin(GL_LINE_LOOP);
|
|
glVertex2f(upperLeft.x, upperLeft.y);
|
|
glVertex2f(lowerRight.x, upperLeft.y);
|
|
glVertex2f(lowerRight.x, lowerRight.y);
|
|
glVertex2f(upperLeft.x, lowerRight.y);
|
|
glEnd();
|
|
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
|
}
|
|
}
|
|
swapBuffers();
|
|
}
|
|
|
|
void GLWidget::resizeEvent(QResizeEvent *event) {
|
|
if (m_context && m_ignoreResizeEvents) {
|
|
event->accept();
|
|
return;
|
|
}
|
|
QGLWidget::resizeEvent(event);
|
|
}
|
|
|
|
void GLWidget::updateScrollBars() {
|
|
if (!m_context) {
|
|
m_hScroll->hide();
|
|
m_vScroll->hide();
|
|
return;
|
|
}
|
|
|
|
int reqWidth = m_context->framebuffer->getWidth(),
|
|
reqHeight = m_context->framebuffer->getHeight(),
|
|
width = size().width(),
|
|
height = size().height();
|
|
|
|
//cout << "Required: " << reqWidth << "x" << reqHeight << ", got " << width << "x" << height << endl;
|
|
|
|
if (m_hScroll->isVisible())
|
|
height += m_hScroll->size().height();
|
|
if (m_vScroll->isVisible())
|
|
width += m_vScroll->size().width();
|
|
|
|
//cout << "Without scrollbars : got " << width << "x" << height << endl;
|
|
|
|
bool needsHScroll = false, needsVScroll = false;
|
|
|
|
m_ignoreScrollEvents = true;
|
|
for (int i=0; i<2; ++i) {
|
|
if (width < reqWidth) {
|
|
if (!m_hScroll->isVisible())
|
|
m_hScroll->show();
|
|
if (!needsHScroll) {
|
|
height -= m_hScroll->sizeHint().height();
|
|
needsHScroll = true;
|
|
}
|
|
m_hScroll->setMaximum(reqWidth-width);
|
|
m_hScroll->setPageStep(width);
|
|
if (m_context->scrollOffset.x + width > reqWidth)
|
|
m_context->scrollOffset.x = reqWidth - width;
|
|
} else if (m_hScroll->isVisible()) {
|
|
m_hScroll->setMaximum(0);
|
|
m_hScroll->hide();
|
|
m_context->scrollOffset.x = 0;
|
|
}
|
|
|
|
if (height < reqHeight) {
|
|
if (!m_vScroll->isVisible()) {
|
|
m_vScroll->show();
|
|
}
|
|
if (!needsVScroll) {
|
|
width -= m_vScroll->sizeHint().width();
|
|
needsVScroll = true;
|
|
}
|
|
m_vScroll->setMaximum(reqHeight-height);
|
|
m_vScroll->setPageStep(height);
|
|
if (m_context->scrollOffset.y + height > reqHeight)
|
|
m_context->scrollOffset.y = reqHeight - height;
|
|
} else if (m_vScroll->isVisible()) {
|
|
m_vScroll->setMaximum(0);
|
|
m_vScroll->hide();
|
|
m_context->scrollOffset.y = 0;
|
|
}
|
|
}
|
|
|
|
//cout << "Final space for contents: " << width << "x" << height << endl;
|
|
|
|
m_hScroll->setValue(m_context->scrollOffset.x);
|
|
m_vScroll->setValue(m_context->scrollOffset.y);
|
|
m_ignoreScrollEvents = false;
|
|
updateGeometry();
|
|
}
|
|
|
|
void GLWidget::setScrollBars(QScrollBar *hScroll, QScrollBar *vScroll) {
|
|
m_hScroll = hScroll;
|
|
m_vScroll = vScroll;
|
|
connect(m_hScroll, SIGNAL(valueChanged(int)), this, SLOT(onScroll()));
|
|
connect(m_vScroll, SIGNAL(valueChanged(int)), this, SLOT(onScroll()));
|
|
}
|
|
|
|
void GLWidget::onScroll() {
|
|
if (!m_context || m_ignoreScrollEvents)
|
|
return;
|
|
m_context->scrollOffset.x = m_hScroll->value();
|
|
m_context->scrollOffset.y = m_vScroll->value();
|
|
updateGL();
|
|
}
|
|
|
|
void GLWidget::dragEnterEvent(QDragEnterEvent *event) {
|
|
if (event->mimeData()->hasFormat("text/uri-list")) {
|
|
event->setDropAction(Qt::CopyAction);
|
|
event->acceptProposedAction();
|
|
}
|
|
}
|
|
|
|
void GLWidget::dropEvent(QDropEvent *event) {
|
|
QList<QUrl> urls = event->mimeData()->urls();
|
|
for (int i=0; i<urls.size(); ++i)
|
|
emit loadFileRequest(urls[i].toLocalFile());
|
|
event->acceptProposedAction();
|
|
}
|
|
|
|
void GLWidget::resumePreview() {
|
|
if (m_preview->isRunning())
|
|
m_preview->resume();
|
|
}
|
|
|
|
void GLWidget::onUpdateView() {
|
|
m_framebufferChanged = true;
|
|
}
|
|
|
|
void GLWidget::resizeGL(int width, int height) {
|
|
glViewport(0, 0, (GLint) width, (GLint) height);
|
|
m_device->setDimension(Point2i(width, height));
|
|
}
|
|
|