overte-thingvellir/libraries/render-utils/src/LightClusters.cpp

581 lines
No EOL
22 KiB
C++

//
// LightClusters.cpp
//
// Created by Sam Gateau on 9/7/2016.
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "LightClusters.h"
#include <gpu/Context.h>
#include <gpu/StandardShaderLib.h>
#include "lightClusters_drawGrid_vert.h"
#include "lightClusters_drawGrid_frag.h"
//#include "lightClusters_drawClusterFromDepth_vert.h"
#include "lightClusters_drawClusterFromDepth_frag.h"
#include "lightClusters_drawClusterContent_vert.h"
#include "lightClusters_drawClusterContent_frag.h"
enum LightClusterGridShader_MapSlot {
DEFERRED_BUFFER_LINEAR_DEPTH_UNIT = 0,
};
enum LightClusterGridShader_BufferSlot {
LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT = 0,
DEFERRED_FRAME_TRANSFORM_BUFFER_SLOT,
CAMERA_CORRECTION_BUFFER_SLOT,
LIGHT_GPU_SLOT = render::ShapePipeline::Slot::LIGHT,
LIGHT_INDEX_GPU_SLOT,
LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT,
LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT,
};
void FrustumGrid::generateGridPlanes(Planes& xPlanes, Planes& yPlanes, Planes& zPlanes) {
xPlanes.resize(dims.x + 1);
yPlanes.resize(dims.y + 1);
zPlanes.resize(dims.z + 1);
float centerY = float(dims.y) * 0.5;
float centerX = float(dims.x) * 0.5;
for (int z = 0; z < zPlanes.size(); z++) {
ivec3 pos(0, 0, z);
zPlanes[z] = glm::vec4(0.0f, 0.0f, -1.0f, frustumGrid_clusterPosToEye(pos, vec3(0.0)).z);
}
for (int x = 0; x < xPlanes.size(); x++) {
auto slicePos = frustumGrid_clusterPosToEye(glm::vec3((float)x, centerY, 0.0));
auto sliceDir = glm::normalize(slicePos);
xPlanes[x] = glm::vec4(sliceDir.z, 0.0, -sliceDir.x, 0.0);
}
for (int y = 0; y < yPlanes.size(); y++) {
auto slicePos = frustumGrid_clusterPosToEye(glm::vec3(centerX, (float)y, 0.0));
auto sliceDir = glm::normalize(slicePos);
yPlanes[y] = glm::vec4(0.0, sliceDir.z, -sliceDir.x, 0.0);
}
}
#include "DeferredLightingEffect.h"
const glm::uvec4 LightClusters::MAX_GRID_DIMENSIONS { 16, 16, 15, 16384 };
LightClusters::LightClusters() :
_lightIndicesBuffer(std::make_shared<gpu::Buffer>()),
_clusterGridBuffer(std::make_shared<gpu::Buffer>(), gpu::Element::INDEX_INT32),
_clusterContentBuffer(std::make_shared<gpu::Buffer>(), gpu::Element::INDEX_INT32) {
auto dims = _frustumGridBuffer.edit().dims;
_frustumGridBuffer.edit().dims = ivec3(0); // make sure we go through the full reset of the dimensionts ion the setDImensions call
setDimensions(dims, MAX_GRID_DIMENSIONS.w);
}
void LightClusters::setDimensions(glm::uvec3 gridDims, uint32_t listBudget) {
ivec3 configDimensions;
configDimensions.x = std::min(MAX_GRID_DIMENSIONS.x, gridDims.x);
configDimensions.y = std::min(MAX_GRID_DIMENSIONS.y, gridDims.y);
configDimensions.z = std::min(MAX_GRID_DIMENSIONS.z, gridDims.z);
auto configListBudget = std::min(MAX_GRID_DIMENSIONS.w, listBudget);
auto& dims = _frustumGridBuffer->dims;
if ((dims.x != configDimensions.x) || (dims.y != configDimensions.y) || (dims.z != configDimensions.z)) {
_frustumGridBuffer.edit().dims = configDimensions;
_frustumGridBuffer.edit().generateGridPlanes(_gridPlanes[0], _gridPlanes[1], _gridPlanes[2]);
}
auto numClusters = _frustumGridBuffer.edit().frustumGrid_numClusters();
if (numClusters != _numClusters) {
_numClusters = numClusters;
_clusterGrid.clear();
_clusterGrid.resize(_numClusters, EMPTY_CLUSTER);
_clusterGridBuffer._size = _clusterGridBuffer._buffer->resize(_numClusters * sizeof(uint32_t));
}
if (configListBudget != _clusterContentBuffer.getNumElements()) {
_clusterContent.clear();
_clusterContent.resize(configListBudget, INVALID_LIGHT);
_clusterContentBuffer._size = _clusterContentBuffer._buffer->resize(configListBudget * sizeof(LightID));
}
}
void LightClusters::updateFrustum(const ViewFrustum& frustum) {
_frustum = frustum;
_frustumGridBuffer.edit().updateFrustum(frustum);
}
void LightClusters::updateLightStage(const LightStagePointer& lightStage) {
_lightStage = lightStage;
}
void LightClusters::updateLightFrame(const LightStage::Frame& lightFrame, bool points, bool spots) {
// start fresh
_visibleLightIndices.clear();
// Now gather the lights
// gather lights
auto& srcPointLights = lightFrame._pointLights;
auto& srcSpotLights = lightFrame._spotLights;
int numPointLights = (int)srcPointLights.size();
// int offsetPointLights = 0;
int numSpotLights = (int)srcSpotLights.size();
// int offsetSpotLights = numPointLights;
_visibleLightIndices.resize(numPointLights + numSpotLights + 1);
_visibleLightIndices[0] = 0;
if (points && !srcPointLights.empty()) {
memcpy(_visibleLightIndices.data() + (_visibleLightIndices[0] + 1), srcPointLights.data(), srcPointLights.size() * sizeof(int));
_visibleLightIndices[0] += (int)srcPointLights.size();
}
if (spots && !srcSpotLights.empty()) {
memcpy(_visibleLightIndices.data() + (_visibleLightIndices[0] + 1), srcSpotLights.data(), srcSpotLights.size() * sizeof(int));
_visibleLightIndices[0] += (int)srcSpotLights.size();
}
_lightIndicesBuffer._buffer->setData(_visibleLightIndices.size() * sizeof(int), (const gpu::Byte*) _visibleLightIndices.data());
_lightIndicesBuffer._size = _visibleLightIndices.size() * sizeof(int);
}
glm::vec4 projectToPlane(glm::vec4& sphere, const glm::vec4& plane) {
float distance = sphere.x * plane.x + sphere.y *plane.y + sphere.z * plane.z + plane.w;
if (distance < sphere.w) {
return glm::vec4(sphere.x - distance * plane.x, sphere.y - distance * plane.y, sphere.z - distance * plane.z, sqrt(sphere.w * sphere.w - distance * distance));
} else {
return sphere;
}
}
bool scanLightVolume(const FrustumGrid& grid, const FrustumGrid::Planes planes[3], int zMin, int zMax, int yMin, int yMax, int xMin, int xMax, LightClusters::LightID lightId, const glm::vec4& eyePosRadius,
uint32_t& numClustersTouched, uint32_t maxNumIndices, std::vector< std::vector<LightClusters::LightID>>& clusterGrid) {
glm::ivec3 gridPosToOffset(1, grid.dims.x, grid.dims.x * grid.dims.y);
int center_z = (zMax + zMin) >> 1;
int center_y = (yMax + yMin) >> 1;
bool hasBudget = true;
for (auto z = zMin; (z <= zMax) && hasBudget; z++) {
auto zSphere = eyePosRadius;
if (z != center_z) {
auto& plane = (z < center_z) ? planes[2][z + 1] : -planes[2][z];
zSphere = projectToPlane(zSphere, plane);
}
for (auto y = yMin; (y <= yMax) && hasBudget; y++) {
auto ySphere = zSphere;
if (y != center_y) {
auto& plane = (y < center_y) ? planes[1][y + 1] : -planes[1][y];
ySphere = projectToPlane(ySphere, plane);
}
auto x = xMin;
do { ++x; } while ((x < xMax) && (glm::dot(planes[0][x], glm::vec4(ySphere.x, ySphere.y, ySphere.z, 1.0)) >= ySphere.w));
auto xs = xMax;
do { --xs; } while ((xs >= x) && (-glm::dot(planes[0][xs], glm::vec4(ySphere.x, ySphere.y, ySphere.z, 1.0)) >= ySphere.w));
for (--x; (x <= xs) && hasBudget; x++) {
auto index = x + gridPosToOffset.y * y + gridPosToOffset.z * z;
clusterGrid[index].emplace_back(lightId);
numClustersTouched++;
if (numClustersTouched >= maxNumIndices) {
hasBudget = false;
}
}
}
}
return hasBudget;
}
void LightClusters::updateClusters() {
// Clean up last info
std::vector< std::vector< LightID > > clusterGrid(_numClusters);
_clusterGrid.resize(_numClusters, EMPTY_CLUSTER);
uint32_t maxNumIndices = (uint32_t) _clusterContent.size();
_clusterContent.resize(maxNumIndices, INVALID_LIGHT);
auto theFrustumGrid(_frustumGridBuffer.get());
glm::ivec3 gridPosToOffset(1, theFrustumGrid.dims.x, theFrustumGrid.dims.x * theFrustumGrid.dims.y);
uint32_t numClusterTouched = 0;
for (size_t lightNum = 1; lightNum < _visibleLightIndices.size(); ++lightNum) {
auto lightId = _visibleLightIndices[lightNum];
auto light = _lightStage->getLight(lightId);
if (!light)
continue;
auto worldOri = light->getPosition();
auto radius = light->getMaximumRadius();
// Bring into frustum eye space
auto eyeOri = theFrustumGrid.frustumGrid_worldToEye(glm::vec4(worldOri, 1.0f));
// Remove light that slipped through and is not in the z range
float eyeZMax = eyeOri.z - radius;
if (eyeZMax > -theFrustumGrid.rangeNear) {
continue;
}
float eyeZMin = eyeOri.z + radius;
if (eyeZMin < -theFrustumGrid.rangeFar) {
continue;
}
// Get z slices
int zMin = theFrustumGrid.frustumGrid_eyeDepthToClusterLayer(eyeZMin);
int zMax = theFrustumGrid.frustumGrid_eyeDepthToClusterLayer(eyeZMax);
// That should never happen
if (zMin == -1 && zMax == -1) {
continue;
}
// is it a light whose origin is behind the near ?
bool behindLight = (eyeOri.z >= -theFrustumGrid.rangeNear);
//
float eyeOriLen2 = glm::length2(eyeOri);
// CLamp the z range
zMin = std::max(0, zMin);
// find 2D corners of the sphere in grid
int xMin { 0 };
int xMax { theFrustumGrid.dims.x - 1 };
int yMin { 0 };
int yMax { theFrustumGrid.dims.y - 1 };
float radius2 = radius * radius;
auto eyeOriH = eyeOri;
auto eyeOriV = eyeOri;
eyeOriH.y = 0.0f;
eyeOriV.x = 0.0f;
float eyeOriLen2H = glm::length2(eyeOriH);
float eyeOriLen2V = glm::length2(eyeOriV);
if ((eyeOriLen2H > radius2)) {
float eyeOriLenH = sqrt(eyeOriLen2H);
auto eyeOriDirH = glm::vec3(eyeOriH) / eyeOriLenH;
float eyeToTangentCircleLenH = sqrt(eyeOriLen2H - radius2);
float eyeToTangentCircleCosH = eyeToTangentCircleLenH / eyeOriLenH;
float eyeToTangentCircleSinH = radius / eyeOriLenH;
// rotate the eyeToOriDir (H & V) in both directions
glm::vec3 leftDir(eyeOriDirH.x * eyeToTangentCircleCosH + eyeOriDirH.z * eyeToTangentCircleSinH, 0.0f, eyeOriDirH.x * -eyeToTangentCircleSinH + eyeOriDirH.z * eyeToTangentCircleCosH);
glm::vec3 rightDir(eyeOriDirH.x * eyeToTangentCircleCosH - eyeOriDirH.z * eyeToTangentCircleSinH, 0.0f, eyeOriDirH.x * eyeToTangentCircleSinH + eyeOriDirH.z * eyeToTangentCircleCosH);
auto lc = theFrustumGrid.frustumGrid_eyeToClusterDirH(leftDir);
auto rc = theFrustumGrid.frustumGrid_eyeToClusterDirH(rightDir);
xMin = std::max(xMin, lc);
xMax = std::max(0, std::min(rc, xMax));
}
if ((eyeOriLen2V > radius2)) {
float eyeOriLenV = sqrt(eyeOriLen2V);
auto eyeOriDirV = glm::vec3(eyeOriV) / eyeOriLenV;
float eyeToTangentCircleLenV = sqrt(eyeOriLen2V - radius2);
float eyeToTangentCircleCosV = eyeToTangentCircleLenV / eyeOriLenV;
float eyeToTangentCircleSinV = radius / eyeOriLenV;
// rotate the eyeToOriDir (H & V) in both directions
glm::vec3 bottomDir(0.0f, eyeOriDirV.y * eyeToTangentCircleCosV + eyeOriDirV.z * eyeToTangentCircleSinV, eyeOriDirV.y * -eyeToTangentCircleSinV + eyeOriDirV.z * eyeToTangentCircleCosV);
glm::vec3 topDir(0.0f, eyeOriDirV.y * eyeToTangentCircleCosV - eyeOriDirV.z * eyeToTangentCircleSinV, eyeOriDirV.y * eyeToTangentCircleSinV + eyeOriDirV.z * eyeToTangentCircleCosV);
auto bc = theFrustumGrid.frustumGrid_eyeToClusterDirV(bottomDir);
auto tc = theFrustumGrid.frustumGrid_eyeToClusterDirV(topDir);
yMin = std::max(yMin, bc);
yMax = std::max(yMin, std::min(tc, yMax));
}
// now voxelize
// bool hasBudget = scanLightVolume(theFrustumGrid, _gridPlanes, zMin, zMax, yMin, yMax, xMin, xMax, lightId, glm::vec4(glm::vec3(eyeOri), radius), numClusterTouched, maxNumIndices, clusterGrid);
bool hasBudget = true;
for (auto z = zMin; (z <= zMax) && hasBudget; z++) {
for (auto y = yMin; (y <= yMax) && hasBudget; y++) {
for (auto x = xMin; (x <= xMax) && hasBudget; x++) {
auto index = x + gridPosToOffset.y * y + gridPosToOffset.z * z;
clusterGrid[index].emplace_back(lightId);
numClusterTouched++;
if (numClusterTouched >= maxNumIndices) {
hasBudget = false;
}
}
}
}
if (!hasBudget) {
break;
}
}
// Lights have been gathered now reexpress in terms of 2 sequential buffers
uint16_t indexOffset = 0;
for (int i = 0; i < clusterGrid.size(); i++) {
auto& cluster = clusterGrid[i];
uint16_t numLights = ((uint16_t)cluster.size());
uint16_t offset = indexOffset;
_clusterGrid[i] = (uint32_t)((numLights << 16) | offset);
if (numLights) {
memcpy(_clusterContent.data() + indexOffset, cluster.data(), numLights * sizeof(LightID));
}
indexOffset += numLights;
}
// update the buffers
_clusterGridBuffer._buffer->setData(_clusterGridBuffer._size, (gpu::Byte*) _clusterGrid.data());
_clusterContentBuffer._buffer->setSubData(0, indexOffset * sizeof(LightID), (gpu::Byte*) _clusterContent.data());
}
LightClusteringPass::LightClusteringPass() {
}
void LightClusteringPass::configure(const Config& config) {
if (_lightClusters) {
if (_lightClusters->_frustumGridBuffer->rangeNear != config.rangeNear) {
_lightClusters->_frustumGridBuffer.edit().rangeNear = config.rangeNear;
}
if (_lightClusters->_frustumGridBuffer->rangeFar != config.rangeFar) {
_lightClusters->_frustumGridBuffer.edit().rangeFar = config.rangeFar;
}
_lightClusters->setDimensions(glm::uvec3(config.dimX, config.dimY, config.dimZ), 10000);
}
_freeze = config.freeze;
}
void LightClusteringPass::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& output) {
auto args = renderContext->args;
auto deferredTransform = inputs.get0();
auto lightingModel = inputs.get1();
auto surfaceGeometryFramebuffer = inputs.get2();
if (!_lightClusters) {
_lightClusters = std::make_shared<LightClusters>();
}
// first update the Grid with the new frustum
if (!_freeze) {
_lightClusters->updateFrustum(args->getViewFrustum());
}
// From the LightStage and the current frame, update the light cluster Grid
auto deferredLightingEffect = DependencyManager::get<DeferredLightingEffect>();
auto lightStage = deferredLightingEffect->getLightStage();
_lightClusters->updateLightStage(lightStage);
_lightClusters->updateLightFrame(lightStage->_currentFrame, lightingModel->isPointLightEnabled(), lightingModel->isSpotLightEnabled());
_lightClusters->updateClusters();
output = _lightClusters;
}
DebugLightClusters::DebugLightClusters() {
}
void DebugLightClusters::configure(const Config& config) {
doDrawGrid = config.doDrawGrid;
doDrawClusterFromDepth = config.doDrawClusterFromDepth;
doDrawContent = config.doDrawContent;
}
const gpu::PipelinePointer DebugLightClusters::getDrawClusterGridPipeline() {
if (!_drawClusterGrid) {
auto vs = gpu::Shader::createVertex(std::string(lightClusters_drawGrid_vert));
auto ps = gpu::Shader::createPixel(std::string(lightClusters_drawGrid_frag));
gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps);
gpu::Shader::BindingSet slotBindings;
slotBindings.insert(gpu::Shader::Binding(std::string("frustumGridBuffer"), LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT));
slotBindings.insert(gpu::Shader::Binding(std::string("clusterGridBuffer"), LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT));
slotBindings.insert(gpu::Shader::Binding(std::string("clusterContentBuffer"), LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT));
gpu::Shader::makeProgram(*program, slotBindings);
auto state = std::make_shared<gpu::State>();
state->setDepthTest(true, false, gpu::LESS_EQUAL);
// Blend on transparent
state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA);
// Good to go add the brand new pipeline
_drawClusterGrid = gpu::Pipeline::create(program, state);
}
return _drawClusterGrid;
}
const gpu::PipelinePointer DebugLightClusters::getDrawClusterFromDepthPipeline() {
if (!_drawClusterFromDepth) {
// auto vs = gpu::Shader::createVertex(std::string(lightClusters_drawGrid_vert));
auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS();
auto ps = gpu::Shader::createPixel(std::string(lightClusters_drawClusterFromDepth_frag));
gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps);
gpu::Shader::BindingSet slotBindings;
slotBindings.insert(gpu::Shader::Binding(std::string("frustumGridBuffer"), LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT));
slotBindings.insert(gpu::Shader::Binding(std::string("clusterGridBuffer"), LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT));
slotBindings.insert(gpu::Shader::Binding(std::string("clusterContentBuffer"), LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT));
slotBindings.insert(gpu::Shader::Binding(std::string("linearZeyeMap"), DEFERRED_BUFFER_LINEAR_DEPTH_UNIT));
slotBindings.insert(gpu::Shader::Binding(std::string("cameraCorrectionBuffer"), CAMERA_CORRECTION_BUFFER_SLOT));
slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), DEFERRED_FRAME_TRANSFORM_BUFFER_SLOT));
gpu::Shader::makeProgram(*program, slotBindings);
auto state = std::make_shared<gpu::State>();
// state->setDepthTest(true, false, gpu::LESS_EQUAL);
// Blend on transparent
state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA);
// Good to go add the brand new pipeline
_drawClusterFromDepth = gpu::Pipeline::create(program, state);
}
return _drawClusterFromDepth;
}
const gpu::PipelinePointer DebugLightClusters::getDrawClusterContentPipeline() {
if (!_drawClusterContent) {
auto vs = gpu::Shader::createVertex(std::string(lightClusters_drawClusterContent_vert));
auto ps = gpu::Shader::createPixel(std::string(lightClusters_drawClusterContent_frag));
gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps);
gpu::Shader::BindingSet slotBindings;
slotBindings.insert(gpu::Shader::Binding(std::string("frustumGridBuffer"), LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT));
slotBindings.insert(gpu::Shader::Binding(std::string("clusterGridBuffer"), LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT));
slotBindings.insert(gpu::Shader::Binding(std::string("clusterContentBuffer"), LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT));
gpu::Shader::makeProgram(*program, slotBindings);
auto state = std::make_shared<gpu::State>();
state->setDepthTest(true, false, gpu::LESS_EQUAL);
// Blend on transparent
state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA);
// Good to go add the brand new pipeline
_drawClusterContent = gpu::Pipeline::create(program, state);
}
return _drawClusterContent;
}
void DebugLightClusters::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const Inputs& inputs) {
auto deferredTransform = inputs.get0();
auto deferredFramebuffer = inputs.get1();
auto lightingModel = inputs.get2();
auto linearDepthTarget = inputs.get3();
auto lightClusters = inputs.get4();
auto args = renderContext->args;
gpu::Batch batch;
// Assign the camera transform
batch.setViewportTransform(args->_viewport);
glm::mat4 projMat;
Transform viewMat;
args->getViewFrustum().evalProjectionMatrix(projMat);
args->getViewFrustum().evalViewTransform(viewMat);
batch.setProjectionTransform(projMat);
batch.setViewTransform(viewMat, true);
// Then the actual ClusterGrid attributes
batch.setModelTransform(Transform());
batch.setUniformBuffer(LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT, lightClusters->_frustumGridBuffer);
batch.setUniformBuffer(LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT, lightClusters->_clusterGridBuffer);
batch.setUniformBuffer(LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT, lightClusters->_clusterContentBuffer);
if (doDrawClusterFromDepth) {
batch.setPipeline(getDrawClusterFromDepthPipeline());
batch.setUniformBuffer(DEFERRED_FRAME_TRANSFORM_BUFFER_SLOT, deferredTransform->getFrameTransformBuffer());
if (linearDepthTarget) {
batch.setResourceTexture(DEFERRED_BUFFER_LINEAR_DEPTH_UNIT, linearDepthTarget->getLinearDepthTexture());
}
batch.draw(gpu::TRIANGLE_STRIP, 4, 0);
// Probably not necessary in the long run because the gpu layer would unbound this texture if used as render target
batch.setResourceTexture(DEFERRED_BUFFER_LINEAR_DEPTH_UNIT, nullptr);
batch.setUniformBuffer(DEFERRED_FRAME_TRANSFORM_BUFFER_SLOT, nullptr);
}
if (doDrawContent) {
// bind the one gpu::Pipeline we need
batch.setPipeline(getDrawClusterContentPipeline());
auto dims = lightClusters->_frustumGridBuffer->dims;
glm::ivec3 summedDims(dims.x*dims.y * dims.z, dims.x*dims.y, dims.x);
batch.drawInstanced(summedDims.x, gpu::LINES, 24, 0);
}
if (doDrawGrid) {
// bind the one gpu::Pipeline we need
batch.setPipeline(getDrawClusterGridPipeline());
auto dims = lightClusters->_frustumGridBuffer->dims;
glm::ivec3 summedDims(dims.x*dims.y * dims.z, dims.x*dims.y, dims.x);
batch.drawInstanced(summedDims.x, gpu::LINES, 24, 0);
}
args->_context->appendFrameBatch(batch);
}