mirror of
https://github.com/overte-org/overte.git
synced 2025-04-16 04:28:07 +02:00
Merge pull request #6871 from Atlante45/feat/draw-call-info
Unify normal and named draw paths
This commit is contained in:
commit
1b52d700a0
29 changed files with 482 additions and 388 deletions
|
@ -96,26 +96,31 @@ void renderWorldBox(gpu::Batch& batch) {
|
|||
glm::vec3(HALF_TREE_SCALE, 0.0f, HALF_TREE_SCALE), GREY);
|
||||
|
||||
|
||||
geometryCache->renderWireCubeInstance(batch, Transform(), GREY4);
|
||||
geometryCache->renderWireCubeInstance(batch, GREY4);
|
||||
|
||||
// Draw meter markers along the 3 axis to help with measuring things
|
||||
const float MARKER_DISTANCE = 1.0f;
|
||||
const float MARKER_RADIUS = 0.05f;
|
||||
|
||||
transform = Transform().setScale(MARKER_RADIUS);
|
||||
geometryCache->renderSolidSphereInstance(batch, transform, RED);
|
||||
batch.setModelTransform(transform);
|
||||
geometryCache->renderSolidSphereInstance(batch, RED);
|
||||
|
||||
transform = Transform().setTranslation(glm::vec3(MARKER_DISTANCE, 0.0f, 0.0f)).setScale(MARKER_RADIUS);
|
||||
geometryCache->renderSolidSphereInstance(batch, transform, RED);
|
||||
batch.setModelTransform(transform);
|
||||
geometryCache->renderSolidSphereInstance(batch, RED);
|
||||
|
||||
transform = Transform().setTranslation(glm::vec3(0.0f, MARKER_DISTANCE, 0.0f)).setScale(MARKER_RADIUS);
|
||||
geometryCache->renderSolidSphereInstance(batch, transform, GREEN);
|
||||
batch.setModelTransform(transform);
|
||||
geometryCache->renderSolidSphereInstance(batch, GREEN);
|
||||
|
||||
transform = Transform().setTranslation(glm::vec3(0.0f, 0.0f, MARKER_DISTANCE)).setScale(MARKER_RADIUS);
|
||||
geometryCache->renderSolidSphereInstance(batch, transform, BLUE);
|
||||
batch.setModelTransform(transform);
|
||||
geometryCache->renderSolidSphereInstance(batch, BLUE);
|
||||
|
||||
transform = Transform().setTranslation(glm::vec3(MARKER_DISTANCE, 0.0f, MARKER_DISTANCE)).setScale(MARKER_RADIUS);
|
||||
geometryCache->renderSolidSphereInstance(batch, transform, GREY);
|
||||
batch.setModelTransform(transform);
|
||||
geometryCache->renderSolidSphereInstance(batch, GREY);
|
||||
}
|
||||
|
||||
// Return a random vector of average length 1
|
||||
|
|
|
@ -456,7 +456,8 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
|
|||
Transform transform;
|
||||
transform.setTranslation(position);
|
||||
transform.postScale(INDICATOR_RADIUS);
|
||||
DependencyManager::get<GeometryCache>()->renderSolidSphereInstance(batch, transform, LOOK_AT_INDICATOR_COLOR);
|
||||
batch.setModelTransform(transform);
|
||||
DependencyManager::get<GeometryCache>()->renderSolidSphereInstance(batch, LOOK_AT_INDICATOR_COLOR);
|
||||
}
|
||||
|
||||
// If the avatar is looking at me, indicate that they are
|
||||
|
@ -484,9 +485,9 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
|
|||
eyeDiameter = DEFAULT_EYE_DIAMETER;
|
||||
}
|
||||
|
||||
batch.setModelTransform(Transform(transform).postScale(eyeDiameter * getUniformScale() / 2.0f + RADIUS_INCREMENT));
|
||||
DependencyManager::get<GeometryCache>()->renderSolidSphereInstance(batch,
|
||||
Transform(transform).postScale(eyeDiameter * getUniformScale() / 2.0f + RADIUS_INCREMENT),
|
||||
glm::vec4(LOOKING_AT_ME_COLOR, alpha));
|
||||
glm::vec4(LOOKING_AT_ME_COLOR, alpha));
|
||||
|
||||
position = getHead()->getRightEyePosition();
|
||||
transform.setTranslation(position);
|
||||
|
@ -494,9 +495,9 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
|
|||
if (eyeDiameter == 0.0f) {
|
||||
eyeDiameter = DEFAULT_EYE_DIAMETER;
|
||||
}
|
||||
batch.setModelTransform(Transform(transform).postScale(eyeDiameter * getUniformScale() / 2.0f + RADIUS_INCREMENT));
|
||||
DependencyManager::get<GeometryCache>()->renderSolidSphereInstance(batch,
|
||||
Transform(transform).postScale(eyeDiameter * getUniformScale() / 2.0f + RADIUS_INCREMENT),
|
||||
glm::vec4(LOOKING_AT_ME_COLOR, alpha));
|
||||
glm::vec4(LOOKING_AT_ME_COLOR, alpha));
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,7 +60,8 @@ void Hand::renderHandTargets(RenderArgs* renderArgs, bool isMine) {
|
|||
transform.setTranslation(position);
|
||||
transform.setRotation(palm.getRotation());
|
||||
transform.postScale(SPHERE_RADIUS);
|
||||
DependencyManager::get<GeometryCache>()->renderSolidSphereInstance(batch, transform, grayColor);
|
||||
batch.setModelTransform(transform);
|
||||
DependencyManager::get<GeometryCache>()->renderSolidSphereInstance(batch, grayColor);
|
||||
|
||||
// draw a green sphere at the old "finger tip"
|
||||
transform = Transform();
|
||||
|
@ -68,7 +69,8 @@ void Hand::renderHandTargets(RenderArgs* renderArgs, bool isMine) {
|
|||
transform.setTranslation(position);
|
||||
transform.setRotation(palm.getRotation());
|
||||
transform.postScale(SPHERE_RADIUS);
|
||||
DependencyManager::get<GeometryCache>()->renderSolidSphereInstance(batch, transform, greenColor);
|
||||
batch.setModelTransform(transform);
|
||||
DependencyManager::get<GeometryCache>()->renderSolidSphereInstance(batch, greenColor);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -467,5 +467,6 @@ void Head::renderLookatTarget(RenderArgs* renderArgs, glm::vec3 lookatPosition)
|
|||
const float LOOK_AT_TARGET_RADIUS = 0.075f;
|
||||
transform.postScale(LOOK_AT_TARGET_RADIUS);
|
||||
const glm::vec4 LOOK_AT_TARGET_COLOR = { 0.8f, 0.0f, 0.0f, 0.75f };
|
||||
geometryCache->renderSolidSphereInstance(batch, transform, LOOK_AT_TARGET_COLOR);
|
||||
batch.setModelTransform(transform);
|
||||
geometryCache->renderSolidSphereInstance(batch, LOOK_AT_TARGET_COLOR);
|
||||
}
|
||||
|
|
|
@ -349,17 +349,15 @@ void SkeletonModel::renderBoundingCollisionShapes(gpu::Batch& batch, float scale
|
|||
// draw a blue sphere at the capsule top point
|
||||
glm::vec3 topPoint = _translation + getRotation() * (scale * (_boundingCapsuleLocalOffset + (0.5f * _boundingCapsuleHeight) * Vectors::UNIT_Y));
|
||||
|
||||
geometryCache->renderSolidSphereInstance(batch,
|
||||
Transform().setTranslation(topPoint).postScale(scale * _boundingCapsuleRadius),
|
||||
glm::vec4(0.6f, 0.6f, 0.8f, alpha));
|
||||
batch.setModelTransform(Transform().setTranslation(topPoint).postScale(scale * _boundingCapsuleRadius));
|
||||
geometryCache->renderSolidSphereInstance(batch, glm::vec4(0.6f, 0.6f, 0.8f, alpha));
|
||||
|
||||
// draw a yellow sphere at the capsule bottom point
|
||||
glm::vec3 bottomPoint = topPoint - glm::vec3(0.0f, scale * _boundingCapsuleHeight, 0.0f);
|
||||
glm::vec3 axis = topPoint - bottomPoint;
|
||||
|
||||
geometryCache->renderSolidSphereInstance(batch,
|
||||
Transform().setTranslation(bottomPoint).postScale(scale * _boundingCapsuleRadius),
|
||||
glm::vec4(0.8f, 0.8f, 0.6f, alpha));
|
||||
batch.setModelTransform(Transform().setTranslation(bottomPoint).postScale(scale * _boundingCapsuleRadius));
|
||||
geometryCache->renderSolidSphereInstance(batch, glm::vec4(0.8f, 0.8f, 0.6f, alpha));
|
||||
|
||||
// draw a green cylinder between the two points
|
||||
glm::vec3 origin(0.0f);
|
||||
|
|
|
@ -60,7 +60,8 @@ void Cube3DOverlay::render(RenderArgs* args) {
|
|||
// }
|
||||
|
||||
transform.setScale(dimensions);
|
||||
DependencyManager::get<GeometryCache>()->renderSolidCubeInstance(*batch, transform, cubeColor);
|
||||
batch->setModelTransform(transform);
|
||||
DependencyManager::get<GeometryCache>()->renderSolidCubeInstance(*batch, cubeColor);
|
||||
} else {
|
||||
|
||||
if (getIsDashedLine()) {
|
||||
|
@ -96,9 +97,9 @@ void Cube3DOverlay::render(RenderArgs* args) {
|
|||
geometryCache->renderDashedLine(*batch, bottomRightFar, topRightFar, cubeColor);
|
||||
|
||||
} else {
|
||||
batch->setModelTransform(Transform());
|
||||
transform.setScale(dimensions);
|
||||
DependencyManager::get<GeometryCache>()->renderWireCubeInstance(*batch, transform, cubeColor);
|
||||
batch->setModelTransform(transform);
|
||||
DependencyManager::get<GeometryCache>()->renderWireCubeInstance(*batch, cubeColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,14 +39,13 @@ void Sphere3DOverlay::render(RenderArgs* args) {
|
|||
auto batch = args->_batch;
|
||||
|
||||
if (batch) {
|
||||
batch->setModelTransform(Transform());
|
||||
|
||||
Transform transform = _transform;
|
||||
transform.postScale(getDimensions() * SPHERE_OVERLAY_SCALE);
|
||||
batch->setModelTransform(transform);
|
||||
if (_isSolid) {
|
||||
DependencyManager::get<GeometryCache>()->renderSolidSphereInstance(*batch, transform, sphereColor);
|
||||
DependencyManager::get<GeometryCache>()->renderSolidSphereInstance(*batch, sphereColor);
|
||||
} else {
|
||||
DependencyManager::get<GeometryCache>()->renderWireSphereInstance(*batch, transform, sphereColor);
|
||||
DependencyManager::get<GeometryCache>()->renderWireSphereInstance(*batch, sphereColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,14 +61,14 @@ void RenderableBoxEntityItem::render(RenderArgs* args) {
|
|||
return;
|
||||
}
|
||||
|
||||
batch.setModelTransform(transToCenter); // we want to include the scale as well
|
||||
if (_procedural->ready()) {
|
||||
batch.setModelTransform(transToCenter); // we want to include the scale as well
|
||||
_procedural->prepare(batch, getPosition(), getDimensions());
|
||||
auto color = _procedural->getColor(cubeColor);
|
||||
batch._glColor4f(color.r, color.g, color.b, color.a);
|
||||
DependencyManager::get<GeometryCache>()->renderCube(batch);
|
||||
} else {
|
||||
DependencyManager::get<GeometryCache>()->renderSolidCubeInstance(batch, transToCenter, cubeColor);
|
||||
DependencyManager::get<GeometryCache>()->renderSolidCubeInstance(batch, cubeColor);
|
||||
}
|
||||
static const auto triCount = DependencyManager::get<GeometryCache>()->getCubeTriangleCount();
|
||||
args->_details._trianglesRendered += (int)triCount;
|
||||
|
|
|
@ -440,8 +440,8 @@ void RenderableModelEntityItem::render(RenderArgs* args) {
|
|||
bool success;
|
||||
auto shapeTransform = getTransformToCenter(success);
|
||||
if (success) {
|
||||
batch.setModelTransform(Transform()); // we want to include the scale as well
|
||||
DependencyManager::get<GeometryCache>()->renderWireCubeInstance(batch, shapeTransform, greenColor);
|
||||
batch.setModelTransform(shapeTransform); // we want to include the scale as well
|
||||
DependencyManager::get<GeometryCache>()->renderWireCubeInstance(batch, greenColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,15 +64,14 @@ void RenderableSphereEntityItem::render(RenderArgs* args) {
|
|||
return;
|
||||
}
|
||||
modelTransform.postScale(SPHERE_ENTITY_SCALE);
|
||||
batch.setModelTransform(modelTransform); // use a transform with scale, rotation, registration point and translation
|
||||
if (_procedural->ready()) {
|
||||
batch.setModelTransform(modelTransform); // use a transform with scale, rotation, registration point and translation
|
||||
_procedural->prepare(batch, getPosition(), getDimensions());
|
||||
auto color = _procedural->getColor(sphereColor);
|
||||
batch._glColor4f(color.r, color.g, color.b, color.a);
|
||||
DependencyManager::get<GeometryCache>()->renderSphere(batch);
|
||||
} else {
|
||||
batch.setModelTransform(Transform());
|
||||
DependencyManager::get<GeometryCache>()->renderSolidSphereInstance(batch, modelTransform, sphereColor);
|
||||
DependencyManager::get<GeometryCache>()->renderSolidSphereInstance(batch, sphereColor);
|
||||
}
|
||||
static const auto triCount = DependencyManager::get<GeometryCache>()->getSphereTriangleCount();
|
||||
args->_details._trianglesRendered += (int)triCount;
|
||||
|
|
|
@ -132,7 +132,6 @@ void RenderableZoneEntityItem::render(RenderArgs* args) {
|
|||
|
||||
Q_ASSERT(args->_batch);
|
||||
gpu::Batch& batch = *args->_batch;
|
||||
batch.setModelTransform(Transform());
|
||||
|
||||
bool success;
|
||||
auto shapeTransform = getTransformToCenter(success);
|
||||
|
@ -142,9 +141,11 @@ void RenderableZoneEntityItem::render(RenderArgs* args) {
|
|||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
if (getShapeType() == SHAPE_TYPE_SPHERE) {
|
||||
shapeTransform.postScale(SPHERE_ENTITY_SCALE);
|
||||
geometryCache->renderWireSphereInstance(batch, shapeTransform, DEFAULT_COLOR);
|
||||
batch.setModelTransform(shapeTransform);
|
||||
geometryCache->renderWireSphereInstance(batch, DEFAULT_COLOR);
|
||||
} else {
|
||||
geometryCache->renderWireCubeInstance(batch, shapeTransform, DEFAULT_COLOR);
|
||||
batch.setModelTransform(shapeTransform);
|
||||
geometryCache->renderWireCubeInstance(batch, DEFAULT_COLOR);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef gpu__GPUConfig__
|
||||
#define gpu__GPUConfig__
|
||||
#ifndef hifi_gpu_GPUConfig_h
|
||||
#define hifi_gpu_GPUConfig_h
|
||||
|
||||
|
||||
#define GL_GLEXT_PROTOTYPES 1
|
||||
|
@ -38,8 +38,6 @@
|
|||
#define GPU_FEATURE_PROFILE GPU_CORE
|
||||
#define GPU_INPUT_PROFILE GPU_CORE_43
|
||||
|
||||
#elif defined(ANDROID)
|
||||
|
||||
#else
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
@ -50,4 +48,11 @@
|
|||
#endif
|
||||
|
||||
|
||||
#if (GPU_INPUT_PROFILE == GPU_CORE_43)
|
||||
// Deactivate SSBO for now, we've run into some issues
|
||||
// on GL 4.3 capable GPUs not behaving as expected
|
||||
//#define GPU_SSBO_DRAW_CALL_INFO
|
||||
#endif
|
||||
|
||||
|
||||
#endif // hifi_gpu_GPUConfig_h
|
||||
|
|
|
@ -29,20 +29,6 @@ ProfileRangeBatch::~ProfileRangeBatch() {
|
|||
|
||||
using namespace gpu;
|
||||
|
||||
Batch::Batch() :
|
||||
_commands(),
|
||||
_commandOffsets(),
|
||||
_params(),
|
||||
_data(),
|
||||
_buffers(),
|
||||
_textures(),
|
||||
_streamFormats(),
|
||||
_transforms(),
|
||||
_pipelines(),
|
||||
_framebuffers()
|
||||
{
|
||||
}
|
||||
|
||||
Batch::Batch(const CacheState& cacheState) : Batch() {
|
||||
_commands.reserve(cacheState.commandsSize);
|
||||
_commandOffsets.reserve(cacheState.offsetsSize);
|
||||
|
@ -88,6 +74,8 @@ void Batch::draw(Primitive primitiveType, uint32 numVertices, uint32 startVertex
|
|||
_params.push_back(startVertex);
|
||||
_params.push_back(numVertices);
|
||||
_params.push_back(primitiveType);
|
||||
|
||||
captureDrawCallInfo();
|
||||
}
|
||||
|
||||
void Batch::drawIndexed(Primitive primitiveType, uint32 numIndices, uint32 startIndex) {
|
||||
|
@ -96,6 +84,8 @@ void Batch::drawIndexed(Primitive primitiveType, uint32 numIndices, uint32 start
|
|||
_params.push_back(startIndex);
|
||||
_params.push_back(numIndices);
|
||||
_params.push_back(primitiveType);
|
||||
|
||||
captureDrawCallInfo();
|
||||
}
|
||||
|
||||
void Batch::drawInstanced(uint32 numInstances, Primitive primitiveType, uint32 numVertices, uint32 startVertex, uint32 startInstance) {
|
||||
|
@ -106,6 +96,8 @@ void Batch::drawInstanced(uint32 numInstances, Primitive primitiveType, uint32 n
|
|||
_params.push_back(numVertices);
|
||||
_params.push_back(primitiveType);
|
||||
_params.push_back(numInstances);
|
||||
|
||||
captureDrawCallInfo();
|
||||
}
|
||||
|
||||
void Batch::drawIndexedInstanced(uint32 numInstances, Primitive primitiveType, uint32 numIndices, uint32 startIndex, uint32 startInstance) {
|
||||
|
@ -116,6 +108,8 @@ void Batch::drawIndexedInstanced(uint32 numInstances, Primitive primitiveType, u
|
|||
_params.push_back(numIndices);
|
||||
_params.push_back(primitiveType);
|
||||
_params.push_back(numInstances);
|
||||
|
||||
captureDrawCallInfo();
|
||||
}
|
||||
|
||||
|
||||
|
@ -123,12 +117,16 @@ void Batch::multiDrawIndirect(uint32 numCommands, Primitive primitiveType) {
|
|||
ADD_COMMAND(multiDrawIndirect);
|
||||
_params.push_back(numCommands);
|
||||
_params.push_back(primitiveType);
|
||||
|
||||
captureDrawCallInfo();
|
||||
}
|
||||
|
||||
void Batch::multiDrawIndexedIndirect(uint32 nbCommands, Primitive primitiveType) {
|
||||
ADD_COMMAND(multiDrawIndexedIndirect);
|
||||
_params.push_back(nbCommands);
|
||||
_params.push_back(primitiveType);
|
||||
|
||||
captureDrawCallInfo();
|
||||
}
|
||||
|
||||
void Batch::setInputFormat(const Stream::FormatPointer& format) {
|
||||
|
@ -185,7 +183,8 @@ void Batch::setIndirectBuffer(const BufferPointer& buffer, Offset offset, Offset
|
|||
void Batch::setModelTransform(const Transform& model) {
|
||||
ADD_COMMAND(setModelTransform);
|
||||
|
||||
_params.push_back(_transforms.cache(model));
|
||||
_currentModel = model;
|
||||
_invalidModel = true;
|
||||
}
|
||||
|
||||
void Batch::setViewTransform(const Transform& view) {
|
||||
|
@ -344,6 +343,19 @@ void Batch::runLambda(std::function<void()> f) {
|
|||
_params.push_back(_lambdas.cache(f));
|
||||
}
|
||||
|
||||
void Batch::startNamedCall(const std::string& name) {
|
||||
ADD_COMMAND(startNamedCall);
|
||||
_params.push_back(_names.cache(name));
|
||||
|
||||
_currentNamedCall = name;
|
||||
}
|
||||
|
||||
void Batch::stopNamedCall() {
|
||||
ADD_COMMAND(stopNamedCall);
|
||||
|
||||
_currentNamedCall.clear();
|
||||
}
|
||||
|
||||
void Batch::enableStereo(bool enable) {
|
||||
_enableStereo = enable;
|
||||
}
|
||||
|
@ -360,14 +372,13 @@ bool Batch::isSkyboxEnabled() const {
|
|||
return _enableSkybox;
|
||||
}
|
||||
|
||||
void Batch::setupNamedCalls(const std::string& instanceName, size_t count, NamedBatchData::Function function) {
|
||||
NamedBatchData& instance = _namedData[instanceName];
|
||||
instance.count += count;
|
||||
instance.function = function;
|
||||
}
|
||||
|
||||
void Batch::setupNamedCalls(const std::string& instanceName, NamedBatchData::Function function) {
|
||||
setupNamedCalls(instanceName, 1, function);
|
||||
NamedBatchData& instance = _namedData[instanceName];
|
||||
if (!instance.function) {
|
||||
instance.function = function;
|
||||
}
|
||||
|
||||
captureNamedDrawCallInfo(instanceName);
|
||||
}
|
||||
|
||||
BufferPointer Batch::getNamedBuffer(const std::string& instanceName, uint8_t index) {
|
||||
|
@ -376,16 +387,74 @@ BufferPointer Batch::getNamedBuffer(const std::string& instanceName, uint8_t ind
|
|||
instance.buffers.resize(index + 1);
|
||||
}
|
||||
if (!instance.buffers[index]) {
|
||||
instance.buffers[index].reset(new Buffer());
|
||||
instance.buffers[index] = std::make_shared<Buffer>();
|
||||
}
|
||||
return instance.buffers[index];
|
||||
}
|
||||
|
||||
Batch::DrawCallInfoBuffer& Batch::getDrawCallInfoBuffer() {
|
||||
if (_currentNamedCall.empty()) {
|
||||
return _drawCallInfos;
|
||||
} else {
|
||||
return _namedData[_currentNamedCall].drawCallInfos;
|
||||
}
|
||||
}
|
||||
|
||||
const Batch::DrawCallInfoBuffer& Batch::getDrawCallInfoBuffer() const {
|
||||
if (_currentNamedCall.empty()) {
|
||||
return _drawCallInfos;
|
||||
} else {
|
||||
auto it = _namedData.find(_currentNamedCall);
|
||||
Q_ASSERT_X(it != _namedData.end(), Q_FUNC_INFO, (_currentNamedCall + " not in _namedData.").data());
|
||||
return it->second.drawCallInfos;
|
||||
}
|
||||
}
|
||||
|
||||
void Batch::captureDrawCallInfoImpl() {
|
||||
if (_invalidModel) {
|
||||
TransformObject object;
|
||||
_currentModel.getMatrix(object._model);
|
||||
|
||||
// FIXME - we don't want to be using glm::inverse() here but it fixes the flickering issue we are
|
||||
// seeing with planky blocks in toybox. Our implementation of getInverseMatrix() is buggy in cases
|
||||
// of non-uniform scale. We need to fix that. In the mean time, glm::inverse() works.
|
||||
//_model.getInverseMatrix(_object._modelInverse);
|
||||
object._modelInverse = glm::inverse(object._model);
|
||||
|
||||
_objects.push_back(object);
|
||||
|
||||
// Flag is clean
|
||||
_invalidModel = false;
|
||||
}
|
||||
|
||||
auto& drawCallInfos = getDrawCallInfoBuffer();
|
||||
drawCallInfos.push_back((uint16)_objects.size() - 1);
|
||||
}
|
||||
|
||||
void Batch::captureDrawCallInfo() {
|
||||
if (!_currentNamedCall.empty()) {
|
||||
// If we are processing a named call, we don't want to register the raw draw calls
|
||||
return;
|
||||
}
|
||||
|
||||
captureDrawCallInfoImpl();
|
||||
}
|
||||
|
||||
void Batch::captureNamedDrawCallInfo(std::string name) {
|
||||
std::swap(_currentNamedCall, name); // Set and save _currentNamedCall
|
||||
captureDrawCallInfoImpl();
|
||||
std::swap(_currentNamedCall, name); // Restore _currentNamedCall
|
||||
}
|
||||
|
||||
void Batch::preExecute() {
|
||||
for (auto& mapItem : _namedData) {
|
||||
mapItem.second.process(*this);
|
||||
auto& name = mapItem.first;
|
||||
auto& instance = mapItem.second;
|
||||
|
||||
startNamedCall(name);
|
||||
instance.process(*this);
|
||||
stopNamedCall();
|
||||
}
|
||||
_namedData.clear();
|
||||
}
|
||||
|
||||
QDebug& operator<<(QDebug& debug, const Batch::CacheState& cacheState) {
|
||||
|
|
|
@ -29,7 +29,12 @@ class QDebug;
|
|||
namespace gpu {
|
||||
|
||||
enum ReservedSlot {
|
||||
|
||||
#ifdef GPU_SSBO_DRAW_CALL_INFO
|
||||
TRANSFORM_OBJECT_SLOT = 6,
|
||||
#else
|
||||
TRANSFORM_OBJECT_SLOT = 31,
|
||||
#endif
|
||||
TRANSFORM_CAMERA_SLOT = 7,
|
||||
};
|
||||
|
||||
|
@ -45,13 +50,31 @@ class Batch {
|
|||
public:
|
||||
typedef Stream::Slot Slot;
|
||||
|
||||
|
||||
class DrawCallInfo {
|
||||
public:
|
||||
using Index = uint16_t;
|
||||
|
||||
DrawCallInfo(Index idx) : index(idx) {}
|
||||
|
||||
Index index { 0 };
|
||||
uint16_t unused { 0 }; // Reserved space for later
|
||||
|
||||
};
|
||||
// Make sure DrawCallInfo has no extra padding
|
||||
static_assert(sizeof(DrawCallInfo) == 4, "DrawCallInfo size is incorrect.");
|
||||
|
||||
using DrawCallInfoBuffer = std::vector<DrawCallInfo>;
|
||||
|
||||
struct NamedBatchData {
|
||||
using BufferPointers = std::vector<BufferPointer>;
|
||||
using Function = std::function<void(gpu::Batch&, NamedBatchData&)>;
|
||||
|
||||
BufferPointers buffers;
|
||||
size_t count { 0 };
|
||||
Function function;
|
||||
DrawCallInfoBuffer drawCallInfos;
|
||||
|
||||
size_t count() const { return drawCallInfos.size(); }
|
||||
|
||||
void process(Batch& batch) {
|
||||
if (function) {
|
||||
|
@ -62,6 +85,16 @@ public:
|
|||
|
||||
using NamedBatchDataMap = std::map<std::string, NamedBatchData>;
|
||||
|
||||
DrawCallInfoBuffer _drawCallInfos;
|
||||
|
||||
std::string _currentNamedCall;
|
||||
|
||||
const DrawCallInfoBuffer& getDrawCallInfoBuffer() const;
|
||||
DrawCallInfoBuffer& getDrawCallInfoBuffer();
|
||||
|
||||
void captureDrawCallInfo();
|
||||
void captureNamedDrawCallInfo(std::string name);
|
||||
|
||||
class CacheState {
|
||||
public:
|
||||
size_t commandsSize;
|
||||
|
@ -88,7 +121,7 @@ public:
|
|||
transformsSize(transformsSize), pipelinesSize(pipelinesSize), framebuffersSize(framebuffersSize), queriesSize(queriesSize) { }
|
||||
};
|
||||
|
||||
Batch();
|
||||
Batch() {}
|
||||
Batch(const CacheState& cacheState);
|
||||
explicit Batch(const Batch& batch);
|
||||
~Batch();
|
||||
|
@ -121,13 +154,8 @@ public:
|
|||
void multiDrawIndirect(uint32 numCommands, Primitive primitiveType);
|
||||
void multiDrawIndexedIndirect(uint32 numCommands, Primitive primitiveType);
|
||||
|
||||
|
||||
void setupNamedCalls(const std::string& instanceName, size_t count, NamedBatchData::Function function);
|
||||
void setupNamedCalls(const std::string& instanceName, NamedBatchData::Function function);
|
||||
BufferPointer getNamedBuffer(const std::string& instanceName, uint8_t index = 0);
|
||||
void setNamedBuffer(const std::string& instanceName, BufferPointer& buffer, uint8_t index = 0);
|
||||
|
||||
|
||||
|
||||
// Input Stage
|
||||
// InputFormat
|
||||
|
@ -304,6 +332,9 @@ public:
|
|||
|
||||
COMMAND_runLambda,
|
||||
|
||||
COMMAND_startNamedCall,
|
||||
COMMAND_stopNamedCall,
|
||||
|
||||
// TODO: As long as we have gl calls explicitely issued from interface
|
||||
// code, we need to be able to record and batch these calls. THe long
|
||||
// term strategy is to get rid of any GL calls in favor of the HIFI GPU API
|
||||
|
@ -395,7 +426,7 @@ public:
|
|||
typedef Cache<PipelinePointer>::Vector PipelineCaches;
|
||||
typedef Cache<FramebufferPointer>::Vector FramebufferCaches;
|
||||
typedef Cache<QueryPointer>::Vector QueryCaches;
|
||||
typedef Cache<std::string>::Vector ProfileRangeCaches;
|
||||
typedef Cache<std::string>::Vector StringCaches;
|
||||
typedef Cache<std::function<void()>>::Vector LambdaCache;
|
||||
|
||||
// Cache Data in a byte array if too big to fit in Param
|
||||
|
@ -415,6 +446,18 @@ public:
|
|||
Params _params;
|
||||
Bytes _data;
|
||||
|
||||
// SSBO class... layout MUST match the layout in TransformCamera.slh
|
||||
class TransformObject {
|
||||
public:
|
||||
Mat4 _model;
|
||||
Mat4 _modelInverse;
|
||||
};
|
||||
|
||||
using TransformObjects = std::vector<TransformObject>;
|
||||
bool _invalidModel { true };
|
||||
Transform _currentModel;
|
||||
TransformObjects _objects;
|
||||
|
||||
BufferCaches _buffers;
|
||||
TextureCaches _textures;
|
||||
StreamFormatCaches _streamFormats;
|
||||
|
@ -423,7 +466,8 @@ public:
|
|||
FramebufferCaches _framebuffers;
|
||||
QueryCaches _queries;
|
||||
LambdaCache _lambdas;
|
||||
ProfileRangeCaches _profileRanges;
|
||||
StringCaches _profileRanges;
|
||||
StringCaches _names;
|
||||
|
||||
NamedBatchDataMap _namedData;
|
||||
|
||||
|
@ -431,8 +475,13 @@ public:
|
|||
bool _enableSkybox{ false };
|
||||
|
||||
protected:
|
||||
void startNamedCall(const std::string& name);
|
||||
void stopNamedCall();
|
||||
|
||||
// Maybe useful but shoudln't be public. Please convince me otherwise
|
||||
void runLambda(std::function<void()> f);
|
||||
|
||||
void captureDrawCallInfoImpl();
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -76,13 +76,6 @@ public:
|
|||
virtual void syncCache() = 0;
|
||||
virtual void downloadFramebuffer(const FramebufferPointer& srcFramebuffer, const Vec4i& region, QImage& destImage) = 0;
|
||||
|
||||
// UBO class... layout MUST match the layout in TransformCamera.slh
|
||||
class TransformObject {
|
||||
public:
|
||||
Mat4 _model;
|
||||
Mat4 _modelInverse;
|
||||
};
|
||||
|
||||
// UBO class... layout MUST match the layout in TransformCamera.slh
|
||||
class TransformCamera {
|
||||
public:
|
||||
|
|
|
@ -62,6 +62,9 @@ GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] =
|
|||
|
||||
(&::gpu::GLBackend::do_runLambda),
|
||||
|
||||
(&::gpu::GLBackend::do_startNamedCall),
|
||||
(&::gpu::GLBackend::do_stopNamedCall),
|
||||
|
||||
(&::gpu::GLBackend::do_glActiveBindTexture),
|
||||
|
||||
(&::gpu::GLBackend::do_glUniform1i),
|
||||
|
@ -121,11 +124,7 @@ Backend* GLBackend::createBackend() {
|
|||
return new GLBackend();
|
||||
}
|
||||
|
||||
GLBackend::GLBackend() :
|
||||
_input(),
|
||||
_pipeline(),
|
||||
_output()
|
||||
{
|
||||
GLBackend::GLBackend() {
|
||||
glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &_uboAlignment);
|
||||
initInput();
|
||||
initTransform();
|
||||
|
@ -155,31 +154,30 @@ void GLBackend::renderPassTransfer(Batch& batch) {
|
|||
|
||||
{ // Sync all the buffers
|
||||
PROFILE_RANGE("syncCPUTransform");
|
||||
_transform._cameras.resize(0);
|
||||
_transform._cameras.clear();
|
||||
_transform._cameraOffsets.clear();
|
||||
_transform._objects.resize(0);
|
||||
_transform._objectOffsets.clear();
|
||||
|
||||
for (_commandIndex = 0; _commandIndex < numCommands; ++_commandIndex) {
|
||||
switch (*command) {
|
||||
case Batch::COMMAND_draw:
|
||||
case Batch::COMMAND_drawIndexed:
|
||||
case Batch::COMMAND_drawInstanced:
|
||||
case Batch::COMMAND_drawIndexedInstanced:
|
||||
_transform.preUpdate(_commandIndex, _stereo);
|
||||
break;
|
||||
case Batch::COMMAND_draw:
|
||||
case Batch::COMMAND_drawIndexed:
|
||||
case Batch::COMMAND_drawInstanced:
|
||||
case Batch::COMMAND_drawIndexedInstanced:
|
||||
case Batch::COMMAND_multiDrawIndirect:
|
||||
case Batch::COMMAND_multiDrawIndexedIndirect:
|
||||
_transform.preUpdate(_commandIndex, _stereo);
|
||||
break;
|
||||
|
||||
case Batch::COMMAND_setModelTransform:
|
||||
case Batch::COMMAND_setViewportTransform:
|
||||
case Batch::COMMAND_setViewTransform:
|
||||
case Batch::COMMAND_setProjectionTransform: {
|
||||
CommandCall call = _commandCalls[(*command)];
|
||||
(this->*(call))(batch, *offset);
|
||||
break;
|
||||
}
|
||||
case Batch::COMMAND_setViewportTransform:
|
||||
case Batch::COMMAND_setViewTransform:
|
||||
case Batch::COMMAND_setProjectionTransform: {
|
||||
CommandCall call = _commandCalls[(*command)];
|
||||
(this->*(call))(batch, *offset);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
command++;
|
||||
offset++;
|
||||
|
@ -188,12 +186,14 @@ void GLBackend::renderPassTransfer(Batch& batch) {
|
|||
|
||||
{ // Sync the transform buffers
|
||||
PROFILE_RANGE("syncGPUTransform");
|
||||
_transform.transfer();
|
||||
_transform.transfer(batch);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
void GLBackend::renderPassDraw(Batch& batch) {
|
||||
_transform._objectsItr = _transform._objectOffsets.begin();
|
||||
_currentDraw = -1;
|
||||
_transform._camerasItr = _transform._cameraOffsets.begin();
|
||||
const size_t numCommands = batch.getCommands().size();
|
||||
const Batch::Commands::value_type* command = batch.getCommands().data();
|
||||
|
@ -209,6 +209,22 @@ void GLBackend::renderPassDraw(Batch& batch) {
|
|||
case Batch::COMMAND_setProjectionTransform:
|
||||
break;
|
||||
|
||||
case Batch::COMMAND_draw:
|
||||
case Batch::COMMAND_drawIndexed:
|
||||
case Batch::COMMAND_drawInstanced:
|
||||
case Batch::COMMAND_drawIndexedInstanced:
|
||||
case Batch::COMMAND_multiDrawIndirect:
|
||||
case Batch::COMMAND_multiDrawIndexedIndirect: {
|
||||
// updates for draw calls
|
||||
++_currentDraw;
|
||||
updateInput();
|
||||
updateTransform(batch);
|
||||
updatePipeline();
|
||||
|
||||
CommandCall call = _commandCalls[(*command)];
|
||||
(this->*(call))(batch, *offset);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
CommandCall call = _commandCalls[(*command)];
|
||||
(this->*(call))(batch, *offset);
|
||||
|
@ -306,10 +322,6 @@ void GLBackend::syncCache() {
|
|||
}
|
||||
|
||||
void GLBackend::do_draw(Batch& batch, size_t paramOffset) {
|
||||
updateInput();
|
||||
updateTransform();
|
||||
updatePipeline();
|
||||
|
||||
Primitive primitiveType = (Primitive)batch._params[paramOffset + 2]._uint;
|
||||
GLenum mode = _primitiveToGLmode[primitiveType];
|
||||
uint32 numVertices = batch._params[paramOffset + 1]._uint;
|
||||
|
@ -319,10 +331,6 @@ void GLBackend::do_draw(Batch& batch, size_t paramOffset) {
|
|||
}
|
||||
|
||||
void GLBackend::do_drawIndexed(Batch& batch, size_t paramOffset) {
|
||||
updateInput();
|
||||
updateTransform();
|
||||
updatePipeline();
|
||||
|
||||
Primitive primitiveType = (Primitive)batch._params[paramOffset + 2]._uint;
|
||||
GLenum mode = _primitiveToGLmode[primitiveType];
|
||||
uint32 numIndices = batch._params[paramOffset + 1]._uint;
|
||||
|
@ -338,10 +346,6 @@ void GLBackend::do_drawIndexed(Batch& batch, size_t paramOffset) {
|
|||
}
|
||||
|
||||
void GLBackend::do_drawInstanced(Batch& batch, size_t paramOffset) {
|
||||
updateInput();
|
||||
updateTransform();
|
||||
updatePipeline();
|
||||
|
||||
GLint numInstances = batch._params[paramOffset + 4]._uint;
|
||||
Primitive primitiveType = (Primitive)batch._params[paramOffset + 3]._uint;
|
||||
GLenum mode = _primitiveToGLmode[primitiveType];
|
||||
|
@ -353,10 +357,6 @@ void GLBackend::do_drawInstanced(Batch& batch, size_t paramOffset) {
|
|||
}
|
||||
|
||||
void GLBackend::do_drawIndexedInstanced(Batch& batch, size_t paramOffset) {
|
||||
updateInput();
|
||||
updateTransform();
|
||||
updatePipeline();
|
||||
|
||||
GLint numInstances = batch._params[paramOffset + 4]._uint;
|
||||
GLenum mode = _primitiveToGLmode[(Primitive)batch._params[paramOffset + 3]._uint];
|
||||
uint32 numIndices = batch._params[paramOffset + 2]._uint;
|
||||
|
@ -381,10 +381,6 @@ void GLBackend::do_drawIndexedInstanced(Batch& batch, size_t paramOffset) {
|
|||
|
||||
void GLBackend::do_multiDrawIndirect(Batch& batch, size_t paramOffset) {
|
||||
#if (GPU_INPUT_PROFILE == GPU_CORE_43)
|
||||
updateInput();
|
||||
updateTransform();
|
||||
updatePipeline();
|
||||
|
||||
uint commandCount = batch._params[paramOffset + 0]._uint;
|
||||
GLenum mode = _primitiveToGLmode[(Primitive)batch._params[paramOffset + 1]._uint];
|
||||
|
||||
|
@ -398,10 +394,6 @@ void GLBackend::do_multiDrawIndirect(Batch& batch, size_t paramOffset) {
|
|||
|
||||
void GLBackend::do_multiDrawIndexedIndirect(Batch& batch, size_t paramOffset) {
|
||||
#if (GPU_INPUT_PROFILE == GPU_CORE_43)
|
||||
updateInput();
|
||||
updateTransform();
|
||||
updatePipeline();
|
||||
|
||||
uint commandCount = batch._params[paramOffset + 0]._uint;
|
||||
GLenum mode = _primitiveToGLmode[(Primitive)batch._params[paramOffset + 1]._uint];
|
||||
GLenum indexType = _elementTypeToGLType[_input._indexBufferType];
|
||||
|
@ -423,6 +415,15 @@ void GLBackend::do_runLambda(Batch& batch, size_t paramOffset) {
|
|||
f();
|
||||
}
|
||||
|
||||
void GLBackend::do_startNamedCall(Batch& batch, size_t paramOffset) {
|
||||
batch._currentNamedCall = batch._names.get(batch._params[paramOffset]._uint);
|
||||
_currentDraw = -1;
|
||||
}
|
||||
|
||||
void GLBackend::do_stopNamedCall(Batch& batch, size_t paramOffset) {
|
||||
batch._currentNamedCall.clear();
|
||||
}
|
||||
|
||||
void GLBackend::resetStages() {
|
||||
resetInputStage();
|
||||
resetPipelineStage();
|
||||
|
|
|
@ -320,42 +320,38 @@ protected:
|
|||
void killTransform();
|
||||
// Synchronize the state cache of this Backend with the actual real state of the GL Context
|
||||
void syncTransformStateCache();
|
||||
void updateTransform() const;
|
||||
void updateTransform(const Batch& batch);
|
||||
void resetTransformStage();
|
||||
|
||||
struct TransformStageState {
|
||||
using TransformObjects = std::vector<TransformObject>;
|
||||
using TransformCameras = std::vector<TransformCamera>;
|
||||
|
||||
TransformObject _object;
|
||||
TransformCamera _camera;
|
||||
TransformObjects _objects;
|
||||
TransformCameras _cameras;
|
||||
|
||||
size_t _cameraUboSize{ 0 };
|
||||
size_t _objectUboSize{ 0 };
|
||||
GLuint _objectBuffer{ 0 };
|
||||
GLuint _cameraBuffer{ 0 };
|
||||
Transform _model;
|
||||
mutable std::map<std::string, GLvoid*> _drawCallInfoOffsets;
|
||||
|
||||
GLuint _objectBuffer { 0 };
|
||||
GLuint _cameraBuffer { 0 };
|
||||
GLuint _drawCallInfoBuffer { 0 };
|
||||
GLuint _objectBufferTexture { 0 };
|
||||
size_t _cameraUboSize { 0 };
|
||||
Transform _view;
|
||||
Mat4 _projection;
|
||||
Vec4i _viewport{ 0, 0, 1, 1 };
|
||||
Vec2 _depthRange{ 0.0f, 1.0f };
|
||||
bool _invalidModel{true};
|
||||
bool _invalidView{false};
|
||||
bool _invalidProj{false};
|
||||
bool _invalidViewport{ false };
|
||||
Vec4i _viewport { 0, 0, 1, 1 };
|
||||
Vec2 _depthRange { 0.0f, 1.0f };
|
||||
bool _invalidView { false };
|
||||
bool _invalidProj { false };
|
||||
bool _invalidViewport { false };
|
||||
|
||||
using Pair = std::pair<size_t, size_t>;
|
||||
using List = std::list<Pair>;
|
||||
List _cameraOffsets;
|
||||
List _objectOffsets;
|
||||
mutable List::const_iterator _objectsItr;
|
||||
mutable List::const_iterator _camerasItr;
|
||||
|
||||
void preUpdate(size_t commandIndex, const StereoState& stereo);
|
||||
void update(size_t commandIndex, const StereoState& stereo) const;
|
||||
void transfer() const;
|
||||
void transfer(const Batch& batch) const;
|
||||
} _transform;
|
||||
|
||||
int32_t _uboAlignment{ 0 };
|
||||
|
@ -465,6 +461,9 @@ protected:
|
|||
|
||||
void do_runLambda(Batch& batch, size_t paramOffset);
|
||||
|
||||
void do_startNamedCall(Batch& batch, size_t paramOffset);
|
||||
void do_stopNamedCall(Batch& batch, size_t paramOffset);
|
||||
|
||||
void resetStages();
|
||||
|
||||
// TODO: As long as we have gl calls explicitely issued from interface
|
||||
|
@ -486,6 +485,8 @@ protected:
|
|||
|
||||
void do_pushProfileRange(Batch& batch, size_t paramOffset);
|
||||
void do_popProfileRange(Batch& batch, size_t paramOffset);
|
||||
|
||||
int _currentDraw { -1 };
|
||||
|
||||
typedef void (GLBackend::*CommandCall)(Batch&, size_t);
|
||||
static CommandCall _commandCalls[Batch::NUM_COMMANDS];
|
||||
|
|
|
@ -75,9 +75,9 @@ void makeBindings(GLBackend::GLShader* shader) {
|
|||
glBindAttribLocation(glprogram, gpu::Stream::SKIN_CLUSTER_WEIGHT, "inSkinClusterWeight");
|
||||
}
|
||||
|
||||
loc = glGetAttribLocation(glprogram, "inInstanceTransform");
|
||||
if (loc >= 0 && loc != gpu::Stream::INSTANCE_XFM) {
|
||||
glBindAttribLocation(glprogram, gpu::Stream::INSTANCE_XFM, "inInstanceTransform");
|
||||
loc = glGetAttribLocation(glprogram, "_drawCallInfo");
|
||||
if (loc >= 0 && loc != gpu::Stream::DRAW_CALL_INFO) {
|
||||
glBindAttribLocation(glprogram, gpu::Stream::DRAW_CALL_INFO, "_drawCallInfo");
|
||||
}
|
||||
|
||||
// Link again to take into account the assigned attrib location
|
||||
|
@ -92,17 +92,27 @@ void makeBindings(GLBackend::GLShader* shader) {
|
|||
// now assign the ubo binding, then DON't relink!
|
||||
|
||||
//Check for gpu specific uniform slotBindings
|
||||
loc = glGetUniformBlockIndex(glprogram, "transformObjectBuffer");
|
||||
#ifdef GPU_SSBO_DRAW_CALL_INFO
|
||||
loc = glGetProgramResourceIndex(glprogram, GL_SHADER_STORAGE_BLOCK, "transformObjectBuffer");
|
||||
if (loc >= 0) {
|
||||
glUniformBlockBinding(glprogram, loc, gpu::TRANSFORM_OBJECT_SLOT);
|
||||
glShaderStorageBlockBinding(glprogram, loc, gpu::TRANSFORM_OBJECT_SLOT);
|
||||
shader->_transformObjectSlot = gpu::TRANSFORM_OBJECT_SLOT;
|
||||
}
|
||||
#else
|
||||
loc = glGetUniformLocation(glprogram, "transformObjectBuffer");
|
||||
if (loc >= 0) {
|
||||
glProgramUniform1i(glprogram, loc, gpu::TRANSFORM_OBJECT_SLOT);
|
||||
shader->_transformObjectSlot = gpu::TRANSFORM_OBJECT_SLOT;
|
||||
}
|
||||
#endif
|
||||
|
||||
loc = glGetUniformBlockIndex(glprogram, "transformCameraBuffer");
|
||||
if (loc >= 0) {
|
||||
glUniformBlockBinding(glprogram, loc, gpu::TRANSFORM_CAMERA_SLOT);
|
||||
shader->_transformCameraSlot = gpu::TRANSFORM_CAMERA_SLOT;
|
||||
}
|
||||
|
||||
(void)CHECK_GL_ERROR();
|
||||
}
|
||||
|
||||
GLBackend::GLShader* compileShader(const Shader& shader) {
|
||||
|
|
|
@ -16,8 +16,6 @@ using namespace gpu;
|
|||
|
||||
// Transform Stage
|
||||
void GLBackend::do_setModelTransform(Batch& batch, size_t paramOffset) {
|
||||
_transform._model = batch._transforms.get(batch._params[paramOffset]._uint);
|
||||
_transform._invalidModel = true;
|
||||
}
|
||||
|
||||
void GLBackend::do_setViewTransform(Batch& batch, size_t paramOffset) {
|
||||
|
@ -63,26 +61,29 @@ void GLBackend::do_setDepthRangeTransform(Batch& batch, size_t paramOffset) {
|
|||
void GLBackend::initTransform() {
|
||||
glGenBuffers(1, &_transform._objectBuffer);
|
||||
glGenBuffers(1, &_transform._cameraBuffer);
|
||||
glGenBuffers(1, &_transform._drawCallInfoBuffer);
|
||||
#ifndef GPU_SSBO_DRAW_CALL_INFO
|
||||
glGenTextures(1, &_transform._objectBufferTexture);
|
||||
#endif
|
||||
size_t cameraSize = sizeof(TransformCamera);
|
||||
while (_transform._cameraUboSize < cameraSize) {
|
||||
_transform._cameraUboSize += _uboAlignment;
|
||||
}
|
||||
size_t objectSize = sizeof(TransformObject);
|
||||
while (_transform._objectUboSize < objectSize) {
|
||||
_transform._objectUboSize += _uboAlignment;
|
||||
}
|
||||
}
|
||||
|
||||
void GLBackend::killTransform() {
|
||||
glDeleteBuffers(1, &_transform._objectBuffer);
|
||||
glDeleteBuffers(1, &_transform._cameraBuffer);
|
||||
glDeleteBuffers(1, &_transform._drawCallInfoBuffer);
|
||||
#ifndef GPU_SSBO_DRAW_CALL_INFO
|
||||
glDeleteTextures(1, &_transform._objectBufferTexture);
|
||||
#endif
|
||||
}
|
||||
|
||||
void GLBackend::syncTransformStateCache() {
|
||||
_transform._invalidViewport = true;
|
||||
_transform._invalidProj = true;
|
||||
_transform._invalidView = true;
|
||||
_transform._invalidModel = true;
|
||||
|
||||
glGetIntegerv(GL_VIEWPORT, (GLint*) &_transform._viewport);
|
||||
|
||||
|
@ -91,7 +92,6 @@ void GLBackend::syncTransformStateCache() {
|
|||
Mat4 modelView;
|
||||
auto modelViewInv = glm::inverse(modelView);
|
||||
_transform._view.evalFromRawMatrix(modelViewInv);
|
||||
_transform._model.setIdentity();
|
||||
}
|
||||
|
||||
void GLBackend::TransformStageState::preUpdate(size_t commandIndex, const StereoState& stereo) {
|
||||
|
@ -108,16 +108,6 @@ void GLBackend::TransformStageState::preUpdate(size_t commandIndex, const Stereo
|
|||
_view.getInverseMatrix(_camera._view);
|
||||
}
|
||||
|
||||
if (_invalidModel) {
|
||||
_model.getMatrix(_object._model);
|
||||
|
||||
// FIXME - we don't want to be using glm::inverse() here but it fixes the flickering issue we are
|
||||
// seeing with planky blocks in toybox. Our implementation of getInverseMatrix() is buggy in cases
|
||||
// of non-uniform scale. We need to fix that. In the mean time, glm::inverse() works.
|
||||
//_model.getInverseMatrix(_object._modelInverse);
|
||||
_object._modelInverse = glm::inverse(_object._model);
|
||||
}
|
||||
|
||||
if (_invalidView || _invalidProj || _invalidViewport) {
|
||||
size_t offset = _cameraUboSize * _cameras.size();
|
||||
if (stereo._enable) {
|
||||
|
@ -131,56 +121,68 @@ void GLBackend::TransformStageState::preUpdate(size_t commandIndex, const Stereo
|
|||
}
|
||||
}
|
||||
|
||||
if (_invalidModel) {
|
||||
size_t offset = _objectUboSize * _objects.size();
|
||||
_objectOffsets.push_back(TransformStageState::Pair(commandIndex, offset));
|
||||
_objects.push_back(_object);
|
||||
}
|
||||
|
||||
// Flags are clean
|
||||
_invalidView = _invalidProj = _invalidModel = _invalidViewport = false;
|
||||
_invalidView = _invalidProj = _invalidViewport = false;
|
||||
}
|
||||
|
||||
void GLBackend::TransformStageState::transfer() const {
|
||||
void GLBackend::TransformStageState::transfer(const Batch& batch) const {
|
||||
// FIXME not thread safe
|
||||
static std::vector<uint8_t> bufferData;
|
||||
if (!_cameras.empty()) {
|
||||
glBindBuffer(GL_UNIFORM_BUFFER, _cameraBuffer);
|
||||
bufferData.resize(_cameraUboSize * _cameras.size());
|
||||
for (size_t i = 0; i < _cameras.size(); ++i) {
|
||||
memcpy(bufferData.data() + (_cameraUboSize * i), &_cameras[i], sizeof(TransformCamera));
|
||||
}
|
||||
glBindBuffer(GL_UNIFORM_BUFFER, _cameraBuffer);
|
||||
glBufferData(GL_UNIFORM_BUFFER, bufferData.size(), bufferData.data(), GL_DYNAMIC_DRAW);
|
||||
}
|
||||
|
||||
if (!_objects.empty()) {
|
||||
glBindBuffer(GL_UNIFORM_BUFFER, _objectBuffer);
|
||||
bufferData.resize(_objectUboSize * _objects.size());
|
||||
for (size_t i = 0; i < _objects.size(); ++i) {
|
||||
memcpy(bufferData.data() + (_objectUboSize * i), &_objects[i], sizeof(TransformObject));
|
||||
}
|
||||
glBufferData(GL_UNIFORM_BUFFER, bufferData.size(), bufferData.data(), GL_DYNAMIC_DRAW);
|
||||
}
|
||||
|
||||
if (!_cameras.empty() || !_objects.empty()) {
|
||||
glBindBuffer(GL_UNIFORM_BUFFER, 0);
|
||||
}
|
||||
|
||||
if (!batch._objects.empty()) {
|
||||
auto byteSize = batch._objects.size() * sizeof(Batch::TransformObject);
|
||||
bufferData.resize(byteSize);
|
||||
memcpy(bufferData.data(), batch._objects.data(), byteSize);
|
||||
|
||||
#ifdef GPU_SSBO_DRAW_CALL_INFO
|
||||
glBindBuffer(GL_SHADER_STORAGE_BUFFER, _objectBuffer);
|
||||
glBufferData(GL_SHADER_STORAGE_BUFFER, bufferData.size(), bufferData.data(), GL_DYNAMIC_DRAW);
|
||||
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
|
||||
#else
|
||||
glBindBuffer(GL_TEXTURE_BUFFER, _objectBuffer);
|
||||
glBufferData(GL_TEXTURE_BUFFER, bufferData.size(), bufferData.data(), GL_DYNAMIC_DRAW);
|
||||
glBindBuffer(GL_TEXTURE_BUFFER, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!batch._namedData.empty()) {
|
||||
bufferData.clear();
|
||||
for (auto& data : batch._namedData) {
|
||||
auto currentSize = bufferData.size();
|
||||
auto bytesToCopy = data.second.drawCallInfos.size() * sizeof(Batch::DrawCallInfo);
|
||||
bufferData.resize(currentSize + bytesToCopy);
|
||||
memcpy(bufferData.data() + currentSize, data.second.drawCallInfos.data(), bytesToCopy);
|
||||
_drawCallInfoOffsets[data.first] = (GLvoid*)currentSize;
|
||||
}
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, _drawCallInfoBuffer);
|
||||
glBufferData(GL_ARRAY_BUFFER, bufferData.size(), bufferData.data(), GL_DYNAMIC_DRAW);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
#ifdef GPU_SSBO_DRAW_CALL_INFO
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, TRANSFORM_OBJECT_SLOT, _objectBuffer);
|
||||
#else
|
||||
glActiveTexture(GL_TEXTURE0 + TRANSFORM_OBJECT_SLOT);
|
||||
glBindTexture(GL_TEXTURE_BUFFER, _objectBufferTexture);
|
||||
glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, _objectBuffer);
|
||||
#endif
|
||||
|
||||
CHECK_GL_ERROR();
|
||||
}
|
||||
|
||||
void GLBackend::TransformStageState::update(size_t commandIndex, const StereoState& stereo) const {
|
||||
static const size_t INVALID_OFFSET = (size_t)-1;
|
||||
size_t offset = INVALID_OFFSET;
|
||||
while ((_objectsItr != _objectOffsets.end()) && (commandIndex >= (*_objectsItr).first)) {
|
||||
offset = (*_objectsItr).second;
|
||||
++_objectsItr;
|
||||
}
|
||||
if (offset != INVALID_OFFSET) {
|
||||
glBindBufferRange(GL_UNIFORM_BUFFER, TRANSFORM_OBJECT_SLOT,
|
||||
_objectBuffer, offset, sizeof(Backend::TransformObject));
|
||||
}
|
||||
|
||||
offset = INVALID_OFFSET;
|
||||
while ((_camerasItr != _cameraOffsets.end()) && (commandIndex >= (*_camerasItr).first)) {
|
||||
offset = (*_camerasItr).second;
|
||||
++_camerasItr;
|
||||
|
@ -191,14 +193,29 @@ void GLBackend::TransformStageState::update(size_t commandIndex, const StereoSta
|
|||
offset += _cameraUboSize;
|
||||
}
|
||||
glBindBufferRange(GL_UNIFORM_BUFFER, TRANSFORM_CAMERA_SLOT,
|
||||
_cameraBuffer, offset, sizeof(Backend::TransformCamera));
|
||||
_cameraBuffer, offset, sizeof(Backend::TransformCamera));
|
||||
}
|
||||
|
||||
(void)CHECK_GL_ERROR();
|
||||
}
|
||||
|
||||
void GLBackend::updateTransform() const {
|
||||
void GLBackend::updateTransform(const Batch& batch) {
|
||||
_transform.update(_commandIndex, _stereo);
|
||||
|
||||
auto& drawCallInfoBuffer = batch.getDrawCallInfoBuffer();
|
||||
if (batch._currentNamedCall.empty()) {
|
||||
auto& drawCallInfo = drawCallInfoBuffer[_currentDraw];
|
||||
glDisableVertexAttribArray(gpu::Stream::DRAW_CALL_INFO); // Make sure attrib array is disabled
|
||||
glVertexAttribI2i(gpu::Stream::DRAW_CALL_INFO, drawCallInfo.index, drawCallInfo.unused);
|
||||
} else {
|
||||
glEnableVertexAttribArray(gpu::Stream::DRAW_CALL_INFO); // Make sure attrib array is enabled
|
||||
glBindBuffer(GL_ARRAY_BUFFER, _transform._drawCallInfoBuffer);
|
||||
glVertexAttribIPointer(gpu::Stream::DRAW_CALL_INFO, 2, GL_UNSIGNED_SHORT, 0,
|
||||
_transform._drawCallInfoOffsets[batch._currentNamedCall]);
|
||||
glVertexAttribDivisor(gpu::Stream::DRAW_CALL_INFO, 1);
|
||||
}
|
||||
|
||||
(void)CHECK_GL_ERROR();
|
||||
}
|
||||
|
||||
void GLBackend::resetTransformStage() {
|
||||
|
|
|
@ -10,13 +10,12 @@
|
|||
!>
|
||||
<@if not GPU_INPUTS_SLH@>
|
||||
<@def GPU_INPUTS_SLH@>
|
||||
in vec4 inPosition;
|
||||
in vec4 inNormal;
|
||||
in vec4 inColor;
|
||||
in vec4 inTexCoord0;
|
||||
in vec4 inTangent;
|
||||
in vec4 inSkinClusterIndex;
|
||||
in vec4 inSkinClusterWeight;
|
||||
in vec4 inTexCoord1;
|
||||
in mat4 inInstanceTransform;
|
||||
layout(location = 0) in vec4 inPosition;
|
||||
layout(location = 1) in vec4 inNormal;
|
||||
layout(location = 2) in vec4 inColor;
|
||||
layout(location = 3) in vec4 inTexCoord0;
|
||||
layout(location = 4) in vec4 inTangent;
|
||||
layout(location = 5) in vec4 inSkinClusterIndex;
|
||||
layout(location = 6) in vec4 inSkinClusterWeight;
|
||||
layout(location = 7) in vec4 inTexCoord1;
|
||||
<@endif@>
|
||||
|
|
|
@ -34,14 +34,7 @@ const ElementArray& getDefaultElements() {
|
|||
//SKIN_CLUSTER_WEIGHT = 6,
|
||||
Element::VEC4F_XYZW,
|
||||
//TEXCOORD1 = 7,
|
||||
Element::VEC2F_UV,
|
||||
//INSTANCE_SCALE = 8,
|
||||
Element::VEC3F_XYZ,
|
||||
//INSTANCE_TRANSLATE = 9,
|
||||
Element::VEC3F_XYZ,
|
||||
//INSTANCE_XFM = 10,
|
||||
// FIXME make a matrix element
|
||||
Element::VEC4F_XYZW
|
||||
Element::VEC2F_UV
|
||||
}};
|
||||
return defaultElements;
|
||||
}
|
||||
|
|
|
@ -37,12 +37,10 @@ public:
|
|||
SKIN_CLUSTER_INDEX = 5,
|
||||
SKIN_CLUSTER_WEIGHT = 6,
|
||||
TEXCOORD1 = 7,
|
||||
INSTANCE_SCALE = 8,
|
||||
INSTANCE_TRANSLATE = 9,
|
||||
INSTANCE_XFM = 10,
|
||||
NUM_INPUT_SLOTS = TEXCOORD1 + 1,
|
||||
|
||||
// Instance XFM is a mat4, and as such takes up 4 slots
|
||||
NUM_INPUT_SLOTS = INSTANCE_XFM + 4,
|
||||
|
||||
DRAW_CALL_INFO = 15, // Reserve last input slot for draw call infos
|
||||
};
|
||||
|
||||
typedef uint8 Slot;
|
||||
|
|
|
@ -10,13 +10,8 @@
|
|||
<@if not GPU_TRANSFORM_STATE_SLH@>
|
||||
<@def GPU_TRANSFORM_STATE_SLH@>
|
||||
|
||||
<@func declareStandardTransform()@>
|
||||
struct TransformObject {
|
||||
mat4 _model;
|
||||
mat4 _modelInverse;
|
||||
};
|
||||
|
||||
struct TransformCamera {
|
||||
<@func declareStandardCameraTransform()@>
|
||||
struct TransformCamera {
|
||||
mat4 _view;
|
||||
mat4 _viewInverse;
|
||||
mat4 _projectionViewUntranslated;
|
||||
|
@ -25,13 +20,6 @@ struct TransformCamera {
|
|||
vec4 _viewport;
|
||||
};
|
||||
|
||||
layout(std140) uniform transformObjectBuffer {
|
||||
TransformObject _object;
|
||||
};
|
||||
TransformObject getTransformObject() {
|
||||
return _object;
|
||||
}
|
||||
|
||||
layout(std140) uniform transformCameraBuffer {
|
||||
TransformCamera _camera;
|
||||
};
|
||||
|
@ -40,6 +28,50 @@ TransformCamera getTransformCamera() {
|
|||
}
|
||||
<@endfunc@>
|
||||
|
||||
|
||||
<@func declareStandardObjectTransform()@>
|
||||
struct TransformObject {
|
||||
mat4 _model;
|
||||
mat4 _modelInverse;
|
||||
};
|
||||
|
||||
layout(location=15) in ivec2 _drawCallInfo;
|
||||
|
||||
<@if FALSE @>
|
||||
// Disable SSBOs for now
|
||||
layout(std140) buffer transformObjectBuffer {
|
||||
TransformObject _object[];
|
||||
};
|
||||
TransformObject getTransformObject() {
|
||||
return _object[_drawCallInfo.x];
|
||||
}
|
||||
<@else@>
|
||||
uniform samplerBuffer transformObjectBuffer;
|
||||
|
||||
TransformObject getTransformObject() {
|
||||
int offset = 8 * _drawCallInfo.x;
|
||||
TransformObject object;
|
||||
object._model[0] = texelFetch(transformObjectBuffer, offset);
|
||||
object._model[1] = texelFetch(transformObjectBuffer, offset + 1);
|
||||
object._model[2] = texelFetch(transformObjectBuffer, offset + 2);
|
||||
object._model[3] = texelFetch(transformObjectBuffer, offset + 3);
|
||||
|
||||
object._modelInverse[0] = texelFetch(transformObjectBuffer, offset + 4);
|
||||
object._modelInverse[1] = texelFetch(transformObjectBuffer, offset + 5);
|
||||
object._modelInverse[2] = texelFetch(transformObjectBuffer, offset + 6);
|
||||
object._modelInverse[3] = texelFetch(transformObjectBuffer, offset + 7);
|
||||
|
||||
return object;
|
||||
}
|
||||
<@endif@>
|
||||
<@endfunc@>
|
||||
|
||||
|
||||
<@func declareStandardTransform()@>
|
||||
<$declareStandardObjectTransform()$>
|
||||
<$declareStandardCameraTransform()$>
|
||||
<@endfunc@>
|
||||
|
||||
<@func transformCameraViewport(cameraTransform, viewport)@>
|
||||
<$viewport$> = <$cameraTransform$>._viewport;
|
||||
<@endfunc@>
|
||||
|
@ -53,15 +85,6 @@ TransformCamera getTransformCamera() {
|
|||
}
|
||||
<@endfunc@>
|
||||
|
||||
<@func transformInstancedModelToClipPos(cameraTransform, objectTransform, modelPos, clipPos)@>
|
||||
<!// Equivalent to the following but hoppefully a tad more accurate
|
||||
//return camera._projection * camera._view * object._model * pos; !>
|
||||
{ // transformModelToClipPos
|
||||
vec4 _eyepos = (inInstanceTransform * <$modelPos$>) + vec4(-<$modelPos$>.w * <$cameraTransform$>._viewInverse[3].xyz, 0.0);
|
||||
<$clipPos$> = <$cameraTransform$>._projectionViewUntranslated * _eyepos;
|
||||
}
|
||||
<@endfunc@>
|
||||
|
||||
<@func $transformModelToEyeAndClipPos(cameraTransform, objectTransform, modelPos, eyePos, clipPos)@>
|
||||
<!// Equivalent to the following but hoppefully a tad more accurate
|
||||
//return camera._projection * camera._view * object._model * pos; !>
|
||||
|
@ -74,30 +97,12 @@ TransformCamera getTransformCamera() {
|
|||
}
|
||||
<@endfunc@>
|
||||
|
||||
<@func $transformInstancedModelToEyeAndClipPos(cameraTransform, objectTransform, modelPos, eyePos, clipPos)@>
|
||||
<!// Equivalent to the following but hoppefully a tad more accurate
|
||||
//return camera._projection * camera._view * object._model * pos; !>
|
||||
{ // transformModelToClipPos
|
||||
vec4 _worldpos = (inInstanceTransform * <$modelPos$>);
|
||||
<$eyePos$> = (<$cameraTransform$>._view * _worldpos);
|
||||
vec4 _eyepos =(inInstanceTransform * <$modelPos$>) + vec4(-<$modelPos$>.w * <$cameraTransform$>._viewInverse[3].xyz, 0.0);
|
||||
<$clipPos$> = <$cameraTransform$>._projectionViewUntranslated * _eyepos;
|
||||
// <$eyePos$> = (<$cameraTransform$>._projectionInverse * <$clipPos$>);
|
||||
}
|
||||
<@endfunc@>
|
||||
|
||||
<@func transformModelToWorldPos(objectTransform, modelPos, worldPos)@>
|
||||
{ // transformModelToWorldPos
|
||||
<$worldPos$> = (<$objectTransform$>._model * <$modelPos$>);
|
||||
}
|
||||
<@endfunc@>
|
||||
|
||||
<@func transformInstancedModelToWorldPos(objectTransform, modelPos, worldPos)@>
|
||||
{ // transformModelToWorldPos
|
||||
<$worldPos$> = (inInstanceTransform * <$modelPos$>);
|
||||
}
|
||||
<@endfunc@>
|
||||
|
||||
<@func transformModelToEyeDir(cameraTransform, objectTransform, modelDir, eyeDir)@>
|
||||
{ // transformModelToEyeDir
|
||||
vec3 mr0 = vec3(<$objectTransform$>._modelInverse[0].x, <$objectTransform$>._modelInverse[1].x, <$objectTransform$>._modelInverse[2].x);
|
||||
|
@ -112,21 +117,6 @@ TransformCamera getTransformCamera() {
|
|||
}
|
||||
<@endfunc@>
|
||||
|
||||
<@func transformInstancedModelToEyeDir(cameraTransform, objectTransform, modelDir, eyeDir)@>
|
||||
{ // transformModelToEyeDir
|
||||
mat4 modelInverse = inverse(inInstanceTransform);
|
||||
vec3 mr0 = vec3(modelInverse[0].x, modelInverse[1].x, modelInverse[2].x);
|
||||
vec3 mr1 = vec3(modelInverse[0].y, modelInverse[1].y, modelInverse[2].y);
|
||||
vec3 mr2 = vec3(modelInverse[0].z, modelInverse[1].z, modelInverse[2].z);
|
||||
|
||||
vec3 mvc0 = vec3(dot(<$cameraTransform$>._viewInverse[0].xyz, mr0), dot(<$cameraTransform$>._viewInverse[0].xyz, mr1), dot(<$cameraTransform$>._viewInverse[0].xyz, mr2));
|
||||
vec3 mvc1 = vec3(dot(<$cameraTransform$>._viewInverse[1].xyz, mr0), dot(<$cameraTransform$>._viewInverse[1].xyz, mr1), dot(<$cameraTransform$>._viewInverse[1].xyz, mr2));
|
||||
vec3 mvc2 = vec3(dot(<$cameraTransform$>._viewInverse[2].xyz, mr0), dot(<$cameraTransform$>._viewInverse[2].xyz, mr1), dot(<$cameraTransform$>._viewInverse[2].xyz, mr2));
|
||||
|
||||
<$eyeDir$> = vec3(dot(mvc0, <$modelDir$>), dot(mvc1, <$modelDir$>), dot(mvc2, <$modelDir$>));
|
||||
}
|
||||
<@endfunc@>
|
||||
|
||||
<@func transformEyeToWorldDir(cameraTransform, eyeDir, worldDir)@>
|
||||
{ // transformEyeToWorldDir
|
||||
<$worldDir$> = vec3(<$cameraTransform$>._viewInverse * vec4(<$eyeDir$>.xyz, 0.0));
|
||||
|
|
|
@ -43,7 +43,6 @@ static const int VERTICES_PER_TRIANGLE = 3;
|
|||
static const gpu::Element POSITION_ELEMENT{ gpu::VEC3, gpu::FLOAT, gpu::XYZ };
|
||||
static const gpu::Element NORMAL_ELEMENT{ gpu::VEC3, gpu::FLOAT, gpu::XYZ };
|
||||
static const gpu::Element COLOR_ELEMENT{ gpu::VEC4, gpu::NUINT8, gpu::RGBA };
|
||||
static const gpu::Element TRANSFORM_ELEMENT{ gpu::MAT4, gpu::FLOAT, gpu::XYZW };
|
||||
|
||||
static gpu::Stream::FormatPointer SOLID_STREAM_FORMAT;
|
||||
static gpu::Stream::FormatPointer INSTANCED_SOLID_STREAM_FORMAT;
|
||||
|
@ -491,7 +490,6 @@ gpu::Stream::FormatPointer& getInstancedSolidStreamFormat() {
|
|||
INSTANCED_SOLID_STREAM_FORMAT->setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, POSITION_ELEMENT);
|
||||
INSTANCED_SOLID_STREAM_FORMAT->setAttribute(gpu::Stream::NORMAL, gpu::Stream::NORMAL, NORMAL_ELEMENT);
|
||||
INSTANCED_SOLID_STREAM_FORMAT->setAttribute(gpu::Stream::COLOR, gpu::Stream::COLOR, COLOR_ELEMENT, 0, gpu::Stream::PER_INSTANCE);
|
||||
INSTANCED_SOLID_STREAM_FORMAT->setAttribute(gpu::Stream::INSTANCE_XFM, gpu::Stream::INSTANCE_XFM, TRANSFORM_ELEMENT, 0, gpu::Stream::PER_INSTANCE);
|
||||
}
|
||||
return INSTANCED_SOLID_STREAM_FORMAT;
|
||||
}
|
||||
|
@ -511,11 +509,9 @@ GeometryCache::~GeometryCache() {
|
|||
#endif //def WANT_DEBUG
|
||||
}
|
||||
|
||||
void setupBatchInstance(gpu::Batch& batch, gpu::BufferPointer transformBuffer, gpu::BufferPointer colorBuffer) {
|
||||
void setupBatchInstance(gpu::Batch& batch, gpu::BufferPointer colorBuffer) {
|
||||
gpu::BufferView colorView(colorBuffer, COLOR_ELEMENT);
|
||||
batch.setInputBuffer(gpu::Stream::COLOR, colorView);
|
||||
gpu::BufferView instanceXfmView(transformBuffer, 0, transformBuffer->getSize(), TRANSFORM_ELEMENT);
|
||||
batch.setInputBuffer(gpu::Stream::INSTANCE_XFM, instanceXfmView);
|
||||
}
|
||||
|
||||
void GeometryCache::renderShape(gpu::Batch& batch, Shape shape) {
|
||||
|
@ -528,24 +524,24 @@ void GeometryCache::renderWireShape(gpu::Batch& batch, Shape shape) {
|
|||
_shapes[shape].drawWire(batch);
|
||||
}
|
||||
|
||||
void GeometryCache::renderShapeInstances(gpu::Batch& batch, Shape shape, size_t count, gpu::BufferPointer& transformBuffer, gpu::BufferPointer& colorBuffer) {
|
||||
void GeometryCache::renderShapeInstances(gpu::Batch& batch, Shape shape, size_t count, gpu::BufferPointer& colorBuffer) {
|
||||
batch.setInputFormat(getInstancedSolidStreamFormat());
|
||||
setupBatchInstance(batch, transformBuffer, colorBuffer);
|
||||
setupBatchInstance(batch, colorBuffer);
|
||||
_shapes[shape].drawInstances(batch, count);
|
||||
}
|
||||
|
||||
void GeometryCache::renderWireShapeInstances(gpu::Batch& batch, Shape shape, size_t count, gpu::BufferPointer& transformBuffer, gpu::BufferPointer& colorBuffer) {
|
||||
void GeometryCache::renderWireShapeInstances(gpu::Batch& batch, Shape shape, size_t count, gpu::BufferPointer& colorBuffer) {
|
||||
batch.setInputFormat(getInstancedSolidStreamFormat());
|
||||
setupBatchInstance(batch, transformBuffer, colorBuffer);
|
||||
setupBatchInstance(batch, colorBuffer);
|
||||
_shapes[shape].drawWireInstances(batch, count);
|
||||
}
|
||||
|
||||
void GeometryCache::renderCubeInstances(gpu::Batch& batch, size_t count, gpu::BufferPointer transformBuffer, gpu::BufferPointer colorBuffer) {
|
||||
renderShapeInstances(batch, Cube, count, transformBuffer, colorBuffer);
|
||||
void GeometryCache::renderCubeInstances(gpu::Batch& batch, size_t count, gpu::BufferPointer colorBuffer) {
|
||||
renderShapeInstances(batch, Cube, count, colorBuffer);
|
||||
}
|
||||
|
||||
void GeometryCache::renderWireCubeInstances(gpu::Batch& batch, size_t count, gpu::BufferPointer transformBuffer, gpu::BufferPointer colorBuffer) {
|
||||
renderWireShapeInstances(batch, Cube, count, transformBuffer, colorBuffer);
|
||||
void GeometryCache::renderWireCubeInstances(gpu::Batch& batch, size_t count, gpu::BufferPointer colorBuffer) {
|
||||
renderWireShapeInstances(batch, Cube, count, colorBuffer);
|
||||
}
|
||||
|
||||
void GeometryCache::renderCube(gpu::Batch& batch) {
|
||||
|
@ -556,8 +552,8 @@ void GeometryCache::renderWireCube(gpu::Batch& batch) {
|
|||
renderWireShape(batch, Cube);
|
||||
}
|
||||
|
||||
void GeometryCache::renderSphereInstances(gpu::Batch& batch, size_t count, gpu::BufferPointer transformBuffer, gpu::BufferPointer colorBuffer) {
|
||||
renderShapeInstances(batch, Sphere, count, transformBuffer, colorBuffer);
|
||||
void GeometryCache::renderSphereInstances(gpu::Batch& batch, size_t count, gpu::BufferPointer colorBuffer) {
|
||||
renderShapeInstances(batch, Sphere, count, colorBuffer);
|
||||
}
|
||||
|
||||
void GeometryCache::renderSphere(gpu::Batch& batch) {
|
||||
|
@ -1854,45 +1850,34 @@ uint32_t toCompactColor(const glm::vec4& color) {
|
|||
return compactColor;
|
||||
}
|
||||
|
||||
static const size_t INSTANCE_TRANSFORM_BUFFER = 0;
|
||||
static const size_t INSTANCE_COLOR_BUFFER = 1;
|
||||
static const size_t INSTANCE_COLOR_BUFFER = 0;
|
||||
|
||||
template <typename F>
|
||||
void renderInstances(const std::string& name, gpu::Batch& batch, const Transform& transform, const glm::vec4& color, F f) {
|
||||
void renderInstances(const std::string& name, gpu::Batch& batch, const glm::vec4& color, F f) {
|
||||
{
|
||||
gpu::BufferPointer instanceTransformBuffer = batch.getNamedBuffer(name, INSTANCE_TRANSFORM_BUFFER);
|
||||
glm::mat4 glmTransform;
|
||||
instanceTransformBuffer->append(transform.getMatrix(glmTransform));
|
||||
|
||||
gpu::BufferPointer instanceColorBuffer = batch.getNamedBuffer(name, INSTANCE_COLOR_BUFFER);
|
||||
auto compactColor = toCompactColor(color);
|
||||
instanceColorBuffer->append(compactColor);
|
||||
}
|
||||
|
||||
batch.setupNamedCalls(name, [f](gpu::Batch& batch, gpu::Batch::NamedBatchData& data) {
|
||||
auto pipeline = DependencyManager::get<GeometryCache>()->bindSimpleProgram(batch);
|
||||
auto location = pipeline->getProgram()->getUniforms().findLocation("Instanced");
|
||||
|
||||
batch._glUniform1i(location, 1);
|
||||
DependencyManager::get<GeometryCache>()->bindSimpleProgram(batch);
|
||||
f(batch, data);
|
||||
batch._glUniform1i(location, 0);
|
||||
});
|
||||
}
|
||||
|
||||
void GeometryCache::renderSolidSphereInstance(gpu::Batch& batch, const Transform& transform, const glm::vec4& color) {
|
||||
void GeometryCache::renderSolidSphereInstance(gpu::Batch& batch, const glm::vec4& color) {
|
||||
static const std::string INSTANCE_NAME = __FUNCTION__;
|
||||
renderInstances(INSTANCE_NAME, batch, transform, color, [](gpu::Batch& batch, gpu::Batch::NamedBatchData& data) {
|
||||
DependencyManager::get<GeometryCache>()->renderShapeInstances(batch, GeometryCache::Sphere, data.count,
|
||||
data.buffers[INSTANCE_TRANSFORM_BUFFER],
|
||||
renderInstances(INSTANCE_NAME, batch, color, [](gpu::Batch& batch, gpu::Batch::NamedBatchData& data) {
|
||||
DependencyManager::get<GeometryCache>()->renderShapeInstances(batch, GeometryCache::Sphere, data.count(),
|
||||
data.buffers[INSTANCE_COLOR_BUFFER]);
|
||||
});
|
||||
}
|
||||
|
||||
void GeometryCache::renderWireSphereInstance(gpu::Batch& batch, const Transform& transform, const glm::vec4& color) {
|
||||
void GeometryCache::renderWireSphereInstance(gpu::Batch& batch, const glm::vec4& color) {
|
||||
static const std::string INSTANCE_NAME = __FUNCTION__;
|
||||
renderInstances(INSTANCE_NAME, batch, transform, color, [](gpu::Batch& batch, gpu::Batch::NamedBatchData& data) {
|
||||
DependencyManager::get<GeometryCache>()->renderWireShapeInstances(batch, GeometryCache::Sphere, data.count,
|
||||
data.buffers[INSTANCE_TRANSFORM_BUFFER],
|
||||
renderInstances(INSTANCE_NAME, batch, color, [](gpu::Batch& batch, gpu::Batch::NamedBatchData& data) {
|
||||
DependencyManager::get<GeometryCache>()->renderWireShapeInstances(batch, GeometryCache::Sphere, data.count(),
|
||||
data.buffers[INSTANCE_COLOR_BUFFER]);
|
||||
});
|
||||
}
|
||||
|
@ -1901,12 +1886,12 @@ void GeometryCache::renderWireSphereInstance(gpu::Batch& batch, const Transform&
|
|||
// available shape types, both solid and wireframes
|
||||
//#define DEBUG_SHAPES
|
||||
|
||||
void GeometryCache::renderSolidCubeInstance(gpu::Batch& batch, const Transform& transform, const glm::vec4& color) {
|
||||
void GeometryCache::renderSolidCubeInstance(gpu::Batch& batch, const glm::vec4& color) {
|
||||
static const std::string INSTANCE_NAME = __FUNCTION__;
|
||||
|
||||
#ifdef DEBUG_SHAPES
|
||||
static auto startTime = usecTimestampNow();
|
||||
renderInstances(INSTANCE_NAME, batch, transform, color, [](gpu::Batch& batch, gpu::Batch::NamedBatchData& data) {
|
||||
renderInstances(INSTANCE_NAME, batch, color, [](gpu::Batch& batch, gpu::Batch::NamedBatchData& data) {
|
||||
|
||||
auto usecs = usecTimestampNow();
|
||||
usecs -= startTime;
|
||||
|
@ -1931,29 +1916,25 @@ void GeometryCache::renderSolidCubeInstance(gpu::Batch& batch, const Transform&
|
|||
|
||||
// For the first half second for a given shape, show the wireframe, for the second half, show the solid.
|
||||
if (fractionalSeconds > 0.5f) {
|
||||
DependencyManager::get<GeometryCache>()->renderShapeInstances(batch, shape, data.count,
|
||||
data.buffers[INSTANCE_TRANSFORM_BUFFER],
|
||||
DependencyManager::get<GeometryCache>()->renderShapeInstances(batch, shape, data.count(),
|
||||
data.buffers[INSTANCE_COLOR_BUFFER]);
|
||||
} else {
|
||||
DependencyManager::get<GeometryCache>()->renderWireShapeInstances(batch, shape, data.count,
|
||||
data.buffers[INSTANCE_TRANSFORM_BUFFER],
|
||||
DependencyManager::get<GeometryCache>()->renderWireShapeInstances(batch, shape, data.count(),
|
||||
data.buffers[INSTANCE_COLOR_BUFFER]);
|
||||
}
|
||||
});
|
||||
#else
|
||||
renderInstances(INSTANCE_NAME, batch, transform, color, [](gpu::Batch& batch, gpu::Batch::NamedBatchData& data) {
|
||||
DependencyManager::get<GeometryCache>()->renderCubeInstances(batch, data.count,
|
||||
data.buffers[INSTANCE_TRANSFORM_BUFFER],
|
||||
renderInstances(INSTANCE_NAME, batch, color, [](gpu::Batch& batch, gpu::Batch::NamedBatchData& data) {
|
||||
DependencyManager::get<GeometryCache>()->renderCubeInstances(batch, data.count(),
|
||||
data.buffers[INSTANCE_COLOR_BUFFER]);
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
void GeometryCache::renderWireCubeInstance(gpu::Batch& batch, const Transform& transform, const glm::vec4& color) {
|
||||
void GeometryCache::renderWireCubeInstance(gpu::Batch& batch, const glm::vec4& color) {
|
||||
static const std::string INSTANCE_NAME = __FUNCTION__;
|
||||
renderInstances(INSTANCE_NAME, batch, transform, color, [](gpu::Batch& batch, gpu::Batch::NamedBatchData& data) {
|
||||
DependencyManager::get<GeometryCache>()->renderWireCubeInstances(batch, data.count,
|
||||
data.buffers[INSTANCE_TRANSFORM_BUFFER],
|
||||
renderInstances(INSTANCE_NAME, batch, color, [](gpu::Batch& batch, gpu::Batch::NamedBatchData& data) {
|
||||
DependencyManager::get<GeometryCache>()->renderWireCubeInstances(batch, data.count(),
|
||||
data.buffers[INSTANCE_COLOR_BUFFER]);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -154,41 +154,41 @@ public:
|
|||
gpu::PipelinePointer bindSimpleProgram(gpu::Batch& batch, bool textured = false, bool culled = true,
|
||||
bool emissive = false, bool depthBias = false);
|
||||
|
||||
void renderSolidSphereInstance(gpu::Batch& batch, const Transform& xfm, const glm::vec4& color);
|
||||
void renderSolidSphereInstance(gpu::Batch& batch, const Transform& xfm, const glm::vec3& color) {
|
||||
renderSolidSphereInstance(batch, xfm, glm::vec4(color, 1.0));
|
||||
void renderSolidSphereInstance(gpu::Batch& batch, const glm::vec4& color);
|
||||
void renderSolidSphereInstance(gpu::Batch& batch, const glm::vec3& color) {
|
||||
renderSolidSphereInstance(batch, glm::vec4(color, 1.0));
|
||||
}
|
||||
|
||||
void renderWireSphereInstance(gpu::Batch& batch, const Transform& xfm, const glm::vec4& color);
|
||||
void renderWireSphereInstance(gpu::Batch& batch, const Transform& xfm, const glm::vec3& color) {
|
||||
renderWireSphereInstance(batch, xfm, glm::vec4(color, 1.0));
|
||||
void renderWireSphereInstance(gpu::Batch& batch, const glm::vec4& color);
|
||||
void renderWireSphereInstance(gpu::Batch& batch, const glm::vec3& color) {
|
||||
renderWireSphereInstance(batch, glm::vec4(color, 1.0));
|
||||
}
|
||||
|
||||
void renderSolidCubeInstance(gpu::Batch& batch, const Transform& xfm, const glm::vec4& color);
|
||||
void renderSolidCubeInstance(gpu::Batch& batch, const Transform& xfm, const glm::vec3& color) {
|
||||
renderSolidCubeInstance(batch, xfm, glm::vec4(color, 1.0));
|
||||
void renderSolidCubeInstance(gpu::Batch& batch, const glm::vec4& color);
|
||||
void renderSolidCubeInstance(gpu::Batch& batch, const glm::vec3& color) {
|
||||
renderSolidCubeInstance(batch, glm::vec4(color, 1.0));
|
||||
}
|
||||
|
||||
void renderWireCubeInstance(gpu::Batch& batch, const Transform& xfm, const glm::vec4& color);
|
||||
void renderWireCubeInstance(gpu::Batch& batch, const Transform& xfm, const glm::vec3& color) {
|
||||
renderWireCubeInstance(batch, xfm, glm::vec4(color, 1.0));
|
||||
void renderWireCubeInstance(gpu::Batch& batch, const glm::vec4& color);
|
||||
void renderWireCubeInstance(gpu::Batch& batch, const glm::vec3& color) {
|
||||
renderWireCubeInstance(batch, glm::vec4(color, 1.0));
|
||||
}
|
||||
|
||||
|
||||
void renderShapeInstances(gpu::Batch& batch, Shape shape, size_t count, gpu::BufferPointer& transformBuffer, gpu::BufferPointer& colorBuffer);
|
||||
void renderWireShapeInstances(gpu::Batch& batch, Shape shape, size_t count, gpu::BufferPointer& transformBuffer, gpu::BufferPointer& colorBuffer);
|
||||
void renderShapeInstances(gpu::Batch& batch, Shape shape, size_t count, gpu::BufferPointer& colorBuffer);
|
||||
void renderWireShapeInstances(gpu::Batch& batch, Shape shape, size_t count, gpu::BufferPointer& colorBuffer);
|
||||
void renderShape(gpu::Batch& batch, Shape shape);
|
||||
void renderWireShape(gpu::Batch& batch, Shape shape);
|
||||
size_t getShapeTriangleCount(Shape shape);
|
||||
|
||||
void renderCubeInstances(gpu::Batch& batch, size_t count, gpu::BufferPointer transformBuffer, gpu::BufferPointer colorBuffer);
|
||||
void renderWireCubeInstances(gpu::Batch& batch, size_t count, gpu::BufferPointer transformBuffer, gpu::BufferPointer colorBuffer);
|
||||
void renderCubeInstances(gpu::Batch& batch, size_t count, gpu::BufferPointer colorBuffer);
|
||||
void renderWireCubeInstances(gpu::Batch& batch, size_t count, gpu::BufferPointer colorBuffer);
|
||||
void renderCube(gpu::Batch& batch);
|
||||
void renderWireCube(gpu::Batch& batch);
|
||||
size_t getCubeTriangleCount();
|
||||
|
||||
void renderSphereInstances(gpu::Batch& batch, size_t count, gpu::BufferPointer transformBuffer, gpu::BufferPointer colorBuffer);
|
||||
void renderWireSphereInstances(gpu::Batch& batch, size_t count, gpu::BufferPointer transformBuffer, gpu::BufferPointer colorBuffer);
|
||||
void renderSphereInstances(gpu::Batch& batch, size_t count, gpu::BufferPointer colorBuffer);
|
||||
void renderWireSphereInstances(gpu::Batch& batch, size_t count, gpu::BufferPointer colorBuffer);
|
||||
void renderSphere(gpu::Batch& batch);
|
||||
void renderWireSphere(gpu::Batch& batch);
|
||||
size_t getSphereTriangleCount();
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
<@include DeferredLighting.slh@>
|
||||
<@include gpu/Transform.slh@>
|
||||
<$declareStandardTransform()$>
|
||||
<$declareStandardCameraTransform()$>
|
||||
|
||||
|
||||
// Everything about light
|
||||
|
|
|
@ -17,9 +17,7 @@
|
|||
<@include gpu/Transform.slh@>
|
||||
<$declareStandardTransform()$>
|
||||
|
||||
uniform bool Instanced = false;
|
||||
// the interpolated normal
|
||||
|
||||
out vec3 _normal;
|
||||
out vec3 _modelNormal;
|
||||
out vec3 _color;
|
||||
|
@ -35,11 +33,6 @@ void main(void) {
|
|||
// standard transform
|
||||
TransformCamera cam = getTransformCamera();
|
||||
TransformObject obj = getTransformObject();
|
||||
if (Instanced) {
|
||||
<$transformInstancedModelToClipPos(cam, obj, inPosition, gl_Position)$>
|
||||
<$transformInstancedModelToEyeDir(cam, obj, inNormal.xyz, _normal)$>
|
||||
} else {
|
||||
<$transformModelToClipPos(cam, obj, inPosition, gl_Position)$>
|
||||
<$transformModelToEyeDir(cam, obj, inNormal.xyz, _normal)$>
|
||||
}
|
||||
<$transformModelToClipPos(cam, obj, inPosition, gl_Position)$>
|
||||
<$transformModelToEyeDir(cam, obj, inNormal.xyz, _normal)$>
|
||||
}
|
|
@ -124,7 +124,6 @@ class QTestWindow : public QWindow {
|
|||
glm::mat4 _projectionMatrix;
|
||||
RateCounter fps;
|
||||
QTime _time;
|
||||
int _instanceLocation{ -1 };
|
||||
|
||||
protected:
|
||||
void renderText();
|
||||
|
@ -164,7 +163,6 @@ public:
|
|||
state->setMultisampleEnable(true);
|
||||
state->setDepthTest(gpu::State::DepthTest { true });
|
||||
_pipeline = gpu::Pipeline::create(shader, state);
|
||||
_instanceLocation = _pipeline->getProgram()->getUniforms().findLocation("Instanced");
|
||||
|
||||
// Clear screen
|
||||
gpu::Batch batch;
|
||||
|
@ -218,16 +216,16 @@ public:
|
|||
static const std::string GRID_INSTANCE = "Grid";
|
||||
static auto compactColor1 = toCompactColor(vec4{ 0.35f, 0.25f, 0.15f, 1.0f });
|
||||
static auto compactColor2 = toCompactColor(vec4{ 0.15f, 0.25f, 0.35f, 1.0f });
|
||||
static gpu::BufferPointer transformBuffer;
|
||||
static std::vector<glm::mat4> transforms;
|
||||
static gpu::BufferPointer colorBuffer;
|
||||
if (!transformBuffer) {
|
||||
transformBuffer = std::make_shared<gpu::Buffer>();
|
||||
if (!transforms.empty()) {
|
||||
transforms.reserve(200);
|
||||
colorBuffer = std::make_shared<gpu::Buffer>();
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
{
|
||||
glm::mat4 transform = glm::translate(mat4(), vec3(0, -1, -50 + i));
|
||||
transform = glm::scale(transform, vec3(100, 1, 1));
|
||||
transformBuffer->append(transform);
|
||||
transforms.push_back(transform);
|
||||
colorBuffer->append(compactColor1);
|
||||
}
|
||||
|
||||
|
@ -235,20 +233,20 @@ public:
|
|||
glm::mat4 transform = glm::mat4_cast(quat(vec3(0, PI / 2.0f, 0)));
|
||||
transform = glm::translate(transform, vec3(0, -1, -50 + i));
|
||||
transform = glm::scale(transform, vec3(100, 1, 1));
|
||||
transformBuffer->append(transform);
|
||||
transforms.push_back(transform);
|
||||
colorBuffer->append(compactColor2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
batch.setupNamedCalls(GRID_INSTANCE, 200, [=](gpu::Batch& batch, gpu::Batch::NamedBatchData& data) {
|
||||
batch.setViewTransform(camera);
|
||||
batch.setModelTransform(Transform());
|
||||
batch.setPipeline(_pipeline);
|
||||
batch._glUniform1i(_instanceLocation, 1);
|
||||
geometryCache->renderWireShapeInstances(batch, GeometryCache::Line, data.count, transformBuffer, colorBuffer);
|
||||
batch._glUniform1i(_instanceLocation, 0);
|
||||
});
|
||||
|
||||
for (auto& transform : transforms) {
|
||||
batch.setModelTransform(transform);
|
||||
batch.setupNamedCalls(GRID_INSTANCE, [=](gpu::Batch& batch, gpu::Batch::NamedBatchData& data) {
|
||||
batch.setViewTransform(camera);
|
||||
batch.setPipeline(_pipeline);
|
||||
geometryCache->renderWireShapeInstances(batch, GeometryCache::Line, data.count(), colorBuffer);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -316,14 +314,11 @@ public:
|
|||
batch.setViewTransform(camera);
|
||||
batch.setModelTransform(Transform());
|
||||
batch.setPipeline(_pipeline);
|
||||
batch._glUniform1i(_instanceLocation, 1);
|
||||
batch.setInputFormat(getInstancedSolidStreamFormat());
|
||||
batch.setInputBuffer(gpu::Stream::COLOR, colorView);
|
||||
batch.setInputBuffer(gpu::Stream::INSTANCE_XFM, instanceXfmView);
|
||||
batch.setIndirectBuffer(indirectBuffer);
|
||||
shapeData.setupBatch(batch);
|
||||
batch.multiDrawIndexedIndirect(TYPE_COUNT, gpu::TRIANGLES);
|
||||
batch._glUniform1i(_instanceLocation, 0);
|
||||
}
|
||||
#else
|
||||
batch.setViewTransform(camera);
|
||||
|
|
|
@ -19,8 +19,6 @@
|
|||
<$declareStandardTransform()$>
|
||||
|
||||
// the interpolated normal
|
||||
uniform bool Instanced = false;
|
||||
|
||||
out vec3 _normal;
|
||||
out vec3 _color;
|
||||
out vec2 _texCoord0;
|
||||
|
@ -32,12 +30,7 @@ void main(void) {
|
|||
// standard transform
|
||||
TransformCamera cam = getTransformCamera();
|
||||
TransformObject obj = getTransformObject();
|
||||
if (Instanced) {
|
||||
<$transformInstancedModelToClipPos(cam, obj, inPosition, gl_Position)$>
|
||||
<$transformInstancedModelToEyeDir(cam, obj, inNormal.xyz, _normal)$>
|
||||
} else {
|
||||
<$transformModelToClipPos(cam, obj, inPosition, gl_Position)$>
|
||||
<$transformModelToEyeDir(cam, obj, inNormal.xyz, _normal)$>
|
||||
}
|
||||
<$transformModelToClipPos(cam, obj, inPosition, gl_Position)$>
|
||||
<$transformModelToEyeDir(cam, obj, inNormal.xyz, _normal)$>
|
||||
_normal = vec3(0.0, 0.0, 1.0);
|
||||
}
|
Loading…
Reference in a new issue