Renderer: Add support for storage buffers

This commit is contained in:
SirLynix 2022-06-17 20:15:16 +02:00
parent 0978feafbc
commit 093d9d344e
16 changed files with 160 additions and 18 deletions

View File

@ -41,17 +41,26 @@ namespace Nz
private:
struct DescriptorPool;
struct StorageBufferDescriptor;
struct TextureDescriptor;
struct UniformBufferDescriptor;
DescriptorPool& AllocatePool();
ShaderBindingPtr AllocateFromPool(std::size_t poolIndex, UInt32 setIndex);
template<typename F> void ForEachDescriptor(std::size_t poolIndex, std::size_t bindingIndex, F&& functor);
StorageBufferDescriptor& GetStorageBufferDescriptor(std::size_t poolIndex, std::size_t bindingIndex, std::size_t descriptorIndex);
TextureDescriptor& GetTextureDescriptor(std::size_t poolIndex, std::size_t bindingIndex, std::size_t descriptorIndex);
UniformBufferDescriptor& GetUniformBufferDescriptor(std::size_t poolIndex, std::size_t bindingIndex, std::size_t descriptorIndex);
void Release(ShaderBinding& binding);
inline void TryToShrink();
struct StorageBufferDescriptor
{
GLuint buffer;
GLintptr offset;
GLsizeiptr size;
};
struct TextureDescriptor
{
GLuint texture;
@ -66,7 +75,7 @@ namespace Nz
GLsizeiptr size;
};
using Descriptor = std::variant<std::monostate, TextureDescriptor, UniformBufferDescriptor>;
using Descriptor = std::variant<std::monostate, StorageBufferDescriptor, TextureDescriptor, UniformBufferDescriptor>;
struct DescriptorPool
{

View File

@ -265,6 +265,7 @@ namespace Nz
case GL::BufferTarget::ElementArray: return GL_ELEMENT_ARRAY_BUFFER;
case GL::BufferTarget::PixelPack: return GL_PIXEL_PACK_BUFFER;
case GL::BufferTarget::PixelUnpack: return GL_PIXEL_UNPACK_BUFFER;
case GL::BufferTarget::Storage: return GL_SHADER_STORAGE_BUFFER;
case GL::BufferTarget::TransformFeedback: return GL_TRANSFORM_FEEDBACK_BUFFER;
case GL::BufferTarget::Uniform: return GL_UNIFORM_BUFFER;
}

View File

@ -36,6 +36,7 @@ namespace Nz::GL
ElementArray,
PixelPack,
PixelUnpack,
Storage,
TransformFeedback,
Uniform,
@ -52,6 +53,7 @@ namespace Nz::GL
{
DepthClamp,
SpirV,
StorageBuffers,
TextureCompressionS3tc,
TextureFilterAnisotropic,
@ -120,6 +122,7 @@ namespace Nz::GL
void BindFramebuffer(FramebufferTarget target, GLuint fbo) const;
void BindProgram(GLuint program) const;
void BindSampler(UInt32 textureUnit, GLuint sampler) const;
void BindStorageBuffer(UInt32 storageUnit, GLuint buffer, GLintptr offset, GLsizeiptr size) const;
void BindTexture(TextureTarget target, GLuint texture) const;
void BindTexture(UInt32 textureUnit, TextureTarget target, GLuint texture) const;
void BindUniformBuffer(UInt32 uboUnit, GLuint buffer, GLintptr offset, GLsizeiptr size) const;
@ -212,22 +215,23 @@ namespace Nz::GL
GLsizei width, height;
};
struct TextureUnit
{
GLuint sampler = 0;
std::array<GLuint, UnderlyingCast(TextureTarget::Max) + 1> textureTargets = { 0 };
};
struct UniformBufferUnit
struct BufferBinding
{
GLuint buffer = 0;
GLintptr offset = 0;
GLsizeiptr size = 0;
};
struct TextureUnit
{
GLuint sampler = 0;
std::array<GLuint, UnderlyingCast(TextureTarget::Max) + 1> textureTargets = { 0 };
};
std::array<GLuint, UnderlyingCast(BufferTarget::Max) + 1> bufferTargets = { 0 };
std::vector<TextureUnit> textureUnits;
std::vector<UniformBufferUnit> uboUnits;
std::vector<BufferBinding> storageUnits;
std::vector<BufferBinding> uboUnits;
Box scissorBox;
Box viewport;
GLuint boundProgram = 0;

View File

