Commit 9102e3ab by Gregoire Payen de La Garanderie Committed by Jamie Madill

Add UBO offset emulation for D3D11.0 and below.

BUG=angleproject:507 Change-Id: I6c5028930051a2af0bd6ffa0ee213e692d3892ef Reviewed-on: https://chromium-review.googlesource.com/261824Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org> Tested-by: 's avatarJamie Madill <jmadill@chromium.org>
parent cc6f55dd
...@@ -188,15 +188,22 @@ Buffer11::Buffer11(Renderer11 *renderer) ...@@ -188,15 +188,22 @@ Buffer11::Buffer11(Renderer11 *renderer)
mRenderer(renderer), mRenderer(renderer),
mSize(0), mSize(0),
mMappedStorage(NULL), mMappedStorage(NULL),
mConstantBufferStorageAdditionalSize(0),
mMaxConstantBufferLruCount(0),
mReadUsageCount(0), mReadUsageCount(0),
mHasSystemMemoryStorage(false) mHasSystemMemoryStorage(false)
{} {}
Buffer11::~Buffer11() Buffer11::~Buffer11()
{ {
for (auto it = mBufferStorages.begin(); it != mBufferStorages.end(); it++) for (auto &p : mBufferStorages)
{
SafeDelete(p.second);
}
for (auto &p : mConstantBufferRangeStoragesCache)
{ {
SafeDelete(it->second); SafeDelete(p.second.storage);
} }
} }
...@@ -464,6 +471,30 @@ ID3D11Buffer *Buffer11::getBuffer(BufferUsage usage) ...@@ -464,6 +471,30 @@ ID3D11Buffer *Buffer11::getBuffer(BufferUsage usage)
return GetAs<NativeStorage>(bufferStorage)->getNativeStorage(); return GetAs<NativeStorage>(bufferStorage)->getNativeStorage();
} }
ID3D11Buffer *Buffer11::getConstantBufferRange(GLintptr offset, GLsizeiptr size)
{
markBufferUsage();
BufferStorage *bufferStorage;
if (offset == 0)
{
bufferStorage = getBufferStorage(BUFFER_USAGE_UNIFORM);
}
else
{
bufferStorage = getContantBufferRangeStorage(offset, size);
}
if (!bufferStorage)
{
// Storage out-of-memory
return NULL;
}
return GetAs<NativeStorage>(bufferStorage)->getNativeStorage();
}
ID3D11ShaderResourceView *Buffer11::getSRV(DXGI_FORMAT srvFormat) ID3D11ShaderResourceView *Buffer11::getSRV(DXGI_FORMAT srvFormat)
{ {
BufferStorage *storage = getBufferStorage(BUFFER_USAGE_PIXEL_UNPACK); BufferStorage *storage = getBufferStorage(BUFFER_USAGE_PIXEL_UNPACK);
...@@ -568,15 +599,80 @@ Buffer11::BufferStorage *Buffer11::getBufferStorage(BufferUsage usage) ...@@ -568,15 +599,80 @@ Buffer11::BufferStorage *Buffer11::getBufferStorage(BufferUsage usage)
} }
} }
updateBufferStorage(newStorage, 0, mSize);
return newStorage;
}
Buffer11::BufferStorage *Buffer11::getContantBufferRangeStorage(GLintptr offset, GLsizeiptr size)
{
BufferStorage *newStorage;
{
// Keep the cacheEntry in a limited scope because it may be invalidated later in the code if we need to reclaim some space.
ConstantBufferCacheEntry *cacheEntry = &mConstantBufferRangeStoragesCache[offset];
if (!cacheEntry->storage)
{
cacheEntry->storage = new NativeStorage(mRenderer, BUFFER_USAGE_UNIFORM);
cacheEntry->lruCount = ++mMaxConstantBufferLruCount;
}
cacheEntry->lruCount = ++mMaxConstantBufferLruCount;
newStorage = cacheEntry->storage;
}
if (newStorage->getSize() < static_cast<size_t>(size))
{
size_t maximumAllowedAdditionalSize = 2 * getSize();
size_t sizeDelta = size - newStorage->getSize();
while (mConstantBufferStorageAdditionalSize + sizeDelta > maximumAllowedAdditionalSize)
{
auto iter = std::min_element(std::begin(mConstantBufferRangeStoragesCache), std::end(mConstantBufferRangeStoragesCache),
[](const ConstantBufferCache::value_type &a, const ConstantBufferCache::value_type &b)
{
return a.second.lruCount < b.second.lruCount;
});
ASSERT(iter->second.storage != newStorage);
ASSERT(mConstantBufferStorageAdditionalSize >= iter->second.storage->getSize());
mConstantBufferStorageAdditionalSize -= iter->second.storage->getSize();
SafeDelete(iter->second.storage);
mConstantBufferRangeStoragesCache.erase(iter);
}
if (newStorage->resize(size, false).isError())
{
// Out of memory error
return nullptr;
}
mConstantBufferStorageAdditionalSize += sizeDelta;
// We don't copy the old data when resizing the constant buffer because the data may be out-of-date
// therefore we reset the data revision and let updateBufferStorage() handle the copy.
newStorage->setDataRevision(0);
}
updateBufferStorage(newStorage, offset, size);
return newStorage;
}
void Buffer11::updateBufferStorage(BufferStorage *storage, size_t sourceOffset, size_t storageSize)
{
BufferStorage *latestBuffer = getLatestBufferStorage(); BufferStorage *latestBuffer = getLatestBufferStorage();
if (latestBuffer && latestBuffer->getDataRevision() > newStorage->getDataRevision()) if (latestBuffer && latestBuffer->getDataRevision() > storage->getDataRevision())
{ {
// Copy through a staging buffer if we're copying from or to a non-staging, mappable // Copy through a staging buffer if we're copying from or to a non-staging, mappable
// buffer storage. This is because we can't map a GPU buffer, and copy CPU // buffer storage. This is because we can't map a GPU buffer, and copy CPU
// data directly. If we're already using a staging buffer we're fine. // data directly. If we're already using a staging buffer we're fine.
if (latestBuffer->getUsage() != BUFFER_USAGE_STAGING && if (latestBuffer->getUsage() != BUFFER_USAGE_STAGING &&
newStorage->getUsage() != BUFFER_USAGE_STAGING && storage->getUsage() != BUFFER_USAGE_STAGING &&
(!latestBuffer->isMappable() || !newStorage->isMappable())) (!latestBuffer->isMappable() || !storage->isMappable()))
{ {
NativeStorage *stagingBuffer = getStagingStorage(); NativeStorage *stagingBuffer = getStagingStorage();
...@@ -588,14 +684,12 @@ Buffer11::BufferStorage *Buffer11::getBufferStorage(BufferUsage usage) ...@@ -588,14 +684,12 @@ Buffer11::BufferStorage *Buffer11::getBufferStorage(BufferUsage usage)
// if copyFromStorage returns true, the D3D buffer has been recreated // if copyFromStorage returns true, the D3D buffer has been recreated
// and we should update our serial // and we should update our serial
if (newStorage->copyFromStorage(latestBuffer, 0, latestBuffer->getSize(), 0)) if (storage->copyFromStorage(latestBuffer, sourceOffset, storageSize, 0))
{ {
updateSerial(); updateSerial();
} }
newStorage->setDataRevision(latestBuffer->getDataRevision()); storage->setDataRevision(latestBuffer->getDataRevision());
} }
return newStorage;
} }
Buffer11::BufferStorage *Buffer11::getLatestBufferStorage() const Buffer11::BufferStorage *Buffer11::getLatestBufferStorage() const
...@@ -703,14 +797,14 @@ bool Buffer11::NativeStorage::copyFromStorage(BufferStorage *source, size_t sour ...@@ -703,14 +797,14 @@ bool Buffer11::NativeStorage::copyFromStorage(BufferStorage *source, size_t sour
{ {
ID3D11DeviceContext *context = mRenderer->getDeviceContext(); ID3D11DeviceContext *context = mRenderer->getDeviceContext();
size_t requiredSize = sourceOffset + size; size_t requiredSize = destOffset + size;
bool createBuffer = !mNativeStorage || mBufferSize < requiredSize; bool createBuffer = !mNativeStorage || mBufferSize < requiredSize;
// (Re)initialize D3D buffer if needed // (Re)initialize D3D buffer if needed
if (createBuffer) if (createBuffer)
{ {
bool preserveData = (destOffset > 0); bool preserveData = (destOffset > 0);
resize(source->getSize(), preserveData); resize(requiredSize, preserveData);
} }
if (source->getUsage() == BUFFER_USAGE_PIXEL_PACK || if (source->getUsage() == BUFFER_USAGE_PIXEL_PACK ||
......
...@@ -51,6 +51,7 @@ class Buffer11 : public BufferD3D ...@@ -51,6 +51,7 @@ class Buffer11 : public BufferD3D
virtual ~Buffer11(); virtual ~Buffer11();
ID3D11Buffer *getBuffer(BufferUsage usage); ID3D11Buffer *getBuffer(BufferUsage usage);
ID3D11Buffer *getConstantBufferRange(GLintptr offset, GLsizeiptr size);
ID3D11ShaderResourceView *getSRV(DXGI_FORMAT srvFormat); ID3D11ShaderResourceView *getSRV(DXGI_FORMAT srvFormat);
bool isMapped() const { return mMappedStorage != NULL; } bool isMapped() const { return mMappedStorage != NULL; }
gl::Error packPixels(ID3D11Texture2D *srcTexure, UINT srcSubresource, const PackPixelsParams &params); gl::Error packPixels(ID3D11Texture2D *srcTexure, UINT srcSubresource, const PackPixelsParams &params);
...@@ -82,6 +83,22 @@ class Buffer11 : public BufferD3D ...@@ -82,6 +83,22 @@ class Buffer11 : public BufferD3D
std::map<BufferUsage, BufferStorage*> mBufferStorages; std::map<BufferUsage, BufferStorage*> mBufferStorages;
struct ConstantBufferCacheEntry
{
ConstantBufferCacheEntry() : storage(nullptr), lruCount(0) { }
BufferStorage *storage;
unsigned int lruCount;
};
// Cache of D3D11 constant buffer for specific ranges of buffer data.
// This is used to emulate UBO ranges on 11.0 devices.
// Constant buffers are indexed by there start offset.
typedef std::map<GLintptr /*offset*/, ConstantBufferCacheEntry> ConstantBufferCache;
ConstantBufferCache mConstantBufferRangeStoragesCache;
size_t mConstantBufferStorageAdditionalSize;
unsigned int mMaxConstantBufferLruCount;
typedef std::pair<ID3D11Buffer *, ID3D11ShaderResourceView *> BufferSRVPair; typedef std::pair<ID3D11Buffer *, ID3D11ShaderResourceView *> BufferSRVPair;
std::map<DXGI_FORMAT, BufferSRVPair> mBufferResourceViews; std::map<DXGI_FORMAT, BufferSRVPair> mBufferResourceViews;
...@@ -93,8 +110,11 @@ class Buffer11 : public BufferD3D ...@@ -93,8 +110,11 @@ class Buffer11 : public BufferD3D
PackStorage *getPackStorage(); PackStorage *getPackStorage();
gl::Error getSystemMemoryStorage(SystemMemoryStorage **storageOut); gl::Error getSystemMemoryStorage(SystemMemoryStorage **storageOut);
void updateBufferStorage(BufferStorage *storage, size_t sourceOffset, size_t storageSize);
BufferStorage *getBufferStorage(BufferUsage usage); BufferStorage *getBufferStorage(BufferUsage usage);
BufferStorage *getLatestBufferStorage() const; BufferStorage *getLatestBufferStorage() const;
BufferStorage *getContantBufferRangeStorage(GLintptr offset, GLsizeiptr size);
}; };
} }
......
...@@ -832,7 +832,16 @@ gl::Error Renderer11::setUniformBuffers(const gl::Data &data, ...@@ -832,7 +832,16 @@ gl::Error Renderer11::setUniformBuffers(const gl::Data &data,
if (uniformBuffer) if (uniformBuffer)
{ {
Buffer11 *bufferStorage = GetImplAs<Buffer11>(uniformBuffer); Buffer11 *bufferStorage = GetImplAs<Buffer11>(uniformBuffer);
ID3D11Buffer *constantBuffer = bufferStorage->getBuffer(BUFFER_USAGE_UNIFORM); ID3D11Buffer *constantBuffer;
if (mSupportsConstantBufferOffsets)
{
constantBuffer = bufferStorage->getBuffer(BUFFER_USAGE_UNIFORM);
}
else
{
constantBuffer = bufferStorage->getConstantBufferRange(uniformBufferOffset, uniformBufferSize);
}
if (!constantBuffer) if (!constantBuffer)
{ {
...@@ -852,7 +861,6 @@ gl::Error Renderer11::setUniformBuffers(const gl::Data &data, ...@@ -852,7 +861,6 @@ gl::Error Renderer11::setUniformBuffers(const gl::Data &data,
} }
else else
{ {
ASSERT(uniformBufferOffset == 0);
mDeviceContext->VSSetConstantBuffers(getReservedVertexUniformBuffers() + uniformBufferIndex, mDeviceContext->VSSetConstantBuffers(getReservedVertexUniformBuffers() + uniformBufferIndex,
1, &constantBuffer); 1, &constantBuffer);
} }
...@@ -880,7 +888,16 @@ gl::Error Renderer11::setUniformBuffers(const gl::Data &data, ...@@ -880,7 +888,16 @@ gl::Error Renderer11::setUniformBuffers(const gl::Data &data,
if (uniformBuffer) if (uniformBuffer)
{ {
Buffer11 *bufferStorage = GetImplAs<Buffer11>(uniformBuffer); Buffer11 *bufferStorage = GetImplAs<Buffer11>(uniformBuffer);
ID3D11Buffer *constantBuffer = bufferStorage->getBuffer(BUFFER_USAGE_UNIFORM); ID3D11Buffer *constantBuffer;
if (mSupportsConstantBufferOffsets)
{
constantBuffer = bufferStorage->getBuffer(BUFFER_USAGE_UNIFORM);
}
else
{
constantBuffer = bufferStorage->getConstantBufferRange(uniformBufferOffset, uniformBufferSize);
}
if (!constantBuffer) if (!constantBuffer)
{ {
...@@ -900,7 +917,6 @@ gl::Error Renderer11::setUniformBuffers(const gl::Data &data, ...@@ -900,7 +917,6 @@ gl::Error Renderer11::setUniformBuffers(const gl::Data &data,
} }
else else
{ {
ASSERT(uniformBufferOffset == 0);
mDeviceContext->PSSetConstantBuffers(getReservedFragmentUniformBuffers() + uniformBufferIndex, mDeviceContext->PSSetConstantBuffers(getReservedFragmentUniformBuffers() + uniformBufferIndex,
1, &constantBuffer); 1, &constantBuffer);
} }
......
...@@ -1052,24 +1052,11 @@ void GenerateCaps(ID3D11Device *device, ID3D11DeviceContext *deviceContext, gl:: ...@@ -1052,24 +1052,11 @@ void GenerateCaps(ID3D11Device *device, ID3D11DeviceContext *deviceContext, gl::
caps->maxUniformBufferBindings = caps->maxVertexUniformBlocks + caps->maxFragmentUniformBlocks; caps->maxUniformBufferBindings = caps->maxVertexUniformBlocks + caps->maxFragmentUniformBlocks;
caps->maxUniformBlockSize = GetMaximumConstantBufferSize(featureLevel); caps->maxUniformBlockSize = GetMaximumConstantBufferSize(featureLevel);
// Setting a large alignment forces uniform buffers to bind with zero offset // With DirectX 11.1, constant buffer offset and size must be a multiple of 16 constants of 16 bytes each.
caps->uniformBufferOffsetAlignment = static_cast<GLuint>(std::numeric_limits<GLint>::max()); // https://msdn.microsoft.com/en-us/library/windows/desktop/hh404649%28v=vs.85%29.aspx
ID3D11DeviceContext1 *deviceContext1 = d3d11::DynamicCastComObject<ID3D11DeviceContext1>(deviceContext); // With DirectX 11.0, we emulate UBO offsets using copies of ranges of the UBO however
// we still keep the same alignment as 11.1 for consistency.
if (deviceContext1) caps->uniformBufferOffsetAlignment = 256;
{
D3D11_FEATURE_DATA_D3D11_OPTIONS d3d11Options;
device->CheckFeatureSupport(D3D11_FEATURE_D3D11_OPTIONS, &d3d11Options, sizeof(D3D11_FEATURE_DATA_D3D11_OPTIONS));
if (d3d11Options.ConstantBufferOffsetting)
{
// With DirectX 11.1, constant buffer offset and size must be a multiple of 16 constants of 16 bytes each.
// https://msdn.microsoft.com/en-us/library/windows/desktop/hh404649%28v=vs.85%29.aspx
caps->uniformBufferOffsetAlignment = 256;
}
SafeRelease(deviceContext1);
}
caps->maxCombinedUniformBlocks = caps->maxVertexUniformBlocks + caps->maxFragmentUniformBlocks; caps->maxCombinedUniformBlocks = caps->maxVertexUniformBlocks + caps->maxFragmentUniformBlocks;
caps->maxCombinedVertexUniformComponents = (static_cast<GLint64>(caps->maxVertexUniformBlocks) * static_cast<GLint64>(caps->maxUniformBlockSize / 4)) + caps->maxCombinedVertexUniformComponents = (static_cast<GLint64>(caps->maxVertexUniformBlocks) * static_cast<GLint64>(caps->maxUniformBlockSize / 4)) +
......
#include "ANGLETest.h" #include "ANGLETest.h"
// Use this to select which configurations (e.g. which renderer, which GLES major version) these tests should be run against. // Use this to select which configurations (e.g. which renderer, which GLES major version) these tests should be run against.
ANGLE_TYPED_TEST_CASE(UniformBufferTest, ES3_D3D11_FL11_1, ES3_D3D11_FL11_1_REFERENCE); ANGLE_TYPED_TEST_CASE(UniformBufferTest, ES3_D3D11, ES3_D3D11_FL11_1, ES3_D3D11_FL11_1_REFERENCE);
template<typename T> template<typename T>
class UniformBufferTest : public ANGLETest class UniformBufferTest : public ANGLETest
...@@ -228,3 +228,80 @@ TYPED_TEST(UniformBufferTest, UniformBufferManyUpdates) ...@@ -228,3 +228,80 @@ TYPED_TEST(UniformBufferTest, UniformBufferManyUpdates)
EXPECT_PIXEL_EQ(px, py, i + 10, i + 20, i + 30, i + 40); EXPECT_PIXEL_EQ(px, py, i + 10, i + 20, i + 30, i + 40);
} }
} }
// Use a large number of buffer ranges (compared to the actual size of the UBO)
TYPED_TEST(UniformBufferTest, ManyUniformBufferRange)
{
int px = getWindowWidth() / 2;
int py = getWindowHeight() / 2;
// Query the uniform buffer alignment requirement
GLint alignment;
glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &alignment);
GLint64 maxUniformBlockSize;
glGetInteger64v(GL_MAX_UNIFORM_BLOCK_SIZE, &maxUniformBlockSize);
if (alignment >= maxUniformBlockSize)
{
// ANGLE doesn't implement UBO offsets for this platform.
// Ignore the test case.
return;
}
ASSERT_GL_NO_ERROR();
// Let's create a buffer which contains eight vec4.
GLuint vec4Size = 4 * sizeof(float);
GLuint stride = 0;
do
{
stride += alignment;
}
while (stride < vec4Size);
std::vector<char> v(8 * stride);
for (size_t i = 0; i < 8; ++i)
{
float *data = reinterpret_cast<float*>(v.data() + i * stride);
data[0] = (i + 10.f) / 255.f;
data[1] = (i + 20.f) / 255.f;
data[2] = (i + 30.f) / 255.f;
data[3] = (i + 40.f) / 255.f;
}
glBindBuffer(GL_UNIFORM_BUFFER, mUniformBuffer);
glBufferData(GL_UNIFORM_BUFFER, v.size(), v.data(), GL_STATIC_DRAW);
glUniformBlockBinding(mProgram, mUniformBufferIndex, 0);
EXPECT_GL_NO_ERROR();
// Bind each possible offset
for (size_t i = 0; i < 8; ++i)
{
glBindBufferRange(GL_UNIFORM_BUFFER, 0, mUniformBuffer, i * stride, stride);
drawQuad(mProgram, "position", 0.5f);
EXPECT_GL_NO_ERROR();
EXPECT_PIXEL_EQ(px, py, 10 + i, 20 + i, 30 + i, 40 + i);
}
// Try to bind larger range
for (size_t i = 0; i < 7; ++i)
{
glBindBufferRange(GL_UNIFORM_BUFFER, 0, mUniformBuffer, i * stride, 2 * stride);
drawQuad(mProgram, "position", 0.5f);
EXPECT_GL_NO_ERROR();
EXPECT_PIXEL_EQ(px, py, 10 + i, 20 + i, 30 + i, 40 + i);
}
// Try to bind even larger range
for (size_t i = 0; i < 5; ++i)
{
glBindBufferRange(GL_UNIFORM_BUFFER, 0, mUniformBuffer, i * stride, 4 * stride);
drawQuad(mProgram, "position", 0.5f);
EXPECT_GL_NO_ERROR();
EXPECT_PIXEL_EQ(px, py, 10 + i, 20 + i, 30 + i, 40 + i);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment