Renderer: Add support for storage buffers
This commit is contained in:
parent
0978feafbc
commit
093d9d344e
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -136,6 +136,7 @@ namespace Nz
|
|||
|
||||
enum class ShaderBindingType
|
||||
{
|
||||
StorageBuffer,
|
||||
Texture,
|
||||
UniformBuffer,
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ namespace Nz
|
|||
{
|
||||
Index,
|
||||
Vertex,
|
||||
Storage,
|
||||
Uniform,
|
||||
|
||||
Max = Uniform
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue