forked from mirrors/0ad
Adds storage buffer support to Vulkan and GL.
The idea is similar to the storage images but we need a separate descriptor set in Vulkan and a program interface to gather used buffer in GL. For Vulkan we also need to track buffers to free used descriptor sets.
This commit is contained in:
@@ -0,0 +1,108 @@
|
|||||||
|
/* Copyright (C) 2024 Wildfire Games.
|
||||||
|
* This file is part of 0 A.D.
|
||||||
|
*
|
||||||
|
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* 0 A.D. is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef INCLUDED_RENDERER_BACKEND_BARRIER
|
||||||
|
#define INCLUDED_RENDERER_BACKEND_BARRIER
|
||||||
|
|
||||||
|
namespace Renderer
|
||||||
|
{
|
||||||
|
|
||||||
|
namespace Backend
|
||||||
|
{
|
||||||
|
|
||||||
|
// PipelineStageFlags and AccessFlags are mostly taken from the Vulkan
|
||||||
|
// specification.
|
||||||
|
|
||||||
|
namespace PipelineStage
|
||||||
|
{
|
||||||
|
static constexpr uint32_t DRAW_INDIRECT{
|
||||||
|
1u << 0u};
|
||||||
|
static constexpr uint32_t VERTEX_INPUT{
|
||||||
|
1u << 1u};
|
||||||
|
static constexpr uint32_t VERTEX_SHADER{
|
||||||
|
1u << 2u};
|
||||||
|
static constexpr uint32_t FRAGMENT_SHADER{
|
||||||
|
1u << 3u};
|
||||||
|
static constexpr uint32_t EARLY_FRAGMENT_TESTS{
|
||||||
|
1u << 4u};
|
||||||
|
static constexpr uint32_t LATE_FRAGMENT_TESTS{
|
||||||
|
1u << 5u};
|
||||||
|
static constexpr uint32_t COLOR_ATTACHMENT_OUTPUT{
|
||||||
|
1u << 6u};
|
||||||
|
static constexpr uint32_t COMPUTE_SHADER{
|
||||||
|
1u << 7u};
|
||||||
|
static constexpr uint32_t TRANSFER{
|
||||||
|
1u << 8u};
|
||||||
|
static constexpr uint32_t HOST{
|
||||||
|
1u << 9u};
|
||||||
|
static constexpr uint32_t ACCELERATION_STRUCTURE_BUILD{
|
||||||
|
1u << 10u};
|
||||||
|
static constexpr uint32_t RAY_TRACING_SHADER{
|
||||||
|
1u << 11u};
|
||||||
|
static constexpr uint32_t TASK_SHADER{
|
||||||
|
1u << 12u};
|
||||||
|
static constexpr uint32_t MESH_SHADER{
|
||||||
|
1u << 13u};
|
||||||
|
} // namespace PipelineStage
|
||||||
|
|
||||||
|
namespace Access
|
||||||
|
{
|
||||||
|
static constexpr uint32_t INDIRECT_COMMAND_READ{
|
||||||
|
1u << 0u};
|
||||||
|
static constexpr uint32_t INDEX_READ{
|
||||||
|
1u << 1u};
|
||||||
|
static constexpr uint32_t VERTEX_ATTRIBUTE_READ{
|
||||||
|
1u << 2u};
|
||||||
|
static constexpr uint32_t UNIFORM_READ{
|
||||||
|
1u << 3u};
|
||||||
|
static constexpr uint32_t INPUT_ATTACHMENT_READ{
|
||||||
|
1u << 4u};
|
||||||
|
static constexpr uint32_t SHADER_READ{
|
||||||
|
1u << 5u};
|
||||||
|
static constexpr uint32_t SHADER_WRITE{
|
||||||
|
1u << 6u};
|
||||||
|
static constexpr uint32_t COLOR_ATTACHMENT_READ{
|
||||||
|
1u << 7u};
|
||||||
|
static constexpr uint32_t COLOR_ATTACHMENT_WRITE{
|
||||||
|
1u << 8u};
|
||||||
|
static constexpr uint32_t DEPTH_STENCIL_ATTACHMENT_READ{
|
||||||
|
1u << 9u};
|
||||||
|
static constexpr uint32_t DEPTH_STENCIL_ATTACHMENT_WRITE{
|
||||||
|
1u << 10u};
|
||||||
|
static constexpr uint32_t TRANSFER_READ{
|
||||||
|
1u << 11u};
|
||||||
|
static constexpr uint32_t TRANSFER_WRITE{
|
||||||
|
1u << 12u};
|
||||||
|
static constexpr uint32_t HOST_READ{
|
||||||
|
1u << 13u};
|
||||||
|
static constexpr uint32_t HOST_WRITE{
|
||||||
|
1u << 14u};
|
||||||
|
static constexpr uint32_t MEMORY_READ{
|
||||||
|
1u << 15u};
|
||||||
|
static constexpr uint32_t MEMORY_WRITE{
|
||||||
|
1u << 16u};
|
||||||
|
static constexpr uint32_t ACCELERATION_STRUCTURE_READ{
|
||||||
|
1u << 17u};
|
||||||
|
static constexpr uint32_t ACCELERATION_STRUCTURE_WRITE{
|
||||||
|
1u << 18u};
|
||||||
|
} // namespace Access
|
||||||
|
|
||||||
|
} // namespace Backend
|
||||||
|
|
||||||
|
} // namespace Renderer
|
||||||
|
|
||||||
|
#endif // INCLUDED_RENDERER_BACKEND_BARRIER
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2023 Wildfire Games.
|
/* Copyright (C) 2024 Wildfire Games.
|
||||||
* This file is part of 0 A.D.
|
* This file is part of 0 A.D.
|
||||||
*
|
*
|
||||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||||
@@ -43,9 +43,13 @@ enum class Format
|
|||||||
R16_UNORM,
|
R16_UNORM,
|
||||||
R16_UINT,
|
R16_UINT,
|
||||||
R16_SINT,
|
R16_SINT,
|
||||||
|
R16_SFLOAT,
|
||||||
R16G16_UNORM,
|
R16G16_UNORM,
|
||||||
R16G16_UINT,
|
R16G16_UINT,
|
||||||
R16G16_SINT,
|
R16G16_SINT,
|
||||||
|
R16G16_SFLOAT,
|
||||||
|
R16G16B16_SFLOAT,
|
||||||
|
R16G16B16A16_SFLOAT,
|
||||||
|
|
||||||
R32_SFLOAT,
|
R32_SFLOAT,
|
||||||
R32G32_SFLOAT,
|
R32G32_SFLOAT,
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ public:
|
|||||||
static constexpr uint32_t DYNAMIC = 1u << 0u;
|
static constexpr uint32_t DYNAMIC = 1u << 0u;
|
||||||
static constexpr uint32_t TRANSFER_SRC = 1u << 1u;
|
static constexpr uint32_t TRANSFER_SRC = 1u << 1u;
|
||||||
static constexpr uint32_t TRANSFER_DST = 1u << 2u;
|
static constexpr uint32_t TRANSFER_DST = 1u << 2u;
|
||||||
|
static constexpr uint32_t STORAGE = 1u << 3u;
|
||||||
};
|
};
|
||||||
|
|
||||||
virtual Type GetType() const = 0;
|
virtual Type GetType() const = 0;
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ public:
|
|||||||
float maxAnisotropy;
|
float maxAnisotropy;
|
||||||
uint32_t maxTextureSize;
|
uint32_t maxTextureSize;
|
||||||
bool instancing;
|
bool instancing;
|
||||||
|
bool storage;
|
||||||
};
|
};
|
||||||
|
|
||||||
virtual ~IDevice() {}
|
virtual ~IDevice() {}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
#define INCLUDED_RENDERER_BACKEND_IDEVICECOMMANDCONTEXT
|
#define INCLUDED_RENDERER_BACKEND_IDEVICECOMMANDCONTEXT
|
||||||
|
|
||||||
#include "ps/containers/Span.h"
|
#include "ps/containers/Span.h"
|
||||||
|
#include "renderer/backend/Barrier.h"
|
||||||
#include "renderer/backend/Format.h"
|
#include "renderer/backend/Format.h"
|
||||||
#include "renderer/backend/IDeviceObject.h"
|
#include "renderer/backend/IDeviceObject.h"
|
||||||
#include "renderer/backend/PipelineState.h"
|
#include "renderer/backend/PipelineState.h"
|
||||||
@@ -184,6 +185,15 @@ public:
|
|||||||
const uint32_t groupCountY,
|
const uint32_t groupCountY,
|
||||||
const uint32_t groupCountZ) = 0;
|
const uint32_t groupCountZ) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a memory barrier which guarantees that all memory accesses
|
||||||
|
* matched by `srcAccessMask` in src are completed before all memory accesses
|
||||||
|
* described by `dstAccessMask` in dst.
|
||||||
|
*/
|
||||||
|
virtual void InsertMemoryBarrier(
|
||||||
|
const uint32_t srcStageMask, const uint32_t dstStageMask,
|
||||||
|
const uint32_t srcAccessMask, const uint32_t dstAccessMask) = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets a read-only texture to the binding slot.
|
* Sets a read-only texture to the binding slot.
|
||||||
*/
|
*/
|
||||||
@@ -193,6 +203,7 @@ public:
|
|||||||
* Sets a read & write resource to the binding slot.
|
* Sets a read & write resource to the binding slot.
|
||||||
*/
|
*/
|
||||||
virtual void SetStorageTexture(const int32_t bindingSlot, ITexture* texture) = 0;
|
virtual void SetStorageTexture(const int32_t bindingSlot, ITexture* texture) = 0;
|
||||||
|
virtual void SetStorageBuffer(const int32_t bindingSlot, IBuffer* buffer) = 0;
|
||||||
|
|
||||||
virtual void SetUniform(
|
virtual void SetUniform(
|
||||||
const int32_t bindingSlot,
|
const int32_t bindingSlot,
|
||||||
|
|||||||
@@ -202,6 +202,11 @@ void CDeviceCommandContext::Dispatch(const uint32_t, const uint32_t, const uint3
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CDeviceCommandContext::InsertMemoryBarrier(
|
||||||
|
const uint32_t, const uint32_t, const uint32_t, const uint32_t)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
void CDeviceCommandContext::SetTexture(const int32_t, ITexture*)
|
void CDeviceCommandContext::SetTexture(const int32_t, ITexture*)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -210,6 +215,10 @@ void CDeviceCommandContext::SetStorageTexture(const int32_t, ITexture*)
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CDeviceCommandContext::SetStorageBuffer(const int32_t, IBuffer*)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
void CDeviceCommandContext::SetUniform(const int32_t, const float)
|
void CDeviceCommandContext::SetUniform(const int32_t, const float)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,9 +120,14 @@ public:
|
|||||||
const uint32_t groupCountY,
|
const uint32_t groupCountY,
|
||||||
const uint32_t groupCountZ) override;
|
const uint32_t groupCountZ) override;
|
||||||
|
|
||||||
|
void InsertMemoryBarrier(
|
||||||
|
const uint32_t srcStageMask, const uint32_t dstStageMask,
|
||||||
|
const uint32_t srcAccessMask, const uint32_t dstAccessMask) override;
|
||||||
|
|
||||||
void SetTexture(const int32_t bindingSlot, ITexture* texture) override;
|
void SetTexture(const int32_t bindingSlot, ITexture* texture) override;
|
||||||
|
|
||||||
void SetStorageTexture(const int32_t bindingSlot, ITexture* texture) override;
|
void SetStorageTexture(const int32_t bindingSlot, ITexture* texture) override;
|
||||||
|
void SetStorageBuffer(const int32_t bindingSlot, IBuffer* buffer) override;
|
||||||
|
|
||||||
void SetUniform(
|
void SetUniform(
|
||||||
const int32_t bindingSlot,
|
const int32_t bindingSlot,
|
||||||
|
|||||||
@@ -35,19 +35,42 @@ namespace Backend
|
|||||||
namespace GL
|
namespace GL
|
||||||
{
|
{
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
GLenum GetTargetFromBufferType(const IBuffer::Type type)
|
||||||
|
{
|
||||||
|
GLenum target{GL_ARRAY_BUFFER};
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case IBuffer::Type::INDEX:
|
||||||
|
target = GL_ELEMENT_ARRAY_BUFFER;
|
||||||
|
break;
|
||||||
|
case IBuffer::Type::UNIFORM:
|
||||||
|
target = GL_UNIFORM_BUFFER;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
target = GL_ARRAY_BUFFER;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
// static
|
// static
|
||||||
std::unique_ptr<CBuffer> CBuffer::Create(
|
std::unique_ptr<CBuffer> CBuffer::Create(
|
||||||
CDevice* device, const char* name,
|
CDevice* device, const char* name,
|
||||||
const Type type, const uint32_t size, const uint32_t usage)
|
const Type type, const uint32_t size, const uint32_t usage)
|
||||||
{
|
{
|
||||||
ENSURE(type == Type::VERTEX || type == Type::INDEX);
|
ENSURE(type == Type::VERTEX || type == Type::INDEX || type == Type::UNIFORM);
|
||||||
std::unique_ptr<CBuffer> buffer(new CBuffer());
|
std::unique_ptr<CBuffer> buffer(new CBuffer());
|
||||||
buffer->m_Device = device;
|
buffer->m_Device = device;
|
||||||
buffer->m_Type = type;
|
buffer->m_Type = type;
|
||||||
buffer->m_Size = size;
|
buffer->m_Size = size;
|
||||||
buffer->m_Usage = usage;
|
buffer->m_Usage = usage;
|
||||||
glGenBuffersARB(1, &buffer->m_Handle);
|
glGenBuffersARB(1, &buffer->m_Handle);
|
||||||
const GLenum target = type == Type::INDEX ? GL_ELEMENT_ARRAY_BUFFER : GL_ARRAY_BUFFER;
|
const GLenum target{GetTargetFromBufferType(type)};
|
||||||
glBindBufferARB(target, buffer->m_Handle);
|
glBindBufferARB(target, buffer->m_Handle);
|
||||||
glBufferDataARB(target, size, nullptr, (usage & IBuffer::Usage::DYNAMIC) ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW);
|
glBufferDataARB(target, size, nullptr, (usage & IBuffer::Usage::DYNAMIC) ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW);
|
||||||
#if !CONFIG2_GLES
|
#if !CONFIG2_GLES
|
||||||
|
|||||||
@@ -441,12 +441,23 @@ std::unique_ptr<IDevice> CDevice::Create(SDL_Window* window, const bool arb)
|
|||||||
|
|
||||||
#if CONFIG2_GLES
|
#if CONFIG2_GLES
|
||||||
capabilities.instancing = false;
|
capabilities.instancing = false;
|
||||||
|
capabilities.storage = false;
|
||||||
#else
|
#else
|
||||||
capabilities.instancing =
|
capabilities.instancing =
|
||||||
!device->m_ARB &&
|
!device->m_ARB &&
|
||||||
(ogl_HaveVersion(3, 3) ||
|
(ogl_HaveVersion(3, 3) ||
|
||||||
(ogl_HaveExtension("GL_ARB_draw_instanced") &&
|
(ogl_HaveExtension("GL_ARB_draw_instanced") &&
|
||||||
ogl_HaveExtension("GL_ARB_instanced_arrays")));
|
ogl_HaveExtension("GL_ARB_instanced_arrays")));
|
||||||
|
GLint maxStorageBufferSize{0};
|
||||||
|
if (ogl_HaveExtension("GL_ARB_shader_storage_buffer_object"))
|
||||||
|
glGetIntegerv(GL_MAX_SHADER_STORAGE_BLOCK_SIZE, &maxStorageBufferSize);
|
||||||
|
capabilities.storage =
|
||||||
|
capabilities.computeShaders && maxStorageBufferSize > 0
|
||||||
|
&& static_cast<size_t>(maxStorageBufferSize) >= 128 * MiB
|
||||||
|
&& ogl_HaveExtension("GL_ARB_uniform_buffer_object")
|
||||||
|
&& ogl_HaveExtension("GL_ARB_shader_storage_buffer_object")
|
||||||
|
&& ogl_HaveExtension("GL_ARB_half_float_vertex")
|
||||||
|
&& ogl_HaveExtension("GL_ARB_program_interface_query");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return device;
|
return device;
|
||||||
@@ -762,6 +773,18 @@ void CDevice::Report(const ScriptRequest& rq, JS::HandleValue settings)
|
|||||||
INTEGER(MAX_VERTEX_VARYING_COMPONENTS_ARB);
|
INTEGER(MAX_VERTEX_VARYING_COMPONENTS_ARB);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ogl_HaveExtension("GL_ARB_uniform_buffer_object"))
|
||||||
|
{
|
||||||
|
INTEGER(MAX_UNIFORM_BLOCK_SIZE);
|
||||||
|
INTEGER(MAX_UNIFORM_BUFFER_BINDINGS);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ogl_HaveExtension("GL_ARB_shader_storage_buffer_object"))
|
||||||
|
{
|
||||||
|
INTEGER(MAX_SHADER_STORAGE_BLOCK_SIZE);
|
||||||
|
INTEGER(MAX_SHADER_STORAGE_BUFFER_BINDINGS);
|
||||||
|
}
|
||||||
|
|
||||||
#else // CONFIG2_GLES
|
#else // CONFIG2_GLES
|
||||||
|
|
||||||
// Core OpenGL ES 2.0:
|
// Core OpenGL ES 2.0:
|
||||||
|
|||||||
@@ -103,8 +103,10 @@ GLenum BufferTypeToGLTarget(const CBuffer::Type type)
|
|||||||
case CBuffer::Type::INDEX:
|
case CBuffer::Type::INDEX:
|
||||||
target = GL_ELEMENT_ARRAY_BUFFER;
|
target = GL_ELEMENT_ARRAY_BUFFER;
|
||||||
break;
|
break;
|
||||||
case CBuffer::Type::UPLOAD:
|
|
||||||
case CBuffer::Type::UNIFORM:
|
case CBuffer::Type::UNIFORM:
|
||||||
|
target = GL_UNIFORM_BUFFER;
|
||||||
|
break;
|
||||||
|
case CBuffer::Type::UPLOAD:
|
||||||
debug_warn("Unsupported buffer type.");
|
debug_warn("Unsupported buffer type.");
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
@@ -451,7 +453,9 @@ void CDeviceCommandContext::UploadBufferRegion(
|
|||||||
ENSURE(dataOffset + dataSize <= buffer->GetSize());
|
ENSURE(dataOffset + dataSize <= buffer->GetSize());
|
||||||
const GLenum target = BufferTypeToGLTarget(buffer->GetType());
|
const GLenum target = BufferTypeToGLTarget(buffer->GetType());
|
||||||
ScopedBufferBind scopedBufferBind(this, buffer->As<CBuffer>());
|
ScopedBufferBind scopedBufferBind(this, buffer->As<CBuffer>());
|
||||||
if (buffer->IsDynamic())
|
// Uniform buffers is a relatively new feature so we don't need to use a
|
||||||
|
// dynamic upload.
|
||||||
|
if (buffer->IsDynamic() && buffer->GetType() != IBuffer::Type::UNIFORM)
|
||||||
{
|
{
|
||||||
UploadDynamicBufferRegionImpl(target, buffer->GetSize(), dataOffset, dataSize, [data, dataSize](u8* mappedData)
|
UploadDynamicBufferRegionImpl(target, buffer->GetSize(), dataOffset, dataSize, [data, dataSize](u8* mappedData)
|
||||||
{
|
{
|
||||||
@@ -1257,12 +1261,16 @@ void CDeviceCommandContext::Dispatch(
|
|||||||
const uint32_t groupCountY,
|
const uint32_t groupCountY,
|
||||||
const uint32_t groupCountZ)
|
const uint32_t groupCountZ)
|
||||||
{
|
{
|
||||||
#if !CONFIG2_GLES
|
#if !CONFIG2_GLES
|
||||||
ENSURE(m_InsideComputePass);
|
ENSURE(m_InsideComputePass);
|
||||||
glDispatchCompute(groupCountX, groupCountY, groupCountZ);
|
glDispatchCompute(groupCountX, groupCountY, groupCountZ);
|
||||||
// TODO: we might want to do binding tracking to avoid redundant barriers.
|
// Storage buffers should be managed explicitly by InsertMemoryBarrier.
|
||||||
glMemoryBarrier(
|
if (m_ShaderProgram->HasImageUniforms())
|
||||||
GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_TEXTURE_FETCH_BARRIER_BIT | GL_TEXTURE_UPDATE_BARRIER_BIT | GL_FRAMEBUFFER_BARRIER_BIT);
|
{
|
||||||
|
// TODO: we might want to do binding tracking to avoid redundant barriers.
|
||||||
|
glMemoryBarrier(
|
||||||
|
GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_TEXTURE_FETCH_BARRIER_BIT | GL_TEXTURE_UPDATE_BARRIER_BIT | GL_FRAMEBUFFER_BARRIER_BIT );
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
UNUSED2(groupCountX);
|
UNUSED2(groupCountX);
|
||||||
UNUSED2(groupCountY);
|
UNUSED2(groupCountY);
|
||||||
@@ -1270,6 +1278,35 @@ void CDeviceCommandContext::Dispatch(
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CDeviceCommandContext::InsertMemoryBarrier(
|
||||||
|
const uint32_t UNUSED(srcStageMask), const uint32_t dstStageMask,
|
||||||
|
const uint32_t srcAccessMask, const uint32_t dstAccessMask)
|
||||||
|
{
|
||||||
|
#if !CONFIG2_GLES
|
||||||
|
ENSURE(!m_InsideFramebufferPass);
|
||||||
|
GLbitfield barriers{0};
|
||||||
|
if (srcAccessMask & Access::SHADER_WRITE)
|
||||||
|
{
|
||||||
|
if (dstStageMask & PipelineStage::VERTEX_INPUT)
|
||||||
|
{
|
||||||
|
if (dstAccessMask & Access::VERTEX_ATTRIBUTE_READ)
|
||||||
|
barriers |= GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT;
|
||||||
|
if (dstAccessMask & Access::INDEX_READ)
|
||||||
|
barriers |= GL_ELEMENT_ARRAY_BARRIER_BIT;
|
||||||
|
}
|
||||||
|
if (dstStageMask & (PipelineStage::VERTEX_SHADER | PipelineStage::FRAGMENT_SHADER | PipelineStage::COMPUTE_SHADER))
|
||||||
|
{
|
||||||
|
if (dstAccessMask & (Access::SHADER_READ | Access::SHADER_WRITE))
|
||||||
|
barriers |= GL_SHADER_STORAGE_BARRIER_BIT;
|
||||||
|
if (dstAccessMask & Access::UNIFORM_READ)
|
||||||
|
barriers |= GL_UNIFORM_BARRIER_BIT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (barriers)
|
||||||
|
glMemoryBarrier(barriers);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void CDeviceCommandContext::SetTexture(const int32_t bindingSlot, ITexture* texture)
|
void CDeviceCommandContext::SetTexture(const int32_t bindingSlot, ITexture* texture)
|
||||||
{
|
{
|
||||||
ENSURE(m_ShaderProgram);
|
ENSURE(m_ShaderProgram);
|
||||||
@@ -1332,6 +1369,21 @@ void CDeviceCommandContext::SetStorageTexture(const int32_t bindingSlot, ITextur
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CDeviceCommandContext::SetStorageBuffer(const int32_t bindingSlot, IBuffer* buffer)
|
||||||
|
{
|
||||||
|
#if !CONFIG2_GLES
|
||||||
|
if (bindingSlot < 0)
|
||||||
|
return;
|
||||||
|
ENSURE(m_ShaderProgram);
|
||||||
|
ENSURE(buffer);
|
||||||
|
ENSURE(buffer->GetUsage() & Renderer::Backend::IBuffer::Usage::STORAGE);
|
||||||
|
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, m_ShaderProgram->GetStorageBuffer(bindingSlot), buffer->As<CBuffer>()->GetHandle());
|
||||||
|
#else
|
||||||
|
UNUSED2(bindingSlot);
|
||||||
|
UNUSED2(buffer);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void CDeviceCommandContext::SetUniform(
|
void CDeviceCommandContext::SetUniform(
|
||||||
const int32_t bindingSlot,
|
const int32_t bindingSlot,
|
||||||
const float value)
|
const float value)
|
||||||
|
|||||||
@@ -129,9 +129,14 @@ public:
|
|||||||
const uint32_t groupCountY,
|
const uint32_t groupCountY,
|
||||||
const uint32_t groupCountZ) override;
|
const uint32_t groupCountZ) override;
|
||||||
|
|
||||||
|
void InsertMemoryBarrier(
|
||||||
|
const uint32_t srcStageMask, const uint32_t dstStageMask,
|
||||||
|
const uint32_t srcAccessMask, const uint32_t dstAccessMask) override;
|
||||||
|
|
||||||
void SetTexture(const int32_t bindingSlot, ITexture* texture) override;
|
void SetTexture(const int32_t bindingSlot, ITexture* texture) override;
|
||||||
|
|
||||||
void SetStorageTexture(const int32_t bindingSlot, ITexture* texture) override;
|
void SetStorageTexture(const int32_t bindingSlot, ITexture* texture) override;
|
||||||
|
void SetStorageBuffer(const int32_t bindingSlot, IBuffer* buffer) override;
|
||||||
|
|
||||||
void SetUniform(
|
void SetUniform(
|
||||||
const int32_t bindingSlot,
|
const int32_t bindingSlot,
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
#include "ps/Filesystem.h"
|
#include "ps/Filesystem.h"
|
||||||
#include "ps/Profile.h"
|
#include "ps/Profile.h"
|
||||||
#include "ps/XML/Xeromyces.h"
|
#include "ps/XML/Xeromyces.h"
|
||||||
|
#include "renderer/backend/gl/Buffer.h"
|
||||||
#include "renderer/backend/gl/Device.h"
|
#include "renderer/backend/gl/Device.h"
|
||||||
#include "renderer/backend/gl/DeviceCommandContext.h"
|
#include "renderer/backend/gl/DeviceCommandContext.h"
|
||||||
|
|
||||||
@@ -80,17 +81,22 @@ GLint GLSizeFromFormat(const Format format)
|
|||||||
{
|
{
|
||||||
GLint size = 1;
|
GLint size = 1;
|
||||||
if (format == Renderer::Backend::Format::R32_SFLOAT ||
|
if (format == Renderer::Backend::Format::R32_SFLOAT ||
|
||||||
format == Renderer::Backend::Format::R16_SINT)
|
format == Renderer::Backend::Format::R16_SINT ||
|
||||||
|
format == Renderer::Backend::Format::R16_SFLOAT)
|
||||||
size = 1;
|
size = 1;
|
||||||
else if (
|
else if (
|
||||||
format == Renderer::Backend::Format::R8G8_UNORM ||
|
format == Renderer::Backend::Format::R8G8_UNORM ||
|
||||||
format == Renderer::Backend::Format::R8G8_UINT ||
|
format == Renderer::Backend::Format::R8G8_UINT ||
|
||||||
format == Renderer::Backend::Format::R16G16_SINT ||
|
format == Renderer::Backend::Format::R16G16_SINT ||
|
||||||
|
format == Renderer::Backend::Format::R16G16_SFLOAT ||
|
||||||
format == Renderer::Backend::Format::R32G32_SFLOAT)
|
format == Renderer::Backend::Format::R32G32_SFLOAT)
|
||||||
size = 2;
|
size = 2;
|
||||||
else if (format == Renderer::Backend::Format::R32G32B32_SFLOAT)
|
else if (
|
||||||
|
format == Renderer::Backend::Format::R16G16B16_SFLOAT ||
|
||||||
|
format == Renderer::Backend::Format::R32G32B32_SFLOAT)
|
||||||
size = 3;
|
size = 3;
|
||||||
else if (
|
else if (
|
||||||
|
format == Renderer::Backend::Format::R16G16B16A16_SFLOAT ||
|
||||||
format == Renderer::Backend::Format::R32G32B32A32_SFLOAT ||
|
format == Renderer::Backend::Format::R32G32B32A32_SFLOAT ||
|
||||||
format == Renderer::Backend::Format::R8G8B8A8_UNORM ||
|
format == Renderer::Backend::Format::R8G8B8A8_UNORM ||
|
||||||
format == Renderer::Backend::Format::R8G8B8A8_UINT)
|
format == Renderer::Backend::Format::R8G8B8A8_UINT)
|
||||||
@@ -108,6 +114,13 @@ GLenum GLTypeFromFormat(const Format format)
|
|||||||
format == Renderer::Backend::Format::R32G32B32_SFLOAT ||
|
format == Renderer::Backend::Format::R32G32B32_SFLOAT ||
|
||||||
format == Renderer::Backend::Format::R32G32B32A32_SFLOAT)
|
format == Renderer::Backend::Format::R32G32B32A32_SFLOAT)
|
||||||
type = GL_FLOAT;
|
type = GL_FLOAT;
|
||||||
|
#if !CONFIG2_GLES
|
||||||
|
else if (format == Renderer::Backend::Format::R16_SFLOAT ||
|
||||||
|
format == Renderer::Backend::Format::R16G16_SFLOAT ||
|
||||||
|
format == Renderer::Backend::Format::R16G16B16_SFLOAT ||
|
||||||
|
format == Renderer::Backend::Format::R16G16B16A16_SFLOAT)
|
||||||
|
type = GL_HALF_FLOAT;
|
||||||
|
#endif
|
||||||
else if (
|
else if (
|
||||||
format == Renderer::Backend::Format::R16_SINT ||
|
format == Renderer::Backend::Format::R16_SINT ||
|
||||||
format == Renderer::Backend::Format::R16G16_SINT)
|
format == Renderer::Backend::Format::R16G16_SINT)
|
||||||
@@ -444,6 +457,12 @@ public:
|
|||||||
return textureUnit;
|
return textureUnit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GLuint GetStorageBuffer(const int32_t UNUSED(bindingSlot)) override
|
||||||
|
{
|
||||||
|
debug_warn("ARB shaders don't support storage buffers.");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
void SetUniform(
|
void SetUniform(
|
||||||
const int32_t bindingSlot,
|
const int32_t bindingSlot,
|
||||||
const float value) override
|
const float value) override
|
||||||
@@ -789,6 +808,59 @@ public:
|
|||||||
|
|
||||||
std::vector<uint8_t> occupiedUnits;
|
std::vector<uint8_t> occupiedUnits;
|
||||||
|
|
||||||
|
#if !CONFIG2_GLES
|
||||||
|
const bool isStorageSupported{m_Device->GetCapabilities().storage};
|
||||||
|
if (isStorageSupported)
|
||||||
|
{
|
||||||
|
constexpr GLint maxBlockNameLength{128};
|
||||||
|
char name[maxBlockNameLength];
|
||||||
|
|
||||||
|
GLint maxUniformBlockNameLength{0};
|
||||||
|
glGetProgramInterfaceiv(m_Program, GL_UNIFORM_BLOCK, GL_MAX_NAME_LENGTH, &maxUniformBlockNameLength);
|
||||||
|
ogl_WarnIfError();
|
||||||
|
|
||||||
|
GLint numberOfActiveUniformBlocks{0};
|
||||||
|
glGetProgramInterfaceiv(m_Program, GL_UNIFORM_BLOCK, GL_ACTIVE_RESOURCES, &numberOfActiveUniformBlocks);
|
||||||
|
ogl_WarnIfError();
|
||||||
|
// Currently we support the only one uniform buffer per shader.
|
||||||
|
if (numberOfActiveUniformBlocks == 1)
|
||||||
|
{
|
||||||
|
GLsizei length{0};
|
||||||
|
glGetProgramResourceName(m_Program, GL_UNIFORM_BLOCK, 0, maxBlockNameLength, &length, name);
|
||||||
|
|
||||||
|
const GLuint location{glGetProgramResourceIndex(m_Program, GL_UNIFORM_BLOCK, name)};
|
||||||
|
glUniformBlockBinding(m_Program, location, location);
|
||||||
|
|
||||||
|
m_UniformBufferLocation = location;
|
||||||
|
}
|
||||||
|
|
||||||
|
GLint maxStorageNameLength{0};
|
||||||
|
glGetProgramInterfaceiv(m_Program, GL_SHADER_STORAGE_BLOCK, GL_MAX_NAME_LENGTH, &maxStorageNameLength);
|
||||||
|
ogl_WarnIfError();
|
||||||
|
ENSURE(maxStorageNameLength <= maxBlockNameLength);
|
||||||
|
GLint numberOfActiveStorages{0};
|
||||||
|
glGetProgramInterfaceiv(m_Program, GL_SHADER_STORAGE_BLOCK, GL_ACTIVE_RESOURCES, &numberOfActiveStorages);
|
||||||
|
ogl_WarnIfError();
|
||||||
|
for (GLint index{0}; index < numberOfActiveStorages; ++index)
|
||||||
|
{
|
||||||
|
GLsizei length{0};
|
||||||
|
glGetProgramResourceName(m_Program, GL_SHADER_STORAGE_BLOCK, index, maxBlockNameLength, &length, name);
|
||||||
|
|
||||||
|
const GLuint location{glGetProgramResourceIndex(m_Program, GL_SHADER_STORAGE_BLOCK, name)};
|
||||||
|
glShaderStorageBlockBinding(m_Program, location, location);
|
||||||
|
|
||||||
|
const CStrIntern nameIntern(name);
|
||||||
|
|
||||||
|
m_BindingSlotsMapping[nameIntern] = m_BindingSlots.size();
|
||||||
|
BindingSlot bindingSlot{};
|
||||||
|
bindingSlot.name = nameIntern;
|
||||||
|
bindingSlot.location = location;
|
||||||
|
bindingSlot.isStorageBuffer = true;
|
||||||
|
m_BindingSlots.emplace_back(std::move(bindingSlot));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
GLint numUniforms = 0;
|
GLint numUniforms = 0;
|
||||||
glGetProgramiv(m_Program, GL_ACTIVE_UNIFORMS, &numUniforms);
|
glGetProgramiv(m_Program, GL_ACTIVE_UNIFORMS, &numUniforms);
|
||||||
ogl_WarnIfError();
|
ogl_WarnIfError();
|
||||||
@@ -824,6 +896,7 @@ public:
|
|||||||
bindingSlot.size = size;
|
bindingSlot.size = size;
|
||||||
bindingSlot.type = type;
|
bindingSlot.type = type;
|
||||||
bindingSlot.isTexture = false;
|
bindingSlot.isTexture = false;
|
||||||
|
bindingSlot.isStorageBuffer = false;
|
||||||
|
|
||||||
#define CASE(TYPE, ELEMENT_TYPE, ELEMENT_COUNT) \
|
#define CASE(TYPE, ELEMENT_TYPE, ELEMENT_COUNT) \
|
||||||
case GL_ ## TYPE: \
|
case GL_ ## TYPE: \
|
||||||
@@ -871,6 +944,7 @@ public:
|
|||||||
case GL_IMAGE_2D:
|
case GL_IMAGE_2D:
|
||||||
bindingSlot.elementType = GL_IMAGE_2D;
|
bindingSlot.elementType = GL_IMAGE_2D;
|
||||||
bindingSlot.isTexture = true;
|
bindingSlot.isTexture = true;
|
||||||
|
m_HasImageUniforms = true;
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
default:
|
default:
|
||||||
@@ -895,6 +969,31 @@ public:
|
|||||||
LOGERROR("CShaderProgramGLSL::Link: unsupported uniform type: 0x%04x", static_cast<int>(type));
|
LOGERROR("CShaderProgramGLSL::Link: unsupported uniform type: 0x%04x", static_cast<int>(type));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !CONFIG2_GLES
|
||||||
|
if (isStorageSupported)
|
||||||
|
{
|
||||||
|
GLuint uniformIndex{0};
|
||||||
|
const GLchar* nameToQuery{name};
|
||||||
|
glGetUniformIndices(m_Program, 1, &nameToQuery, &uniformIndex);
|
||||||
|
ogl_WarnIfError();
|
||||||
|
|
||||||
|
GLint uniformOffset{0};
|
||||||
|
glGetActiveUniformsiv(m_Program, 1, &uniformIndex, GL_UNIFORM_OFFSET, &uniformOffset);
|
||||||
|
ogl_WarnIfError();
|
||||||
|
|
||||||
|
// According to the OpenGL spec:
|
||||||
|
// https://registry.khronos.org/OpenGL-Refpages/es3/html/glGetActiveUniformsiv.xhtml
|
||||||
|
// For uniforms in the default uniform block, -1 will be returned.
|
||||||
|
if (uniformOffset >= 0)
|
||||||
|
{
|
||||||
|
const uint32_t sizeInBytes{static_cast<uint32_t>(bindingSlot.size * bindingSlot.elementCount * sizeof(float))};
|
||||||
|
m_UniformBufferSize = std::max(m_UniformBufferSize, uniformOffset + sizeInBytes);
|
||||||
|
bindingSlot.location = -1;
|
||||||
|
bindingSlot.offset = uniformOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
m_BindingSlots.emplace_back(std::move(bindingSlot));
|
m_BindingSlots.emplace_back(std::move(bindingSlot));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -918,6 +1017,13 @@ public:
|
|||||||
ogl_WarnIfError();
|
ogl_WarnIfError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_UniformBufferSize > 0 && m_UniformBufferLocation != -1)
|
||||||
|
{
|
||||||
|
m_UniformBuffer = m_Device->CreateBuffer(
|
||||||
|
"ShaderProgramUniformBuffer", IBuffer::Type::UNIFORM, m_UniformBufferSize,
|
||||||
|
IBuffer::Usage::DYNAMIC | IBuffer::Usage::TRANSFER_DST);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: verify that we're not using more samplers than is supported
|
// TODO: verify that we're not using more samplers than is supported
|
||||||
|
|
||||||
Unbind();
|
Unbind();
|
||||||
@@ -933,6 +1039,8 @@ public:
|
|||||||
ENSURE(this != previousShaderProgramGLSL);
|
ENSURE(this != previousShaderProgramGLSL);
|
||||||
|
|
||||||
glUseProgram(m_Program);
|
glUseProgram(m_Program);
|
||||||
|
if (m_UniformBuffer)
|
||||||
|
glBindBufferBase(GL_UNIFORM_BUFFER, m_UniformBufferLocation, m_UniformBuffer->As<CBuffer>()->GetHandle());
|
||||||
|
|
||||||
if (previousShaderProgramGLSL)
|
if (previousShaderProgramGLSL)
|
||||||
{
|
{
|
||||||
@@ -1009,6 +1117,15 @@ public:
|
|||||||
return textureUnit;
|
return textureUnit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GLuint GetStorageBuffer(const int32_t bindingSlot) override
|
||||||
|
{
|
||||||
|
if (bindingSlot < 0 || bindingSlot >= static_cast<int32_t>(m_BindingSlots.size()))
|
||||||
|
return 0;
|
||||||
|
if (!m_BindingSlots[bindingSlot].isStorageBuffer)
|
||||||
|
LOGERROR("CShaderProgramGLSL::GetStorageBuffer(): Invalid slot (expected storage buffer): '%s'", m_BindingSlots[bindingSlot].name.c_str());
|
||||||
|
return m_BindingSlots[bindingSlot].location;
|
||||||
|
}
|
||||||
|
|
||||||
void SetUniform(
|
void SetUniform(
|
||||||
const int32_t bindingSlot,
|
const int32_t bindingSlot,
|
||||||
const float value) override
|
const float value) override
|
||||||
@@ -1094,6 +1211,17 @@ public:
|
|||||||
const GLint location = m_BindingSlots[bindingSlot].location;
|
const GLint location = m_BindingSlots[bindingSlot].location;
|
||||||
const GLenum type = m_BindingSlots[bindingSlot].type;
|
const GLenum type = m_BindingSlots[bindingSlot].type;
|
||||||
|
|
||||||
|
if (location == -1)
|
||||||
|
{
|
||||||
|
const uint32_t sizeInBytes{
|
||||||
|
static_cast<uint32_t>(m_BindingSlots[bindingSlot].size * m_BindingSlots[bindingSlot].elementCount * sizeof(float))};
|
||||||
|
const uint32_t dataSizeToUpload{std::min(
|
||||||
|
static_cast<uint32_t>(values.size() * sizeof(float)), sizeInBytes)};
|
||||||
|
m_Device->GetActiveCommandContext()->UploadBufferRegion(
|
||||||
|
m_UniformBuffer.get(), values.data(), m_BindingSlots[bindingSlot].offset, dataSizeToUpload);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (type == GL_FLOAT)
|
if (type == GL_FLOAT)
|
||||||
glUniform1fv(location, 1, values.data());
|
glUniform1fv(location, 1, values.data());
|
||||||
else if (type == GL_FLOAT_VEC2)
|
else if (type == GL_FLOAT_VEC2)
|
||||||
@@ -1172,14 +1300,20 @@ private:
|
|||||||
{
|
{
|
||||||
CStrIntern name;
|
CStrIntern name;
|
||||||
GLint location;
|
GLint location;
|
||||||
|
GLint offset;
|
||||||
GLint size;
|
GLint size;
|
||||||
GLenum type;
|
GLenum type;
|
||||||
GLenum elementType;
|
GLenum elementType;
|
||||||
GLint elementCount;
|
GLint elementCount;
|
||||||
bool isTexture;
|
bool isTexture;
|
||||||
|
bool isStorageBuffer;
|
||||||
};
|
};
|
||||||
std::vector<BindingSlot> m_BindingSlots;
|
std::vector<BindingSlot> m_BindingSlots;
|
||||||
std::unordered_map<CStrIntern, int32_t> m_BindingSlotsMapping;
|
std::unordered_map<CStrIntern, int32_t> m_BindingSlotsMapping;
|
||||||
|
|
||||||
|
GLint m_UniformBufferLocation{-1};
|
||||||
|
uint32_t m_UniformBufferSize{0};
|
||||||
|
std::unique_ptr<IBuffer> m_UniformBuffer;
|
||||||
};
|
};
|
||||||
|
|
||||||
CShaderProgram::CShaderProgram(int streamflags)
|
CShaderProgram::CShaderProgram(int streamflags)
|
||||||
|
|||||||
@@ -116,6 +116,8 @@ public:
|
|||||||
};
|
};
|
||||||
virtual TextureUnit GetTextureUnit(const int32_t bindingSlot) = 0;
|
virtual TextureUnit GetTextureUnit(const int32_t bindingSlot) = 0;
|
||||||
|
|
||||||
|
virtual GLuint GetStorageBuffer(const int32_t bindingSlot) = 0;
|
||||||
|
|
||||||
virtual void SetUniform(
|
virtual void SetUniform(
|
||||||
const int32_t bindingSlot,
|
const int32_t bindingSlot,
|
||||||
const float value) = 0;
|
const float value) = 0;
|
||||||
@@ -141,6 +143,8 @@ public:
|
|||||||
|
|
||||||
bool IsStreamActive(const VertexAttributeStream stream) const;
|
bool IsStreamActive(const VertexAttributeStream stream) const;
|
||||||
|
|
||||||
|
bool HasImageUniforms() const { return m_HasImageUniforms; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks that all the required vertex attributes have been set.
|
* Checks that all the required vertex attributes have been set.
|
||||||
* Call this before calling Draw/DrawIndexed etc to avoid potential crashes.
|
* Call this before calling Draw/DrawIndexed etc to avoid potential crashes.
|
||||||
@@ -161,6 +165,8 @@ protected:
|
|||||||
void BindClientStates();
|
void BindClientStates();
|
||||||
void UnbindClientStates();
|
void UnbindClientStates();
|
||||||
int m_ValidStreams; // which streams have been specified via VertexPointer etc since the last Bind
|
int m_ValidStreams; // which streams have been specified via VertexPointer etc since the last Bind
|
||||||
|
|
||||||
|
bool m_HasImageUniforms{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace GL
|
} // namespace GL
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ VkBufferUsageFlags ToVkBufferUsageFlags(const uint32_t usage)
|
|||||||
usageFlags |= VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
|
usageFlags |= VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
|
||||||
if (usage & IBuffer::Usage::TRANSFER_DST)
|
if (usage & IBuffer::Usage::TRANSFER_DST)
|
||||||
usageFlags |= VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
usageFlags |= VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
||||||
|
if (usage & IBuffer::Usage::STORAGE)
|
||||||
|
usageFlags |= VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
|
||||||
return usageFlags;
|
return usageFlags;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,19 +53,20 @@ std::tuple<VkBufferUsageFlags, VkMemoryPropertyFlags, VmaMemoryUsage> MakeCreati
|
|||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case IBuffer::Type::VERTEX:
|
case IBuffer::Type::VERTEX:
|
||||||
ENSURE(usage & IBuffer::Usage::TRANSFER_DST);
|
ENSURE(usage & (IBuffer::Usage::TRANSFER_DST | IBuffer::Usage::STORAGE));
|
||||||
return {
|
return {
|
||||||
commonFlags | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
|
commonFlags | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
|
||||||
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
|
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
|
||||||
VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE};
|
VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE};
|
||||||
case IBuffer::Type::INDEX:
|
case IBuffer::Type::INDEX:
|
||||||
ENSURE(usage & IBuffer::Usage::TRANSFER_DST);
|
ENSURE(usage & (IBuffer::Usage::TRANSFER_DST | IBuffer::Usage::STORAGE));
|
||||||
return {
|
return {
|
||||||
commonFlags | VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
|
commonFlags | VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
|
||||||
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
|
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
|
||||||
VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE};
|
VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE};
|
||||||
case IBuffer::Type::UPLOAD:
|
case IBuffer::Type::UPLOAD:
|
||||||
ENSURE(usage & IBuffer::Usage::TRANSFER_SRC);
|
ENSURE(usage & IBuffer::Usage::TRANSFER_SRC);
|
||||||
|
ENSURE(!(usage & IBuffer::Usage::STORAGE));
|
||||||
return {
|
return {
|
||||||
commonFlags,
|
commonFlags,
|
||||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
|
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
|
||||||
@@ -131,6 +134,8 @@ CBuffer::~CBuffer()
|
|||||||
if (m_Allocation != VK_NULL_HANDLE)
|
if (m_Allocation != VK_NULL_HANDLE)
|
||||||
m_Device->ScheduleObjectToDestroy(
|
m_Device->ScheduleObjectToDestroy(
|
||||||
VK_OBJECT_TYPE_BUFFER, m_Buffer, m_Allocation);
|
VK_OBJECT_TYPE_BUFFER, m_Buffer, m_Allocation);
|
||||||
|
|
||||||
|
m_Device->ScheduleBufferToDestroy(m_UID);
|
||||||
}
|
}
|
||||||
|
|
||||||
IDevice* CBuffer::GetDevice()
|
IDevice* CBuffer::GetDevice()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2023 Wildfire Games.
|
/* Copyright (C) 2024 Wildfire Games.
|
||||||
* This file is part of 0 A.D.
|
* This file is part of 0 A.D.
|
||||||
*
|
*
|
||||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||||
@@ -27,6 +27,7 @@
|
|||||||
#include "renderer/backend/vulkan/Utilities.h"
|
#include "renderer/backend/vulkan/Utilities.h"
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <iterator>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
|
|
||||||
namespace Renderer
|
namespace Renderer
|
||||||
@@ -306,6 +307,56 @@ VkDescriptorSet CDescriptorManager::GetSingleTypeDescritorSet(
|
|||||||
return set;
|
return set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VkDescriptorSet CDescriptorManager::GetSingleTypeDescritorSet(
|
||||||
|
VkDescriptorType type, VkDescriptorSetLayout layout,
|
||||||
|
const std::vector<DeviceObjectUID>& buffersUID,
|
||||||
|
const std::vector<CBuffer*>& buffers)
|
||||||
|
{
|
||||||
|
ENSURE(buffersUID.size() == buffers.size());
|
||||||
|
ENSURE(!buffersUID.empty());
|
||||||
|
const auto[set, justCreated] = GetSingleTypeDescritorSetImpl(type, layout, buffersUID);
|
||||||
|
if (!justCreated)
|
||||||
|
return set;
|
||||||
|
|
||||||
|
ENSURE(
|
||||||
|
type == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER || type == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC ||
|
||||||
|
type == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER || type == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC);
|
||||||
|
const VkPhysicalDeviceLimits& physicalDeviceLimits = m_Device->GetChoosenPhysicalDevice().properties.limits;
|
||||||
|
const uint32_t maxBufferRange =
|
||||||
|
type == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER || type == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC
|
||||||
|
? physicalDeviceLimits.maxStorageBufferRange
|
||||||
|
: physicalDeviceLimits.maxUniformBufferRange;
|
||||||
|
|
||||||
|
PS::StaticVector<VkDescriptorBufferInfo, 16> infos;
|
||||||
|
std::transform(buffers.begin(), buffers.end(), std::back_inserter(infos),
|
||||||
|
[maxBufferRange](CBuffer* buffer)
|
||||||
|
{
|
||||||
|
ENSURE(buffer);
|
||||||
|
ENSURE(buffer->GetUsage() & IBuffer::Usage::STORAGE);
|
||||||
|
ENSURE(buffer->GetSize() <= maxBufferRange);
|
||||||
|
|
||||||
|
VkDescriptorBufferInfo descriptorBufferInfo{};
|
||||||
|
descriptorBufferInfo.buffer = buffer->GetVkBuffer();
|
||||||
|
descriptorBufferInfo.offset = 0;
|
||||||
|
descriptorBufferInfo.range = buffer->GetSize();
|
||||||
|
return descriptorBufferInfo;
|
||||||
|
});
|
||||||
|
|
||||||
|
VkWriteDescriptorSet writeDescriptorSet{};
|
||||||
|
writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||||
|
writeDescriptorSet.dstSet = set;
|
||||||
|
writeDescriptorSet.dstBinding = 0;
|
||||||
|
writeDescriptorSet.dstArrayElement = 0;
|
||||||
|
writeDescriptorSet.descriptorType = type;
|
||||||
|
writeDescriptorSet.descriptorCount = static_cast<uint32_t>(infos.size());
|
||||||
|
writeDescriptorSet.pBufferInfo = infos.data();
|
||||||
|
|
||||||
|
vkUpdateDescriptorSets(
|
||||||
|
m_Device->GetVkDevice(), 1, &writeDescriptorSet, 0, nullptr);
|
||||||
|
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t CDescriptorManager::GetUniformSet() const
|
uint32_t CDescriptorManager::GetUniformSet() const
|
||||||
{
|
{
|
||||||
return m_UseDescriptorIndexing ? 1 : 0;
|
return m_UseDescriptorIndexing ? 1 : 0;
|
||||||
@@ -377,26 +428,36 @@ void CDescriptorManager::OnTextureDestroy(const DeviceObjectUID uid)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto it = m_UIDToSingleTypePoolMap.find(uid);
|
OnDeviceObjectDestroy(uid);
|
||||||
if (it == m_UIDToSingleTypePoolMap.end())
|
|
||||||
return;
|
|
||||||
for (const auto& entry : it->second)
|
|
||||||
{
|
|
||||||
SingleTypePool& pool = GetSingleTypePool(entry.type, entry.size);
|
|
||||||
SingleTypePool::Element& element = pool.elements[entry.elementIndex];
|
|
||||||
// Multiple textures might be used by the same descriptor set and
|
|
||||||
// we don't need to reset it if it was already.
|
|
||||||
if (element.version == entry.version && element.nextFreeIndex == SingleTypePool::INVALID_INDEX)
|
|
||||||
{
|
|
||||||
ENSURE(pool.firstFreeIndex != entry.elementIndex);
|
|
||||||
element.nextFreeIndex = pool.firstFreeIndex;
|
|
||||||
pool.firstFreeIndex = entry.elementIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m_UIDToSingleTypePoolMap.erase(it);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CDescriptorManager::OnBufferDestroy(const DeviceObjectUID uid)
|
||||||
|
{
|
||||||
|
OnDeviceObjectDestroy(uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CDescriptorManager::OnDeviceObjectDestroy(const DeviceObjectUID uid)
|
||||||
|
{
|
||||||
|
auto it = m_UIDToSingleTypePoolMap.find(uid);
|
||||||
|
if (it == m_UIDToSingleTypePoolMap.end())
|
||||||
|
return;
|
||||||
|
for (const auto& entry : it->second)
|
||||||
|
{
|
||||||
|
SingleTypePool& pool = GetSingleTypePool(entry.type, entry.size);
|
||||||
|
SingleTypePool::Element& element = pool.elements[entry.elementIndex];
|
||||||
|
// Multiple textures might be used by the same descriptor set and
|
||||||
|
// we don't need to reset it if it was already.
|
||||||
|
if (element.version == entry.version && element.nextFreeIndex == SingleTypePool::INVALID_INDEX)
|
||||||
|
{
|
||||||
|
ENSURE(pool.firstFreeIndex != entry.elementIndex);
|
||||||
|
element.nextFreeIndex = pool.firstFreeIndex;
|
||||||
|
pool.firstFreeIndex = entry.elementIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_UIDToSingleTypePoolMap.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Vulkan
|
} // namespace Vulkan
|
||||||
|
|
||||||
} // namespace Backend
|
} // namespace Backend
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
#include "ps/CStrIntern.h"
|
#include "ps/CStrIntern.h"
|
||||||
#include "renderer/backend/Sampler.h"
|
#include "renderer/backend/Sampler.h"
|
||||||
|
#include "renderer/backend/vulkan/Buffer.h"
|
||||||
#include "renderer/backend/vulkan/Device.h"
|
#include "renderer/backend/vulkan/Device.h"
|
||||||
#include "renderer/backend/vulkan/Texture.h"
|
#include "renderer/backend/vulkan/Texture.h"
|
||||||
|
|
||||||
@@ -61,12 +62,19 @@ public:
|
|||||||
const std::vector<DeviceObjectUID>& texturesUID,
|
const std::vector<DeviceObjectUID>& texturesUID,
|
||||||
const std::vector<CTexture*>& textures);
|
const std::vector<CTexture*>& textures);
|
||||||
|
|
||||||
|
VkDescriptorSet GetSingleTypeDescritorSet(
|
||||||
|
VkDescriptorType type, VkDescriptorSetLayout layout,
|
||||||
|
const std::vector<DeviceObjectUID>& buffersUID,
|
||||||
|
const std::vector<CBuffer*>& buffers);
|
||||||
|
|
||||||
uint32_t GetUniformSet() const;
|
uint32_t GetUniformSet() const;
|
||||||
|
|
||||||
uint32_t GetTextureDescriptor(CTexture* texture);
|
uint32_t GetTextureDescriptor(CTexture* texture);
|
||||||
|
|
||||||
void OnTextureDestroy(const DeviceObjectUID uid);
|
void OnTextureDestroy(const DeviceObjectUID uid);
|
||||||
|
|
||||||
|
void OnBufferDestroy(const DeviceObjectUID uid);
|
||||||
|
|
||||||
const VkDescriptorSetLayout& GetDescriptorIndexingSetLayout() const { return m_DescriptorIndexingSetLayout; }
|
const VkDescriptorSetLayout& GetDescriptorIndexingSetLayout() const { return m_DescriptorIndexingSetLayout; }
|
||||||
const VkDescriptorSetLayout& GetUniformDescriptorSetLayout() const { return m_UniformDescriptorSetLayout; }
|
const VkDescriptorSetLayout& GetUniformDescriptorSetLayout() const { return m_UniformDescriptorSetLayout; }
|
||||||
const VkDescriptorSet& GetDescriptorIndexingSet() { return m_DescriptorIndexingSet; }
|
const VkDescriptorSet& GetDescriptorIndexingSet() { return m_DescriptorIndexingSet; }
|
||||||
@@ -94,6 +102,8 @@ private:
|
|||||||
VkDescriptorType type, VkDescriptorSetLayout layout,
|
VkDescriptorType type, VkDescriptorSetLayout layout,
|
||||||
const std::vector<DeviceObjectUID>& uids);
|
const std::vector<DeviceObjectUID>& uids);
|
||||||
|
|
||||||
|
void OnDeviceObjectDestroy(const DeviceObjectUID uid);
|
||||||
|
|
||||||
CDevice* m_Device = nullptr;
|
CDevice* m_Device = nullptr;
|
||||||
|
|
||||||
bool m_UseDescriptorIndexing = false;
|
bool m_UseDescriptorIndexing = false;
|
||||||
|
|||||||
@@ -580,6 +580,7 @@ std::unique_ptr<CDevice> CDevice::Create(SDL_Window* window)
|
|||||||
capabilities.ARBShaders = false;
|
capabilities.ARBShaders = false;
|
||||||
capabilities.ARBShadersShadow = false;
|
capabilities.ARBShadersShadow = false;
|
||||||
capabilities.computeShaders = true;
|
capabilities.computeShaders = true;
|
||||||
|
capabilities.storage = choosenDevice.properties.limits.maxStorageBufferRange >= GiB;
|
||||||
capabilities.instancing = true;
|
capabilities.instancing = true;
|
||||||
capabilities.maxSampleCount = 1;
|
capabilities.maxSampleCount = 1;
|
||||||
const VkSampleCountFlags sampleCountFlags =
|
const VkSampleCountFlags sampleCountFlags =
|
||||||
@@ -657,7 +658,7 @@ CDevice::~CDevice()
|
|||||||
|
|
||||||
m_SubmitScheduler.reset();
|
m_SubmitScheduler.reset();
|
||||||
|
|
||||||
ProcessTextureToDestroyQueue(true);
|
ProcessDeviceObjectToDestroyQueue(true);
|
||||||
|
|
||||||
m_RenderPassManager.reset();
|
m_RenderPassManager.reset();
|
||||||
m_SamplerManager.reset();
|
m_SamplerManager.reset();
|
||||||
@@ -813,7 +814,7 @@ void CDevice::Present()
|
|||||||
m_SubmitScheduler->Present(*m_SwapChain);
|
m_SubmitScheduler->Present(*m_SwapChain);
|
||||||
|
|
||||||
ProcessObjectToDestroyQueue();
|
ProcessObjectToDestroyQueue();
|
||||||
ProcessTextureToDestroyQueue();
|
ProcessDeviceObjectToDestroyQueue();
|
||||||
|
|
||||||
++m_FrameID;
|
++m_FrameID;
|
||||||
}
|
}
|
||||||
@@ -928,6 +929,11 @@ void CDevice::ScheduleTextureToDestroy(const DeviceObjectUID uid)
|
|||||||
m_TextureToDestroyQueue.push({m_FrameID, uid});
|
m_TextureToDestroyQueue.push({m_FrameID, uid});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CDevice::ScheduleBufferToDestroy(const DeviceObjectUID uid)
|
||||||
|
{
|
||||||
|
m_BufferToDestroyQueue.push({m_FrameID, uid});
|
||||||
|
}
|
||||||
|
|
||||||
void CDevice::SetObjectName(VkObjectType type, const uint64_t handle, const char* name)
|
void CDevice::SetObjectName(VkObjectType type, const uint64_t handle, const char* name)
|
||||||
{
|
{
|
||||||
if (!m_Capabilities.debugLabels)
|
if (!m_Capabilities.debugLabels)
|
||||||
@@ -1013,7 +1019,7 @@ void CDevice::ProcessObjectToDestroyQueue(const bool ignoreFrameID)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CDevice::ProcessTextureToDestroyQueue(const bool ignoreFrameID)
|
void CDevice::ProcessDeviceObjectToDestroyQueue(const bool ignoreFrameID)
|
||||||
{
|
{
|
||||||
while (!m_TextureToDestroyQueue.empty() &&
|
while (!m_TextureToDestroyQueue.empty() &&
|
||||||
(ignoreFrameID || m_TextureToDestroyQueue.front().first + NUMBER_OF_FRAMES_IN_FLIGHT < m_FrameID))
|
(ignoreFrameID || m_TextureToDestroyQueue.front().first + NUMBER_OF_FRAMES_IN_FLIGHT < m_FrameID))
|
||||||
@@ -1021,6 +1027,13 @@ void CDevice::ProcessTextureToDestroyQueue(const bool ignoreFrameID)
|
|||||||
GetDescriptorManager().OnTextureDestroy(m_TextureToDestroyQueue.front().second);
|
GetDescriptorManager().OnTextureDestroy(m_TextureToDestroyQueue.front().second);
|
||||||
m_TextureToDestroyQueue.pop();
|
m_TextureToDestroyQueue.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
while (!m_BufferToDestroyQueue.empty() &&
|
||||||
|
(ignoreFrameID || m_BufferToDestroyQueue.front().first + NUMBER_OF_FRAMES_IN_FLIGHT < m_FrameID))
|
||||||
|
{
|
||||||
|
GetDescriptorManager().OnBufferDestroy(m_BufferToDestroyQueue.front().second);
|
||||||
|
m_BufferToDestroyQueue.pop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CTexture* CDevice::GetCurrentBackbufferTexture()
|
CTexture* CDevice::GetCurrentBackbufferTexture()
|
||||||
|
|||||||
@@ -145,6 +145,8 @@ public:
|
|||||||
|
|
||||||
void ScheduleTextureToDestroy(const DeviceObjectUID uid);
|
void ScheduleTextureToDestroy(const DeviceObjectUID uid);
|
||||||
|
|
||||||
|
void ScheduleBufferToDestroy(const DeviceObjectUID uid);
|
||||||
|
|
||||||
void SetObjectName(VkObjectType type, const void* handle, const char* name)
|
void SetObjectName(VkObjectType type, const void* handle, const char* name)
|
||||||
{
|
{
|
||||||
SetObjectName(type, reinterpret_cast<uint64_t>(handle), name);
|
SetObjectName(type, reinterpret_cast<uint64_t>(handle), name);
|
||||||
@@ -174,7 +176,7 @@ private:
|
|||||||
void RecreateSwapChain();
|
void RecreateSwapChain();
|
||||||
bool IsSwapChainValid();
|
bool IsSwapChainValid();
|
||||||
void ProcessObjectToDestroyQueue(const bool ignoreFrameID = false);
|
void ProcessObjectToDestroyQueue(const bool ignoreFrameID = false);
|
||||||
void ProcessTextureToDestroyQueue(const bool ignoreFrameID = false);
|
void ProcessDeviceObjectToDestroyQueue(const bool ignoreFrameID = false);
|
||||||
|
|
||||||
bool IsFormatSupportedForUsage(const Format format, const uint32_t usage) const;
|
bool IsFormatSupportedForUsage(const Format format, const uint32_t usage) const;
|
||||||
|
|
||||||
@@ -216,6 +218,7 @@ private:
|
|||||||
};
|
};
|
||||||
std::queue<ObjectToDestroy> m_ObjectToDestroyQueue;
|
std::queue<ObjectToDestroy> m_ObjectToDestroyQueue;
|
||||||
std::queue<std::pair<uint32_t, DeviceObjectUID>> m_TextureToDestroyQueue;
|
std::queue<std::pair<uint32_t, DeviceObjectUID>> m_TextureToDestroyQueue;
|
||||||
|
std::queue<std::pair<uint32_t, DeviceObjectUID>> m_BufferToDestroyQueue;
|
||||||
|
|
||||||
std::unique_ptr<CRenderPassManager> m_RenderPassManager;
|
std::unique_ptr<CRenderPassManager> m_RenderPassManager;
|
||||||
std::unique_ptr<CSamplerManager> m_SamplerManager;
|
std::unique_ptr<CSamplerManager> m_SamplerManager;
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
#include "renderer/backend/vulkan/DescriptorManager.h"
|
#include "renderer/backend/vulkan/DescriptorManager.h"
|
||||||
#include "renderer/backend/vulkan/Device.h"
|
#include "renderer/backend/vulkan/Device.h"
|
||||||
#include "renderer/backend/vulkan/Framebuffer.h"
|
#include "renderer/backend/vulkan/Framebuffer.h"
|
||||||
|
#include "renderer/backend/vulkan/Mapping.h"
|
||||||
#include "renderer/backend/vulkan/PipelineState.h"
|
#include "renderer/backend/vulkan/PipelineState.h"
|
||||||
#include "renderer/backend/vulkan/RingCommandContext.h"
|
#include "renderer/backend/vulkan/RingCommandContext.h"
|
||||||
#include "renderer/backend/vulkan/ShaderProgram.h"
|
#include "renderer/backend/vulkan/ShaderProgram.h"
|
||||||
@@ -51,7 +52,7 @@ namespace Vulkan
|
|||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
|
||||||
constexpr uint32_t UNIFORM_BUFFER_INITIAL_SIZE = 1024 * 1024;
|
constexpr uint32_t UNIFORM_BUFFER_INITIAL_SIZE = 1024 * 1024 * 32;
|
||||||
constexpr uint32_t FRAME_INPLACE_BUFFER_INITIAL_SIZE = 128 * 1024;
|
constexpr uint32_t FRAME_INPLACE_BUFFER_INITIAL_SIZE = 128 * 1024;
|
||||||
|
|
||||||
struct SBaseImageState
|
struct SBaseImageState
|
||||||
@@ -915,6 +916,17 @@ void CDeviceCommandContext::Dispatch(
|
|||||||
m_ShaderProgram->PostDispatch(*m_CommandContext);
|
m_ShaderProgram->PostDispatch(*m_CommandContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CDeviceCommandContext::InsertMemoryBarrier(
|
||||||
|
const uint32_t srcStageMask, const uint32_t dstStageMask,
|
||||||
|
const uint32_t srcAccessMask, const uint32_t dstAccessMask)
|
||||||
|
{
|
||||||
|
ENSURE(!m_InsideFramebufferPass);
|
||||||
|
Utilities::SubmitMemoryBarrier(
|
||||||
|
m_CommandContext->GetCommandBuffer(),
|
||||||
|
Mapping::FromAccessMask(srcAccessMask), Mapping::FromAccessMask(dstAccessMask),
|
||||||
|
Mapping::FromPipelineStageMask(srcStageMask), Mapping::FromPipelineStageMask(dstStageMask));
|
||||||
|
}
|
||||||
|
|
||||||
void CDeviceCommandContext::SetTexture(const int32_t bindingSlot, ITexture* texture)
|
void CDeviceCommandContext::SetTexture(const int32_t bindingSlot, ITexture* texture)
|
||||||
{
|
{
|
||||||
if (bindingSlot < 0)
|
if (bindingSlot < 0)
|
||||||
@@ -948,6 +960,16 @@ void CDeviceCommandContext::SetStorageTexture(const int32_t bindingSlot, ITextur
|
|||||||
m_ShaderProgram->SetStorageTexture(bindingSlot, textureToBind);
|
m_ShaderProgram->SetStorageTexture(bindingSlot, textureToBind);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CDeviceCommandContext::SetStorageBuffer(const int32_t bindingSlot, IBuffer* buffer)
|
||||||
|
{
|
||||||
|
ENSURE(m_InsidePass || m_InsideComputePass);
|
||||||
|
ENSURE(buffer);
|
||||||
|
CBuffer* bufferToBind = buffer->As<CBuffer>();
|
||||||
|
ENSURE(bufferToBind->GetType() == IBuffer::Type::VERTEX || bufferToBind->GetType() == IBuffer::Type::INDEX);
|
||||||
|
ENSURE(bufferToBind->GetUsage() & IBuffer::Usage::STORAGE);
|
||||||
|
m_ShaderProgram->SetStorageBuffer(bindingSlot, bufferToBind);
|
||||||
|
}
|
||||||
|
|
||||||
void CDeviceCommandContext::SetUniform(
|
void CDeviceCommandContext::SetUniform(
|
||||||
const int32_t bindingSlot,
|
const int32_t bindingSlot,
|
||||||
const float value)
|
const float value)
|
||||||
|
|||||||
@@ -125,9 +125,14 @@ public:
|
|||||||
const uint32_t groupCountY,
|
const uint32_t groupCountY,
|
||||||
const uint32_t groupCountZ) override;
|
const uint32_t groupCountZ) override;
|
||||||
|
|
||||||
|
void InsertMemoryBarrier(
|
||||||
|
const uint32_t srcStageMask, const uint32_t dstStageMask,
|
||||||
|
const uint32_t srcAccessMask, const uint32_t dstAccessMask) override;
|
||||||
|
|
||||||
void SetTexture(const int32_t bindingSlot, ITexture* texture) override;
|
void SetTexture(const int32_t bindingSlot, ITexture* texture) override;
|
||||||
|
|
||||||
void SetStorageTexture(const int32_t bindingSlot, ITexture* texture) override;
|
void SetStorageTexture(const int32_t bindingSlot, ITexture* texture) override;
|
||||||
|
void SetStorageBuffer(const int32_t bindingSlot, IBuffer* buffer) override;
|
||||||
|
|
||||||
void SetUniform(
|
void SetUniform(
|
||||||
const int32_t bindingSlot,
|
const int32_t bindingSlot,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2023 Wildfire Games.
|
/* Copyright (C) 2024 Wildfire Games.
|
||||||
* This file is part of 0 A.D.
|
* This file is part of 0 A.D.
|
||||||
*
|
*
|
||||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||||
@@ -175,9 +175,15 @@ VkFormat FromFormat(const Format format)
|
|||||||
CASE(R16_UNORM)
|
CASE(R16_UNORM)
|
||||||
CASE(R16_UINT)
|
CASE(R16_UINT)
|
||||||
CASE(R16_SINT)
|
CASE(R16_SINT)
|
||||||
|
CASE(R16_SFLOAT)
|
||||||
CASE(R16G16_UNORM)
|
CASE(R16G16_UNORM)
|
||||||
CASE(R16G16_UINT)
|
CASE(R16G16_UINT)
|
||||||
CASE(R16G16_SINT)
|
CASE(R16G16_SINT)
|
||||||
|
CASE(R16G16_SFLOAT)
|
||||||
|
|
||||||
|
CASE(R16G16B16_SFLOAT)
|
||||||
|
|
||||||
|
CASE(R16G16B16A16_SFLOAT)
|
||||||
|
|
||||||
CASE(R32_SFLOAT)
|
CASE(R32_SFLOAT)
|
||||||
CASE(R32G32_SFLOAT)
|
CASE(R32G32_SFLOAT)
|
||||||
@@ -273,6 +279,68 @@ VkAttachmentStoreOp FromAttachmentStoreOp(const AttachmentStoreOp storeOp)
|
|||||||
return resultStoreOp;
|
return resultStoreOp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VkPipelineStageFlags FromPipelineStageMask(const uint32_t mask)
|
||||||
|
{
|
||||||
|
VkPipelineStageFlags flags{0};
|
||||||
|
uint32_t checkedMask{0};
|
||||||
|
#define CASE(NAME) \
|
||||||
|
if (mask & PipelineStage::NAME) { flags |= VK_PIPELINE_STAGE_##NAME##_BIT; checkedMask |= PipelineStage::NAME; }
|
||||||
|
#define CASE2(NAME, VK_NAME) \
|
||||||
|
if (mask & PipelineStage::NAME) { flags |= VK_NAME; checkedMask |= PipelineStage::NAME; }
|
||||||
|
|
||||||
|
CASE(DRAW_INDIRECT)
|
||||||
|
CASE(VERTEX_INPUT)
|
||||||
|
CASE(VERTEX_SHADER)
|
||||||
|
CASE(FRAGMENT_SHADER)
|
||||||
|
CASE(EARLY_FRAGMENT_TESTS)
|
||||||
|
CASE(LATE_FRAGMENT_TESTS)
|
||||||
|
CASE(COLOR_ATTACHMENT_OUTPUT)
|
||||||
|
CASE(COMPUTE_SHADER)
|
||||||
|
CASE(TRANSFER)
|
||||||
|
CASE(HOST)
|
||||||
|
CASE2(ACCELERATION_STRUCTURE_BUILD, VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR)
|
||||||
|
CASE2(RAY_TRACING_SHADER, VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR)
|
||||||
|
CASE2(TASK_SHADER, VK_PIPELINE_STAGE_TASK_SHADER_BIT_EXT)
|
||||||
|
CASE2(MESH_SHADER, VK_PIPELINE_STAGE_MESH_SHADER_BIT_EXT)
|
||||||
|
#undef CASE
|
||||||
|
#undef CASE2
|
||||||
|
ENSURE(mask == checkedMask);
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkAccessFlags FromAccessMask(const uint32_t mask)
|
||||||
|
{
|
||||||
|
VkAccessFlags flags{0};
|
||||||
|
uint32_t checkedMask{0};
|
||||||
|
#define CASE(NAME) \
|
||||||
|
if (mask & Access::NAME) { flags |= VK_ACCESS_##NAME##_BIT; checkedMask |= Access::NAME; }
|
||||||
|
#define CASE2(NAME, VK_NAME) \
|
||||||
|
if (mask & Access::NAME) { flags |= VK_NAME; checkedMask |= Access::NAME; }
|
||||||
|
|
||||||
|
CASE(INDIRECT_COMMAND_READ)
|
||||||
|
CASE(INDEX_READ)
|
||||||
|
CASE(VERTEX_ATTRIBUTE_READ)
|
||||||
|
CASE(UNIFORM_READ)
|
||||||
|
CASE(INPUT_ATTACHMENT_READ)
|
||||||
|
CASE(SHADER_READ)
|
||||||
|
CASE(SHADER_WRITE)
|
||||||
|
CASE(COLOR_ATTACHMENT_READ)
|
||||||
|
CASE(COLOR_ATTACHMENT_WRITE)
|
||||||
|
CASE(DEPTH_STENCIL_ATTACHMENT_READ)
|
||||||
|
CASE(DEPTH_STENCIL_ATTACHMENT_WRITE)
|
||||||
|
CASE(TRANSFER_READ)
|
||||||
|
CASE(TRANSFER_WRITE)
|
||||||
|
CASE(HOST_READ)
|
||||||
|
CASE(HOST_WRITE)
|
||||||
|
CASE(MEMORY_READ)
|
||||||
|
CASE(MEMORY_WRITE)
|
||||||
|
CASE2(ACCELERATION_STRUCTURE_READ, VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR)
|
||||||
|
CASE2(ACCELERATION_STRUCTURE_WRITE, VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR)
|
||||||
|
#undef CASE
|
||||||
|
ENSURE(mask == checkedMask);
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Mapping
|
} // namespace Mapping
|
||||||
|
|
||||||
} // namespace Vulkan
|
} // namespace Vulkan
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2023 Wildfire Games.
|
/* Copyright (C) 2024 Wildfire Games.
|
||||||
* This file is part of 0 A.D.
|
* This file is part of 0 A.D.
|
||||||
*
|
*
|
||||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
#ifndef INCLUDED_RENDERER_BACKEND_VULKAN_MAPPING
|
#ifndef INCLUDED_RENDERER_BACKEND_VULKAN_MAPPING
|
||||||
#define INCLUDED_RENDERER_BACKEND_VULKAN_MAPPING
|
#define INCLUDED_RENDERER_BACKEND_VULKAN_MAPPING
|
||||||
|
|
||||||
|
#include "renderer/backend/Barrier.h"
|
||||||
#include "renderer/backend/Format.h"
|
#include "renderer/backend/Format.h"
|
||||||
#include "renderer/backend/IFramebuffer.h"
|
#include "renderer/backend/IFramebuffer.h"
|
||||||
#include "renderer/backend/PipelineState.h"
|
#include "renderer/backend/PipelineState.h"
|
||||||
@@ -61,6 +62,10 @@ VkAttachmentLoadOp FromAttachmentLoadOp(const AttachmentLoadOp loadOp);
|
|||||||
|
|
||||||
VkAttachmentStoreOp FromAttachmentStoreOp(const AttachmentStoreOp storeOp);
|
VkAttachmentStoreOp FromAttachmentStoreOp(const AttachmentStoreOp storeOp);
|
||||||
|
|
||||||
|
VkPipelineStageFlags FromPipelineStageMask(const uint32_t mask);
|
||||||
|
|
||||||
|
VkAccessFlags FromAccessMask(const uint32_t mask);
|
||||||
|
|
||||||
} // namespace Mapping
|
} // namespace Mapping
|
||||||
|
|
||||||
} // namespace Vulkan
|
} // namespace Vulkan
|
||||||
|
|||||||
@@ -53,7 +53,8 @@ enum class BindingSlotType
|
|||||||
PUSH_CONSTANT,
|
PUSH_CONSTANT,
|
||||||
UNIFORM,
|
UNIFORM,
|
||||||
TEXTURE,
|
TEXTURE,
|
||||||
STORAGE_IMAGE
|
STORAGE_IMAGE,
|
||||||
|
STORAGE_BUFFER
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr uint32_t BINDING_SLOT_TYPE_SHIFT{16u};
|
constexpr uint32_t BINDING_SLOT_TYPE_SHIFT{16u};
|
||||||
@@ -247,6 +248,10 @@ std::unique_ptr<CShaderProgram> CShaderProgram::Create(
|
|||||||
uint32_t storageImageDescriptorSetSize = 0;
|
uint32_t storageImageDescriptorSetSize = 0;
|
||||||
std::unordered_map<CStrIntern, uint32_t> storageImageMapping;
|
std::unordered_map<CStrIntern, uint32_t> storageImageMapping;
|
||||||
|
|
||||||
|
VkDescriptorType storageBufferDescriptorType = VK_DESCRIPTOR_TYPE_MAX_ENUM;
|
||||||
|
uint32_t storageBufferDescriptorSetSize = 0;
|
||||||
|
std::unordered_map<CStrIntern, uint32_t> storageBufferMapping;
|
||||||
|
|
||||||
auto addDescriptorSets = [&](const XMBElement& element) -> bool
|
auto addDescriptorSets = [&](const XMBElement& element) -> bool
|
||||||
{
|
{
|
||||||
const bool useDescriptorIndexing =
|
const bool useDescriptorIndexing =
|
||||||
@@ -325,15 +330,13 @@ std::unique_ptr<CShaderProgram> CShaderProgram::Create(
|
|||||||
texturesDescriptorSetSize =
|
texturesDescriptorSetSize =
|
||||||
std::max(texturesDescriptorSetSize, binding + 1);
|
std::max(texturesDescriptorSetSize, binding + 1);
|
||||||
}
|
}
|
||||||
else if (type == "storageImage" || type == "storageBuffer")
|
else if (type == "storageImage")
|
||||||
{
|
{
|
||||||
const CStrIntern name{attributes.GetNamedItem(at_name)};
|
const CStrIntern name{attributes.GetNamedItem(at_name)};
|
||||||
storageImageMapping[name] = binding;
|
storageImageMapping[name] = binding;
|
||||||
storageImageDescriptorSetSize =
|
storageImageDescriptorSetSize =
|
||||||
std::max(storageImageDescriptorSetSize, binding + 1);
|
std::max(storageImageDescriptorSetSize, binding + 1);
|
||||||
const VkDescriptorType descriptorType = type == "storageBuffer"
|
const VkDescriptorType descriptorType{VK_DESCRIPTOR_TYPE_STORAGE_IMAGE};
|
||||||
? VK_DESCRIPTOR_TYPE_STORAGE_BUFFER
|
|
||||||
: VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
|
|
||||||
if (storageImageDescriptorType == VK_DESCRIPTOR_TYPE_MAX_ENUM)
|
if (storageImageDescriptorType == VK_DESCRIPTOR_TYPE_MAX_ENUM)
|
||||||
storageImageDescriptorType = descriptorType;
|
storageImageDescriptorType = descriptorType;
|
||||||
else if (storageImageDescriptorType != descriptorType)
|
else if (storageImageDescriptorType != descriptorType)
|
||||||
@@ -342,6 +345,21 @@ std::unique_ptr<CShaderProgram> CShaderProgram::Create(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (type == "storageBuffer")
|
||||||
|
{
|
||||||
|
const CStrIntern name{attributes.GetNamedItem(at_name)};
|
||||||
|
storageBufferMapping[name] = binding;
|
||||||
|
storageBufferDescriptorSetSize =
|
||||||
|
std::max(storageBufferDescriptorSetSize, binding + 1);
|
||||||
|
const VkDescriptorType descriptorType{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER};
|
||||||
|
if (storageBufferDescriptorType == VK_DESCRIPTOR_TYPE_MAX_ENUM)
|
||||||
|
storageBufferDescriptorType = descriptorType;
|
||||||
|
else if (storageBufferDescriptorType != descriptorType)
|
||||||
|
{
|
||||||
|
LOGERROR("Shader should have storages of the same type.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LOGERROR("Unsupported binding: '%s'", type.c_str());
|
LOGERROR("Unsupported binding: '%s'", type.c_str());
|
||||||
@@ -573,6 +591,12 @@ std::unique_ptr<CShaderProgram> CShaderProgram::Create(
|
|||||||
device, storageImageDescriptorType, storageImageDescriptorSetSize, std::move(storageImageMapping));
|
device, storageImageDescriptorType, storageImageDescriptorSetSize, std::move(storageImageMapping));
|
||||||
layouts.emplace_back(shaderProgram->m_StorageImageBinding->GetDescriptorSetLayout());
|
layouts.emplace_back(shaderProgram->m_StorageImageBinding->GetDescriptorSetLayout());
|
||||||
}
|
}
|
||||||
|
if (storageBufferDescriptorSetSize > 0)
|
||||||
|
{
|
||||||
|
shaderProgram->m_StorageBufferBinding.emplace(
|
||||||
|
device, storageBufferDescriptorType, storageBufferDescriptorSetSize, std::move(storageBufferMapping));
|
||||||
|
layouts.emplace_back(shaderProgram->m_StorageBufferBinding->GetDescriptorSetLayout());
|
||||||
|
}
|
||||||
|
|
||||||
VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{};
|
VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{};
|
||||||
pipelineLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
|
pipelineLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
|
||||||
@@ -620,6 +644,8 @@ int32_t CShaderProgram::GetBindingSlot(const CStrIntern name) const
|
|||||||
return (static_cast<uint32_t>(BindingSlotType::TEXTURE) << BINDING_SLOT_TYPE_SHIFT) | bindingSlot;
|
return (static_cast<uint32_t>(BindingSlotType::TEXTURE) << BINDING_SLOT_TYPE_SHIFT) | bindingSlot;
|
||||||
if (const int32_t bindingSlot = m_StorageImageBinding.has_value() ? m_StorageImageBinding->GetBindingSlot(name) : -1; bindingSlot != -1)
|
if (const int32_t bindingSlot = m_StorageImageBinding.has_value() ? m_StorageImageBinding->GetBindingSlot(name) : -1; bindingSlot != -1)
|
||||||
return (static_cast<uint32_t>(BindingSlotType::STORAGE_IMAGE) << BINDING_SLOT_TYPE_SHIFT) | bindingSlot;
|
return (static_cast<uint32_t>(BindingSlotType::STORAGE_IMAGE) << BINDING_SLOT_TYPE_SHIFT) | bindingSlot;
|
||||||
|
if (const int32_t bindingSlot = m_StorageBufferBinding.has_value() ? m_StorageBufferBinding->GetBindingSlot(name) : -1; bindingSlot != -1)
|
||||||
|
return (static_cast<uint32_t>(BindingSlotType::STORAGE_BUFFER) << BINDING_SLOT_TYPE_SHIFT) | bindingSlot;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -646,6 +672,8 @@ void CShaderProgram::Unbind()
|
|||||||
m_TextureBinding->Unbind();
|
m_TextureBinding->Unbind();
|
||||||
if (m_StorageImageBinding.has_value())
|
if (m_StorageImageBinding.has_value())
|
||||||
m_StorageImageBinding->Unbind();
|
m_StorageImageBinding->Unbind();
|
||||||
|
if (m_StorageBufferBinding.has_value())
|
||||||
|
m_StorageBufferBinding->Unbind();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CShaderProgram::PreDraw(CRingCommandContext& commandContext)
|
void CShaderProgram::PreDraw(CRingCommandContext& commandContext)
|
||||||
@@ -730,6 +758,15 @@ void CShaderProgram::BindOutdatedDescriptorSets(
|
|||||||
constexpr uint32_t STORAGE_IMAGE_BINDING_SET = 2u;
|
constexpr uint32_t STORAGE_IMAGE_BINDING_SET = 2u;
|
||||||
descriptortSets.emplace_back(STORAGE_IMAGE_BINDING_SET, m_StorageImageBinding->UpdateAndReturnDescriptorSet());
|
descriptortSets.emplace_back(STORAGE_IMAGE_BINDING_SET, m_StorageImageBinding->UpdateAndReturnDescriptorSet());
|
||||||
}
|
}
|
||||||
|
if (m_StorageBufferBinding.has_value() && m_StorageBufferBinding->IsOutdated())
|
||||||
|
{
|
||||||
|
// Currently we assume that in computer shaders we use either textures
|
||||||
|
// or buffers but not together.
|
||||||
|
const uint32_t STORAGE_BUFFER_BINDING_SET{
|
||||||
|
m_Device->GetDescriptorManager().UseDescriptorIndexing() ? 2u : 1u};
|
||||||
|
descriptortSets.emplace_back(
|
||||||
|
STORAGE_BUFFER_BINDING_SET, m_StorageBufferBinding->UpdateAndReturnDescriptorSet());
|
||||||
|
}
|
||||||
|
|
||||||
for (const auto& [firstSet, descriptorSet] : descriptortSets)
|
for (const auto& [firstSet, descriptorSet] : descriptortSets)
|
||||||
{
|
{
|
||||||
@@ -801,8 +838,7 @@ std::pair<std::byte*, uint32_t> CShaderProgram::GetUniformData(
|
|||||||
m_MaterialConstantsDataOutdated = true;
|
m_MaterialConstantsDataOutdated = true;
|
||||||
const uint32_t size = uniform.size;
|
const uint32_t size = uniform.size;
|
||||||
const uint32_t offset = uniform.offset;
|
const uint32_t offset = uniform.offset;
|
||||||
ENSURE(size <= dataSize);
|
return {m_MaterialConstantsData.get() + offset, std::min(dataSize, size)};
|
||||||
return {m_MaterialConstantsData.get() + offset, size};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -841,6 +877,16 @@ void CShaderProgram::SetStorageTexture(const int32_t bindingSlot, CTexture* text
|
|||||||
m_StorageImageBinding->SetObject(index, texture);
|
m_StorageImageBinding->SetObject(index, texture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CShaderProgram::SetStorageBuffer(const int32_t bindingSlot, CBuffer* buffer)
|
||||||
|
{
|
||||||
|
if (bindingSlot < 0)
|
||||||
|
return;
|
||||||
|
ENSURE(static_cast<BindingSlotType>(bindingSlot >> BINDING_SLOT_TYPE_SHIFT) == BindingSlotType::STORAGE_BUFFER);
|
||||||
|
const uint32_t index{bindingSlot & BINDING_SLOT_VALUE_MASK};
|
||||||
|
ENSURE(m_StorageBufferBinding.has_value());
|
||||||
|
m_StorageBufferBinding->SetObject(index, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Vulkan
|
} // namespace Vulkan
|
||||||
|
|
||||||
} // namespace Backend
|
} // namespace Backend
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ public:
|
|||||||
|
|
||||||
void SetTexture(const int32_t bindingSlot, CTexture* texture);
|
void SetTexture(const int32_t bindingSlot, CTexture* texture);
|
||||||
void SetStorageTexture(const int32_t bindingSlot, CTexture* texture);
|
void SetStorageTexture(const int32_t bindingSlot, CTexture* texture);
|
||||||
|
void SetStorageBuffer(const int32_t bindingSlot, CBuffer* buffer);
|
||||||
|
|
||||||
// TODO: rename to something related to buffer.
|
// TODO: rename to something related to buffer.
|
||||||
bool IsMaterialConstantsDataOutdated() const { return m_MaterialConstantsDataOutdated; }
|
bool IsMaterialConstantsDataOutdated() const { return m_MaterialConstantsDataOutdated; }
|
||||||
@@ -178,6 +179,7 @@ private:
|
|||||||
|
|
||||||
std::optional<CSingleTypeDescriptorSetBinding<CTexture>> m_TextureBinding;
|
std::optional<CSingleTypeDescriptorSetBinding<CTexture>> m_TextureBinding;
|
||||||
std::optional<CSingleTypeDescriptorSetBinding<CTexture>> m_StorageImageBinding;
|
std::optional<CSingleTypeDescriptorSetBinding<CTexture>> m_StorageImageBinding;
|
||||||
|
std::optional<CSingleTypeDescriptorSetBinding<CBuffer>> m_StorageBufferBinding;
|
||||||
|
|
||||||
std::unordered_map<VertexAttributeStream, uint32_t> m_StreamLocations;
|
std::unordered_map<VertexAttributeStream, uint32_t> m_StreamLocations;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user