blob: 577717aa315887d13f8f39e8b0119f3701146bb6 [file] [log] [blame]
//
// Copyright 2016 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.
//
// VertexArrayVk.cpp:
// Implements the class methods for VertexArrayVk.
//
#include "libANGLE/renderer/vulkan/VertexArrayVk.h"
#include "common/debug.h"
#include "libANGLE/Context.h"
#include "libANGLE/renderer/vulkan/BufferVk.h"
#include "libANGLE/renderer/vulkan/CommandGraph.h"
#include "libANGLE/renderer/vulkan/ContextVk.h"
#include "libANGLE/renderer/vulkan/RendererVk.h"
#include "libANGLE/renderer/vulkan/vk_format_utils.h"
namespace rx
{
namespace
{
constexpr size_t kDynamicVertexDataSize = 1024 * 1024;
constexpr size_t kDynamicIndexDataSize = 1024 * 8;
} // anonymous namespace
VertexArrayVk::VertexArrayVk(const gl::VertexArrayState &state, RendererVk *renderer)
: VertexArrayImpl(state),
mCurrentArrayBufferHandles{},
mCurrentArrayBufferOffsets{},
mCurrentArrayBufferResources{},
mCurrentElementArrayBufferHandle(VK_NULL_HANDLE),
mCurrentElementArrayBufferOffset(0),
mCurrentElementArrayBufferResource(nullptr),
mDynamicVertexData(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, kDynamicVertexDataSize),
mDynamicIndexData(VK_BUFFER_USAGE_INDEX_BUFFER_BIT, kDynamicIndexDataSize),
mLineLoopHelper(renderer),
mDirtyLineLoopTranslation(true),
mVertexBuffersDirty(false),
mIndexBufferDirty(false)
{
mCurrentArrayBufferHandles.fill(VK_NULL_HANDLE);
mCurrentArrayBufferOffsets.fill(0);
mCurrentArrayBufferResources.fill(nullptr);
mPackedInputBindings.fill({0, 0});
mPackedInputAttributes.fill({0, 0, 0});
mDynamicVertexData.init(1, renderer);
mDynamicIndexData.init(1, renderer);
}
VertexArrayVk::~VertexArrayVk()
{
}
void VertexArrayVk::destroy(const gl::Context *context)
{
VkDevice device = vk::GetImpl(context)->getRenderer()->getDevice();
mDynamicVertexData.destroy(device);
mDynamicIndexData.destroy(device);
mLineLoopHelper.destroy(device);
}
gl::Error VertexArrayVk::streamVertexData(RendererVk *renderer,
const gl::AttributesMask &attribsToStream,
const gl::DrawCallParams &drawCallParams)
{
ASSERT(!attribsToStream.none());
const auto &attribs = mState.getVertexAttributes();
const auto &bindings = mState.getVertexBindings();
const size_t lastVertex = drawCallParams.firstVertex() + drawCallParams.vertexCount();
// TODO(fjhenigman): When we have a bunch of interleaved attributes, they end up
// un-interleaved, wasting space and copying time. Consider improving on that.
for (size_t attribIndex : attribsToStream)
{
const gl::VertexAttribute &attrib = attribs[attribIndex];
const gl::VertexBinding &binding = bindings[attrib.bindingIndex];
ASSERT(attrib.enabled && binding.getBuffer().get() == nullptr);
// TODO(fjhenigman): Work with more formats than just GL_FLOAT.
if (attrib.type != GL_FLOAT)
{
UNIMPLEMENTED();
return gl::InternalError();
}
// Only [firstVertex, lastVertex] is needed by the upcoming draw so that
// is all we copy, but we allocate space for [0, lastVertex] so indexing
// will work. If we don't start at zero all the indices will be off.
// TODO(fjhenigman): See if we can account for indices being off by adjusting
// the offset, thus avoiding wasted memory.
const size_t firstByte = drawCallParams.firstVertex() * binding.getStride();
const size_t lastByte =
lastVertex * binding.getStride() + gl::ComputeVertexAttributeTypeSize(attrib);
uint8_t *dst = nullptr;
uint32_t offset = 0;
ANGLE_TRY(mDynamicVertexData.allocate(
renderer, lastByte, &dst, &mCurrentArrayBufferHandles[attribIndex], &offset, nullptr));
mCurrentArrayBufferOffsets[attribIndex] = static_cast<VkDeviceSize>(offset);
memcpy(dst + firstByte, static_cast<const uint8_t *>(attrib.pointer) + firstByte,
lastByte - firstByte);
}
ANGLE_TRY(mDynamicVertexData.flush(renderer->getDevice()));
mDynamicVertexData.releaseRetainedBuffers(renderer);
return gl::NoError();
}
gl::Error VertexArrayVk::streamIndexData(RendererVk *renderer,
const gl::DrawCallParams &drawCallParams)
{
ASSERT(!mState.getElementArrayBuffer().get());
uint32_t offset = 0;
const GLsizei amount = sizeof(GLushort) * drawCallParams.indexCount();
GLubyte *dst = nullptr;
ANGLE_TRY(mDynamicIndexData.allocate(renderer, amount, &dst, &mCurrentElementArrayBufferHandle,
&offset, nullptr));
if (drawCallParams.type() == GL_UNSIGNED_BYTE)
{
// Unsigned bytes don't have direct support in Vulkan so we have to expand the
// memory to a GLushort.
const GLubyte *in = static_cast<const GLubyte *>(drawCallParams.indices());
GLushort *expandedDst = reinterpret_cast<GLushort *>(dst);
for (GLsizei index = 0; index < drawCallParams.indexCount(); index++)
{
expandedDst[index] = static_cast<GLushort>(in[index]);
}
}
else
{
memcpy(dst, drawCallParams.indices(), amount);
}
ANGLE_TRY(mDynamicIndexData.flush(renderer->getDevice()));
mDynamicIndexData.releaseRetainedBuffers(renderer);
mCurrentElementArrayBufferOffset = offset;
return gl::NoError();
}
#define ANGLE_VERTEX_DIRTY_ATTRIB_FUNC(INDEX) \
case gl::VertexArray::DIRTY_BIT_ATTRIB_0 + INDEX: \
syncDirtyAttrib(attribs[INDEX], bindings[attribs[INDEX].bindingIndex], INDEX); \
invalidatePipeline = true; \
break;
#define ANGLE_VERTEX_DIRTY_BINDING_FUNC(INDEX) \
case gl::VertexArray::DIRTY_BIT_BINDING_0 + INDEX: \
syncDirtyAttrib(attribs[INDEX], bindings[attribs[INDEX].bindingIndex], INDEX); \
invalidatePipeline = true; \
break;
#define ANGLE_VERTEX_DIRTY_BUFFER_DATA_FUNC(INDEX) \
case gl::VertexArray::DIRTY_BIT_BUFFER_DATA_0 + INDEX: \
break;
gl::Error VertexArrayVk::syncState(const gl::Context *context,
const gl::VertexArray::DirtyBits &dirtyBits,
const gl::VertexArray::DirtyAttribBitsArray &attribBits,
const gl::VertexArray::DirtyBindingBitsArray &bindingBits)
{
ASSERT(dirtyBits.any());
bool invalidatePipeline = false;
// Invalidate current pipeline.
ContextVk *contextVk = vk::GetImpl(context);
// Rebuild current attribute buffers cache. This will fail horribly if the buffer changes.
// TODO(jmadill): Handle buffer storage changes.
const auto &attribs = mState.getVertexAttributes();
const auto &bindings = mState.getVertexBindings();
for (size_t dirtyBit : dirtyBits)
{
switch (dirtyBit)
{
case gl::VertexArray::DIRTY_BIT_ELEMENT_ARRAY_BUFFER:
{
gl::Buffer *bufferGL = mState.getElementArrayBuffer().get();
if (bufferGL)
{
BufferVk *bufferVk = vk::GetImpl(bufferGL);
mCurrentElementArrayBufferResource = bufferVk;
mCurrentElementArrayBufferHandle = bufferVk->getVkBuffer().getHandle();
mCurrentElementArrayBufferOffset = 0;
}
else
{
mCurrentElementArrayBufferResource = nullptr;
mCurrentElementArrayBufferHandle = VK_NULL_HANDLE;
mCurrentElementArrayBufferOffset = 0;
}
mIndexBufferDirty = true;
mDirtyLineLoopTranslation = true;
break;
}
case gl::VertexArray::DIRTY_BIT_ELEMENT_ARRAY_BUFFER_DATA:
mLineLoopBufferFirstIndex.reset();
mLineLoopBufferLastIndex.reset();
mDirtyLineLoopTranslation = true;
break;
ANGLE_VERTEX_INDEX_CASES(ANGLE_VERTEX_DIRTY_ATTRIB_FUNC);
ANGLE_VERTEX_INDEX_CASES(ANGLE_VERTEX_DIRTY_BINDING_FUNC);
ANGLE_VERTEX_INDEX_CASES(ANGLE_VERTEX_DIRTY_BUFFER_DATA_FUNC);
default:
UNREACHABLE();
break;
}
}
if (invalidatePipeline)
{
mVertexBuffersDirty = true;
contextVk->invalidateCurrentPipeline();
}
return gl::NoError();
}
void VertexArrayVk::syncDirtyAttrib(const gl::VertexAttribute &attrib,
const gl::VertexBinding &binding,
size_t attribIndex)
{
// Invalidate the input description for pipelines.
mDirtyPackedInputs.set(attribIndex);
if (attrib.enabled)
{
gl::Buffer *bufferGL = binding.getBuffer().get();
if (bufferGL)
{
BufferVk *bufferVk = vk::GetImpl(bufferGL);
mCurrentArrayBufferResources[attribIndex] = bufferVk;
mCurrentArrayBufferHandles[attribIndex] = bufferVk->getVkBuffer().getHandle();
}
else
{
mCurrentArrayBufferResources[attribIndex] = nullptr;
mCurrentArrayBufferHandles[attribIndex] = VK_NULL_HANDLE;
}
// TODO(jmadill): Offset handling. Assume zero for now.
mCurrentArrayBufferOffsets[attribIndex] = 0;
}
else
{
UNIMPLEMENTED();
}
}
const gl::AttribArray<VkBuffer> &VertexArrayVk::getCurrentArrayBufferHandles() const
{
return mCurrentArrayBufferHandles;
}
const gl::AttribArray<VkDeviceSize> &VertexArrayVk::getCurrentArrayBufferOffsets() const
{
return mCurrentArrayBufferOffsets;
}
void VertexArrayVk::updateArrayBufferReadDependencies(vk::CommandGraphNode *readingNode,
const gl::AttributesMask &activeAttribsMask,
Serial serial)
{
// Handle the bound array buffers.
for (size_t attribIndex : activeAttribsMask)
{
if (mCurrentArrayBufferResources[attribIndex])
mCurrentArrayBufferResources[attribIndex]->onReadResource(readingNode, serial);
}
}
void VertexArrayVk::updateElementArrayBufferReadDependency(vk::CommandGraphNode *readingNode,
Serial serial)
{
// Handle the bound element array buffer.
if (mCurrentElementArrayBufferResource)
{
mCurrentElementArrayBufferResource->onReadResource(readingNode, serial);
}
}
void VertexArrayVk::getPackedInputDescriptions(vk::PipelineDesc *pipelineDesc)
{
updatePackedInputDescriptions();
pipelineDesc->updateVertexInputInfo(mPackedInputBindings, mPackedInputAttributes);
}
void VertexArrayVk::updatePackedInputDescriptions()
{
if (!mDirtyPackedInputs.any())
{
return;
}
const auto &attribs = mState.getVertexAttributes();
const auto &bindings = mState.getVertexBindings();
for (auto attribIndex : mDirtyPackedInputs)
{
const auto &attrib = attribs[attribIndex];
const auto &binding = bindings[attrib.bindingIndex];
if (attrib.enabled)
{
updatePackedInputInfo(static_cast<uint32_t>(attribIndex), binding, attrib);
}
else
{
UNIMPLEMENTED();
}
}
mDirtyPackedInputs.reset();
}
void VertexArrayVk::updatePackedInputInfo(uint32_t attribIndex,
const gl::VertexBinding &binding,
const gl::VertexAttribute &attrib)
{
vk::PackedVertexInputBindingDesc &bindingDesc = mPackedInputBindings[attribIndex];
size_t attribSize = gl::ComputeVertexAttributeTypeSize(attrib);
ASSERT(attribSize <= std::numeric_limits<uint16_t>::max());
bindingDesc.stride = static_cast<uint16_t>(binding.getStride());
bindingDesc.inputRate = static_cast<uint16_t>(
binding.getDivisor() > 0 ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX);
gl::VertexFormatType vertexFormatType = gl::GetVertexFormatType(attrib);
VkFormat vkFormat = vk::GetNativeVertexFormat(vertexFormatType);
ASSERT(vkFormat <= std::numeric_limits<uint16_t>::max());
vk::PackedVertexInputAttributeDesc &attribDesc = mPackedInputAttributes[attribIndex];
attribDesc.format = static_cast<uint16_t>(vkFormat);
attribDesc.location = static_cast<uint16_t>(attribIndex);
attribDesc.offset = static_cast<uint32_t>(ComputeVertexAttributeOffset(attrib, binding));
}
gl::Error VertexArrayVk::drawArrays(const gl::Context *context,
RendererVk *renderer,
const gl::DrawCallParams &drawCallParams,
vk::CommandGraphNode *drawNode,
bool newCommandBuffer)
{
vk::CommandBuffer *commandBuffer = drawNode->getInsideRenderPassCommands();
ASSERT(commandBuffer->valid());
ANGLE_TRY(onDraw(context, renderer, drawCallParams, drawNode, newCommandBuffer));
// Note: Vertex indexes can be arbitrarily large.
uint32_t clampedVertexCount = drawCallParams.getClampedVertexCount<uint32_t>();
if (drawCallParams.mode() != gl::PrimitiveMode::LineLoop)
{
commandBuffer->draw(clampedVertexCount, 1, drawCallParams.firstVertex(), 0);
return gl::NoError();
}
// Handle GL_LINE_LOOP drawArrays.
size_t lastVertex = static_cast<size_t>(drawCallParams.firstVertex() + clampedVertexCount);
if (!mLineLoopBufferFirstIndex.valid() || !mLineLoopBufferLastIndex.valid() ||
mLineLoopBufferFirstIndex != drawCallParams.firstVertex() ||
mLineLoopBufferLastIndex != lastVertex)
{
ANGLE_TRY(mLineLoopHelper.getIndexBufferForDrawArrays(renderer, drawCallParams,
&mCurrentElementArrayBufferHandle,
&mCurrentElementArrayBufferOffset));
mLineLoopBufferFirstIndex = drawCallParams.firstVertex();
mLineLoopBufferLastIndex = lastVertex;
}
commandBuffer->bindIndexBuffer(mCurrentElementArrayBufferHandle,
mCurrentElementArrayBufferOffset, VK_INDEX_TYPE_UINT32);
vk::LineLoopHelper::Draw(clampedVertexCount, commandBuffer);
return gl::NoError();
}
gl::Error VertexArrayVk::drawElements(const gl::Context *context,
RendererVk *renderer,
const gl::DrawCallParams &drawCallParams,
vk::CommandGraphNode *drawNode,
bool newCommandBuffer)
{
vk::CommandBuffer *commandBuffer = drawNode->getInsideRenderPassCommands();
ASSERT(commandBuffer->valid());
if (drawCallParams.mode() != gl::PrimitiveMode::LineLoop)
{
ANGLE_TRY(onIndexedDraw(context, renderer, drawCallParams, drawNode, newCommandBuffer));
commandBuffer->drawIndexed(drawCallParams.indexCount(), 1, 0, 0, 0);
return gl::NoError();
}
// Handle GL_LINE_LOOP drawElements.
if (mDirtyLineLoopTranslation)
{
gl::Buffer *elementArrayBuffer = mState.getElementArrayBuffer().get();
VkIndexType indexType = gl_vk::GetIndexType(drawCallParams.type());
if (!elementArrayBuffer)
{
ANGLE_TRY(mLineLoopHelper.getIndexBufferForClientElementArray(
renderer, drawCallParams.indices(), indexType, drawCallParams.indexCount(),
&mCurrentElementArrayBufferHandle, &mCurrentElementArrayBufferOffset));
}
else
{
// When using an element array buffer, 'indices' is an offset to the first element.
intptr_t offset = reinterpret_cast<intptr_t>(drawCallParams.indices());
BufferVk *elementArrayBufferVk = vk::GetImpl(elementArrayBuffer);
ANGLE_TRY(mLineLoopHelper.getIndexBufferForElementArrayBuffer(
renderer, elementArrayBufferVk, indexType, drawCallParams.indexCount(), offset,
&mCurrentElementArrayBufferHandle, &mCurrentElementArrayBufferOffset));
}
}
ANGLE_TRY(onIndexedDraw(context, renderer, drawCallParams, drawNode, newCommandBuffer));
vk::LineLoopHelper::Draw(drawCallParams.indexCount(), commandBuffer);
return gl::NoError();
}
gl::Error VertexArrayVk::onDraw(const gl::Context *context,
RendererVk *renderer,
const gl::DrawCallParams &drawCallParams,
vk::CommandGraphNode *drawNode,
bool newCommandBuffer)
{
const gl::State &state = context->getGLState();
const gl::Program *programGL = state.getProgram();
const gl::AttributesMask &clientAttribs = mState.getEnabledClientMemoryAttribsMask();
const gl::AttributesMask &activeAttribs = programGL->getActiveAttribLocationsMask();
uint32_t maxAttrib = programGL->getState().getMaxActiveAttribLocation();
if (clientAttribs.any())
{
const gl::AttributesMask &attribsToStream = (clientAttribs & activeAttribs);
if (attribsToStream.any())
{
ANGLE_TRY(drawCallParams.ensureIndexRangeResolved(context));
ANGLE_TRY(streamVertexData(renderer, attribsToStream, drawCallParams));
vk::CommandBuffer *commandBuffer = drawNode->getInsideRenderPassCommands();
commandBuffer->bindVertexBuffers(0, maxAttrib, mCurrentArrayBufferHandles.data(),
mCurrentArrayBufferOffsets.data());
}
}
else if (mVertexBuffersDirty || newCommandBuffer)
{
if (maxAttrib > 0)
{
vk::CommandBuffer *commandBuffer = drawNode->getInsideRenderPassCommands();
commandBuffer->bindVertexBuffers(0, maxAttrib, mCurrentArrayBufferHandles.data(),
mCurrentArrayBufferOffsets.data());
updateArrayBufferReadDependencies(drawNode, activeAttribs,
renderer->getCurrentQueueSerial());
}
mVertexBuffersDirty = false;
// This forces the binding to happen if we follow a drawElement call from a drawArrays call.
mIndexBufferDirty = true;
// If we've had a drawElements call with a line loop before, we want to make sure this is
// invalidated the next time drawElements is called since we use the same index buffer for
// both calls.
mDirtyLineLoopTranslation = true;
}
return gl::NoError();
}
gl::Error VertexArrayVk::onIndexedDraw(const gl::Context *context,
RendererVk *renderer,
const gl::DrawCallParams &drawCallParams,
vk::CommandGraphNode *drawNode,
bool newCommandBuffer)
{
ANGLE_TRY(onDraw(context, renderer, drawCallParams, drawNode, newCommandBuffer));
if (!mState.getElementArrayBuffer().get() &&
drawCallParams.mode() != gl::PrimitiveMode::LineLoop)
{
ANGLE_TRY(drawCallParams.ensureIndexRangeResolved(context));
ANGLE_TRY(streamIndexData(renderer, drawCallParams));
vk::CommandBuffer *commandBuffer = drawNode->getInsideRenderPassCommands();
commandBuffer->bindIndexBuffer(mCurrentElementArrayBufferHandle,
mCurrentElementArrayBufferOffset,
gl_vk::GetIndexType(drawCallParams.type()));
}
else if (mIndexBufferDirty || newCommandBuffer)
{
if (drawCallParams.type() == GL_UNSIGNED_BYTE)
{
// TODO(fjhenigman): Index format translation.
UNIMPLEMENTED();
return gl::InternalError()
<< "Unsigned byte translation is not implemented for indices in a buffer object";
}
vk::CommandBuffer *commandBuffer = drawNode->getInsideRenderPassCommands();
commandBuffer->bindIndexBuffer(mCurrentElementArrayBufferHandle,
mCurrentElementArrayBufferOffset,
gl_vk::GetIndexType(drawCallParams.type()));
updateElementArrayBufferReadDependency(drawNode, renderer->getCurrentQueueSerial());
mIndexBufferDirty = false;
// If we've had a drawArrays call with a line loop before, we want to make sure this is
// invalidated the next time drawArrays is called since we use the same index buffer for
// both calls.
mLineLoopBufferFirstIndex.reset();
mLineLoopBufferLastIndex.reset();
}
return gl::NoError();
}
} // namespace rx