@ -136,6 +136,7 @@ namespace Nz
enum class ShaderBindingType
{
StorageBuffer,
Texture,
UniformBuffer,

View File

@ -18,11 +18,14 @@ namespace Nz
bool anisotropicFiltering = false;
bool depthClamping = false;
bool nonSolidFaceFilling = false;
bool storageBuffers = false;
};
struct RenderDeviceLimits
{
UInt64 minUniformBufferOffsetAlignment;
UInt64 maxStorageBufferSize;
UInt64 maxUniformBufferSize;
};
struct RenderDeviceInfo

View File

@ -40,6 +40,13 @@ namespace Nz
ShaderBinding& operator=(const ShaderBinding&) = delete;
ShaderBinding& operator=(ShaderBinding&&) = delete;
struct StorageBufferBinding
{
RenderBuffer* buffer;
UInt64 offset;
UInt64 range;
};
struct TextureBinding
{
const Texture* texture;
@ -56,7 +63,7 @@ namespace Nz
struct Binding
{
std::size_t bindingIndex;
std::variant<TextureBinding, UniformBufferBinding> content;
std::variant<StorageBufferBinding, TextureBinding, UniformBufferBinding> content;
};
protected:

View File

@ -60,6 +60,7 @@ namespace Nz
{
Index,
Vertex,
Storage,
Uniform,
Max = Uniform

View File

@ -98,6 +98,7 @@ namespace Nz
switch (bufferType)
{
case BufferType::Index: return VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
case BufferType::Storage: return VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
case BufferType::Vertex: return VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
case BufferType::Uniform: return VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
}
@ -381,6 +382,7 @@ namespace Nz
{
switch (bindingType)
{
case ShaderBindingType::StorageBuffer: return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
case ShaderBindingType::Texture: return VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
case ShaderBindingType::UniformBuffer: return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
}

View File

@ -19,6 +19,7 @@ namespace Nz
switch (type)
{
case BufferType::Index: target = GL::BufferTarget::ElementArray; break;
case BufferType::Storage: target = GL::BufferTarget::Storage; break;
case BufferType::Uniform: target = GL::BufferTarget::Uniform; break;
case BufferType::Vertex: target = GL::BufferTarget::Array; break;

View File

@ -64,6 +64,9 @@ namespace Nz
if (m_referenceContext->IsExtensionSupported(GL::Extension::DepthClamp))
m_deviceInfo.features.depthClamping = true;
if (m_referenceContext->IsExtensionSupported(GL::Extension::StorageBuffers))
m_deviceInfo.features.storageBuffers = true;
if (m_referenceContext->glPolygonMode) //< not supported in core OpenGL ES, but supported in OpenGL or with GL_NV_polygon_mode extension
m_deviceInfo.features.nonSolidFaceFilling = true;
@ -74,6 +77,23 @@ namespace Nz
assert(minUboOffsetAlignment >= 1);
m_deviceInfo.limits.minUniformBufferOffsetAlignment = static_cast<UInt64>(minUboOffsetAlignment);
GLint maxUboBlockSize;
m_referenceContext->glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &maxUboBlockSize);
assert(maxUboBlockSize >= 1);
m_deviceInfo.limits.maxUniformBufferSize = static_cast<UInt64>(maxUboBlockSize);
if (m_deviceInfo.features.storageBuffers)
{
GLint maxStorageBlockSize;
m_referenceContext->glGetIntegerv(GL_MAX_SHADER_STORAGE_BLOCK_SIZE, &maxStorageBlockSize);
assert(maxStorageBlockSize >= 1);
m_deviceInfo.limits.maxStorageBufferSize = static_cast<UInt64>(maxStorageBlockSize);
}
else
m_deviceInfo.limits.maxStorageBufferSize = 0;
m_contexts.insert(m_referenceContext.get());
}

View File

@ -87,6 +87,16 @@ namespace Nz
return ShaderBindingPtr(PlacementNew(freeBindingMemory, *this, poolIndex, freeBindingId));
}
auto OpenGLRenderPipelineLayout::GetStorageBufferDescriptor(std::size_t poolIndex, std::size_t bindingIndex, std::size_t descriptorIndex) -> StorageBufferDescriptor&
{
assert(poolIndex < m_descriptorPools.size());
auto& pool = m_descriptorPools[poolIndex];
assert(!pool.freeBindings.Test(bindingIndex));
assert(descriptorIndex < m_maxDescriptorCount);
return pool.descriptors[bindingIndex * m_maxDescriptorCount + descriptorIndex].emplace<StorageBufferDescriptor>();
}
auto OpenGLRenderPipelineLayout::GetTextureDescriptor(std::size_t poolIndex, std::size_t bindingIndex, std::size_t descriptorIndex) -> TextureDescriptor&
{
assert(poolIndex < m_descriptorPools.size());

View File

@ -34,7 +34,14 @@ namespace Nz
UInt32 bindingPoint = bindingMappingIt->second;
if constexpr (std::is_same_v<DescriptorType, OpenGLRenderPipelineLayout::TextureDescriptor>)
if constexpr (std::is_same_v<DescriptorType, OpenGLRenderPipelineLayout::StorageBufferDescriptor>)
{
if (bindingInfo.type != ShaderBindingType::StorageBuffer)
throw std::runtime_error("descriptor (set=" + std::to_string(setIndex) + ", binding=" + std::to_string(bindingIndex) + ") is not a storage buffer");
context.BindStorageBuffer(bindingPoint, descriptor.buffer, descriptor.offset, descriptor.size);
}
else if constexpr (std::is_same_v<DescriptorType, OpenGLRenderPipelineLayout::TextureDescriptor>)
{
if (bindingInfo.type != ShaderBindingType::Texture)
throw std::runtime_error("descriptor (set=" + std::to_string(setIndex) + ", binding=" + std::to_string(bindingIndex) + ") is not a texture");
@ -63,8 +70,24 @@ namespace Nz
std::visit([&](auto&& arg)
{
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, StorageBufferBinding>)
{
auto& storageDescriptor = m_owner.GetStorageBufferDescriptor(m_poolIndex, m_bindingIndex, binding.bindingIndex);
storageDescriptor.offset = arg.offset;
storageDescriptor.size = arg.range;
if constexpr (std::is_same_v<T, TextureBinding>)
if (OpenGLBuffer* glBuffer = static_cast<OpenGLBuffer*>(arg.buffer))
{
if (glBuffer->GetType() != BufferType::Storage)
throw std::runtime_error("expected storage buffer");
storageDescriptor.buffer = glBuffer->GetBuffer().GetObjectId();
}
else
storageDescriptor.buffer = 0;
}
else if constexpr (std::is_same_v<T, TextureBinding>)
{
auto& textureDescriptor = m_owner.GetTextureDescriptor(m_poolIndex, m_bindingIndex, binding.bindingIndex);
@ -103,7 +126,7 @@ namespace Nz
uboDescriptor.buffer = 0;
}
else
static_assert(AlwaysFalse<T>::value, "non-exhaustive visitor");
static_assert(AlwaysFalse<T>(), "non-exhaustive visitor");
}, binding.content);
}

View File

@ -191,6 +191,28 @@ namespace Nz::GL
}
}
void Context::BindStorageBuffer(UInt32 storageUnit, GLuint buffer, GLintptr offset, GLsizeiptr size) const
{
if (storageUnit >= m_state.storageUnits.size())
throw std::runtime_error("unsupported storage buffer unit #" + std::to_string(storageUnit));
auto& unit = m_state.storageUnits[storageUnit];
if (unit.buffer != buffer || unit.offset != offset || unit.size != size)
{
if (!SetCurrentContext(this))
throw std::runtime_error("failed to activate context");
glBindBufferRange(GL_SHADER_STORAGE_BUFFER, storageUnit, buffer, offset, size);
unit.buffer = buffer;
unit.offset = offset;
unit.size = size;
// glBindBufferRange does replace the currently bound buffer
m_state.bufferTargets[UnderlyingCast(BufferTarget::Storage)] = buffer;
}
}
void Context::BindTexture(TextureTarget target, GLuint texture) const
{
BindTexture(m_state.currentTextureUnit, target, texture);
@ -375,6 +397,12 @@ namespace Nz::GL
else if (m_supportedExtensions.count("GL_ARB_gl_spirv"))
m_extensionStatus[UnderlyingCast(Extension::SpirV)] = ExtensionStatus::ARB;
// Storage buffers (SSBO)
if ((m_params.type == ContextType::OpenGL && glVersion >= 430) || (m_params.type == ContextType::OpenGL_ES && glVersion >= 310))
m_extensionStatus[UnderlyingCast(Extension::StorageBuffers)] = ExtensionStatus::Core;
else if (m_supportedExtensions.count("GL_ARB_shader_storage_buffer_object"))
m_extensionStatus[UnderlyingCast(Extension::StorageBuffers)] = ExtensionStatus::ARB;
// Texture compression (S3tc)
if (m_supportedExtensions.count("GL_EXT_texture_compression_s3tc"))
m_extensionStatus[UnderlyingCast(Extension::TextureCompressionS3tc)] = ExtensionStatus::EXT;
@ -449,7 +477,7 @@ namespace Nz::GL
GLint maxTextureUnits = -1;
glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &maxTextureUnits);
if (maxTextureUnits < 32) //< OpenGL ES 3.0 requires at least 32 textures units
NazaraWarning("GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS is " + std::to_string(maxTextureUnits) + ", >= 32 expected");
NazaraWarning("GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS is " + std::to_string(maxTextureUnits) + ", expected >= 32");
assert(maxTextureUnits > 0);
m_state.textureUnits.resize(maxTextureUnits);
@ -457,11 +485,22 @@ namespace Nz::GL
GLint maxUniformBufferUnits = -1;
glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &maxUniformBufferUnits);
if (maxUniformBufferUnits < 24) //< OpenGL ES 3.0 requires at least 24 uniform buffers units
NazaraWarning("GL_MAX_UNIFORM_BUFFER_BINDINGS is " + std::to_string(maxUniformBufferUnits) + ", >= 24 expected");
NazaraWarning("GL_MAX_UNIFORM_BUFFER_BINDINGS is " + std::to_string(maxUniformBufferUnits) + ", expected >= 24");
assert(maxUniformBufferUnits > 0);
m_state.uboUnits.resize(maxUniformBufferUnits);
if (IsExtensionSupported(Extension::StorageBuffers))
{
GLint maxStorageBufferUnits = -1;
glGetIntegerv(GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS, &maxStorageBufferUnits);
if (maxStorageBufferUnits < 24) //< OpenGL ES 3.1 requires at least 8 storage buffers units
NazaraWarning("GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS is " + std::to_string(maxUniformBufferUnits) + ", expected >= 8");
assert(maxStorageBufferUnits > 0);
m_state.storageUnits.resize(maxStorageBufferUnits);
}
std::array<GLint, 4> res;
glGetIntegerv(GL_SCISSOR_BOX, res.data());

