Files
love/src/modules/graphics/opengl/GLBuffer.cpp
T
Alex Szpakowski d26002f5cc Revamped and streamlined retina / high-DPI support (resolves issue #1122).
With the highdpi window flag enabled on a retina-capable display and OS, content should now appear to the user at the same size and in the same positions as with the flag disabled.

As a result, mouse and touch coordinates, Texture and graphics dimensions, and the graphics coordinate system now use pixel density-scaled units instead of pixels. Raw pixel units should generally only be used for things such as shader algorithms which execute per-pixel and rely on accurate pixel dimensions. love.window.fromPixels and friends typically don’t need to be used anymore.

Images, Canvases, and Fonts can have an optional explicit ‘pixel density’ set when creating them. This allows for easily loading high pixel density content which displays at the same size as regular or low pixel density content.

API changes:

- Added Texture:getPixelWidth/getPixelHeight/getPixelDimensions and Texture:getPixelDensity. Texture:getWidth/getHeight return the pixel density-scaled width and height (as it will appear on the screen when drawn) rather than the number of pixels on each texture dimension.

- Added love.graphics.getPixelWidth/getPixelHeight/getPixelDimensions.

- Added optional ‘pixeldensity’ field to the settings table parameter of love.graphics.newImage. It defaults to 1, or if the file the Image was loaded from has “@2x”, “@3x”, etc. at the end of its name, it uses that number as the pixel density scale by default.

- love.graphics.newCanvas now takes a table as its third parameter, with fields “format”, “msaa”, and “pixeldensity”. pixeldensity defaults to the main screen’s pixel density. The width and height parameters specify the visual size that the Canvas will be drawn at / can be drawn to (pixel density-scaled units).

- love.graphics.newVideo accepts a table as its second parameter, with optional fields “audio” and “pixeldensity”. pixeldensity defaults to 1.

- love.graphics.newFont variants have an optional pixeldensity parameter at the end of the argument list. For TrueType fonts this defaults to the current pixel density scale of the screen, and for BMFonts and ImageFonts this defaults to 1.

- Added Font:getPixelDensity.

- Renamed love.window.getPixelScale to love.window.getPixelDensity.

--HG--
branch : minor
2016-12-29 23:55:51 -04:00

375 lines
8.1 KiB
C++

/**
* Copyright (c) 2006-2016 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
#include "GLBuffer.h"
#include "common/Exception.h"
#include "graphics/vertex.h"
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <limits>
namespace love
{
namespace graphics
{
namespace opengl
{
GLBuffer::GLBuffer(size_t size, const void *data, BufferType type, GLenum usage, uint32 mapflags)
: is_mapped(false)
, size(size)
, type(type)
, usage(usage)
, vbo(0)
, memory_map(nullptr)
, modified_offset(0)
, modified_size(0)
, map_flags(mapflags)
{
target = OpenGL::getGLBufferType(type);
try
{
memory_map = new char[size];
}
catch (std::bad_alloc &)
{
throw love::Exception("Out of memory.");
}
if (data != nullptr)
memcpy(memory_map, data, size);
if (!load(data != nullptr))
{
delete[] memory_map;
throw love::Exception("Could not load vertex buffer (out of VRAM?)");
}
}
GLBuffer::~GLBuffer()
{
if (vbo != 0)
unload();
delete[] memory_map;
}
void *GLBuffer::map()
{
if (is_mapped)
return memory_map;
is_mapped = true;
modified_offset = 0;
modified_size = 0;
return memory_map;
}
void GLBuffer::unmapStatic(size_t offset, size_t size)
{
if (size == 0)
return;
// Upload the mapped data to the buffer.
gl.bindBuffer(type, vbo);
glBufferSubData(target, (GLintptr) offset, (GLsizeiptr) size, memory_map + offset);
}
void GLBuffer::unmapStream()
{
// "orphan" current buffer to avoid implicit synchronisation on the GPU:
// http://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-AsynchronousBufferTransfers.pdf
gl.bindBuffer(type, vbo);
glBufferData(target, (GLsizeiptr) getSize(), nullptr, getUsage());
glBufferData(target, (GLsizeiptr) getSize(), memory_map, getUsage());
}
void GLBuffer::unmap()
{
if (!is_mapped)
return;
if ((map_flags & MAP_EXPLICIT_RANGE_MODIFY) != 0)
{
modified_offset = std::min(modified_offset, getSize() - 1);
modified_size = std::min(modified_size, getSize() - modified_offset);
}
else
{
modified_offset = 0;
modified_size = getSize();
}
if (modified_size > 0)
{
switch (getUsage())
{
case GL_STATIC_DRAW:
unmapStatic(modified_offset, modified_size);
break;
case GL_STREAM_DRAW:
unmapStream();
break;
case GL_DYNAMIC_DRAW:
default:
// It's probably more efficient to treat it like a streaming buffer if
// at least a third of its contents have been modified during the map().
if (modified_size >= getSize() / 3)
unmapStream();
else
unmapStatic(modified_offset, modified_size);
break;
}
}
modified_offset = 0;
modified_size = 0;
is_mapped = false;
}
void GLBuffer::setMappedRangeModified(size_t offset, size_t modifiedsize)
{
if (!is_mapped || !(map_flags & MAP_EXPLICIT_RANGE_MODIFY))
return;
// We're being conservative right now by internally marking the whole range
// from the start of section a to the end of section b as modified if both
// a and b are marked as modified.
size_t old_range_end = modified_offset + modified_size;
modified_offset = std::min(modified_offset, offset);
size_t new_range_end = std::max(offset + modifiedsize, old_range_end);
modified_size = new_range_end - modified_offset;
}
void GLBuffer::bind()
{
gl.bindBuffer(getType(), vbo);
}
void GLBuffer::fill(size_t offset, size_t size, const void *data)
{
memcpy(memory_map + offset, data, size);
if (is_mapped)
setMappedRangeModified(offset, size);
else
{
gl.bindBuffer(type, vbo);
glBufferSubData(target, (GLintptr) offset, (GLsizeiptr) size, data);
}
}
const void *GLBuffer::getPointer(size_t offset) const
{
return BUFFER_OFFSET(offset);
}
bool GLBuffer::loadVolatile()
{
return load(true);
}
void GLBuffer::unloadVolatile()
{
unload();
}
bool GLBuffer::load(bool restore)
{
glGenBuffers(1, &vbo);
bind();
while (glGetError() != GL_NO_ERROR)
/* Clear the error buffer. */;
// Copy the old buffer only if 'restore' was requested.
const GLvoid *src = restore ? memory_map : nullptr;
// Note that if 'src' is '0', no data will be copied.
glBufferData(target, (GLsizeiptr) getSize(), src, getUsage());
return (glGetError() == GL_NO_ERROR);
}
void GLBuffer::unload()
{
is_mapped = false;
gl.deleteBuffer(vbo);
vbo = 0;
}
// QuadIndices
size_t QuadIndices::maxSize = 0;
size_t QuadIndices::elementSize = 0;
size_t QuadIndices::objectCount = 0;
GLBuffer *QuadIndices::indexBuffer = nullptr;
char *QuadIndices::indices = nullptr;
QuadIndices::QuadIndices(size_t size)
: size(size)
{
// The upper limit is the maximum of GLuint divided by six (the number
// of indices per size) and divided by the size of GLuint. This guarantees
// no overflows when calculating the array size in bytes.
if (size == 0 || size > ((GLuint) -1) / 6 / sizeof(GLuint))
throw love::Exception("Invalid number of quads.");
// Create a new / larger buffer if needed.
if (indexBuffer == nullptr || size > maxSize)
{
GLBuffer *newbuffer = nullptr;
char *newindices = nullptr;
// Depending on the size, a switch to int and more memory is needed.
GLenum targettype = getType(size);
size_t elemsize = (targettype == GL_UNSIGNED_SHORT) ? sizeof(GLushort) : sizeof(GLuint);
size_t buffersize = elemsize * 6 * size;
// Create may throw out-of-memory exceptions.
// QuadIndices will propagate the exception and keep the old GLBuffer.
try
{
newbuffer = new GLBuffer(buffersize, nullptr, BUFFER_INDEX, GL_STATIC_DRAW);
newindices = new char[buffersize];
}
catch (std::bad_alloc &)
{
delete newbuffer;
delete[] newindices;
throw love::Exception("Out of memory.");
}
// Allocation of the new GLBuffer succeeded.
// The old GLBuffer can now be deleted.
delete indexBuffer;
indexBuffer = newbuffer;
delete[] indices;
indices = newindices;
maxSize = size;
elementSize = elemsize;
switch (targettype)
{
case GL_UNSIGNED_SHORT:
fill<GLushort>();
break;
case GL_UNSIGNED_INT:
fill<GLuint>();
break;
}
}
objectCount++;
}
QuadIndices::QuadIndices(const QuadIndices &other)
: size(other.size)
{
objectCount++;
}
QuadIndices &QuadIndices::operator = (const QuadIndices &other)
{
size = other.size;
return *this;
}
QuadIndices::~QuadIndices()
{
--objectCount;
// Delete the buffer if we were the last living QuadIndices object.
if (objectCount <= 0)
{
delete indexBuffer;
indexBuffer = nullptr;
delete[] indices;
indices = nullptr;
}
}
size_t QuadIndices::getSize() const
{
return size;
}
size_t QuadIndices::getIndexCount(size_t elements) const
{
return elements * 6;
}
GLenum QuadIndices::getType(size_t s) const
{
// Calculates if unsigned short is big enough to hold all the vertex indices.
static const GLenum types[] = {GL_UNSIGNED_SHORT, GL_UNSIGNED_INT};
return types[s * 4 > std::numeric_limits<GLushort>::max()];
// if buffer-size > max(GLushort) then GL_UNSIGNED_INT else GL_UNSIGNED_SHORT
}
size_t QuadIndices::getElementSize()
{
return elementSize;
}
GLBuffer *QuadIndices::getBuffer() const
{
return indexBuffer;
}
const void *QuadIndices::getPointer(size_t offset) const
{
return indexBuffer->getPointer(offset);
}
const void *QuadIndices::getIndices(size_t offset) const
{
return indices + offset;
}
template <typename T>
void QuadIndices::fill()
{
using namespace love::graphics::vertex;
fillIndices(TriangleIndexMode::QUADS, 0, maxSize * 4, (T *) indices);
indexBuffer->fill(0, indexBuffer->getSize(), indices);
}
} // opengl
} // graphics
} // love