Vulkan, OpenGL, Metal: Let draws bind a second vertex stream

Vulkan pipelines can have a second vertex binding, and draws a second vertex buffer for it. OpenGL input
layout entries can come from a second buffer, with its own stride, given with the draw. Metal gets a second
vertex buffer index. Nothing uses them yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-11-05 00:29:16 +01:00
1 parent 0051dca319
commit 39b7e8dc2f
9 files changed
+65 -20

No files matched your search

+4 -2
View File
@@ -49,6 +49,8 @@
// bottom. Vertex data goes above all of them.
enum {
METAL_VERTEX_BUFFER_INDEX = 16,
// A second vertex stream, for the pipelines with one.
METAL_VERTEX_BUFFER_INDEX1 = 17,
// Push constants (only compute shaders have them) go in a buffer of their own, set with setBytes.
METAL_PUSH_CONSTANT_INDEX = 15,
METAL_MAX_TEXTURES = 8,
@@ -527,8 +529,8 @@ private:
MTLDepthClipMode depthClip;
MTLViewport viewport;
MTLScissorRect scissor;
__unsafe_unretained id<MTLBuffer> vertexBuffers[METAL_VERTEX_BUFFER_INDEX + 1];
uint32_t vertexBufferOffsets[METAL_VERTEX_BUFFER_INDEX + 1];
__unsafe_unretained id<MTLBuffer> vertexBuffers[METAL_VERTEX_BUFFER_INDEX1 + 1];
uint32_t vertexBufferOffsets[METAL_VERTEX_BUFFER_INDEX1 + 1];
__unsafe_unretained id<MTLBuffer> fragmentBuffers[METAL_MAX_BUFFERS];
uint32_t fragmentBufferOffsets[METAL_MAX_BUFFERS];
__unsafe_unretained id<MTLTexture> fragmentTextures[METAL_MAX_TEXTURES];
+21 -5
View File
@@ -1341,19 +1341,35 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
case GLRRenderCommand::DRAW:
{
GLRInputLayout *layout = c.draw.inputLayout;
if (attrMask != layout->semanticsMask_) {
EnableDisableVertexArrays(attrMask, layout->semanticsMask_);
attrMask = layout->semanticsMask_;
}
// Stream 1 first, so stream 0 is left bound.
if (layout->stride1) {
_dbg_assert_(c.draw.vertexBuffer1 && !c.draw.vertexBuffer1->Mapped());
GLuint buf1 = c.draw.vertexBuffer1->buffer_;
if (buf1 != curArrayBuffer) {
glBindBuffer(GL_ARRAY_BUFFER, buf1);
curArrayBuffer = buf1;
}
for (const auto &entry : layout->entries) {
if (entry.stream == 1) {
glVertexAttribPointer(entry.location, entry.count, entry.type, entry.normalized, layout->stride1, (const void *)(c.draw.vertexOffset1 + entry.offset));
}
}
}
GLuint buf = c.draw.vertexBuffer->buffer_;
_dbg_assert_(!c.draw.vertexBuffer->Mapped());
if (buf != curArrayBuffer) {
glBindBuffer(GL_ARRAY_BUFFER, buf);
curArrayBuffer = buf;
}
if (attrMask != layout->semanticsMask_) {
EnableDisableVertexArrays(attrMask, layout->semanticsMask_);
attrMask = layout->semanticsMask_;
}
for (size_t i = 0; i < layout->entries.size(); i++) {
auto &entry = layout->entries[i];
glVertexAttribPointer(entry.location, entry.count, entry.type, entry.normalized, layout->stride, (const void *)(c.draw.vertexOffset + entry.offset));
if (entry.stream == 0) {
glVertexAttribPointer(entry.location, entry.count, entry.type, entry.normalized, layout->stride, (const void *)(c.draw.vertexOffset + entry.offset));
}
}
if (c.draw.indexBuffer) {
GLuint buf = c.draw.indexBuffer->buffer_;
+3
View File
@@ -109,8 +109,11 @@ struct GLRRenderData {
GLRInputLayout *inputLayout;
GLRBuffer *vertexBuffer;
GLRBuffer *indexBuffer;
// For the input layout's stream 1.
GLRBuffer *vertexBuffer1;
uint32_t vertexOffset;
uint32_t indexOffset;
uint32_t vertexOffset1;
GLenum mode; // primitive
GLint first;
GLint count;
+12 -3
View File
@@ -204,9 +204,12 @@ public:
GLenum type;
GLboolean normalized;
intptr_t offset;
// 1: from the draw's second vertex buffer, with stride1.
int stream = 0;
};
std::vector<Entry> entries;
int stride;
int stride1 = 0;
int semanticsMask_ = 0;
};
@@ -355,13 +358,14 @@ public:
return step.create_program.program;
}
GLRInputLayout *CreateInputLayout(const std::vector<GLRInputLayout::Entry> &entries, int stride) {
GLRInputLayout *CreateInputLayout(const std::vector<GLRInputLayout::Entry> &entries, int stride, int stride1 = 0) {
std::lock_guard<std::mutex> lock(initStepsMutex_);
GLRInitStep &step = initSteps_.push_uninitialized();
step.stepType = GLRInitStepType::CREATE_INPUT_LAYOUT;
step.create_input_layout.inputLayout = new GLRInputLayout();
step.create_input_layout.inputLayout->entries = entries;
step.create_input_layout.inputLayout->stride = stride;
step.create_input_layout.inputLayout->stride1 = stride1;
for (auto &iter : step.create_input_layout.inputLayout->entries) {
step.create_input_layout.inputLayout->semanticsMask_ |= 1 << iter.location;
}
@@ -798,13 +802,16 @@ public:
data.clear.scissorH = scissorH;
}
void Draw(GLRInputLayout *inputLayout, GLRBuffer *vertexBuffer, uint32_t vertexOffset, GLenum mode, int first, int count) {
// vertexBuffer1 is for the input layout's stream 1 entries, if it has any.
void Draw(GLRInputLayout *inputLayout, GLRBuffer *vertexBuffer, uint32_t vertexOffset, GLenum mode, int first, int count, GLRBuffer *vertexBuffer1 = nullptr, uint32_t vertexOffset1 = 0) {
_dbg_assert_(vertexBuffer && curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::DRAW;
data.draw.inputLayout = inputLayout;
data.draw.vertexOffset = vertexOffset;
data.draw.vertexBuffer = vertexBuffer;
data.draw.vertexBuffer1 = vertexBuffer1;
data.draw.vertexOffset1 = vertexOffset1;
data.draw.indexBuffer = nullptr;
data.draw.mode = mode;
data.draw.first = first;
@@ -815,13 +822,15 @@ public:
// Would really love to have a basevertex parameter, but impossible in unextended GLES, without glDrawElementsBaseVertex, unfortunately.
// If maxIndex is known (>= 0), it's passed on to the driver through glDrawRangeElements, which saves drivers that need the index
// range (like Panfrost) from scanning the index data on the CPU for every draw.
void DrawIndexed(GLRInputLayout *inputLayout, GLRBuffer *vertexBuffer, uint32_t vertexOffset, GLRBuffer *indexBuffer, uint32_t indexOffset, GLenum mode, int count, GLenum indexType, int instances = 1, int maxIndex = -1) {
void DrawIndexed(GLRInputLayout *inputLayout, GLRBuffer *vertexBuffer, uint32_t vertexOffset, GLRBuffer *indexBuffer, uint32_t indexOffset, GLenum mode, int count, GLenum indexType, int instances = 1, int maxIndex = -1, GLRBuffer *vertexBuffer1 = nullptr, uint32_t vertexOffset1 = 0) {
_dbg_assert_(vertexBuffer && indexBuffer && curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::DRAW;
data.draw.inputLayout = inputLayout;
data.draw.vertexOffset = vertexOffset;
data.draw.vertexBuffer = vertexBuffer;
data.draw.vertexBuffer1 = vertexBuffer1;
data.draw.vertexOffset1 = vertexOffset1;
data.draw.indexBuffer = indexBuffer;
data.draw.indexOffset = indexOffset;
data.draw.mode = mode;
+6 -3
View File
@@ -1374,8 +1374,9 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c
_dbg_assert_(set != VK_NULL_HANDLE);
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &set, c.drawIndexed.numUboOffsets, c.drawIndexed.uboOffsets);
vkCmdBindIndexBuffer(cmd, c.drawIndexed.ibuffer, c.drawIndexed.ioffset, VK_INDEX_TYPE_UINT16);
VkDeviceSize voffset = c.drawIndexed.voffset;
vkCmdBindVertexBuffers(cmd, 0, 1, &c.drawIndexed.vbuffer, &voffset);
const VkBuffer vbuffers[2] = { c.drawIndexed.vbuffer, c.drawIndexed.vbuffer1 };
const VkDeviceSize voffsets[2] = { c.drawIndexed.voffset, c.drawIndexed.voffset1 };
vkCmdBindVertexBuffers(cmd, 0, c.drawIndexed.vbuffer1 ? 2 : 1, vbuffers, voffsets);
vkCmdDrawIndexed(cmd, c.drawIndexed.count, c.drawIndexed.instances, 0, 0, 0);
}
break;
@@ -1387,7 +1388,9 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c
_dbg_assert_(set != VK_NULL_HANDLE);
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &set, c.draw.numUboOffsets, c.draw.uboOffsets);
if (c.draw.vbuffer) {
vkCmdBindVertexBuffers(cmd, 0, 1, &c.draw.vbuffer, &c.draw.voffset);
const VkBuffer vbuffers[2] = { c.draw.vbuffer, c.draw.vbuffer1 };
const VkDeviceSize voffsets[2] = { c.draw.voffset, c.draw.voffset1 };
vkCmdBindVertexBuffers(cmd, 0, c.draw.vbuffer1 ? 2 : 1, vbuffers, voffsets);
}
vkCmdDraw(cmd, c.draw.count, 1, c.draw.offset, 0);
}
+6
View File
@@ -69,6 +69,9 @@ struct VkRenderData {
VkDeviceSize voffset;
uint32_t count;
uint32_t offset;
// For the second vertex stream, if any.
VkBuffer vbuffer1;
uint32_t voffset1;
} draw;
struct {
uint32_t descSetIndex;
@@ -80,6 +83,9 @@ struct VkRenderData {
uint32_t voffset;
uint32_t ioffset;
uint32_t count;
// For the second vertex stream, if any.
VkBuffer vbuffer1;
uint32_t voffset1;
} drawIndexed;
struct {
uint32_t clearColor;
+9 -3
View File
@@ -101,7 +101,8 @@ public:
VkPrimitiveTopology topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
VkVertexInputAttributeDescription attrs[8]{};
VkVertexInputBindingDescription ibd{};
// The second is only used by pipelines with two vertex streams.
VkVertexInputBindingDescription ibd[2]{};
VkPipelineVertexInputStateCreateInfo vis{ VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO };
VkPipelineViewportStateCreateInfo views{ VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO };
@@ -466,7 +467,8 @@ public:
return retval;
}
void Draw(int descSetIndex, int numUboOffsets, const uint32_t *uboOffsets, VkBuffer vbuffer, int voffset, int count, int offset = 0) {
// vbuffer1 is for the pipeline's second vertex stream, if it has one.
void Draw(int descSetIndex, int numUboOffsets, const uint32_t *uboOffsets, VkBuffer vbuffer, int voffset, int count, int offset = 0, VkBuffer vbuffer1 = VK_NULL_HANDLE, uint32_t voffset1 = 0) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER && curStepHasViewport_ && curStepHasScissor_);
VkRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = VKRRenderCommand::DRAW;
@@ -475,6 +477,8 @@ public:
data.draw.descSetIndex = descSetIndex;
data.draw.vbuffer = vbuffer;
data.draw.voffset = voffset;
data.draw.vbuffer1 = vbuffer1;
data.draw.voffset1 = voffset1;
data.draw.numUboOffsets = numUboOffsets;
_dbg_assert_(numUboOffsets <= ARRAY_SIZE(data.draw.uboOffsets));
for (int i = 0; i < numUboOffsets; i++)
@@ -482,7 +486,7 @@ public:
curRenderStep_->render.numDraws++;
}
void DrawIndexed(int descSetIndex, int numUboOffsets, const uint32_t *uboOffsets, VkBuffer vbuffer, int voffset, VkBuffer ibuffer, int ioffset, int count, int numInstances) {
void DrawIndexed(int descSetIndex, int numUboOffsets, const uint32_t *uboOffsets, VkBuffer vbuffer, int voffset, VkBuffer ibuffer, int ioffset, int count, int numInstances, VkBuffer vbuffer1 = VK_NULL_HANDLE, uint32_t voffset1 = 0) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER && curStepHasViewport_ && curStepHasScissor_);
VkRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = VKRRenderCommand::DRAW_INDEXED;
@@ -491,6 +495,8 @@ public:
data.drawIndexed.descSetIndex = descSetIndex;
data.drawIndexed.vbuffer = vbuffer;
data.drawIndexed.voffset = voffset;
data.drawIndexed.vbuffer1 = vbuffer1;
data.drawIndexed.voffset1 = voffset1;
data.drawIndexed.ibuffer = ibuffer;
data.drawIndexed.ioffset = ioffset;
data.drawIndexed.numUboOffsets = numUboOffsets;
+2 -2
View File
@@ -1320,13 +1320,13 @@ Pipeline *VKContext::CreateGraphicsPipeline(const PipelineDesc &desc, const char
_dbg_assert_((int)input->attributes.size() == (int)input->visc.vertexAttributeDescriptionCount);
pipeline->stride = input->binding.stride;
gDesc.ibd = input->binding;
gDesc.ibd[0] = input->binding;
for (size_t i = 0; i < input->attributes.size(); i++) {
gDesc.attrs[i] = input->attributes[i];
}
gDesc.vis.vertexAttributeDescriptionCount = input->visc.vertexAttributeDescriptionCount;
gDesc.vis.vertexBindingDescriptionCount = input->visc.vertexBindingDescriptionCount;
gDesc.vis.pVertexBindingDescriptions = &gDesc.ibd;
gDesc.vis.pVertexBindingDescriptions = gDesc.ibd;
gDesc.vis.pVertexAttributeDescriptions = gDesc.attrs;
gDesc.blend0 = blend->attachments[0];
+2 -2
View File
@@ -312,7 +312,7 @@ static VulkanPipeline *CreateVulkanPipeline(VulkanRenderManager *renderManager,
vertexStride = (int)sizeof(TransformedVertex);
}
VkVertexInputBindingDescription &ibd = desc->ibd;
VkVertexInputBindingDescription &ibd = desc->ibd[0];
ibd.binding = 0;
ibd.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
ibd.stride = vertexStride;
@@ -320,7 +320,7 @@ static VulkanPipeline *CreateVulkanPipeline(VulkanRenderManager *renderManager,
VkPipelineVertexInputStateCreateInfo &vis = desc->vis;
vis.flags = 0;
vis.vertexBindingDescriptionCount = 1;
vis.pVertexBindingDescriptions = &desc->ibd;
vis.pVertexBindingDescriptions = desc->ibd;
vis.vertexAttributeDescriptionCount = attributeCount;
vis.pVertexAttributeDescriptions = attrs;