mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-09 04:49:39 -10:00
This converts the previous plain-int type for a buffer handle to an instance of our ID class which provides type safety.
211 lines
7.2 KiB
C++
211 lines
7.2 KiB
C++
|
|
|
|
#include "UniformBufferManager.h"
|
|
#include "uniform_structs.h"
|
|
|
|
#include "tracing/tracing.h"
|
|
|
|
namespace {
|
|
|
|
size_t getElementSize(uniform_block_type type)
|
|
{
|
|
switch (type) {
|
|
case uniform_block_type::Lights:
|
|
return sizeof(graphics::deferred_light_data);
|
|
case uniform_block_type::ModelData:
|
|
return sizeof(graphics::model_uniform_data);
|
|
case uniform_block_type::NanoVGData:
|
|
return sizeof(graphics::nanovg_draw_data);
|
|
case uniform_block_type::DecalInfo:
|
|
return sizeof(graphics::decal_info);
|
|
case uniform_block_type::Matrices:
|
|
return sizeof(graphics::matrix_uniforms);
|
|
case uniform_block_type::NUM_BLOCK_TYPES:
|
|
default:
|
|
UNREACHABLE("Invalid block type encountered!");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
size_t getHeaderSize(uniform_block_type type)
|
|
{
|
|
switch (type) {
|
|
case uniform_block_type::Lights:
|
|
return sizeof(graphics::deferred_global_data);
|
|
case uniform_block_type::DecalInfo:
|
|
return sizeof(graphics::decal_globals);
|
|
case uniform_block_type::ModelData:
|
|
case uniform_block_type::NanoVGData:
|
|
case uniform_block_type::Matrices:
|
|
case uniform_block_type::GenericData:
|
|
return 0;
|
|
case uniform_block_type::NUM_BLOCK_TYPES:
|
|
default:
|
|
UNREACHABLE("Invalid block type encountered!");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
namespace graphics {
|
|
namespace util {
|
|
|
|
UniformBufferManager::UniformBufferManager()
|
|
{
|
|
bool success = gr_get_property(gr_property::UNIFORM_BUFFER_OFFSET_ALIGNMENT, &_offset_alignment);
|
|
Assertion(success, "Uniform buffer usage requires a backend which allows to query the offset alignment!");
|
|
|
|
_use_persistent_mapping = gr_is_capable(CAPABILITY_PERSISTENT_BUFFER_MAPPING);
|
|
|
|
_segment_fences.fill(nullptr);
|
|
changeSegmentSize(4096);
|
|
}
|
|
UniformBufferManager::~UniformBufferManager()
|
|
{
|
|
if (_active_uniform_buffer.isValid()) {
|
|
gr_delete_buffer(_active_uniform_buffer);
|
|
_active_uniform_buffer = gr_buffer_handle();
|
|
}
|
|
for (auto& fence : _segment_fences) {
|
|
if (fence != nullptr) {
|
|
gr_sync_delete(fence);
|
|
fence = nullptr;
|
|
}
|
|
}
|
|
|
|
for (auto& buffer : _retired_buffers) {
|
|
gr_sync_delete(std::get<0>(buffer));
|
|
gr_delete_buffer(std::get<1>(buffer));
|
|
// The shadow buffer pointer will be deleted automatically
|
|
}
|
|
_retired_buffers.clear();
|
|
}
|
|
void UniformBufferManager::onFrameEnd()
|
|
{
|
|
GR_DEBUG_SCOPE("Performing uniform frame end operations");
|
|
|
|
if (_segment_offset > _segment_size) {
|
|
// We needed more data than what is available in the segment
|
|
changeSegmentSize(_segment_offset);
|
|
} else {
|
|
// Set up the fence for the currently active segment
|
|
_segment_fences[_active_segment] = gr_sync_fence();
|
|
|
|
// Move the current segment to the next one
|
|
_active_segment = (_active_segment + 1) % NUM_SEGMENTS;
|
|
_segment_offset = 0;
|
|
|
|
// Now we need to wait until the segment is available again. In most cases this should succeed immediately.
|
|
if (_segment_fences[_active_segment] != nullptr) {
|
|
int i = 0;
|
|
while (i < 10 && !gr_sync_wait(_segment_fences[_active_segment], 500000000)) {
|
|
// This isn't good!
|
|
mprintf(("Missed uniform fence deadline!!\n"));
|
|
++i;
|
|
}
|
|
gr_sync_delete(_segment_fences[_active_segment]);
|
|
_segment_fences[_active_segment] = nullptr;
|
|
|
|
if (i == 10) {
|
|
// I don't know how to handle this properly but this probably means that something went wrong with the
|
|
// GPU
|
|
Error(LOCATION, "Failed to wait until uniform range is available! Get a coder.");
|
|
}
|
|
}
|
|
}
|
|
|
|
while (!_retired_buffers.empty()) {
|
|
if (gr_sync_wait(std::get<0>(_retired_buffers.front()), 0)) {
|
|
// Fence was signaled => buffer is not in use anymore
|
|
// Delete the sync object since we don't need it anymore
|
|
gr_sync_delete(std::get<0>(_retired_buffers.front()));
|
|
gr_delete_buffer(std::get<1>(_retired_buffers.front()));
|
|
|
|
// This will automatically clean up the shadow buffer pointer
|
|
_retired_buffers.erase(_retired_buffers.begin());
|
|
} else {
|
|
// The first fence element was not signaled yet so all the other fences also haven't been signaled yet
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
UniformBuffer UniformBufferManager::getUniformBuffer(uniform_block_type type, size_t num_elements,
|
|
size_t element_size_override)
|
|
{
|
|
if (element_size_override == 0) {
|
|
element_size_override = getElementSize(type);
|
|
}
|
|
auto size = UniformAligner::getBufferSize(num_elements, (size_t)_offset_alignment, element_size_override,
|
|
getHeaderSize(type));
|
|
|
|
auto end_offset = _segment_offset + size;
|
|
|
|
auto absolute_end = _segment_size * _active_segment + end_offset;
|
|
|
|
if (absolute_end >= _active_buffer_size) {
|
|
// This new element uses too much memory to fit into the active buffer so we need to allocate a new one right
|
|
// now. It may happen that we use more than the segment size but that is not an issue since the frame-end code
|
|
// will reallocate the buffer if that happens.
|
|
// We don't really know how much we are going to need here so 2 times the current amount seems like a good idea
|
|
changeSegmentSize(_segment_size * 2);
|
|
|
|
// Try the stuff above again.
|
|
return getUniformBuffer(type, num_elements, element_size_override);
|
|
}
|
|
|
|
auto data_offset = _segment_size * _active_segment + _segment_offset;
|
|
_segment_offset = end_offset;
|
|
|
|
// Even in the persistent mapping case we still use a temporary buffer since writing to GPU memory is not very fast
|
|
// when doing a lot of small writes (e.g. when building model uniform data). Instead we use a shadow buffer and
|
|
// do a single memcpy when we are done
|
|
return UniformBuffer(this, data_offset, _shadow_uniform_buffer.get() + data_offset, size, element_size_override,
|
|
getHeaderSize(type), static_cast<size_t>(_offset_alignment));
|
|
}
|
|
void UniformBufferManager::changeSegmentSize(size_t new_size)
|
|
{
|
|
if (_active_uniform_buffer.isValid()) {
|
|
// Retire the old buffer first
|
|
_retired_buffers.emplace_back(gr_sync_fence(), _active_uniform_buffer, std::move(_shadow_uniform_buffer));
|
|
}
|
|
|
|
// The current fences are meaningless now so we need to delete them
|
|
for (auto& fence : _segment_fences) {
|
|
if (fence != nullptr) {
|
|
gr_sync_delete(fence);
|
|
fence = nullptr;
|
|
}
|
|
}
|
|
|
|
_active_buffer_size = new_size * NUM_SEGMENTS;
|
|
_shadow_uniform_buffer.reset(new uint8_t[_active_buffer_size]);
|
|
_active_uniform_buffer = gr_create_buffer(
|
|
BufferType::Uniform, _use_persistent_mapping ? BufferUsageHint::PersistentMapping : BufferUsageHint::Dynamic);
|
|
|
|
gr_update_buffer_data(_active_uniform_buffer, _active_buffer_size, nullptr);
|
|
if (_use_persistent_mapping) {
|
|
_buffer_ptr = gr_map_buffer(_active_uniform_buffer);
|
|
}
|
|
|
|
_active_segment = 0;
|
|
_segment_size = new_size;
|
|
_segment_offset = 0;
|
|
}
|
|
void UniformBufferManager::submitData(void* buffer, size_t data_size, size_t offset)
|
|
{
|
|
if (_use_persistent_mapping) {
|
|
auto buffer_dest = reinterpret_cast<void*>(reinterpret_cast<uint8_t*>(_buffer_ptr) + offset);
|
|
memcpy(buffer_dest, buffer, data_size);
|
|
// The data is already in the buffer but we still need to flush the memory range
|
|
gr_flush_mapped_buffer(_active_uniform_buffer, offset, data_size);
|
|
} else {
|
|
gr_update_buffer_data_offset(_active_uniform_buffer, offset, data_size, buffer);
|
|
}
|
|
}
|
|
gr_buffer_handle UniformBufferManager::getActiveBufferHandle() { return _active_uniform_buffer; }
|
|
size_t UniformBufferManager::getBufferSize() { return _active_buffer_size; }
|
|
size_t UniformBufferManager::getCurrentlyUsedSize() { return _segment_offset; }
|
|
} // namespace util
|
|
} // namespace graphics
|