blob: 136bdf5fa970151c280db62fc97194cd32808b64 [file] [edit]
//
// Copyright 2014 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
#include "libANGLE/TransformFeedback.h"
#include "common/mathutil.h"
#include "libANGLE/Buffer.h"
#include "libANGLE/Caps.h"
#include "libANGLE/Context.h"
#include "libANGLE/Program.h"
#include "libANGLE/State.h"
#include "libANGLE/renderer/GLImplFactory.h"
#include "libANGLE/renderer/TransformFeedbackImpl.h"
#include <limits>
namespace gl
{
angle::CheckedNumeric<GLsizeiptr> GetVerticesNeededForDraw(PrimitiveMode primitiveMode,
GLsizei count,
GLsizei primcount)
{
if (count < 0 || primcount < 0)
{
return 0;
}
// Transform feedback only outputs complete primitives, so we need to round down to the nearest
// complete primitive before multiplying by the number of instances.
angle::CheckedNumeric<GLsizeiptr> checkedCount = count;
angle::CheckedNumeric<GLsizeiptr> checkedPrimcount = primcount;
switch (primitiveMode)
{
case PrimitiveMode::Triangles:
return checkedPrimcount * (checkedCount - checkedCount % 3);
case PrimitiveMode::Lines:
return checkedPrimcount * (checkedCount - checkedCount % 2);
case PrimitiveMode::Points:
return checkedPrimcount * checkedCount;
default:
UNREACHABLE();
return checkedPrimcount * checkedCount;
}
}
TransformFeedbackState::TransformFeedbackState(size_t maxIndexedBuffers)
: mLabel(),
mActive(false),
mPrimitiveMode(PrimitiveMode::InvalidEnum),
mPaused(false),
mVerticesDrawn(0),
mProgram(nullptr),
mProgramPipeline(nullptr),
mIndexedBuffers(maxIndexedBuffers)
{}
TransformFeedbackState::~TransformFeedbackState() {}
const OffsetBindingPointer<Buffer> &TransformFeedbackState::getIndexedBuffer(size_t idx) const
{
return mIndexedBuffers[idx];
}
const std::vector<OffsetBindingPointer<Buffer>> &TransformFeedbackState::getIndexedBuffers() const
{
return mIndexedBuffers;
}
GLsizeiptr TransformFeedbackState::getPrimitivesDrawn() const
{
switch (mPrimitiveMode)
{
case gl::PrimitiveMode::Points:
return mVerticesDrawn;
case gl::PrimitiveMode::Lines:
return mVerticesDrawn / 2;
case gl::PrimitiveMode::Triangles:
return mVerticesDrawn / 3;
default:
return 0;
}
}
TransformFeedback::TransformFeedback(rx::GLImplFactory *implFactory,
TransformFeedbackID id,
const Caps &caps)
: RefCountObject(implFactory->generateSerial(), id),
mState(caps.maxTransformFeedbackSeparateAttributes),
mImplementation(implFactory->createTransformFeedback(mState))
{
ASSERT(mImplementation != nullptr);
}
void TransformFeedback::onDestroy(const Context *context)
{
ASSERT(!context || !context->isCurrentTransformFeedback(this));
if (mState.mProgram)
{
mState.mProgram->release(context);
mState.mProgram = nullptr;
}
ASSERT(!mState.mProgram);
for (size_t i = 0; i < mState.mIndexedBuffers.size(); i++)
{
mState.mIndexedBuffers[i].set(context, nullptr, 0, 0);
}
if (mImplementation)
{
mImplementation->onDestroy(context);
}
}
TransformFeedback::~TransformFeedback()
{
SafeDelete(mImplementation);
}
angle::Result TransformFeedback::setLabel(const Context *context, const std::string &label)
{
mState.mLabel = label;
if (mImplementation)
{
return mImplementation->onLabelUpdate(context);
}
return angle::Result::Continue;
}
const std::string &TransformFeedback::getLabel() const
{
return mState.mLabel;
}
angle::Result TransformFeedback::begin(const Context *context,
PrimitiveMode primitiveMode,
Program *program,
ProgramPipeline *programPipeline)
{
// TODO: http://anglebug.com/42264023: This method should take in as parameter a
// ProgramExecutable instead of a Program.
ANGLE_TRY(mImplementation->begin(context, primitiveMode));
mState.mActive = true;
mState.mPrimitiveMode = primitiveMode;
mState.mPaused = false;
mState.mVerticesDrawn = 0;
// Program and PPO are not both passed to the transform feedback object. In the event both are
// bound, program takes precedence.
ASSERT(program == nullptr || programPipeline == nullptr);
bindProgram(context, program);
bindProgramPipeline(context, programPipeline);
bindPPOPrograms(programPipeline);
for (auto &buffer : mState.mIndexedBuffers)
{
if (buffer.get())
{
buffer->onTFActiveChanged(context, true);
}
}
return angle::Result::Continue;
}
angle::Result TransformFeedback::end(const Context *context)
{
ANGLE_TRY(mImplementation->end(context));
mState.mActive = false;
mState.mPrimitiveMode = PrimitiveMode::InvalidEnum;
mState.mPaused = false;
mState.mVerticesDrawn = 0;
mState.mVertexCapacity = std::nullopt;
if (mState.mProgram != nullptr)
{
mState.mProgram->release(context);
mState.mProgram = nullptr;
}
if (mState.mProgramPipeline != nullptr)
{
mState.mProgramPipeline->release(context);
mState.mProgramPipeline = nullptr;
}
for (const ShaderType shaderType : gl::AllShaderTypes())
{
mState.mPPOPrograms[shaderType].value = 0;
}
for (auto &buffer : mState.mIndexedBuffers)
{
if (buffer.get())
{
buffer->onTFActiveChanged(context, false);
}
}
return angle::Result::Continue;
}
angle::Result TransformFeedback::pause(const Context *context)
{
ANGLE_TRY(mImplementation->pause(context));
mState.mPaused = true;
return angle::Result::Continue;
}
angle::Result TransformFeedback::resume(const Context *context)
{
ANGLE_TRY(mImplementation->resume(context));
mState.mPaused = false;
return angle::Result::Continue;
}
bool TransformFeedback::isPaused() const
{
return mState.mPaused;
}
PrimitiveMode TransformFeedback::getPrimitiveMode() const
{
return mState.mPrimitiveMode;
}
bool TransformFeedback::checkBufferSpaceForDraw(const Context *context,
const GLsizei *counts,
const GLsizei *primcounts,
GLsizei drawcount)
{
auto vertices = angle::CheckedNumeric<GLsizeiptr>(mState.mVerticesDrawn);
for (GLsizei drawID = 0; drawID < drawcount; ++drawID)
{
GLsizei primcount = ANGLE_UNSAFE_BUFFERS(primcounts ? primcounts[drawID] : 1);
GLsizei count = ANGLE_UNSAFE_BUFFERS(counts[drawID]);
vertices += GetVerticesNeededForDraw(mState.mPrimitiveMode, count, primcount);
}
if (!mState.mVertexCapacity.has_value())
{
const ProgramExecutable *programExecutable =
context->getState().getLinkedProgramExecutable(context);
if (programExecutable)
{
// Compute the number of vertices we can draw before overflowing the bound buffers.
auto strides = programExecutable->getTransformFeedbackStrides();
ASSERT(strides.size() <= mState.mIndexedBuffers.size() && !strides.empty());
GLsizeiptr minCapacity = std::numeric_limits<GLsizeiptr>::max();
for (size_t index = 0; index < strides.size(); index++)
{
GLsizeiptr capacity =
GetBoundBufferAvailableSize(mState.mIndexedBuffers[index]) / strides[index];
minCapacity = std::min(minCapacity, capacity);
}
mState.mVertexCapacity = minCapacity;
}
else
{
UNREACHABLE();
mState.mVertexCapacity = 0;
}
}
return vertices.IsValid() && vertices.ValueOrDie() <= *mState.mVertexCapacity;
}
void TransformFeedback::onVerticesDrawn(const Context *context, GLsizei count, GLsizei primcount)
{
ASSERT(mState.mActive && !mState.mPaused);
// All draws should be validated with checkBufferSpaceForDraw so ValueOrDie should never fail.
mState.mVerticesDrawn =
(mState.mVerticesDrawn + GetVerticesNeededForDraw(mState.mPrimitiveMode, count, primcount))
.ValueOrDie();
for (auto &buffer : mState.mIndexedBuffers)
{
if (buffer.get() != nullptr)
{
buffer->onDataChanged(context);
}
}
}
void TransformFeedback::bindProgram(const Context *context, Program *program)
{
if (mState.mProgram != program)
{
if (mState.mProgram != nullptr)
{
mState.mProgram->release(context);
}
mState.mProgram = program;
if (mState.mProgram != nullptr)
{
mState.mProgram->addRef();
}
}
}
void TransformFeedback::bindProgramPipeline(const Context *context,
ProgramPipeline *programPipeline)
{
if (mState.mProgramPipeline != programPipeline)
{
if (mState.mProgramPipeline != nullptr)
{
mState.mProgramPipeline->release(context);
}
mState.mProgramPipeline = programPipeline;
if (mState.mProgramPipeline != nullptr)
{
mState.mProgramPipeline->addRef();
}
}
}
void TransformFeedback::bindPPOPrograms(ProgramPipeline *programPipeline)
{
if (programPipeline == nullptr)
{
for (const ShaderType shaderType : gl::AllShaderTypes())
{
mState.mPPOPrograms[shaderType].value = 0;
}
return;
}
for (const ShaderType shaderType : gl::AllShaderTypes())
{
const Program *program = programPipeline->getShaderProgram(shaderType);
mState.mPPOPrograms[shaderType].value = program != nullptr ? program->id().value : 0;
}
}
bool TransformFeedback::hasBoundProgram(ShaderProgramID program) const
{
return mState.mProgram != nullptr && mState.mProgram->id().value == program.value;
}
bool TransformFeedback::hasBoundProgramPipeline(ProgramPipelineID programPipeline) const
{
return mState.mProgramPipeline != nullptr &&
mState.mProgramPipeline->id().value == programPipeline.value;
}
bool TransformFeedback::hasSamePPOPrograms(ProgramPipeline *programPipeline) const
{
for (const ShaderType shaderType : gl::AllShaderTypes())
{
GLuint shaderProgramIDValue =
(programPipeline != nullptr && programPipeline->getShaderProgram(shaderType) != nullptr)
? programPipeline->getShaderProgram(shaderType)->id().value
: 0;
if (mState.mPPOPrograms[shaderType].value != shaderProgramIDValue)
{
return false;
}
}
return true;
}
angle::Result TransformFeedback::detachBuffer(const Context *context, BufferID bufferID)
{
bool isBound = context->isCurrentTransformFeedback(this);
for (size_t index = 0; index < mState.mIndexedBuffers.size(); index++)
{
if (mState.mIndexedBuffers[index].id() == bufferID)
{
if (isBound)
{
mState.mIndexedBuffers[index]->onTFBindingChanged(context, false, true);
}
mState.mIndexedBuffers[index].set(context, nullptr, 0, 0);
ANGLE_TRY(
mImplementation->bindIndexedBuffer(context, index, mState.mIndexedBuffers[index]));
}
}
return angle::Result::Continue;
}
angle::Result TransformFeedback::bindIndexedBuffer(const Context *context,
size_t index,
Buffer *buffer,
size_t offset,
size_t size)
{
ASSERT(index < mState.mIndexedBuffers.size());
bool isBound = context && context->isCurrentTransformFeedback(this);
if (isBound && mState.mIndexedBuffers[index].get())
{
mState.mIndexedBuffers[index]->onTFBindingChanged(context, false, true);
}
mState.mIndexedBuffers[index].set(context, buffer, offset, size);
if (isBound && buffer)
{
buffer->onTFBindingChanged(context, true, true);
}
return mImplementation->bindIndexedBuffer(context, index, mState.mIndexedBuffers[index]);
}
const OffsetBindingPointer<Buffer> &TransformFeedback::getIndexedBuffer(size_t index) const
{
ASSERT(index < mState.mIndexedBuffers.size());
return mState.mIndexedBuffers[index];
}
size_t TransformFeedback::getIndexedBufferCount() const
{
return mState.mIndexedBuffers.size();
}
bool TransformFeedback::buffersBoundForOtherUseInWebGL() const
{
for (auto &buffer : mState.mIndexedBuffers)
{
if (buffer.get() && buffer->isBoundToTFAndNonTFSimultaneously())
{
return true;
}
}
return false;
}
bool TransformFeedback::isBufferBound(BufferID bufferID) const
{
for (const auto &buffer : mState.mIndexedBuffers)
{
if (buffer.id() == bufferID)
{
return true;
}
}
return false;
}
void TransformFeedback::onBindingChanged(const Context *context, bool bound)
{
for (auto &buffer : mState.mIndexedBuffers)
{
if (buffer.get())
{
buffer->onTFBindingChanged(context, bound, true);
}
}
}
const std::vector<OffsetBindingPointer<Buffer>> &TransformFeedback::getIndexedBuffers() const
{
return mState.mIndexedBuffers;
}
} // namespace gl