Always use a 16 bit index array for quad indices, with multiple draws when necessary. See issue #1356.

This commit is contained in:
Alex Szpakowski
2017-12-24 15:54:03 -04:00
parent 0b7cb4a353
commit 3fb621af95
8 changed files with 97 additions and 231 deletions
+50 -85
View File
@@ -42,82 +42,35 @@ Buffer::~Buffer()
// QuadIndices
size_t QuadIndices::maxSize = 0;
size_t QuadIndices::elementSize = 0;
static const int MAX_VERTICES_PER_DRAW = LOVE_UINT16_MAX;
static const int MAX_QUADS_PER_DRAW = MAX_VERTICES_PER_DRAW / 4;
size_t QuadIndices::objectCount = 0;
Buffer *QuadIndices::indexBuffer = nullptr;
char *QuadIndices::indices = nullptr;
QuadIndices::QuadIndices(Graphics *gfx, size_t size)
: size(size)
QuadIndices::QuadIndices(Graphics *gfx)
{
// The upper limit is the maximum of uint32 divided by six (the number
// of indices per size) and divided by the size of uint32. This guarantees
// no overflows when calculating the array size in bytes.
if (size == 0 || size > ((uint32) -1) / 6 / sizeof(uint32))
throw love::Exception("Invalid number of quads.");
// Create a new / larger buffer if needed.
if (indexBuffer == nullptr || size > maxSize)
if (indexBuffer == nullptr)
{
Buffer *newbuffer = nullptr;
char *newindices = nullptr;
size_t buffersize = sizeof(uint16) * MAX_QUADS_PER_DRAW * 6;
// Depending on the size, a switch to int and more memory is needed.
IndexDataType targettype = getType(size);
size_t elemsize = vertex::getIndexDataSize(targettype);
indexBuffer = gfx->newBuffer(buffersize, nullptr, BUFFER_INDEX, vertex::USAGE_STATIC, 0);
size_t buffersize = elemsize * 6 * size;
try
{
newbuffer = gfx->newBuffer(buffersize, nullptr, BUFFER_INDEX, vertex::USAGE_STATIC, 0);
newindices = new char[buffersize];
}
catch (std::bad_alloc &)
{
delete newbuffer;
delete[] newindices;
throw love::Exception("Out of memory.");
}
// Allocation of the new Buffer succeeded.
// The old Buffer can now be deleted.
delete indexBuffer;
indexBuffer = newbuffer;
delete[] indices;
indices = newindices;
maxSize = size;
elementSize = elemsize;
switch (targettype)
{
case INDEX_UINT16:
fill<uint16>();
break;
case INDEX_UINT32:
fill<uint32>();
break;
case INDEX_MAX_ENUM:
break;
}
Buffer::Mapper map(*indexBuffer);
vertex::fillIndices(vertex::TriangleIndexMode::QUADS, 0, MAX_VERTICES_PER_DRAW, (uint16 *) map.get());
}
objectCount++;
}
QuadIndices::QuadIndices(const QuadIndices &other)
: size(other.size)
QuadIndices::QuadIndices(const QuadIndices &)
{
objectCount++;
}
QuadIndices &QuadIndices::operator = (const QuadIndices &other)
QuadIndices &QuadIndices::operator = (const QuadIndices &)
{
size = other.size;
return *this;
}
@@ -130,30 +83,54 @@ QuadIndices::~QuadIndices()
{
delete indexBuffer;
indexBuffer = nullptr;
delete[] indices;
indices = nullptr;
}
}
size_t QuadIndices::getSize() const
static inline void advanceVertexOffsets(const vertex::Attributes &attributes, vertex::Buffers &buffers, int vertexcount)
{
return size;
// TODO: Figure out a better way to avoid touching the same buffer multiple
// times, if multiple attributes share the buffer.
uint32 touchedbuffers = 0;
for (unsigned int i = 0; i < vertex::Attributes::MAX; i++)
{
if (!attributes.isEnabled(i))
continue;
auto &attrib = attributes.attribs[i];
uint32 bufferbit = 1u << attrib.bufferindex;
if ((touchedbuffers & bufferbit) == 0)
{
touchedbuffers |= bufferbit;
buffers.info[attrib.bufferindex].offset += attrib.stride * vertexcount;
}
}
}
size_t QuadIndices::getIndexCount(size_t elements) const
void QuadIndices::draw(Graphics *gfx, int quadstart, int quadcount, const vertex::Attributes &attributes, vertex::Buffers buffers, Texture *texture)
{
return elements * 6;
}
Graphics::DrawIndexedCommand cmd(&attributes, &buffers, indexBuffer);
cmd.primitiveType = PRIMITIVE_TRIANGLES;
cmd.indexBufferOffset = 0;
cmd.indexType = INDEX_UINT16;
cmd.texture = texture;
IndexDataType QuadIndices::getType(size_t s) const
{
return vertex::getIndexDataTypeFromMax(getIndexCount(s));
}
// TODO: We can use glDrawElementsBaseVertex when supported, instead of
// advancing the vertex offset.
if (quadstart > 0)
advanceVertexOffsets(attributes, buffers, quadstart * 4);
size_t QuadIndices::getElementSize() const
{
return elementSize;
for (int quadindex = 0; quadindex < quadcount; quadindex += MAX_QUADS_PER_DRAW)
{
int quaddrawcount = std::min(MAX_QUADS_PER_DRAW, quadcount - quadindex);
if (quadindex > 0)
advanceVertexOffsets(attributes, buffers, quaddrawcount * 4);
cmd.indexCount = quaddrawcount * 6;
gfx->draw(cmd);
}
}
Buffer *QuadIndices::getBuffer() const
@@ -161,17 +138,5 @@ Buffer *QuadIndices::getBuffer() const
return indexBuffer;
}
const void *QuadIndices::getIndices(size_t offset) const
{
return indices + offset;
}
template <typename T>
void QuadIndices::fill()
{
vertex::fillIndices(vertex::TriangleIndexMode::QUADS, 0, maxSize * 4, (T *) indices);
indexBuffer->fill(0, indexBuffer->getSize(), indices);
}
} // graphics
} // love
+10 -91
View File
@@ -35,6 +35,7 @@ namespace graphics
{
class Graphics;
class Texture;
/**
* A block of GPU-owned memory. Currently meant for internal use.
@@ -52,25 +53,13 @@ public:
Buffer(size_t size, BufferType type, vertex::Usage usage, uint32 mapflags);
virtual ~Buffer();
size_t getSize() const
{
return size;
}
size_t getSize() const { return size; }
BufferType getType() const
{
return type;
}
BufferType getType() const { return type; }
vertex::Usage getUsage() const
{
return usage;
}
vertex::Usage getUsage() const { return usage; }
bool isMapped() const
{
return is_mapped;
}
bool isMapped() const { return is_mapped; }
/**
* Map the Buffer to client memory.
@@ -106,10 +95,7 @@ public:
**/
virtual void copyTo(size_t offset, size_t size, Buffer *other, size_t otheroffset) = 0;
uint32 getMapFlags() const
{
return map_flags;
}
uint32 getMapFlags() const { return map_flags; }
class Mapper
{
@@ -119,7 +105,7 @@ public:
* Memory-maps a Buffer.
*/
Mapper(Buffer &buffer)
: buf(buffer)
: buf(buffer)
{
elems = buf.map();
}
@@ -189,12 +175,13 @@ protected:
class QuadIndices
{
public:
/**
* Adds an entry to the list of sizes and resizes the Buffer
* if needed. A size of 1 allocates a group of 6 indices for 4 vertices
* creating 1 face.
*/
QuadIndices(Graphics *gfx, size_t size);
QuadIndices(Graphics *gfx);
QuadIndices(const QuadIndices &other);
QuadIndices &operator = (const QuadIndices &other);
@@ -205,81 +192,13 @@ public:
*/
~QuadIndices();
/**
* Returns the number of index groups.
* This can be used for getIndexCount to get the full count of indices.
*/
size_t getSize() const;
/**
* Returns the number of indices that the passed element count will have.
* Use QuadIndices::getSize to get the full index count for that
* QuadIndices instance.
*/
size_t getIndexCount(size_t elements) const;
/**
* Returns the integer type of the element array.
* If an optional nonzero size argument is passed, the function returns
* the integer type of the element array of that size.
*/
IndexDataType getType(size_t s) const;
inline IndexDataType getType() const
{
return getType(maxSize);
}
/**
* Returns the size in bytes of an element in the element array.
* Can be used with getPointer to calculate an offset into the array based
* on a number of elements.
**/
size_t getElementSize() const;
/**
* Returns the pointer to the Buffer.
* The pointer will change if a new size request or removal causes a Buffer
* resize. It is recommended to retrieve the pointer value directly before
* the drawing call.
*/
void draw(Graphics *gfx, int quadstart, int quadcount, const vertex::Attributes &attributes, vertex::Buffers buffers, Texture *texture);
Buffer *getBuffer() const;
/**
* Returns a direct pointer to the index data.
*
* At least one graphics driver (the one for Kepler nvidia GPUs in OS X)
* fails to render geometry if the vertex data was a direct CPU pointer but
* the index data came from an Index Buffer.
* So the direct pointer to the index buffer should be used instead of the
* index buffer when rendering using client-side vertex arrays.
**/
const void *getIndices(size_t offset) const;
private:
/**
* Adds all indices to the array with the type T.
* There are no checks for the correct types or overflows. The calling
* function should check for that.
*/
template <typename T> void fill();
// The size of the array requested by this instance.
size_t size;
// The size in bytes of an element in the element array.
static size_t elementSize;
// The current GLBuffer size. 0 means no Buffer.
static size_t maxSize;
static size_t objectCount;
// The Buffer for the element array. Can be null.
static Buffer *indexBuffer;
// The array of indices that will also be stored in the index buffer.
static char *indices;
}; // QuadIndices
+4 -16
View File
@@ -440,21 +440,15 @@ void Graphics::restoreStateChecked(const DisplayState &s)
{
for (size_t i = 0; i < sRTs.colors.size() && i < curRTs.colors.size(); i++)
{
const auto &rt1 = sRTs.colors[i];
const auto &rt2 = curRTs.colors[i];
if (rt1.canvas.get() != rt2.canvas.get() || rt1.slice != rt2.slice || rt1.mipmap != rt2.mipmap)
if (sRTs.colors[i] != curRTs.colors[i])
{
canvaseschanged = true;
break;
}
}
if (!canvaseschanged && (sRTs.depthStencil.canvas.get() != curRTs.depthStencil.canvas.get()
|| sRTs.depthStencil.slice != curRTs.depthStencil.slice
|| sRTs.depthStencil.mipmap != curRTs.depthStencil.mipmap))
{
if (!canvaseschanged && sRTs.depthStencil != curRTs.depthStencil)
canvaseschanged = true;
}
if (sRTs.temporaryRTFlags != curRTs.temporaryRTFlags)
canvaseschanged = true;
@@ -577,21 +571,15 @@ void Graphics::setCanvas(const RenderTargets &rts)
for (int i = 0; i < ncanvases; i++)
{
if (rts.colors[i].canvas != prevRTs.colors[i].canvas.get()
|| rts.colors[i].slice != prevRTs.colors[i].slice
|| rts.colors[i].mipmap != prevRTs.colors[i].mipmap)
if (rts.colors[i] != prevRTs.colors[i])
{
modified = true;
break;
}
}
if (!modified && (rts.depthStencil.canvas != prevRTs.depthStencil.canvas
|| rts.depthStencil.slice != prevRTs.depthStencil.slice
|| rts.depthStencil.mipmap != prevRTs.depthStencil.mipmap))
{
if (!modified && rts.depthStencil != prevRTs.depthStencil)
modified = true;
}
if (rts.temporaryRTFlags != prevRTs.temporaryRTFlags)
modified = true;
+22
View File
@@ -354,6 +354,8 @@ public:
void *data = nullptr;
};
struct RenderTargetStrongRef;
struct RenderTarget
{
Canvas *canvas;
@@ -370,6 +372,16 @@ public:
: canvas(nullptr)
, slice(0)
{}
bool operator != (const RenderTarget &other) const
{
return canvas != other.canvas || slice != other.slice || mipmap != other.mipmap;
}
bool operator != (const RenderTargetStrongRef &other) const
{
return canvas != other.canvas.get() || slice != other.slice || mipmap != other.mipmap;
}
};
struct RenderTargetStrongRef
@@ -383,6 +395,16 @@ public:
, slice(slice)
, mipmap(mipmap)
{}
bool operator != (const RenderTargetStrongRef &other) const
{
return canvas.get() != other.canvas.get() || slice != other.slice || mipmap != other.mipmap;
}
bool operator != (const RenderTarget &other) const
{
return canvas.get() != other.canvas || slice != other.slice || mipmap != other.mipmap;
}
};
struct RenderTargets
+5 -11
View File
@@ -95,7 +95,7 @@ ParticleSystem::ParticleSystem(Graphics *gfx, Texture *texture, uint32 size)
, relativeRotation(false)
, vertexAttributes(vertex::CommonFormat::XYf_STf_RGBAub, 0)
, buffer(nullptr)
, quadIndices(gfx, size)
, quadIndices(gfx)
{
if (size == 0 || size > MAX_PARTICLES)
throw love::Exception("Invalid ParticleSystem size.");
@@ -195,7 +195,7 @@ void ParticleSystem::createBuffers(size_t size)
size_t bytes = sizeof(Vertex) * size * 4;
buffer = gfx->newBuffer(bytes, nullptr, BUFFER_VERTEX, vertex::USAGE_STREAM, 0);
quadIndices = QuadIndices(gfx, size);
quadIndices = QuadIndices(gfx);
}
catch (std::bad_alloc &)
{
@@ -1094,20 +1094,14 @@ void ParticleSystem::draw(Graphics *gfx, const Matrix4 &m)
p = p->next;
}
Graphics::TempTransform transform(gfx, m);
buffer->unmap();
vertex::Buffers vertexbuffers;
vertexbuffers.set(0, buffer, 0);
Graphics::TempTransform transform(gfx, m);
Graphics::DrawIndexedCommand cmd(&vertexAttributes, &vertexbuffers, quadIndices.getBuffer());
cmd.primitiveType = PRIMITIVE_TRIANGLES;
cmd.indexBufferOffset = 0;
cmd.indexCount = (int) quadIndices.getIndexCount(pCount);
cmd.indexType = quadIndices.getType();
cmd.texture = texture;
gfx->draw(cmd);
quadIndices.draw(gfx, 0, pCount, vertexAttributes, vertexbuffers, texture);
}
bool ParticleSystem::getConstant(const char *in, AreaSpreadDistribution &out)
+3 -12
View File
@@ -47,7 +47,7 @@ SpriteBatch::SpriteBatch(Graphics *gfx, Texture *texture, int size, vertex::Usag
, color(255, 255, 255, 255)
, color_active(false)
, array_buf(nullptr)
, quad_indices(gfx, size)
, quad_indices(gfx)
, range_start(-1)
, range_count(-1)
{
@@ -238,8 +238,6 @@ void SpriteBatch::setBufferSize(int newsize)
// Copy as much of the old data into the new GLBuffer as can fit.
size_t copy_size = vertex_stride * 4 * new_next;
array_buf->copyTo(0, copy_size, new_array_buf, 0);
quad_indices = QuadIndices(gfx, newsize);
}
catch (love::Exception &)
{
@@ -385,7 +383,7 @@ void SpriteBatch::draw(Graphics *gfx, const Matrix4 &m)
}
}
Graphics::DrawIndexedCommand cmd(&attributes, &buffers, quad_indices.getBuffer());
Graphics::TempTransform transform(gfx, m);
int start = std::min(std::max(0, range_start), next - 1);
@@ -395,15 +393,8 @@ void SpriteBatch::draw(Graphics *gfx, const Matrix4 &m)
count = std::min(count, next - start);
cmd.indexBufferOffset = quad_indices.getIndexCount(start) * quad_indices.getElementSize();
cmd.indexCount = (int) quad_indices.getIndexCount(count);
cmd.indexType = quad_indices.getType();
cmd.texture = texture;
Graphics::TempTransform transform(gfx, m);
if (count > 0)
gfx->draw(cmd);
quad_indices.draw(gfx, start, count, attributes, buffers, texture);
}
} // graphics
+2 -14
View File
@@ -34,7 +34,7 @@ Text::Text(Graphics *gfx, Font *font, const std::vector<Font::ColoredString> &te
: font(font)
, vertexAttributes(Font::vertexFormat, 0)
, vbo(nullptr)
, quadIndices(gfx, 20)
, quadIndices(gfx)
, vert_offset(0)
, texture_cache_id((uint32) -1)
{
@@ -259,24 +259,12 @@ void Text::draw(Graphics *gfx, const Matrix4 &m)
for (const Font::DrawCommand &cmd : draw_commands)
totalverts = std::max(cmd.startvertex + cmd.vertexcount, totalverts);
if ((size_t) totalverts / 4 > quadIndices.getSize())
quadIndices = QuadIndices(gfx, (size_t) totalverts / 4);
vbo->unmap(); // Make sure all pending data is flushed to the GPU.
Graphics::TempTransform transform(gfx, m);
Graphics::DrawIndexedCommand drawcmd(&vertexAttributes, &vertexBuffers, quadIndices.getBuffer());
drawcmd.indexType = quadIndices.getType();
size_t elemsize = quadIndices.getElementSize();
for (const Font::DrawCommand &cmd : draw_commands)
{
drawcmd.indexCount = (cmd.vertexcount / 4) * 6;
drawcmd.indexBufferOffset = (cmd.startvertex / 4) * 6 * elemsize;
drawcmd.texture = cmd.texture;
gfx->draw(drawcmd);
}
quadIndices.draw(gfx, cmd.startvertex / 4, cmd.vertexcount / 4, vertexAttributes, vertexBuffers, cmd.texture);
}
} // graphics
+1 -2
View File
@@ -232,7 +232,7 @@ bool Graphics::setMode(int width, int height, int pixelwidth, int pixelheight, b
// index buffer objects, since the shared index buffer used by QuadIndices
// objects is destroyed when the last object is destroyed.
if (quadIndices == nullptr)
quadIndices = new QuadIndices(this, 20);
quadIndices = new QuadIndices(this);
// Restore the graphics state.
restoreState(states.back());
@@ -543,7 +543,6 @@ void Graphics::endPass()
if (mask != 0)
glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, mask, GL_NEAREST);
}
}
for (const auto &rt : rts.colors)