View File

@ -51,5 +51,11 @@ namespace Nz
NazaraWarning("non-solid face filling was enabled but device doesn't support it, disabling...");
enabledFeatures.nonSolidFaceFilling = false;
}
if (enabledFeatures.storageBuffers && !supportedFeatures.storageBuffers)
{
NazaraWarning("storage buffers support was enabled but device doesn't support it, disabling...");
enabledFeatures.storageBuffers = false;
}
}
}

View File

@ -56,8 +56,10 @@ namespace Nz
deviceInfo.features.anisotropicFiltering = physDevice.features.samplerAnisotropy;
deviceInfo.features.depthClamping = physDevice.features.depthClamp;
deviceInfo.features.nonSolidFaceFilling = physDevice.features.fillModeNonSolid;
deviceInfo.features.storageBuffers = true;
deviceInfo.limits.minUniformBufferOffsetAlignment = physDevice.properties.limits.minUniformBufferOffsetAlignment;
deviceInfo.limits.maxStorageBufferSize = physDevice.properties.limits.maxStorageBufferRange;
deviceInfo.limits.maxUniformBufferSize = physDevice.properties.limits.maxUniformBufferRange;
switch (physDevice.properties.deviceType)
{

View File

@ -32,7 +32,20 @@ namespace Nz
{
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, TextureBinding>)
if constexpr (std::is_same_v<T, StorageBufferBinding>)
{
VulkanBuffer* vkBuffer = static_cast<VulkanBuffer*>(arg.buffer);
VkDescriptorBufferInfo& bufferInfo = bufferBinding.emplace_back();
bufferInfo.buffer = (vkBuffer) ? vkBuffer->GetBuffer() : VK_NULL_HANDLE;
bufferInfo.offset = arg.offset;
bufferInfo.range = arg.range;
writeOp.descriptorCount = 1;
writeOp.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
writeOp.pBufferInfo = &bufferInfo;
}
else if constexpr (std::is_same_v<T, TextureBinding>)
{
const VulkanTexture* vkTexture = static_cast<const VulkanTexture*>(arg.texture);
const VulkanTextureSampler* vkSampler = static_cast<const VulkanTextureSampler*>(arg.sampler);
@ -60,7 +73,7 @@ namespace Nz
writeOp.pBufferInfo = &bufferInfo;
}
else
static_assert(AlwaysFalse<T>::value, "non-exhaustive visitor");
static_assert(AlwaysFalse<T>(), "non-exhaustive visitor");
}, binding.content);
}