diff --git a/include/Nazara/OpenGLRenderer/OpenGLRenderPipelineLayout.hpp b/include/Nazara/OpenGLRenderer/OpenGLRenderPipelineLayout.hpp index 14c96bc3c..71f1b1bac 100644 --- a/include/Nazara/OpenGLRenderer/OpenGLRenderPipelineLayout.hpp +++ b/include/Nazara/OpenGLRenderer/OpenGLRenderPipelineLayout.hpp @@ -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 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; + using Descriptor = std::variant; struct DescriptorPool { diff --git a/include/Nazara/OpenGLRenderer/Utils.inl b/include/Nazara/OpenGLRenderer/Utils.inl index 576d12823..58e9d95cd 100644 --- a/include/Nazara/OpenGLRenderer/Utils.inl +++ b/include/Nazara/OpenGLRenderer/Utils.inl @@ -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; } diff --git a/include/Nazara/OpenGLRenderer/Wrapper/Context.hpp b/include/Nazara/OpenGLRenderer/Wrapper/Context.hpp index 07699d212..26d2a8f68 100644 --- a/include/Nazara/OpenGLRenderer/Wrapper/Context.hpp +++ b/include/Nazara/OpenGLRenderer/Wrapper/Context.hpp @@ -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 textureTargets = { 0 }; - }; - - struct UniformBufferUnit + struct BufferBinding { GLuint buffer = 0; GLintptr offset = 0; GLsizeiptr size = 0; }; + struct TextureUnit + { + GLuint sampler = 0; + std::array textureTargets = { 0 }; + }; + std::array bufferTargets = { 0 }; std::vector textureUnits; - std::vector uboUnits; + std::vector storageUnits; + std::vector uboUnits; Box scissorBox; Box viewport; GLuint boundProgram = 0; diff --git a/include/Nazara/Renderer/Enums.hpp b/include/Nazara/Renderer/Enums.hpp index d4bfcabbd..cd884c3e3 100644 --- a/include/Nazara/Renderer/Enums.hpp +++ b/include/Nazara/Renderer/Enums.hpp @@ -136,6 +136,7 @@ namespace Nz enum class ShaderBindingType { + StorageBuffer, Texture, UniformBuffer, diff --git a/include/Nazara/Renderer/RenderDeviceInfo.hpp b/include/Nazara/Renderer/RenderDeviceInfo.hpp index 77abfe298..bab466481 100644 --- a/include/Nazara/Renderer/RenderDeviceInfo.hpp +++ b/include/Nazara/Renderer/RenderDeviceInfo.hpp @@ -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 diff --git a/include/Nazara/Renderer/ShaderBinding.hpp b/include/Nazara/Renderer/ShaderBinding.hpp index a5c4b12d5..b870bf1b7 100644 --- a/include/Nazara/Renderer/ShaderBinding.hpp +++ b/include/Nazara/Renderer/ShaderBinding.hpp @@ -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 content; + std::variant content; }; protected: diff --git a/include/Nazara/Utility/Enums.hpp b/include/Nazara/Utility/Enums.hpp index 897187dbf..a31b3f5ed 100644 --- a/include/Nazara/Utility/Enums.hpp +++ b/include/Nazara/Utility/Enums.hpp @@ -60,6 +60,7 @@ namespace Nz { Index, Vertex, + Storage, Uniform, Max = Uniform diff --git a/include/Nazara/VulkanRenderer/Utils.inl b/include/Nazara/VulkanRenderer/Utils.inl index ead7a939f..a7cbefb1d 100644 --- a/include/Nazara/VulkanRenderer/Utils.inl +++ b/include/Nazara/VulkanRenderer/Utils.inl @@ -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; } diff --git a/src/Nazara/OpenGLRenderer/OpenGLBuffer.cpp b/src/Nazara/OpenGLRenderer/OpenGLBuffer.cpp index fa8f91f78..4b55ef20b 100644 --- a/src/Nazara/OpenGLRenderer/OpenGLBuffer.cpp +++ b/src/Nazara/OpenGLRenderer/OpenGLBuffer.cpp @@ -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; diff --git a/src/Nazara/OpenGLRenderer/OpenGLDevice.cpp b/src/Nazara/OpenGLRenderer/OpenGLDevice.cpp index b66dbb97f..702bad047 100644 --- a/src/Nazara/OpenGLRenderer/OpenGLDevice.cpp +++ b/src/Nazara/OpenGLRenderer/OpenGLDevice.cpp @@ -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(minUboOffsetAlignment); + GLint maxUboBlockSize; + m_referenceContext->glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &maxUboBlockSize); + + assert(maxUboBlockSize >= 1); + m_deviceInfo.limits.maxUniformBufferSize = static_cast(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(maxStorageBlockSize); + } + else + m_deviceInfo.limits.maxStorageBufferSize = 0; + m_contexts.insert(m_referenceContext.get()); } diff --git a/src/Nazara/OpenGLRenderer/OpenGLRenderPipelineLayout.cpp b/src/Nazara/OpenGLRenderer/OpenGLRenderPipelineLayout.cpp index f2dbc70f5..ecae84423 100644 --- a/src/Nazara/OpenGLRenderer/OpenGLRenderPipelineLayout.cpp +++ b/src/Nazara/OpenGLRenderer/OpenGLRenderPipelineLayout.cpp @@ -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(); + } + auto OpenGLRenderPipelineLayout::GetTextureDescriptor(std::size_t poolIndex, std::size_t bindingIndex, std::size_t descriptorIndex) -> TextureDescriptor& { assert(poolIndex < m_descriptorPools.size()); diff --git a/src/Nazara/OpenGLRenderer/OpenGLShaderBinding.cpp b/src/Nazara/OpenGLRenderer/OpenGLShaderBinding.cpp index c4203d109..cfdf6be89 100644 --- a/src/Nazara/OpenGLRenderer/OpenGLShaderBinding.cpp +++ b/src/Nazara/OpenGLRenderer/OpenGLShaderBinding.cpp @@ -34,7 +34,14 @@ namespace Nz UInt32 bindingPoint = bindingMappingIt->second; - if constexpr (std::is_same_v) + if constexpr (std::is_same_v) + { + 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) { 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; + + if constexpr (std::is_same_v) + { + 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) + if (OpenGLBuffer* glBuffer = static_cast(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) { auto& textureDescriptor = m_owner.GetTextureDescriptor(m_poolIndex, m_bindingIndex, binding.bindingIndex); @@ -103,7 +126,7 @@ namespace Nz uboDescriptor.buffer = 0; } else - static_assert(AlwaysFalse::value, "non-exhaustive visitor"); + static_assert(AlwaysFalse(), "non-exhaustive visitor"); }, binding.content); } diff --git a/src/Nazara/OpenGLRenderer/Wrapper/Context.cpp b/src/Nazara/OpenGLRenderer/Wrapper/Context.cpp index 3d3542add..77f8b619e 100644 --- a/src/Nazara/OpenGLRenderer/Wrapper/Context.cpp +++ b/src/Nazara/OpenGLRenderer/Wrapper/Context.cpp @@ -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 res; glGetIntegerv(GL_SCISSOR_BOX, res.data()); diff --git a/src/Nazara/Renderer/RenderDevice.cpp b/src/Nazara/Renderer/RenderDevice.cpp index a9ed230e7..618af4e99 100644 --- a/src/Nazara/Renderer/RenderDevice.cpp +++ b/src/Nazara/Renderer/RenderDevice.cpp @@ -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; + } } } diff --git a/src/Nazara/VulkanRenderer/Vulkan.cpp b/src/Nazara/VulkanRenderer/Vulkan.cpp index 5c181623e..da7e20908 100644 --- a/src/Nazara/VulkanRenderer/Vulkan.cpp +++ b/src/Nazara/VulkanRenderer/Vulkan.cpp @@ -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) { diff --git a/src/Nazara/VulkanRenderer/VulkanShaderBinding.cpp b/src/Nazara/VulkanRenderer/VulkanShaderBinding.cpp index f8b79f5da..6035a2a2f 100644 --- a/src/Nazara/VulkanRenderer/VulkanShaderBinding.cpp +++ b/src/Nazara/VulkanRenderer/VulkanShaderBinding.cpp @@ -32,7 +32,20 @@ namespace Nz { using T = std::decay_t; - if constexpr (std::is_same_v) + if constexpr (std::is_same_v) + { + VulkanBuffer* vkBuffer = static_cast(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) { const VulkanTexture* vkTexture = static_cast(arg.texture); const VulkanTextureSampler* vkSampler = static_cast(arg.sampler); @@ -60,7 +73,7 @@ namespace Nz writeOp.pBufferInfo = &bufferInfo; } else - static_assert(AlwaysFalse::value, "non-exhaustive visitor"); + static_assert(AlwaysFalse(), "non-exhaustive visitor"); }, binding.content); }