blob: fc7ecf86e56404004b46b29aeb4c408ae0b306df [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2008-2012 NVIDIA Corporation.
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Quick 3D.
**
** $QT_BEGIN_LICENSE:GPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 or (at your option) any later version
** approved by the KDE Free Qt Foundation. The licenses are as published by
** the Free Software Foundation and appearing in the file LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtQuick3DRuntimeRender/private/qssgrenderer_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrendererimpl_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrendercontextcore_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrendercamera_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrenderlight_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrenderimage_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrenderbuffermanager_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrendercontextcore_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrenderresourcemanager_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrendercustommaterialsystem_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrendershadercodegeneratorv2_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrenderdefaultmaterialshadergenerator_p.h>
#include <QtQuick3DRuntimeRender/private/qssgperframeallocator_p.h>
#include <QtQuick3DRender/private/qssgrenderframebuffer_p.h>
#include <QtQuick3DUtils/private/qssgdataref_p.h>
#include <QtQuick3DUtils/private/qssgutils_p.h>
#include <cstdlib>
#include <algorithm>
#ifdef Q_CC_MSVC
#pragma warning(disable : 4355)
#endif
// Quick tests you can run to find performance problems
//#define QSSG_RENDER_DISABLE_HARDWARE_BLENDING 1
//#define QSSG_RENDER_DISABLE_LIGHTING 1
//#define QSSG_RENDER_DISABLE_TEXTURING 1
//#define QSSG_RENDER_DISABLE_TRANSPARENCY 1
//#define QSSG_RENDER_DISABLE_FRUSTUM_CULLING 1
// If you are fillrate bound then sorting opaque objects can help in some circumstances
//#define QSSG_RENDER_DISABLE_OPAQUE_SORT 1
QT_BEGIN_NAMESPACE
struct QSSGRenderableImage;
struct QSSGShaderGeneratorGeneratedShader;
struct QSSGSubsetRenderable;
QSSGRendererImpl::QSSGRendererImpl(const QSSGRef<QSSGRenderContextInterface> &ctx)
: m_contextInterface(ctx)
, m_context(ctx->renderContext())
, m_bufferManager(ctx->bufferManager())
, m_currentLayer(nullptr)
, m_pickRenderPlugins(true)
, m_layerCachingEnabled(true)
, m_layerGPuProfilingEnabled(false)
, m_progressiveAARenderRequest(false)
{
}
QSSGRendererImpl::~QSSGRendererImpl()
{
m_shaders.clear();
m_instanceRenderMap.clear();
m_constantBuffers.clear();
}
void QSSGRendererImpl::childrenUpdated(QSSGRenderNode &inParent)
{
if (inParent.type == QSSGRenderGraphObject::Type::Layer) {
const QSSGRenderLayer *theLayer = layerForNode(inParent);
auto theIter = m_instanceRenderMap.find(theLayer);
if (theIter != m_instanceRenderMap.end()) {
theIter.value()->cameras.clear();
theIter.value()->lights.clear();
theIter.value()->renderableNodes.clear();
}
} else if (inParent.parent) {
childrenUpdated(*inParent.parent);
}
}
static inline QSSGRenderLayer *getNextLayer(QSSGRenderLayer &inLayer)
{
if (inLayer.nextSibling && inLayer.nextSibling->type == QSSGRenderGraphObject::Type::Layer)
return static_cast<QSSGRenderLayer *>(inLayer.nextSibling);
return nullptr;
}
// Found by fair roll of the dice (in practice we'll never have more then 1 layer!).
using QSSGRenderLayerList = QVarLengthArray<QSSGRenderLayer *, 4>;
static inline void maybePushLayer(QSSGRenderLayer &inLayer, QSSGRenderLayerList &outLayerList)
{
inLayer.calculateGlobalVariables();
if (inLayer.flags.testFlag(QSSGRenderLayer::Flag::GloballyActive) && inLayer.flags.testFlag(QSSGRenderLayer::Flag::LayerRenderToTarget))
outLayerList.push_back(&inLayer);
}
bool QSSGRendererImpl::prepareLayerForRender(QSSGRenderLayer &inLayer,
const QSize &surfaceSize)
{
QSSGRenderLayerList renderableLayers;
maybePushLayer(inLayer, renderableLayers);
bool retval = false;
auto iter = renderableLayers.crbegin();
const auto end = renderableLayers.crend();
for (; iter != end; ++iter) {
// Store the previous state of if we were rendering a layer.
QSSGRenderLayer *theLayer = *iter;
QSSGRef<QSSGLayerRenderData> theRenderData = getOrCreateLayerRenderDataForNode(*theLayer);
if (Q_LIKELY(theRenderData)) {
theRenderData->prepareForRender(surfaceSize);
retval = retval || theRenderData->layerPrepResult->flags.wasDirty();
} else {
Q_ASSERT(false);
}
}
return retval;
}
void QSSGRendererImpl::renderLayer(QSSGRenderLayer &inLayer,
const QSize &surfaceSize,
bool clear,
const QColor &clearColor)
{
Q_UNUSED(surfaceSize)
Q_UNUSED(clearColor)
QSSGRenderLayerList renderableLayers;
maybePushLayer(inLayer, renderableLayers);
const QSSGRef<QSSGRenderContext> &theRenderContext(m_contextInterface->renderContext());
// Do not use reference since it will just shadow the hardware context variable in the
// render context breaking the caching.
const QSSGRef<QSSGRenderFrameBuffer> theFB = theRenderContext->renderTarget();
auto iter = renderableLayers.crbegin();
const auto end = renderableLayers.crend();
m_progressiveAARenderRequest = false;
for (iter = renderableLayers.crbegin(); iter != end; ++iter) {
// Store the previous state of if we were rendering a layer.
QSSGRenderLayer *theLayer = *iter;
const QSSGRef<QSSGLayerRenderData> &theRenderData = getOrCreateLayerRenderDataForNode(*theLayer);
if (Q_LIKELY(theRenderData)) {
// Make sure that we don't clear the window, when requested not to.
theRenderData->layerPrepResult->flags.setRequiresTransparentClear(clear);
if (theRenderData->layerPrepResult->isLayerVisible()) {
theRenderData->runnableRenderToViewport(theFB);
m_progressiveAARenderRequest |= theRenderData->progressiveAARenderRequest();
}
} else {
Q_ASSERT(false);
}
}
}
QSSGRenderLayer *QSSGRendererImpl::layerForNode(const QSSGRenderNode &inNode) const
{
if (inNode.type == QSSGRenderGraphObject::Type::Layer)
return &const_cast<QSSGRenderLayer &>(static_cast<const QSSGRenderLayer &>(inNode));
if (inNode.parent)
return layerForNode(*inNode.parent);
return nullptr;
}
QSSGRef<QSSGLayerRenderData> QSSGRendererImpl::getOrCreateLayerRenderDataForNode(const QSSGRenderNode &inNode)
{
const QSSGRenderLayer *theLayer = layerForNode(inNode);
if (theLayer) {
auto it = m_instanceRenderMap.constFind(theLayer);
if (it != m_instanceRenderMap.cend())
return it.value();
it = m_instanceRenderMap.insert(theLayer, new QSSGLayerRenderData(const_cast<QSSGRenderLayer &>(*theLayer), this));
// create a profiler if enabled
if (isLayerGpuProfilingEnabled() && it.value())
it.value()->createGpuProfiler();
return *it;
}
return nullptr;
}
QSSGRenderCamera *QSSGRendererImpl::cameraForNode(const QSSGRenderNode &inNode) const
{
const QSSGRef<QSSGLayerRenderData> &theLayer = const_cast<QSSGRendererImpl &>(*this).getOrCreateLayerRenderDataForNode(inNode);
if (theLayer)
return theLayer->camera;
return nullptr;
}
QSSGOption<QSSGCuboidRect> QSSGRendererImpl::cameraBounds(const QSSGRenderGraphObject &inObject)
{
if (inObject.isNodeType()) {
const QSSGRenderNode &theNode = static_cast<const QSSGRenderNode &>(inObject);
const QSSGRef<QSSGLayerRenderData> &theLayer = getOrCreateLayerRenderDataForNode(theNode);
QSSGRenderCamera *theCamera = theLayer->camera;
if (theCamera)
return theCamera->getCameraBounds(theLayer->layerPrepResult->viewport());
}
return QSSGOption<QSSGCuboidRect>();
}
void QSSGRendererImpl::drawScreenRect(QRectF inRect, const QVector3D &inColor)
{
QSSGRenderCamera theScreenCamera;
theScreenCamera.markDirty(QSSGRenderCamera::TransformDirtyFlag::TransformIsDirty);
QRectF theViewport(m_context->viewport());
theScreenCamera.flags.setFlag(QSSGRenderCamera::Flag::Orthographic);
theScreenCamera.calculateGlobalVariables(theViewport);
generateXYQuad();
if (!m_screenRectShader) {
QSSGRef<QSSGShaderProgramGeneratorInterface> theGenerator(getProgramGenerator());
theGenerator->beginProgram();
QSSGShaderStageGeneratorInterface &vertexGenerator(*theGenerator->getStage(QSSGShaderGeneratorStage::Vertex));
QSSGShaderStageGeneratorInterface &fragmentGenerator(*theGenerator->getStage(QSSGShaderGeneratorStage::Fragment));
// TODO: Move out and change type!
vertexGenerator.addIncoming("attr_pos", "vec3");
vertexGenerator.addUniform("modelViewProjection", "mat4");
vertexGenerator.addUniform("rectangle_dims", "vec3");
vertexGenerator.append("void main() {");
vertexGenerator.append("\tgl_Position = modelViewProjection * vec4(attr_pos * rectangle_dims, 1.0);");
vertexGenerator.append("}");
fragmentGenerator.addUniform("output_color", "vec3");
fragmentGenerator.append("void main() {");
fragmentGenerator.append("\tgl_FragColor.rgb = output_color;");
fragmentGenerator.append("\tgl_FragColor.a = 1.0;");
fragmentGenerator.append("}");
// No flags enabled
m_screenRectShader = theGenerator->compileGeneratedShader("DrawScreenRect", QSSGShaderCacheProgramFlags(), ShaderFeatureSetList());
}
if (m_screenRectShader) {
// Fudge the rect by one pixel to ensure we see all the corners.
if (inRect.width() > 1)
inRect.setWidth(inRect.width() - 1);
if (inRect.height() > 1)
inRect.setHeight(inRect.height() - 1);
inRect.setX(inRect.x() + 1);
inRect.setY(inRect.y() + 1);
// Figure out the rect center.
QSSGRenderNode theNode;
const QPointF &center = inRect.center();
QVector2D rectGlobalCenter = { float(center.x()), float(center.y()) };
QVector2D rectCenter(toNormalizedRectRelative(theViewport, rectGlobalCenter));
theNode.position.setX(rectCenter.x());
theNode.position.setY(rectCenter.y());
theNode.markDirty(QSSGRenderNode::TransformDirtyFlag::TransformIsDirty);
theNode.calculateGlobalVariables();
QMatrix4x4 theViewProjection;
theScreenCamera.calculateViewProjectionMatrix(theViewProjection);
QMatrix4x4 theMVP;
QMatrix3x3 theNormal;
theNode.calculateMVPAndNormalMatrix(theViewProjection, theMVP, theNormal);
m_context->setBlendingEnabled(false);
m_context->setDepthWriteEnabled(false);
m_context->setDepthTestEnabled(false);
m_context->setCullingEnabled(false);
m_context->setActiveShader(m_screenRectShader);
m_screenRectShader->setPropertyValue("modelViewProjection", theMVP);
m_screenRectShader->setPropertyValue("output_color", inColor);
m_screenRectShader->setPropertyValue("rectangle_dims", QVector3D(float(inRect.width()) / 2.0f, float(inRect.height()) / 2.0f, 0.0f));
}
if (!m_rectInputAssembler) {
Q_ASSERT(m_quadVertexBuffer);
const quint8 indexData[] = { 0, 1, 1, 2, 2, 3, 3, 0 };
m_rectIndexBuffer = new QSSGRenderIndexBuffer(m_context, QSSGRenderBufferUsageType::Static,
QSSGRenderComponentType::UnsignedInteger8,
toDataView(indexData, sizeof(indexData)));
QSSGRenderVertexBufferEntry theEntries[] = {
QSSGRenderVertexBufferEntry("attr_pos", QSSGRenderComponentType::Float32, 3),
};
// create our attribute layout
m_rectAttribLayout = m_context->createAttributeLayout(toDataView(theEntries, 1));
quint32 strides = m_quadVertexBuffer->stride();
quint32 offsets = 0;
m_rectInputAssembler = m_context->createInputAssembler(m_rectAttribLayout,
toDataView(&m_quadVertexBuffer, 1),
m_rectIndexBuffer,
toDataView(&strides, 1),
toDataView(&offsets, 1));
}
m_context->setInputAssembler(m_rectInputAssembler);
m_context->draw(QSSGRenderDrawMode::Lines, m_rectIndexBuffer->numIndices(), 0);
}
void QSSGRendererImpl::setupWidgetLayer()
{
const QSSGRef<QSSGRenderContext> &theContext = m_contextInterface->renderContext();
if (!m_widgetTexture) {
const QSSGRef<QSSGResourceManager> &theManager = m_contextInterface->resourceManager();
m_widgetTexture = theManager->allocateTexture2D(m_beginFrameViewport.width(),
m_beginFrameViewport.height(),
QSSGRenderTextureFormat::RGBA8);
m_widgetFbo = theManager->allocateFrameBuffer();
m_widgetFbo->attach(QSSGRenderFrameBufferAttachment::Color0, QSSGRenderTextureOrRenderBuffer(m_widgetTexture));
theContext->setRenderTarget(m_widgetFbo);
// QSSGRenderRect theScissorRect( 0, 0, m_BeginFrameViewport.m_Width,
// m_BeginFrameViewport.m_Height );
// QSSGRenderContextScopedProperty<QSSGRenderRect> __scissorRect( theContext,
// &QSSGRenderContext::GetScissorRect, &QSSGRenderContext::SetScissorRect, theScissorRect );
QSSGRenderContextScopedProperty<bool> __scissorEnabled(*theContext,
&QSSGRenderContext::isScissorTestEnabled,
&QSSGRenderContext::setScissorTestEnabled,
false);
m_context->setClearColor(QVector4D(0, 0, 0, 0));
m_context->clear(QSSGRenderClearValues::Color);
} else {
theContext->setRenderTarget(m_widgetFbo);
}
}
void QSSGRendererImpl::addMaterialDirtyClear(QSSGRenderGraphObject *material)
{
m_materialClearDirty.insert(material);
}
void QSSGRendererImpl::beginFrame()
{
for (int idx = 0, end = m_lastFrameLayers.size(); idx < end; ++idx)
m_lastFrameLayers[idx]->resetForFrame();
m_lastFrameLayers.clear();
m_beginFrameViewport = m_contextInterface->viewport();
for (auto *matObj : qAsConst(m_materialClearDirty)) {
if (matObj->type == QSSGRenderGraphObject::Type::CustomMaterial)
static_cast<QSSGRenderCustomMaterial *>(matObj)->updateDirtyForFrame();
else if (matObj->type == QSSGRenderGraphObject::Type::DefaultMaterial)
static_cast<QSSGRenderDefaultMaterial *>(matObj)->dirty.updateDirtyForFrame();
}
m_materialClearDirty.clear();
}
void QSSGRendererImpl::endFrame()
{
if (m_widgetTexture) {
// Releasing the widget FBO can set it as the active frame buffer.
QSSGRenderContextScopedProperty<const QSSGRef<QSSGRenderFrameBuffer> &> __fbo(*m_context,
&QSSGRenderContext::renderTarget,
&QSSGRenderContext::setRenderTarget);
QSSGTextureDetails theDetails = m_widgetTexture->textureDetails();
m_context->setBlendingEnabled(true);
// Colors are expected to be non-premultiplied, so we premultiply alpha into them at
// this point.
m_context->setBlendFunction(QSSGRenderBlendFunctionArgument(QSSGRenderSrcBlendFunc::One,
QSSGRenderDstBlendFunc::OneMinusSrcAlpha,
QSSGRenderSrcBlendFunc::One,
QSSGRenderDstBlendFunc::OneMinusSrcAlpha));
m_context->setBlendEquation(QSSGRenderBlendEquationArgument(QSSGRenderBlendEquation::Add, QSSGRenderBlendEquation::Add));
m_context->setDepthTestEnabled(false);
m_context->setScissorTestEnabled(false);
m_context->setViewport(m_beginFrameViewport);
QSSGRenderCamera theCamera;
theCamera.markDirty(QSSGRenderCamera::TransformDirtyFlag::TransformIsDirty);
theCamera.flags.setFlag(QSSGRenderCamera::Flag::Orthographic);
QVector2D theTextureDims(float(theDetails.width), float(theDetails.height));
theCamera.calculateGlobalVariables(QRectF(0, 0, theDetails.width, theDetails.height));
QMatrix4x4 theViewProj;
theCamera.calculateViewProjectionMatrix(theViewProj);
renderQuad(theTextureDims, theViewProj, *m_widgetTexture);
const QSSGRef<QSSGResourceManager> &theManager(m_contextInterface->resourceManager());
theManager->release(m_widgetFbo);
theManager->release(m_widgetTexture);
m_widgetTexture = nullptr;
m_widgetFbo = nullptr;
}
}
inline bool pickResultLessThan(const QSSGRenderPickResult &lhs, const QSSGRenderPickResult &rhs)
{
return lhs.m_cameraDistanceSq < rhs.m_cameraDistanceSq;
}
inline float clampUVCoord(float inUVCoord, QSSGRenderTextureCoordOp inCoordOp)
{
if (inUVCoord > 1.0f || inUVCoord < 0.0f) {
switch (inCoordOp) {
default:
Q_ASSERT(false);
break;
case QSSGRenderTextureCoordOp::ClampToEdge:
inUVCoord = qMin(inUVCoord, 1.0f);
inUVCoord = qMax(inUVCoord, 0.0f);
break;
case QSSGRenderTextureCoordOp::Repeat: {
float multiplier = inUVCoord > 0.0f ? 1.0f : -1.0f;
float clamp = std::fabs(inUVCoord);
clamp = clamp - std::floor(clamp);
if (multiplier < 0)
inUVCoord = 1.0f - clamp;
else
inUVCoord = clamp;
} break;
case QSSGRenderTextureCoordOp::MirroredRepeat: {
float multiplier = inUVCoord > 0.0f ? 1.0f : -1.0f;
float clamp = std::fabs(inUVCoord);
if (multiplier > 0.0f)
clamp -= 1.0f;
quint32 isMirrored = (quint32(clamp)) % 2 == 0;
float remainder = clamp - std::floor(clamp);
inUVCoord = remainder;
if (isMirrored) {
if (multiplier > 0.0f)
inUVCoord = 1.0f - inUVCoord;
} else {
if (multiplier < 0.0f)
inUVCoord = 1.0f - remainder;
}
} break;
}
}
return inUVCoord;
}
QSSGPickResultProcessResult QSSGRendererImpl::processPickResultList(bool inPickEverything)
{
Q_UNUSED(inPickEverything)
if (m_lastPickResults.empty())
return QSSGPickResultProcessResult();
// Things are rendered in a particular order and we need to respect that ordering.
std::stable_sort(m_lastPickResults.begin(), m_lastPickResults.end(), pickResultLessThan);
// We need to pick against sub objects basically somewhat recursively
// but if we don't hit any sub objects and the parent isn't pickable then
// we need to move onto the next item in the list.
// We need to keep in mind that theQuery->Pick will enter this method in a later
// stack frame so *if* we get to sub objects we need to pick against them but if the pick
// completely misses *and* the parent object locally pickable is false then we need to move
// onto the next object.
const int numToCopy = m_lastPickResults.size();
Q_ASSERT(numToCopy >= 0);
size_t numCopyBytes = size_t(numToCopy) * sizeof(QSSGRenderPickResult);
QSSGRenderPickResult *thePickResults = reinterpret_cast<QSSGRenderPickResult *>(
m_contextInterface->perFrameAllocator().allocate(numCopyBytes));
::memcpy(thePickResults, m_lastPickResults.data(), numCopyBytes);
m_lastPickResults.clear();
QSSGPickResultProcessResult thePickResult(thePickResults[0]);
return thePickResult;
}
QSSGRenderPickResult QSSGRendererImpl::pick(QSSGRenderLayer &inLayer,
const QVector2D &inViewportDimensions,
const QVector2D &inMouseCoords,
bool inPickSiblings,
bool inPickEverything)
{
m_lastPickResults.clear();
QSSGRenderLayer *theLayer = &inLayer;
// Stepping through how the original runtime did picking it picked layers in order
// stopping at the first hit. So objects on the top layer had first crack at the pick
// vector itself.
do {
if (theLayer->flags.testFlag(QSSGRenderLayer::Flag::Active)) {
const auto theIter = m_instanceRenderMap.constFind(theLayer);
if (theIter != m_instanceRenderMap.cend()) {
m_lastPickResults.clear();
getLayerHitObjectList(*theIter.value(), inViewportDimensions, inMouseCoords, inPickEverything, m_lastPickResults);
QSSGPickResultProcessResult retval(processPickResultList(inPickEverything));
if (retval.m_wasPickConsumed)
return retval;
} else {
// Q_ASSERT( false );
}
}
if (inPickSiblings)
theLayer = getNextLayer(*theLayer);
else
theLayer = nullptr;
} while (theLayer != nullptr);
return QSSGRenderPickResult();
}
QSSGRenderPickResult QSSGRendererImpl::syncPick(QSSGRenderLayer &inLayer, const QVector2D &inViewportDimensions, const QVector2D &inMouseCoords, bool inPickSiblings, bool inPickEverything)
{
using PickResultList = QVarLengthArray<QSSGRenderPickResult, 20>; // Lets assume most items are filtered out already
static const auto processResults = [](PickResultList &pickResults) {
if (pickResults.empty())
return QSSGPickResultProcessResult();
// Things are rendered in a particular order and we need to respect that ordering.
std::stable_sort(pickResults.begin(), pickResults.end(), [](const QSSGRenderPickResult &lhs, const QSSGRenderPickResult &rhs) {
return lhs.m_cameraDistanceSq < rhs.m_cameraDistanceSq;
});
return QSSGPickResultProcessResult{ pickResults.at(0), true };
};
PickResultList pickResults;
QSSGRenderLayer *layer = &inLayer;
while (layer != nullptr) {
if (layer->flags.testFlag(QSSGRenderLayer::Flag::Active)) {
pickResults.clear();
getLayerHitObjectList(*layer, inViewportDimensions, inMouseCoords, inPickEverything, pickResults);
QSSGPickResultProcessResult retval = processResults(pickResults);
if (retval.m_wasPickConsumed)
return retval;
}
layer = inPickSiblings ? getNextLayer(*layer) : nullptr;
}
return QSSGPickResultProcessResult();
}
QSSGOption<QVector2D> QSSGRendererImpl::facePosition(QSSGRenderNode &inNode,
QSSGBounds3 inBounds,
const QMatrix4x4 &inGlobalTransform,
const QVector2D &inViewportDimensions,
const QVector2D &inMouseCoords,
QSSGDataView<QSSGRenderGraphObject *> inMapperObjects,
QSSGRenderBasisPlanes inPlane)
{
Q_UNUSED(inMapperObjects)
const QSSGRef<QSSGLayerRenderData> &theLayerData = getOrCreateLayerRenderDataForNode(inNode);
if (theLayerData == nullptr)
return QSSGEmpty();
// This function assumes the layer was rendered to the scene itself. There is another
// function
// for completely offscreen layers that don't get rendered to the scene.
bool wasRenderToTarget(theLayerData->layer.flags.testFlag(QSSGRenderLayer::Flag::LayerRenderToTarget));
if (!wasRenderToTarget || theLayerData->camera == nullptr || theLayerData->layerPrepResult.hasValue() == false)
return QSSGEmpty();
QVector2D theMouseCoords(inMouseCoords);
QVector2D theViewportDimensions(inViewportDimensions);
const auto camera = theLayerData->layerPrepResult->camera();
const auto viewport = theLayerData->layerPrepResult->viewport();
QSSGOption<QSSGRenderRay> theHitRay = QSSGLayerRenderHelper::pickRay(*camera, viewport, theMouseCoords, theViewportDimensions, false);
if (!theHitRay.hasValue())
return QSSGEmpty();
// Scale the mouse coords to change them into the camera's numerical space.
QSSGRenderRay thePickRay = *theHitRay;
QSSGOption<QVector2D> newValue = thePickRay.relative(inGlobalTransform, inBounds, inPlane);
return newValue;
}
QVector3D QSSGRendererImpl::unprojectToPosition(QSSGRenderNode &inNode, QVector3D &inPosition, const QVector2D &inMouseVec) const
{
// Translate mouse into layer's coordinates
const QSSGRef<QSSGLayerRenderData> &theData = const_cast<QSSGRendererImpl &>(*this).getOrCreateLayerRenderDataForNode(inNode);
if (theData == nullptr || theData->camera == nullptr) {
return QVector3D(0, 0, 0);
} // Q_ASSERT( false ); return QVector3D(0,0,0); }
QSize theWindow = m_contextInterface->windowDimensions();
QVector2D theDims(float(theWindow.width()), float(theWindow.height()));
QSSGLayerRenderPreparationResult &thePrepResult(*theData->layerPrepResult);
const auto camera = thePrepResult.camera();
const auto viewport = thePrepResult.viewport();
QSSGRenderRay theRay = QSSGLayerRenderHelper::pickRay(*camera, viewport, inMouseVec, theDims, true);
return theData->camera->unprojectToPosition(inPosition, theRay);
}
QVector3D QSSGRendererImpl::unprojectWithDepth(QSSGRenderNode &inNode, QVector3D &, const QVector3D &inMouseVec) const
{
// Translate mouse into layer's coordinates
const QSSGRef<QSSGLayerRenderData> &theData = const_cast<QSSGRendererImpl &>(*this).getOrCreateLayerRenderDataForNode(inNode);
if (theData == nullptr || theData->camera == nullptr) {
return QVector3D(0, 0, 0);
} // Q_ASSERT( false ); return QVector3D(0,0,0); }
// Flip the y into gl coordinates from window coordinates.
QVector2D theMouse(inMouseVec.x(), inMouseVec.y());
float theDepth = inMouseVec.z();
QSSGLayerRenderPreparationResult &thePrepResult(*theData->layerPrepResult);
QSize theWindow = m_contextInterface->windowDimensions();
const auto camera = thePrepResult.camera();
const auto viewport = thePrepResult.viewport();
QSSGRenderRay theRay = QSSGLayerRenderHelper::pickRay(*camera, viewport, theMouse, QVector2D(float(theWindow.width()), float(theWindow.height())), true);
QVector3D theTargetPosition = theRay.origin + theRay.direction * theDepth;
if (inNode.parent != nullptr && inNode.parent->type != QSSGRenderGraphObject::Type::Layer)
theTargetPosition = mat44::transform(inNode.parent->globalTransform.inverted(), theTargetPosition);
// Our default global space is right handed, so if you are left handed z means something
// opposite.
if (inNode.flags.testFlag(QSSGRenderNode::Flag::LeftHanded))
theTargetPosition.setZ(theTargetPosition.z() * -1);
return theTargetPosition;
}
QVector3D QSSGRendererImpl::projectPosition(QSSGRenderNode &inNode, const QVector3D &inPosition) const
{
// Translate mouse into layer's coordinates
const QSSGRef<QSSGLayerRenderData> &theData = const_cast<QSSGRendererImpl &>(*this).getOrCreateLayerRenderDataForNode(inNode);
if (theData == nullptr || theData->camera == nullptr) {
return QVector3D(0, 0, 0);
}
QMatrix4x4 viewProj;
theData->camera->calculateViewProjectionMatrix(viewProj);
QVector4D projPos = mat44::transform(viewProj, QVector4D(inPosition, 1.0f));
projPos.setX(projPos.x() / projPos.w());
projPos.setY(projPos.y() / projPos.w());
QRectF theViewport = theData->layerPrepResult->viewport();
QVector2D theDims(float(theViewport.width()), float(theViewport.height()));
projPos.setX(projPos.x() + 1.0f);
projPos.setY(projPos.y() + 1.0f);
projPos.setX(projPos.x() * 0.5f);
projPos.setY(projPos.y() * 0.5f);
QVector3D cameraToObject = theData->camera->getGlobalPos() - inPosition;
projPos.setZ(sqrtf(QVector3D::dotProduct(cameraToObject, cameraToObject)));
QVector3D mouseVec = QVector3D(projPos.x(), projPos.y(), projPos.z());
mouseVec.setX(mouseVec.x() * theDims.x());
mouseVec.setY(mouseVec.y() * theDims.y());
mouseVec.setX(mouseVec.x() + float(theViewport.x()));
mouseVec.setY(mouseVec.y() + float(theViewport.y()));
// Flip the y into window coordinates so it matches the mouse.
QSize theWindow = m_contextInterface->windowDimensions();
mouseVec.setY(theWindow.height() - mouseVec.y());
return mouseVec;
}
QSSGOption<QSSGLayerPickSetup> QSSGRendererImpl::getLayerPickSetup(QSSGRenderLayer &inLayer,
const QVector2D &inMouseCoords,
const QSize &inPickDims)
{
const QSSGRef<QSSGLayerRenderData> &theData = getOrCreateLayerRenderDataForNode(inLayer);
if (Q_UNLIKELY(theData == nullptr || theData->camera == nullptr)) {
Q_ASSERT(false);
return QSSGEmpty();
}
QSize theWindow = m_contextInterface->windowDimensions();
QVector2D theDims(float(theWindow.width()), float(theWindow.height()));
// The mouse is relative to the layer
QSSGOption<QVector2D> theLocalMouse = getLayerMouseCoords(*theData, inMouseCoords, theDims, false);
if (!theLocalMouse.hasValue())
return QSSGEmpty();
QSSGLayerRenderPreparationResult &thePrepResult(*theData->layerPrepResult);
if (thePrepResult.camera() == nullptr) {
return QSSGEmpty();
}
// Perform gluPickMatrix and pre-multiply it into the view projection
QSSGRenderCamera &theCamera(*thePrepResult.camera());
QRectF layerToPresentation = thePrepResult.viewport();
// Offsetting is already taken care of in the camera's projection.
// All we need to do is to scale and translate the image.
layerToPresentation.setX(0);
layerToPresentation.setY(0);
QVector2D theMouse(*theLocalMouse);
// The viewport will need to center at this location
QVector2D viewportDims(float(inPickDims.width()), float(inPickDims.height()));
QVector2D bottomLeft = QVector2D(theMouse.x() - viewportDims.x() / 2.0f, theMouse.y() - viewportDims.y() / 2.0f);
// For some reason, and I haven't figured out why yet, the offsets need to be backwards for
// this to work.
// bottomLeft.x = layerToPresentation.m_Width - bottomLeft.x;
// bottomLeft.y = layerToPresentation.m_Height - bottomLeft.y;
// Virtual rect is relative to the layer.
QRectF thePickRect(qreal(bottomLeft.x()), qreal(bottomLeft.y()), qreal(viewportDims.x()), qreal(viewportDims.y()));
QMatrix4x4 projectionPremult;
projectionPremult = QSSGRenderContext::applyVirtualViewportToProjectionMatrix(projectionPremult, layerToPresentation, thePickRect);
projectionPremult = projectionPremult.inverted();
QMatrix4x4 globalInverse = theCamera.globalTransform.inverted();
QMatrix4x4 theVP = theCamera.projection * globalInverse;
// For now we won't setup the scissor, so we may be off by inPickDims at most because
// GetLayerMouseCoords will return
// false if the mouse is too far off the layer.
return QSSGLayerPickSetup(projectionPremult,
theVP,
QRect(0, 0, int(layerToPresentation.width()), int(layerToPresentation.height())));
}
QSSGOption<QRectF> QSSGRendererImpl::layerRect(QSSGRenderLayer &inLayer)
{
QSSGRef<QSSGLayerRenderData> theData = getOrCreateLayerRenderDataForNode(inLayer);
if (Q_UNLIKELY(theData == nullptr || theData->camera == nullptr)) {
Q_ASSERT(false);
return QSSGEmpty();
}
QSSGLayerRenderPreparationResult &thePrepResult(*theData->layerPrepResult);
return thePrepResult.viewport();
}
// This doesn't have to be cheap.
void QSSGRendererImpl::runLayerRender(QSSGRenderLayer &inLayer, const QMatrix4x4 &inViewProjection)
{
QSSGRef<QSSGLayerRenderData> theData = getOrCreateLayerRenderDataForNode(inLayer);
if (Q_UNLIKELY(theData == nullptr || theData->camera == nullptr)) {
Q_ASSERT(false);
return;
}
theData->prepareAndRender(inViewProjection);
}
void QSSGRendererImpl::renderLayerRect(QSSGRenderLayer &inLayer, const QVector3D &inColor)
{
QSSGRef<QSSGLayerRenderData> theData = getOrCreateLayerRenderDataForNode(inLayer);
if (theData)
theData->m_boundingRectColor = inColor;
}
void QSSGRendererImpl::releaseLayerRenderResources(QSSGRenderLayer &inLayer)
{
auto theIter = m_instanceRenderMap.find(&inLayer);
if (theIter != m_instanceRenderMap.end()) {
auto theLastFrm = std::find(m_lastFrameLayers.begin(), m_lastFrameLayers.end(), theIter.value());
if (theLastFrm != m_lastFrameLayers.end()) {
theIter.value()->resetForFrame();
m_lastFrameLayers.erase(theLastFrm);
}
m_instanceRenderMap.erase(theIter);
}
}
void QSSGRendererImpl::renderQuad(const QVector2D inDimensions, const QMatrix4x4 &inMVP, QSSGRenderTexture2D &inQuadTexture)
{
m_context->setCullingEnabled(false);
QSSGRef<QSSGLayerSceneShader> theShader = getSceneLayerShader();
m_context->setActiveShader(theShader->shader);
theShader->mvp.set(inMVP);
theShader->dimensions.set(inDimensions);
theShader->sampler.set(&inQuadTexture);
generateXYQuad();
m_context->setInputAssembler(m_quadInputAssembler);
m_context->draw(QSSGRenderDrawMode::Triangles, m_quadIndexBuffer->numIndices(), 0);
}
void QSSGRendererImpl::renderQuad()
{
m_context->setCullingEnabled(false);
generateXYQuad();
m_context->setInputAssembler(m_quadInputAssembler);
m_context->draw(QSSGRenderDrawMode::Triangles, m_quadIndexBuffer->numIndices(), 0);
}
void QSSGRendererImpl::layerNeedsFrameClear(QSSGLayerRenderData &inLayer)
{
m_lastFrameLayers.push_back(&inLayer);
}
void QSSGRendererImpl::beginLayerDepthPassRender(QSSGLayerRenderData &inLayer)
{
m_currentLayer = &inLayer;
}
void QSSGRendererImpl::endLayerDepthPassRender()
{
m_currentLayer = nullptr;
}
void QSSGRendererImpl::beginLayerRender(QSSGLayerRenderData &inLayer)
{
m_currentLayer = &inLayer;
}
void QSSGRendererImpl::endLayerRender()
{
m_currentLayer = nullptr;
}
void QSSGRendererImpl::prepareImageForIbl(QSSGRenderImage &inImage)
{
if (inImage.m_textureData.m_texture && inImage.m_textureData.m_texture->numMipmaps() < 1)
inImage.m_textureData.m_texture->generateMipmaps();
}
bool nodeContainsBoneRoot(QSSGRenderNode &childNode, qint32 rootID)
{
for (QSSGRenderNode *childChild = childNode.firstChild; childChild != nullptr; childChild = childChild->nextSibling) {
if (childChild->skeletonId == rootID)
return true;
}
return false;
}
void fillBoneIdNodeMap(QSSGRenderNode &childNode, QHash<long, QSSGRenderNode *> &ioMap)
{
if (childNode.skeletonId >= 0)
ioMap[childNode.skeletonId] = &childNode;
for (QSSGRenderNode *childChild = childNode.firstChild; childChild != nullptr; childChild = childChild->nextSibling)
fillBoneIdNodeMap(*childChild, ioMap);
}
QSSGOption<QVector2D> QSSGRendererImpl::getLayerMouseCoords(QSSGLayerRenderData &inLayerRenderData,
const QVector2D &inMouseCoords,
const QVector2D &inViewportDimensions,
bool forceImageIntersect) const
{
if (inLayerRenderData.layerPrepResult.hasValue()) {
const auto viewport = inLayerRenderData.layerPrepResult->viewport();
return QSSGLayerRenderHelper::layerMouseCoords(viewport, inMouseCoords, inViewportDimensions, forceImageIntersect);
}
return QSSGEmpty();
}
bool QSSGRendererImpl::rendererRequestsFrames() const
{
return m_progressiveAARenderRequest;
}
void QSSGRendererImpl::getLayerHitObjectList(QSSGLayerRenderData &inLayerRenderData,
const QVector2D &inViewportDimensions,
const QVector2D &inPresCoords,
bool inPickEverything,
TPickResultArray &outIntersectionResult)
{
// This function assumes the layer was rendered to the scene itself. There is another
// function for completely offscreen layers that don't get rendered to the scene.
bool wasRenderToTarget(inLayerRenderData.layer.flags.testFlag(QSSGRenderLayer::Flag::LayerRenderToTarget));
if (wasRenderToTarget && inLayerRenderData.camera != nullptr) {
QSSGOption<QSSGRenderRay> theHitRay;
if (inLayerRenderData.layerPrepResult.hasValue()) {
const auto camera = inLayerRenderData.layerPrepResult->camera();
const auto viewport = inLayerRenderData.layerPrepResult->viewport();
theHitRay = QSSGLayerRenderHelper::pickRay(*camera, viewport, inPresCoords, inViewportDimensions, false);
}
if (theHitRay.hasValue()) {
// Scale the mouse coords to change them into the camera's numerical space.
QSSGRenderRay thePickRay = *theHitRay;
for (int idx = inLayerRenderData.opaqueObjects.size(), end = 0; idx > end; --idx) {
QSSGRenderableObject *theRenderableObject = inLayerRenderData.opaqueObjects.at(idx - 1).obj;
if (inPickEverything || theRenderableObject->renderableFlags.isPickable())
intersectRayWithSubsetRenderable(thePickRay, *theRenderableObject, outIntersectionResult);
}
for (int idx = inLayerRenderData.transparentObjects.size(), end = 0; idx > end; --idx) {
QSSGRenderableObject *theRenderableObject = inLayerRenderData.transparentObjects.at(idx - 1).obj;
if (inPickEverything || theRenderableObject->renderableFlags.isPickable())
intersectRayWithSubsetRenderable(thePickRay, *theRenderableObject, outIntersectionResult);
}
}
}
}
using RenderableList = QVarLengthArray<const QSSGRenderNode *>;
static void dfs(const QSSGRenderNode &node, RenderableList &renderables)
{
if (node.isRenderableType())
renderables.push_back(&node);
for (QSSGRenderNode *child = node.firstChild; child != nullptr; child = child->nextSibling)
dfs(*child, renderables);
}
void QSSGRendererImpl::getLayerHitObjectList(QSSGRenderLayer &layer,
const QVector2D &inViewportDimensions,
const QVector2D &inPresCoords,
bool inPickEverything,
PickResultList &outIntersectionResult)
{
// This function assumes the layer was rendered to the scene itself. There is another
// function for completely offscreen layers that don't get rendered to the scene.
const bool wasRenderToTarget(layer.flags.testFlag(QSSGRenderLayer::Flag::LayerRenderToTarget));
if (wasRenderToTarget && layer.renderedCamera != nullptr) {
const auto camera = layer.renderedCamera;
// TODO: Need to make sure we get the right Viewport rect here.
const auto viewport = QRectF(QPointF(), QSizeF(qreal(inViewportDimensions.x()), qreal(inViewportDimensions.y())));
const QSSGOption<QSSGRenderRay> hitRay = QSSGLayerRenderHelper::pickRay(*camera, viewport, inPresCoords, inViewportDimensions, false);
if (hitRay.hasValue()) {
// Scale the mouse coords to change them into the camera's numerical space.
RenderableList renderables;
for (QSSGRenderNode *childNode = layer.firstChild; childNode; childNode = childNode->nextSibling)
dfs(*childNode, renderables);
const auto &bufferManager = contextInterface()->bufferManager();
for (int idx = renderables.size(), end = 0; idx > end; --idx) {
const auto &pickableObject = renderables.at(idx - 1);
if (inPickEverything || pickableObject->flags.testFlag(QSSGRenderNode::Flag::LocallyPickable))
intersectRayWithSubsetRenderable(bufferManager, *hitRay, *pickableObject, outIntersectionResult);
}
}
}
}
void QSSGRendererImpl::intersectRayWithSubsetRenderable(const QSSGRef<QSSGBufferManager> &bufferManager,
const QSSGRenderRay &inRay,
const QSSGRenderNode &node,
QSSGRendererImpl::PickResultList &outIntersectionResultList)
{
if (node.type != QSSGRenderGraphObject::Type::Model)
return;
const QSSGRenderModel *model = static_cast<const QSSGRenderModel *>(&node);
// TODO: Technically we should have some guard here, as the meshes are usually loaded on a different thread,
// so this isn't really nice (assumes all meshes are loaded before picking and none are removed, which currently should be the case).
auto mesh = bufferManager->getMesh(model->meshPath);
if (!mesh)
return;
const auto &globalTransform = model->globalTransform;
const auto &subMeshes = mesh->subsets;
QSSGBounds3 modelBounds = QSSGBounds3::empty();
for (const auto &subMesh : subMeshes)
modelBounds.include(subMesh.bounds);
if (modelBounds.isEmpty())
return;
QSSGRenderRay::IntersectionResult intersectionResult = inRay.intersectWithAABB(globalTransform, modelBounds);
// If we don't intersect with the model at all, then there's no need to go furher down!
if (!intersectionResult.intersects)
return;
for (const auto &subMesh : subMeshes) {
intersectionResult = inRay.intersectWithAABB(globalTransform, subMesh.bounds);
if (intersectionResult.intersects)
break;
}
if (!intersectionResult.intersects)
return;
outIntersectionResultList.push_back(
QSSGRenderPickResult(*model,
intersectionResult.rayLengthSquared,
intersectionResult.relXY,
intersectionResult.scenePosition));
}
void QSSGRendererImpl::intersectRayWithSubsetRenderable(const QSSGRenderRay &inRay,
QSSGRenderableObject &inRenderableObject,
TPickResultArray &outIntersectionResultList)
{
QSSGRenderRay::IntersectionResult intersectionResult = inRay.intersectWithAABB(inRenderableObject.globalTransform, inRenderableObject.bounds);
if (!intersectionResult.intersects)
return;
// Leave the coordinates relative for right now.
const QSSGRenderGraphObject *thePickObject = nullptr;
if (inRenderableObject.renderableFlags.isDefaultMaterialMeshSubset())
thePickObject = &static_cast<QSSGSubsetRenderable *>(&inRenderableObject)->modelContext.model;
else if (inRenderableObject.renderableFlags.isCustomMaterialMeshSubset())
thePickObject = &static_cast<QSSGCustomMaterialRenderable *>(&inRenderableObject)->modelContext.model;
if (thePickObject != nullptr) {
outIntersectionResultList.push_back(
QSSGRenderPickResult(*thePickObject,
intersectionResult.rayLengthSquared,
intersectionResult.relXY,
intersectionResult.scenePosition));
}
}
QSSGRef<QSSGRenderShaderProgram> QSSGRendererImpl::compileShader(const QByteArray &inName, const char *inVert, const char *inFrag)
{
getProgramGenerator()->beginProgram();
getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Vertex)->append(inVert);
getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Fragment)->append(inFrag);
return getProgramGenerator()->compileGeneratedShader(inName);
}
QSSGRef<QSSGShaderGeneratorGeneratedShader> QSSGRendererImpl::getShader(QSSGSubsetRenderable &inRenderable,
const ShaderFeatureSetList &inFeatureSet)
{
if (Q_UNLIKELY(m_currentLayer == nullptr)) {
Q_ASSERT(false);
return nullptr;
}
auto shaderIt = m_shaders.constFind(inRenderable.shaderDescription);
if (shaderIt == m_shaders.cend()) {
// Generate the shader.
const QSSGRef<QSSGRenderShaderProgram> &theShader(generateShader(inRenderable, inFeatureSet));
if (theShader) {
QSSGRef<QSSGShaderGeneratorGeneratedShader> theGeneratedShader = QSSGRef<QSSGShaderGeneratorGeneratedShader>(
new QSSGShaderGeneratorGeneratedShader(m_generatedShaderString, theShader));
shaderIt = m_shaders.insert(inRenderable.shaderDescription, theGeneratedShader);
} else {
// We still insert something because we don't to attempt to generate the same bad shader
// twice.
shaderIt = m_shaders.insert(inRenderable.shaderDescription, nullptr);
}
}
if (!shaderIt->isNull()) {
if (m_currentLayer && m_currentLayer->camera) {
QSSGRenderCamera &theCamera(*m_currentLayer->camera);
if (!m_currentLayer->cameraDirection.hasValue())
m_currentLayer->cameraDirection = theCamera.getScalingCorrectDirection();
}
}
return *shaderIt;
}
static QVector3D g_fullScreenRectFace[] = {
QVector3D(-1, -1, 0),
QVector3D(-1, 1, 0),
QVector3D(1, 1, 0),
QVector3D(1, -1, 0),
};
static QVector2D g_fullScreenRectUVs[] = { QVector2D(0, 0), QVector2D(0, 1), QVector2D(1, 1), QVector2D(1, 0) };
void QSSGRendererImpl::generateXYQuad()
{
if (m_quadInputAssembler)
return;
QSSGRenderVertexBufferEntry theEntries[] = {
QSSGRenderVertexBufferEntry("attr_pos", QSSGRenderComponentType::Float32, 3),
QSSGRenderVertexBufferEntry("attr_uv", QSSGRenderComponentType::Float32, 2, 12),
};
float tempBuf[20];
float *bufPtr = tempBuf;
QVector3D *facePtr(g_fullScreenRectFace);
QVector2D *uvPtr(g_fullScreenRectUVs);
for (int j = 0; j < 4; j++, ++facePtr, ++uvPtr, bufPtr += 5) {
bufPtr[0] = facePtr->x();
bufPtr[1] = facePtr->y();
bufPtr[2] = facePtr->z();
bufPtr[3] = uvPtr->x();
bufPtr[4] = uvPtr->y();
}
m_quadVertexBuffer = new QSSGRenderVertexBuffer(m_context, QSSGRenderBufferUsageType::Static,
3 * sizeof(float) + 2 * sizeof(float),
toByteView(tempBuf, 20));
quint8 indexData[] = {
0, 1, 2, 0, 2, 3,
};
m_quadIndexBuffer = new QSSGRenderIndexBuffer(m_context, QSSGRenderBufferUsageType::Static,
QSSGRenderComponentType::UnsignedInteger8,
toByteView(indexData, sizeof(indexData)));
// create our attribute layout
m_quadAttribLayout = m_context->createAttributeLayout(toDataView(theEntries, 2));
// create input assembler object
quint32 strides = m_quadVertexBuffer->stride();
quint32 offsets = 0;
m_quadInputAssembler = m_context->createInputAssembler(m_quadAttribLayout,
toDataView(&m_quadVertexBuffer, 1),
m_quadIndexBuffer,
toDataView(&strides, 1),
toDataView(&offsets, 1));
}
void QSSGRendererImpl::generateXYZPoint()
{
if (m_pointInputAssembler)
return;
QSSGRenderVertexBufferEntry theEntries[] = {
QSSGRenderVertexBufferEntry("attr_pos", QSSGRenderComponentType::Float32, 3),
QSSGRenderVertexBufferEntry("attr_uv", QSSGRenderComponentType::Float32, 2, 12),
};
float tempBuf[5] { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
m_pointVertexBuffer = new QSSGRenderVertexBuffer(m_context, QSSGRenderBufferUsageType::Static,
3 * sizeof(float) + 2 * sizeof(float),
toByteView(tempBuf, 5));
// create our attribute layout
m_pointAttribLayout = m_context->createAttributeLayout(toDataView(theEntries, 2));
// create input assembler object
quint32 strides = m_pointVertexBuffer->stride();
quint32 offsets = 0;
m_pointInputAssembler = m_context->createInputAssembler(m_pointAttribLayout,
toDataView(&m_pointVertexBuffer, 1),
nullptr,
toDataView(&strides, 1),
toDataView(&offsets, 1));
}
QPair<QSSGRef<QSSGRenderVertexBuffer>, QSSGRef<QSSGRenderIndexBuffer>> QSSGRendererImpl::getXYQuad()
{
if (!m_quadInputAssembler)
generateXYQuad();
return QPair<QSSGRef<QSSGRenderVertexBuffer>, QSSGRef<QSSGRenderIndexBuffer>>(m_quadVertexBuffer, m_quadIndexBuffer);
}
QSSGLayerGlobalRenderProperties QSSGRendererImpl::getLayerGlobalRenderProperties()
{
QSSGLayerRenderData &theData = *m_currentLayer;
const QSSGRenderLayer &theLayer = theData.layer;
if (!theData.cameraDirection.hasValue())
theData.cameraDirection = theData.camera->getScalingCorrectDirection();
return QSSGLayerGlobalRenderProperties{ theLayer,
*theData.camera,
*theData.cameraDirection,
theData.globalLights,
theData.lightDirections,
theData.shadowMapManager,
theData.m_layerDepthTexture,
theData.m_layerSsaoTexture,
theLayer.lightProbe,
theLayer.lightProbe2,
theLayer.probeHorizon,
theLayer.probeBright,
theLayer.probe2Window,
theLayer.probe2Pos,
theLayer.probe2Fade,
theLayer.probeFov };
}
void QSSGRendererImpl::generateXYQuadStrip()
{
if (m_quadStripInputAssembler)
return;
QSSGRenderVertexBufferEntry theEntries[] = {
QSSGRenderVertexBufferEntry("attr_pos", QSSGRenderComponentType::Float32, 3),
QSSGRenderVertexBufferEntry("attr_uv", QSSGRenderComponentType::Float32, 2, 12),
};
// this buffer is filled dynmically
m_quadStripVertexBuffer = new QSSGRenderVertexBuffer(m_context, QSSGRenderBufferUsageType::Dynamic,
3 * sizeof(float) + 2 * sizeof(float), // stride
QSSGByteView());
// create our attribute layout
m_quadStripAttribLayout = m_context->createAttributeLayout(toDataView(theEntries, 2));
// create input assembler object
quint32 strides = m_quadStripVertexBuffer->stride();
quint32 offsets = 0;
m_quadStripInputAssembler = m_context->createInputAssembler(m_quadStripAttribLayout,
toDataView(&m_quadStripVertexBuffer, 1),
nullptr,
toDataView(&strides, 1),
toDataView(&offsets, 1));
}
void QSSGRendererImpl::updateCbAoShadow(const QSSGRenderLayer *pLayer, const QSSGRenderCamera *pCamera, QSSGResourceTexture2D &inDepthTexture)
{
if (m_context->supportsConstantBuffer()) {
const char *theName = "aoShadow";
QSSGRef<QSSGRenderConstantBuffer> pCB = m_context->getConstantBuffer(theName);
if (!pCB) {
// the size is determined automatically later on
pCB = new QSSGRenderConstantBuffer(m_context, theName,
QSSGRenderBufferUsageType::Static,
QSSGByteView());
if (!pCB) {
Q_ASSERT(false);
return;
}
m_constantBuffers.insert(theName, pCB);
// Add paramters. Note should match the appearance in the shader program
pCB->addParam(QSSGRenderConstantBuffer::ParamData<QSSGRenderConstantBuffer::Param::AoProperties>::handle(), QSSGRenderShaderDataType::Vec4, 1);
pCB->addParam(QSSGRenderConstantBuffer::ParamData<QSSGRenderConstantBuffer::Param::AoProperties2>::handle(), QSSGRenderShaderDataType::Vec4, 1);
pCB->addParam(QSSGRenderConstantBuffer::ParamData<QSSGRenderConstantBuffer::Param::ShadowProperties>::handle(), QSSGRenderShaderDataType::Vec4, 1);
pCB->addParam(QSSGRenderConstantBuffer::ParamData<QSSGRenderConstantBuffer::Param::AoScreenConst>::handle(), QSSGRenderShaderDataType::Vec4, 1);
pCB->addParam(QSSGRenderConstantBuffer::ParamData<QSSGRenderConstantBuffer::Param::UvToEyeConst>::handle(), QSSGRenderShaderDataType::Vec4, 1);
}
// update values
QVector4D aoProps(pLayer->aoStrength * 0.01f, pLayer->aoDistance * 0.4f, pLayer->aoSoftness * 0.02f, pLayer->aoBias);
pCB->updateParam(QSSGRenderConstantBuffer::ParamData<QSSGRenderConstantBuffer::Param::AoProperties>::handle(), toByteView(aoProps));
QVector4D aoProps2(float(pLayer->aoSamplerate), (pLayer->aoDither) ? 1.0f : 0.0f, 0.0f, 0.0f);
pCB->updateParam(QSSGRenderConstantBuffer::ParamData<QSSGRenderConstantBuffer::Param::AoProperties2>::handle(), toByteView(aoProps2));
QVector4D shadowProps(pLayer->shadowStrength * 0.01f, pLayer->shadowDist, pLayer->shadowSoftness * 0.01f, pLayer->shadowBias);
pCB->updateParam(QSSGRenderConstantBuffer::ParamData<QSSGRenderConstantBuffer::Param::ShadowProperties>::handle(), toByteView(shadowProps));
float R2 = pLayer->aoDistance * pLayer->aoDistance * 0.16f;
float rw = 100, rh = 100;
if (inDepthTexture.getTexture()) {
rw = float(inDepthTexture.getTexture()->textureDetails().width);
rh = float(inDepthTexture.getTexture()->textureDetails().height);
}
float fov = (pCamera) ? pCamera->verticalFov(rw / rh) : 1.0f;
float tanHalfFovY = tanf(0.5f * fov * (rh / rw));
float invFocalLenX = tanHalfFovY * (rw / rh);
QVector4D aoScreenConst(1.0f / R2, rh / (2.0f * tanHalfFovY), 1.0f / rw, 1.0f / rh);
pCB->updateParam(QSSGRenderConstantBuffer::ParamData<QSSGRenderConstantBuffer::Param::AoScreenConst>::handle(), toByteView(aoScreenConst));
QVector4D uvToEyeConst(2.0f * invFocalLenX, -2.0f * tanHalfFovY, -invFocalLenX, tanHalfFovY);
pCB->updateParam(QSSGRenderConstantBuffer::ParamData<QSSGRenderConstantBuffer::Param::UvToEyeConst>::handle(), toByteView(uvToEyeConst));
// update buffer to hardware
pCB->update();
}
}
// widget context implementation
QSSGRef<QSSGRenderVertexBuffer> QSSGRendererImpl::getOrCreateVertexBuffer(const QByteArray &inStr,
quint32 stride,
QSSGByteView bufferData)
{
const QSSGRef<QSSGRenderVertexBuffer> &retval = getVertexBuffer(inStr);
if (retval) {
// we update the buffer
retval->updateBuffer(bufferData);
return retval;
}
return *m_widgetVertexBuffers.insert(inStr, new QSSGRenderVertexBuffer(m_context, QSSGRenderBufferUsageType::Dynamic, stride, bufferData));
}
QSSGRef<QSSGRenderIndexBuffer> QSSGRendererImpl::getOrCreateIndexBuffer(const QByteArray &inStr,
QSSGRenderComponentType componentType,
QSSGByteView bufferData)
{
const QSSGRef<QSSGRenderIndexBuffer> &retval = getIndexBuffer(inStr);
if (retval) {
// we update the buffer
retval->updateBuffer(bufferData);
return retval;
}
return *m_widgetIndexBuffers.insert(inStr, new QSSGRenderIndexBuffer(m_context, QSSGRenderBufferUsageType::Dynamic, componentType, bufferData));
}
QSSGRef<QSSGRenderAttribLayout> QSSGRendererImpl::createAttributeLayout(QSSGDataView<QSSGRenderVertexBufferEntry> attribs)
{
// create our attribute layout
return m_context->createAttributeLayout(attribs);
}
QSSGRef<QSSGRenderInputAssembler> QSSGRendererImpl::getOrCreateInputAssembler(const QByteArray &inStr,
const QSSGRef<QSSGRenderAttribLayout> &attribLayout,
QSSGDataView<QSSGRef<QSSGRenderVertexBuffer>> buffers,
const QSSGRef<QSSGRenderIndexBuffer> indexBuffer,
QSSGDataView<quint32> strides,
QSSGDataView<quint32> offsets)
{
const QSSGRef<QSSGRenderInputAssembler> &retval = getInputAssembler(inStr);
if (retval)
return retval;
return *m_widgetInputAssembler.insert(inStr, m_context->createInputAssembler(attribLayout, buffers, indexBuffer, strides, offsets));
}
QSSGRef<QSSGRenderVertexBuffer> QSSGRendererImpl::getVertexBuffer(const QByteArray &inStr) const
{
const auto theIter = m_widgetVertexBuffers.constFind(inStr);
if (theIter != m_widgetVertexBuffers.cend())
return theIter.value();
return nullptr;
}
QSSGRef<QSSGRenderIndexBuffer> QSSGRendererImpl::getIndexBuffer(const QByteArray &inStr) const
{
const auto theIter = m_widgetIndexBuffers.constFind(inStr);
if (theIter != m_widgetIndexBuffers.cend())
return theIter.value();
return nullptr;
}
QSSGRef<QSSGRenderInputAssembler> QSSGRendererImpl::getInputAssembler(const QByteArray &inStr) const
{
const auto theIter = m_widgetInputAssembler.constFind(inStr);
if (theIter != m_widgetInputAssembler.cend())
return theIter.value();
return nullptr;
}
QSSGRef<QSSGRenderShaderProgram> QSSGRendererImpl::getShader(const QByteArray &inStr) const
{
const auto theIter = m_widgetShaders.constFind(inStr);
if (theIter != m_widgetShaders.cend())
return theIter.value();
return nullptr;
}
QSSGRef<QSSGRenderShaderProgram> QSSGRendererImpl::compileAndStoreShader(const QByteArray &inStr)
{
const QSSGRef<QSSGRenderShaderProgram> &newProgram = getProgramGenerator()->compileGeneratedShader(inStr);
if (newProgram)
m_widgetShaders.insert(inStr, newProgram);
return newProgram;
}
const QSSGRef<QSSGShaderProgramGeneratorInterface> &QSSGRendererImpl::getProgramGenerator()
{
return m_contextInterface->shaderProgramGenerator();
}
void QSSGRendererImpl::dumpGpuProfilerStats()
{
if (!isLayerGpuProfilingEnabled())
return;
auto it = m_instanceRenderMap.cbegin();
const auto end = m_instanceRenderMap.cend();
for (; it != end; it++) {
const QSSGRef<QSSGLayerRenderData> &theLayerRenderData = it.value();
const QSSGRenderLayer *theLayer = &theLayerRenderData->layer;
if (theLayer->flags.testFlag(QSSGRenderLayer::Flag::Active) && theLayerRenderData->m_layerProfilerGpu) {
const QVector<QString> &idList = theLayerRenderData->m_layerProfilerGpu->timerIDs();
if (!idList.empty()) {
#if QSSG_DEBUG_ID
qDebug() << theLayer->id;
#endif
auto theIdIter = idList.begin();
for (; theIdIter != idList.end(); theIdIter++) {
char messageLine[1024];
sprintf(messageLine,
"%s: %.3f ms",
theIdIter->toLatin1().constData(),
theLayerRenderData->m_layerProfilerGpu->elapsed(*theIdIter));
qDebug() << " " << messageLine;
}
}
}
}
}
QSSGOption<QVector2D> QSSGRendererImpl::getLayerMouseCoords(QSSGRenderLayer &inLayer,
const QVector2D &inMouseCoords,
const QVector2D &inViewportDimensions,
bool forceImageIntersect) const
{
QSSGRef<QSSGLayerRenderData> theData = const_cast<QSSGRendererImpl &>(*this).getOrCreateLayerRenderDataForNode(inLayer);
return getLayerMouseCoords(*theData, inMouseCoords, inViewportDimensions, forceImageIntersect);
}
bool QSSGRendererInterface::isGlEsContext(const QSSGRenderContextType &inContextType)
{
QSSGRenderContextTypes esContextTypes(QSSGRenderContextType::GLES2 | QSSGRenderContextType::GLES3
| QSSGRenderContextType::GLES3PLUS);
return (esContextTypes & inContextType);
}
bool QSSGRendererInterface::isGlEs3Context(const QSSGRenderContextType &inContextType)
{
return (inContextType == QSSGRenderContextType::GLES3 || inContextType == QSSGRenderContextType::GLES3PLUS);
}
bool QSSGRendererInterface::isGl2Context(const QSSGRenderContextType &inContextType)
{
return (inContextType == QSSGRenderContextType::GL2);
}
QSSGRef<QSSGRendererInterface> QSSGRendererInterface::createRenderer(QSSGRenderContextInterface *inContext)
{
return QSSGRef<QSSGRendererImpl>(new QSSGRendererImpl(inContext));
}
QSSGRenderPickResult::QSSGRenderPickResult(const QSSGRenderGraphObject &inHitObject,
float inCameraDistance,
const QVector2D &inLocalUVCoords,
const QVector3D &scenePosition)
: m_hitObject(&inHitObject)
, m_cameraDistanceSq(inCameraDistance)
, m_localUVCoords(inLocalUVCoords)
, m_scenePosition(scenePosition)
{
}
QT_END_NAMESPACE